diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac4d88d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.ignore diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..84792a2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "ch32v-insert-coin/ext/ch32-hal"] + path = ch32v-insert-coin/ext/ch32-hal + url = https://github.com/sigil-03/ch32-hal.git +[submodule "ch32v-insert-coin/ext/qingke"] + path = ch32v-insert-coin/ext/qingke + url = https://github.com/ch32-rs/qingke.git +[submodule "ch32v-insert-coin/ext/adpcm-pwm-dac"] + path = ch32v-insert-coin/ext/adpcm-pwm-dac + url = https://git.glyphs.tech/sigil-03/adpcm-pwm-dac.git +[submodule "ch32v-insert-coin/ext/wavetable-synth"] + path = ch32v-insert-coin/ext/wavetable-synth + url = https://git.glyphs.tech/sigil-03/wavetable-synth.git diff --git a/02175f27333ffe6b0c61218fae3142b6/description b/02175f27333ffe6b0c61218fae3142b6/description deleted file mode 100644 index c2ce541..0000000 --- a/02175f27333ffe6b0c61218fae3142b6/description +++ /dev/null @@ -1 +0,0 @@ -enter deep sleep after long button press diff --git a/02175f27333ffe6b0c61218fae3142b6/tags b/02175f27333ffe6b0c61218fae3142b6/tags deleted file mode 100644 index a7453f0..0000000 --- a/02175f27333ffe6b0c61218fae3142b6/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description b/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description deleted file mode 100644 index 57ce453..0000000 --- a/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description +++ /dev/null @@ -1 +0,0 @@ -modified blinky.rs to have 3 heartbeat blinks, was able to flash and observe the update. diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/description b/0acf72abc9e4641d0683e1bb5f2cf829/description deleted file mode 100644 index 85a5ead..0000000 --- a/0acf72abc9e4641d0683e1bb5f2cf829/description +++ /dev/null @@ -1 +0,0 @@ -blink an LED diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/state b/0acf72abc9e4641d0683e1bb5f2cf829/state deleted file mode 100644 index 348ebd9..0000000 --- a/0acf72abc9e4641d0683e1bb5f2cf829/state +++ /dev/null @@ -1 +0,0 @@ -done \ No newline at end of file diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/tags b/0acf72abc9e4641d0683e1bb5f2cf829/tags deleted file mode 100644 index 970be2b..0000000 --- a/0acf72abc9e4641d0683e1bb5f2cf829/tags +++ /dev/null @@ -1 +0,0 @@ -phase-0 diff --git a/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description b/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description deleted file mode 100644 index 71e5f82..0000000 --- a/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description +++ /dev/null @@ -1 +0,0 @@ -flashing successful using wch linkE programmer diff --git a/1892880782c8c31990d591c7b05fa6c3/description b/1892880782c8c31990d591c7b05fa6c3/description deleted file mode 100644 index cd8422a..0000000 --- a/1892880782c8c31990d591c7b05fa6c3/description +++ /dev/null @@ -1 +0,0 @@ -flash CH32V board diff --git a/1892880782c8c31990d591c7b05fa6c3/state b/1892880782c8c31990d591c7b05fa6c3/state deleted file mode 100644 index 348ebd9..0000000 --- a/1892880782c8c31990d591c7b05fa6c3/state +++ /dev/null @@ -1 +0,0 @@ -done \ No newline at end of file diff --git a/1892880782c8c31990d591c7b05fa6c3/tags b/1892880782c8c31990d591c7b05fa6c3/tags deleted file mode 100644 index 970be2b..0000000 --- a/1892880782c8c31990d591c7b05fa6c3/tags +++ /dev/null @@ -1 +0,0 @@ -phase-0 diff --git a/2207e89d7201efbdceadd1c528d95341/description b/2207e89d7201efbdceadd1c528d95341/description deleted file mode 100644 index 2bf140f..0000000 --- a/2207e89d7201efbdceadd1c528d95341/description +++ /dev/null @@ -1,4 +0,0 @@ -adpcm decoder - -REQUIREMENTS: -* write a module which takes as input adpcm data and outputs amplitudes diff --git a/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description b/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description deleted file mode 100644 index 1fa60ea..0000000 --- a/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description +++ /dev/null @@ -1,21 +0,0 @@ -had some pretty good progress tonight. got the board, soldered headers on, got it breadboarded, and got started trying to compile for the riscv core, and get the device flashed. - -there's good HAL support, actually, which is awesome: -[ch32-hal](https://github.com/ch32-rs/ch32-hal) - -i was able to eventually get it compiling, however flashing was a different story. - -apparently these chips don't have a 'standard' SWD implementation, and instead have some proprietary stuff from WCH which is annoying. - -there was this project, which has been inactive for 2 years: -[picorvd](https://github.com/aappleby/picorvd) - -it took a decent amount of work to get that project running - had to fix a bunch of broken dependencies, CMAKE version issues causing CMAKE to exit (protip: `export CMAKE_POLICY_VERSION_MINIMUM=3.5` from [stackoverflow](https://stackoverflow.com/questions/79534856/cannot-build-cmake-project-because-compatibility-with-cmake-3-5-has-been-remo). - -once that was all fixed, and i had soldered headers on / flashed a pico - i then had to get the riscv64 version of GDB running, and talking to the pico. GDB did seem to have trouble with the probe sometimes, but i was able to (i think) flash a program to one of the boards.... and it bricked. - -immediately i saw a bunch of activity on the SWD line, and the LED stopped blinking. - -what concerned me is that the waveform did not go away after a long period of time. i did determine that the pico was the one driving the line (on the ch32v it's SWIO, so because there were debug prints in the code, i thought it might have been from that). - -ultimately i think i'm going to just have to use the WCH Link(E?)... which is only $7 or so, but it does mean i'm going to have to wait for it... diff --git a/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description b/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description deleted file mode 100644 index c8fcba0..0000000 --- a/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description +++ /dev/null @@ -1 +0,0 @@ -this is done - required getting a wch-linkE programmer, which was able to interface with probe-rs and program the ch32v003 chip. diff --git a/2ff2ce2cae3914fbb8382372498abdd5/description b/2ff2ce2cae3914fbb8382372498abdd5/description deleted file mode 100644 index a0ed520..0000000 --- a/2ff2ce2cae3914fbb8382372498abdd5/description +++ /dev/null @@ -1 +0,0 @@ -set up toolchain for CH32V development diff --git a/2ff2ce2cae3914fbb8382372498abdd5/state b/2ff2ce2cae3914fbb8382372498abdd5/state deleted file mode 100644 index 348ebd9..0000000 --- a/2ff2ce2cae3914fbb8382372498abdd5/state +++ /dev/null @@ -1 +0,0 @@ -done \ No newline at end of file diff --git a/2ff2ce2cae3914fbb8382372498abdd5/tags b/2ff2ce2cae3914fbb8382372498abdd5/tags deleted file mode 100644 index 970be2b..0000000 --- a/2ff2ce2cae3914fbb8382372498abdd5/tags +++ /dev/null @@ -1 +0,0 @@ -phase-0 diff --git a/3eb0b69b70c8853f9f533c6daa6acd1f/description b/3eb0b69b70c8853f9f533c6daa6acd1f/description deleted file mode 100644 index bca79ec..0000000 --- a/3eb0b69b70c8853f9f533c6daa6acd1f/description +++ /dev/null @@ -1 +0,0 @@ -ADC battery voltage reading diff --git a/3eb0b69b70c8853f9f533c6daa6acd1f/tags b/3eb0b69b70c8853f9f533c6daa6acd1f/tags deleted file mode 100644 index a7453f0..0000000 --- a/3eb0b69b70c8853f9f533c6daa6acd1f/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/49260fa4a2467b55f7b4d197825683a2/description b/49260fa4a2467b55f7b4d197825683a2/description deleted file mode 100644 index f44cd6d..0000000 --- a/49260fa4a2467b55f7b4d197825683a2/description +++ /dev/null @@ -1,7 +0,0 @@ -gpio inputs - -4x GPIO inputs - -* 1-2 of these wake from sleep interrupt - * 1 for button press - * 1 for coin detect diff --git a/49260fa4a2467b55f7b4d197825683a2/tags b/49260fa4a2467b55f7b4d197825683a2/tags deleted file mode 100644 index a7453f0..0000000 --- a/49260fa4a2467b55f7b4d197825683a2/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/4b5cce76398043576a0f9a6e29492127/assignee b/4b5cce76398043576a0f9a6e29492127/assignee deleted file mode 100644 index 284bb3b..0000000 --- a/4b5cce76398043576a0f9a6e29492127/assignee +++ /dev/null @@ -1 +0,0 @@ -sigil-03 \ No newline at end of file diff --git a/4b5cce76398043576a0f9a6e29492127/description b/4b5cce76398043576a0f9a6e29492127/description deleted file mode 100644 index dc791fd..0000000 --- a/4b5cce76398043576a0f9a6e29492127/description +++ /dev/null @@ -1,6 +0,0 @@ -phase 0: bootstrap - -REQUIREMENTS: -* set up toolchain for CH32V development -* flash CH32V board -* blink an LED diff --git a/4b5cce76398043576a0f9a6e29492127/state b/4b5cce76398043576a0f9a6e29492127/state deleted file mode 100644 index 348ebd9..0000000 --- a/4b5cce76398043576a0f9a6e29492127/state +++ /dev/null @@ -1 +0,0 @@ -done \ No newline at end of file diff --git a/4b5cce76398043576a0f9a6e29492127/tags b/4b5cce76398043576a0f9a6e29492127/tags deleted file mode 100644 index 970be2b..0000000 --- a/4b5cce76398043576a0f9a6e29492127/tags +++ /dev/null @@ -1 +0,0 @@ -phase-0 diff --git a/777077169fce2ffba439b0af82e0f574/assignee b/777077169fce2ffba439b0af82e0f574/assignee deleted file mode 100644 index 284bb3b..0000000 --- a/777077169fce2ffba439b0af82e0f574/assignee +++ /dev/null @@ -1 +0,0 @@ -sigil-03 \ No newline at end of file diff --git a/777077169fce2ffba439b0af82e0f574/description b/777077169fce2ffba439b0af82e0f574/description deleted file mode 100644 index b5f019d..0000000 --- a/777077169fce2ffba439b0af82e0f574/description +++ /dev/null @@ -1,41 +0,0 @@ -insert-coin - -# HW -CHV32V003 or STM32F0 variant - -LM386/SC8002B amplifier -* HW PWM filtering for audio - - - -# FW FEATURES -## DEEP SLEEP -* wake from deep sleep on rising edge -* deep sleep when battery voltage is low -* deep sleep on long button press -* deep sleep after some long period of time - * does not need to be accurate - * time domain approx. several hours - -## BATTERY VOLTAGE MONITORING -* ADC battery voltage reading - - - -## GPIO INPUT -* 4x switch input - * one for button press - * one for coin detect - -## LED DRIVER -* 3 channels of LEDs - * need PWM-ish dimming but can be rough (bit-banged) - -## AUDIO -* store audio in MCU flash memory -* 10 audio files with total of 3-5 seconds of audio time -* output using PWM channels, will filter in HW - - -# REFERENCE -[spec](https://docs.google.com/document/d/1WP9aMegpzNuwF81Cxm263mibuAER9fZ7Ktj7NuHr9Rs) diff --git a/777077169fce2ffba439b0af82e0f574/state b/777077169fce2ffba439b0af82e0f574/state deleted file mode 100644 index 505c028..0000000 --- a/777077169fce2ffba439b0af82e0f574/state +++ /dev/null @@ -1 +0,0 @@ -inprogress \ No newline at end of file diff --git a/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description b/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description deleted file mode 100644 index a0497af..0000000 --- a/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description +++ /dev/null @@ -1,3 +0,0 @@ -this is basically done, there's a PWM dac that can read DPCM data and output it via PWM. - -additionally, there's an encoder which can take a raw u8 encoded PCM file and turn it into a 4-bit DPCM file diff --git a/8b98f887e6f027890908edf066debce1/dependencies/2207e89d7201efbdceadd1c528d95341 b/8b98f887e6f027890908edf066debce1/dependencies/2207e89d7201efbdceadd1c528d95341 deleted file mode 100644 index e69de29..0000000 diff --git a/8b98f887e6f027890908edf066debce1/dependencies/c6c969915b74005bc0b7f0c4b182243e b/8b98f887e6f027890908edf066debce1/dependencies/c6c969915b74005bc0b7f0c4b182243e deleted file mode 100644 index e69de29..0000000 diff --git a/8b98f887e6f027890908edf066debce1/description b/8b98f887e6f027890908edf066debce1/description deleted file mode 100644 index d2095a8..0000000 --- a/8b98f887e6f027890908edf066debce1/description +++ /dev/null @@ -1,5 +0,0 @@ -audio player - -requirements: -* DAC using PWM output -* read DPCM data and export to DAC diff --git a/8b98f887e6f027890908edf066debce1/tags b/8b98f887e6f027890908edf066debce1/tags deleted file mode 100644 index a7453f0..0000000 --- a/8b98f887e6f027890908edf066debce1/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/95dbcb73dd29dafe84621252289a4179/description b/95dbcb73dd29dafe84621252289a4179/description deleted file mode 100644 index 5a93718..0000000 --- a/95dbcb73dd29dafe84621252289a4179/description +++ /dev/null @@ -1 +0,0 @@ -wake from deep sleep on rising edge diff --git a/95dbcb73dd29dafe84621252289a4179/tags b/95dbcb73dd29dafe84621252289a4179/tags deleted file mode 100644 index a7453f0..0000000 --- a/95dbcb73dd29dafe84621252289a4179/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/README.md b/README.md deleted file mode 100644 index 2bd9d23..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -This branch is used by entomologist to track issues. \ No newline at end of file diff --git a/a05a70c9994ab6c8e3f9c69c7d536952/description b/a05a70c9994ab6c8e3f9c69c7d536952/description deleted file mode 100644 index e8fd071..0000000 --- a/a05a70c9994ab6c8e3f9c69c7d536952/description +++ /dev/null @@ -1 +0,0 @@ -enter deep sleep after inactivity timeout diff --git a/a05a70c9994ab6c8e3f9c69c7d536952/tags b/a05a70c9994ab6c8e3f9c69c7d536952/tags deleted file mode 100644 index a7453f0..0000000 --- a/a05a70c9994ab6c8e3f9c69c7d536952/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/b0682a96fb4d2cb7c6d06141b5d36849/description b/b0682a96fb4d2cb7c6d06141b5d36849/description deleted file mode 100644 index a41443b..0000000 --- a/b0682a96fb4d2cb7c6d06141b5d36849/description +++ /dev/null @@ -1,5 +0,0 @@ -LED control - -3 channels of LEDs - -* need PWM-ish dimming, but can be bit-banged / slow diff --git a/b0682a96fb4d2cb7c6d06141b5d36849/tags b/b0682a96fb4d2cb7c6d06141b5d36849/tags deleted file mode 100644 index a7453f0..0000000 --- a/b0682a96fb4d2cb7c6d06141b5d36849/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/c6c969915b74005bc0b7f0c4b182243e/description b/c6c969915b74005bc0b7f0c4b182243e/description deleted file mode 100644 index 734f5ad..0000000 --- a/c6c969915b74005bc0b7f0c4b182243e/description +++ /dev/null @@ -1,4 +0,0 @@ -pwm dac - -REQUIREMENTS: -* create an interface that accepts an input amplitude and outputs a PWM signal diff --git a/ch32v-insert-coin/.cargo/config.toml b/ch32v-insert-coin/.cargo/config.toml new file mode 100644 index 0000000..68ad23c --- /dev/null +++ b/ch32v-insert-coin/.cargo/config.toml @@ -0,0 +1,27 @@ +[build] +target = "riscv32ec-unknown-none-elf.json" +#target = "riscv32i-unknown-none-elf" + +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +# runner = "riscv64-elf-gdb -q -x openocd.gdb" +# runner = "riscv-none-embed-gdb -q -x openocd.gdb" +# runner = "gdb -q -x openocd.gdb" +# runner = "wlink -v flash" + +runner = "wlink -v flash --enable-sdi-print --watch-serial" + +# Flash and debug chip with probe-rs. https://probe.rs/ +# runner = "probe-rs run --chip ch32v003" + +[unstable] +build-std = ["core"] +# build-std = ["core", "compiler_builtins"] +# build-std-features = ["compiler-builtins-mem"] + +[target."riscv32ec-unknown-none-elf"] +rustflags = ["-C", "link-arg=-Tlink.x"] + +#[target."riscv32i-unknown-none-elf"] +#rustflags = [ +## "-C", "link-arg=-Tlink.x", +#] diff --git a/ch32v-insert-coin/.gitignore b/ch32v-insert-coin/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/ch32v-insert-coin/.gitignore @@ -0,0 +1 @@ +target diff --git a/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock new file mode 100644 index 0000000..7fd7006 --- /dev/null +++ b/ch32v-insert-coin/Cargo.lock @@ -0,0 +1,633 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adpcm-pwm-dac" +version = "0.1.0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "ch32-hal" +version = "0.1.0" +dependencies = [ + "ch32-metapac", + "critical-section", + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-utils", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "futures", + "nb 1.1.0", + "proc-macro2", + "qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qingke-rt", + "quote", + "rand_core", + "sdio-host", +] + +[[package]] +name = "ch32-metapac" +version = "0.1.0" +source = "git+https://github.com/ch32-rs/ch32-metapac?rev=b1cbc7a98e43af3fd3170821654784e2c01cb26b#b1cbc7a98e43af3fd3170821654784e2c01cb26b" +dependencies = [ + "riscv 0.11.1", + "vcell", +] + +[[package]] +name = "ch32v-insert-coin" +version = "0.1.0" +dependencies = [ + "adpcm-pwm-dac", + "ch32-hal", + "critical-section", + "embassy-executor", + "embedded-hal 1.0.0", + "panic-halt", + "qingke 0.5.0", + "qingke-rt", + "wavetable-synth", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-executor" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-hal-internal" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59" +dependencies = [ + "num-traits", +] + +[[package]] +name = "embassy-sync" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83" +dependencies = [ + "embassy-executor", + "heapless", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" +dependencies = [ + "embedded-io-async", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qingke" +version = "0.5.0" +dependencies = [ + "bit_field", + "riscv 0.12.1", +] + +[[package]] +name = "qingke" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0230c5310b68c08a3cf8b59fbeec3e9d8e352bc6500f62cbaf9c677f42c8dfc" +dependencies = [ + "bit_field", + "critical-section", + "riscv 0.12.1", +] + +[[package]] +name = "qingke-rt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b955c60adac70c6d40205b1dbe9f57e1151d06aa842069cdbaef7bc07ad283fd" +dependencies = [ + "qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qingke-rt-macros", +] + +[[package]] +name = "qingke-rt-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ed46d18953ea5765ab26a07d1f092dffac2da1b4830c4397e02c3cec08501" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "sdio-host" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93c025f9cfe4c388c328ece47d11a54a823da3b5ad0370b22d95ad47137f85a" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wavetable-synth" +version = "0.1.0" diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml new file mode 100644 index 0000000..e09776c --- /dev/null +++ b/ch32v-insert-coin/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "ch32v-insert-coin" +version = "0.1.0" +edition = "2024" + +[features] +enable_print = [] + +[dependencies] +ch32-hal = { path = "ext/ch32-hal/", features = [ + "ch32v003f4u6", + "memory-x", + "embassy", + "time-driver-tim2", + "rt", +] } + +embassy-executor = { version = "0.7.0", features = [ + "arch-spin", + "executor-thread", + "task-arena-size-128", # or better use nightly, but fails on recent Rust versions +] } + +panic-halt = "1.0" + +embedded-hal = "1.0.0" + +qingke-rt = { version = "*", features = ["highcode"] } + +qingke = {path = "ext/qingke"} + +adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" } + +wavetable-synth = { path = "ext/wavetable-synth" } + +critical-section = { version = "1.2.0" } + +[profile.release] +strip = false # symbols are not flashed to the microcontroller, so don't strip them. +lto = true +opt-level = "s" # Optimize for size. + +[profile.dev] +overflow-checks = false diff --git a/ch32v-insert-coin/audio/button_1.raw b/ch32v-insert-coin/audio/button_1.raw new file mode 100644 index 0000000..ade05bd Binary files /dev/null and b/ch32v-insert-coin/audio/button_1.raw differ diff --git a/ch32v-insert-coin/audio/button_2.raw b/ch32v-insert-coin/audio/button_2.raw new file mode 100644 index 0000000..16b9b36 Binary files /dev/null and b/ch32v-insert-coin/audio/button_2.raw differ diff --git a/ch32v-insert-coin/audio/button_3.raw b/ch32v-insert-coin/audio/button_3.raw new file mode 100644 index 0000000..0477e75 Binary files /dev/null and b/ch32v-insert-coin/audio/button_3.raw differ diff --git a/ch32v-insert-coin/audio/coin.raw b/ch32v-insert-coin/audio/coin.raw new file mode 100644 index 0000000..2a0af56 Binary files /dev/null and b/ch32v-insert-coin/audio/coin.raw differ diff --git a/ch32v-insert-coin/audio/coin2.raw b/ch32v-insert-coin/audio/coin2.raw new file mode 100644 index 0000000..1caabb9 Binary files /dev/null and b/ch32v-insert-coin/audio/coin2.raw differ diff --git a/ch32v-insert-coin/audio/coin3.raw b/ch32v-insert-coin/audio/coin3.raw new file mode 100644 index 0000000..8c590d9 Binary files /dev/null and b/ch32v-insert-coin/audio/coin3.raw differ diff --git a/ch32v-insert-coin/audio/coin4.raw b/ch32v-insert-coin/audio/coin4.raw new file mode 100644 index 0000000..f350cd0 Binary files /dev/null and b/ch32v-insert-coin/audio/coin4.raw differ diff --git a/ch32v-insert-coin/audio/coin5.raw b/ch32v-insert-coin/audio/coin5.raw new file mode 100644 index 0000000..922bb2c Binary files /dev/null and b/ch32v-insert-coin/audio/coin5.raw differ diff --git a/ch32v-insert-coin/audio/coin8ksps.raw b/ch32v-insert-coin/audio/coin8ksps.raw new file mode 100644 index 0000000..72caab0 Binary files /dev/null and b/ch32v-insert-coin/audio/coin8ksps.raw differ diff --git a/ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw b/ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw new file mode 100644 index 0000000..36cc103 Binary files /dev/null and b/ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw differ diff --git a/ch32v-insert-coin/audio/sequences b/ch32v-insert-coin/audio/sequences new file mode 100644 index 0000000..edef1a1 --- /dev/null +++ b/ch32v-insert-coin/audio/sequences @@ -0,0 +1,18 @@ +"wahwahwahwah" sound - let play a few times +note timing: 6hz +data: let freqs = [100, 200, 300, 400, 500, 600, 700, 800, 900]; + +"falling" sound +note timing: 6hz +[ +1000, +990, 980, 970, 960, 950, 940, 930, 920, 910, 900, +890, 880, 870, 860, 850, 840, 830, 820, 810, 800, +790, 780, 770, 760, 750, 740, 730, 720, 710, 700, +690, 680, 670, 660, 650, 640, 630, 620, 610, 600, +590, 580, 570, 560, 550, 540, 530, 520, 510, 500, +490, 480, 470, 460, 450, 440, 430, 420, 410, 400, +390, 380, 370, 360, 350, 340, 330, 320, 310, 300, +290, 280, 270, 260, 250, 240, 230, 220, 210, 200, +190, 180, 170, 160, 150, 140, 130, 120, 110, 100, +] diff --git a/ch32v-insert-coin/audio/sweep_dpcm_u4.raw b/ch32v-insert-coin/audio/sweep_dpcm_u4.raw new file mode 100644 index 0000000..1adc494 Binary files /dev/null and b/ch32v-insert-coin/audio/sweep_dpcm_u4.raw differ diff --git a/ch32v-insert-coin/build-run.sh b/ch32v-insert-coin/build-run.sh new file mode 100755 index 0000000..8461b3a --- /dev/null +++ b/ch32v-insert-coin/build-run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cargo +nightly run --release \ No newline at end of file diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac new file mode 160000 index 0000000..99ce71e --- /dev/null +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -0,0 +1 @@ +Subproject commit 99ce71e8d03e382b51732db7d0771349c51c7f48 diff --git a/ch32v-insert-coin/ext/ch32-hal b/ch32v-insert-coin/ext/ch32-hal new file mode 160000 index 0000000..4f11d68 --- /dev/null +++ b/ch32v-insert-coin/ext/ch32-hal @@ -0,0 +1 @@ +Subproject commit 4f11d68e62dcb0e7098eecf357168724a8322d80 diff --git a/ch32v-insert-coin/ext/patches/optional-exti.patch b/ch32v-insert-coin/ext/patches/optional-exti.patch new file mode 100644 index 0000000..2ea58f3 --- /dev/null +++ b/ch32v-insert-coin/ext/patches/optional-exti.patch @@ -0,0 +1,652 @@ +From 7b086336e3820714c564aac13dc44fbaf7f5bc17 Mon Sep 17 00:00:00 2001 +From: mindshub +Date: Sat, 9 Aug 2025 10:16:33 +0200 +Subject: [PATCH] optional exti + +--- + Cargo.toml | 3 +- + src/exti.rs | 548 ++++++++++++++++++++++++++-------------------------- + src/lib.rs | 1 + + 3 files changed, 281 insertions(+), 271 deletions(-) + +diff --git a/Cargo.toml b/Cargo.toml +index ec451bf..55fa288 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -56,7 +56,7 @@ proc-macro2 = "1.0" + quote = "1.0" + + [features] +-default = ["embassy", "rt"] ++default = ["embassy", "rt", "exti"] + rt = ["dep:qingke-rt"] + highcode = ["qingke-rt/highcode"] + embassy = [ +@@ -66,6 +66,7 @@ embassy = [ + ] + defmt = ["dep:defmt"] + memory-x = ["ch32-metapac/memory-x"] ++exti = [] + + + # Features starting with `_` are for internal use only. They're not intended +diff --git a/src/exti.rs b/src/exti.rs +index a2458d1..3d613ea 100644 +--- a/src/exti.rs ++++ b/src/exti.rs +@@ -1,37 +1,11 @@ +-use core::future::Future; +-use core::marker::PhantomData; +-use core::pin::Pin; +-use core::task::{Context, Poll}; +- + use embassy_sync::waitqueue::AtomicWaker; +-use qingke_rt::interrupt; + +-use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull}; +-use crate::{impl_peripheral, into_ref, peripherals, Peripheral}; ++use crate::{impl_peripheral, peripherals}; + + const EXTI_COUNT: usize = 24; + const NEW_AW: AtomicWaker = AtomicWaker::new(); + static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [NEW_AW; EXTI_COUNT]; + +-pub unsafe fn on_irq() { +- let exti = &crate::pac::EXTI; +- +- let bits = exti.intfr().read(); +- +- // We don't handle or change any EXTI lines above 24. +- let bits = bits.0 & 0x00FFFFFF; +- +- // Clear pending - Clears the EXTI's line pending bits. +- exti.intfr().write(|w| w.0 = bits); +- +- exti.intenr().modify(|w| w.0 = w.0 & !bits); +- +- // Wake the tasks +- for pin in BitIter(bits) { +- EXTI_WAKERS[pin as usize].wake(); +- } +-} +- + struct BitIter(u32); + + impl Iterator for BitIter { +@@ -48,150 +22,6 @@ impl Iterator for BitIter { + } + } + +-/// EXTI input driver +-pub struct ExtiInput<'d> { +- pin: Input<'d>, +-} +- +-impl<'d> Unpin for ExtiInput<'d> {} +- +-impl<'d> ExtiInput<'d> { +- pub fn new( +- pin: impl Peripheral

+ 'd, +- ch: impl Peripheral

+ 'd, +- pull: Pull, +- ) -> Self { +- into_ref!(pin, ch); +- // Needed if using AnyPin+AnyChannel. +- assert_eq!(pin.pin(), ch.number()); +- +- Self { +- pin: Input::new(pin, pull), +- } +- } +- +- pub fn is_high(&self) -> bool { +- self.pin.is_high() +- } +- +- pub fn is_low(&self) -> bool { +- self.pin.is_low() +- } +- +- pub fn get_level(&self) -> Level { +- self.pin.get_level() +- } +- +- pub async fn wait_for_high<'a>(&'a mut self) { +- let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false); +- if self.is_high() { +- return; +- } +- fut.await +- } +- +- pub async fn wait_for_low<'a>(&'a mut self) { +- let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true); +- if self.is_low() { +- return; +- } +- fut.await +- } +- +- pub async fn wait_for_rising_edge<'a>(&'a mut self) { +- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await +- } +- +- pub async fn wait_for_falling_edge<'a>(&'a mut self) { +- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await +- } +- +- pub async fn wait_for_any_edge<'a>(&'a mut self) { +- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await +- } +-} +- +-#[must_use = "futures do nothing unless you `.await` or poll them"] +-struct ExtiInputFuture<'a> { +- pin: u8, +- phantom: PhantomData<&'a mut AnyPin>, +-} +- +-// EXTI0-EXTI23 Px0-Px23(x=A/B/C) +-impl<'a> ExtiInputFuture<'a> { +- fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self { +- critical_section::with(|_| { +- let exti = &crate::pac::EXTI; +- let afio = &crate::pac::AFIO; +- +- let port = port as u8; +- let pin = pin as usize; +- +- #[cfg(afio_v0)] +- { +- // AFIO_EXTICR +- // stride: 2, len: 15, 8 lines +- afio.exticr().modify(|w| w.set_exti(pin, port)); +- } +- // V1, V2, V3, L1 +- #[cfg(any(afio_v3, afio_l1))] +- { +- // AFIO_EXTICRx +- // stride: 4, len: 4, 16 lines +- afio.exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); +- } +- #[cfg(afio_x0)] +- { +- // stride: 2, len: 15, 24 lines +- afio.exticr(pin / 16).modify(|w| w.set_exti(pin % 16, port)); +- } +- #[cfg(afio_ch641)] +- { +- // single register +- afio.exticr().modify(|w| w.set_exti(pin, port != 0)); +- } +- +- // See-also: 7.4.3 +- exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt +- +- exti.rtenr().modify(|w| w.set_tr(pin, rising)); +- exti.ftenr().modify(|w| w.set_tr(pin, falling)); +- }); +- +- Self { +- pin, +- phantom: PhantomData, +- } +- } +-} +- +-impl<'a> Drop for ExtiInputFuture<'a> { +- fn drop(&mut self) { +- critical_section::with(|_| { +- let exti = &crate::pac::EXTI; +- let pin = self.pin; +- exti.intenr().modify(|w| w.0 = w.0 & !(1 << pin)); +- }); +- } +-} +- +-impl<'a> Future for ExtiInputFuture<'a> { +- type Output = (); +- +- fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { +- let exti = &crate::pac::EXTI; +- +- EXTI_WAKERS[self.pin as usize].register(cx.waker()); +- +- if exti.intenr().read().mr(self.pin as _) == false { +- // intenr cleared by on_irq, then we can assume it is triggered +- Poll::Ready(()) +- } else { +- Poll::Pending +- } +- } +-} +- + trait SealedChannel {} + + #[allow(private_bounds)] +@@ -207,6 +37,7 @@ pub trait Channel: SealedChannel + Sized { + pub struct AnyChannel { + number: u8, + } ++ + impl_peripheral!(AnyChannel); + impl SealedChannel for AnyChannel {} + impl Channel for AnyChannel { +@@ -267,128 +98,305 @@ mod _exti_24lines { + impl_exti!(EXTI23, 23); + } + +-/* +-EXTI0 +-EXTI1 +-EXTI2 +-EXTI3 +-EXTI4 +-EXTI9_5 +-EXTI15_10 +-EXTI7_0 +-EXTI15_8 +-EXTI25_16 +-*/ +- +-/// safety: must be called only once +-#[cfg(gpio_x0)] +-mod irq_impl { +- use super::*; ++#[cfg(feature = "exti")] ++pub use exti_inner::*; ++ ++#[cfg(feature = "exti")] ++mod exti_inner { ++ use super::{BitIter, Channel, EXTI_WAKERS}; ++ use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull}; ++ use crate::{into_ref, Peripheral}; ++ use core::future::Future; ++ use core::marker::PhantomData; ++ use core::pin::Pin; ++ use core::task::{Context, Poll}; ++ use qingke_rt::interrupt; ++ ++ /// EXTI input driver ++ pub struct ExtiInput<'d> { ++ pin: Input<'d>, ++ } ++ ++ impl<'d> Unpin for ExtiInput<'d> {} ++ ++ impl<'d> ExtiInput<'d> { ++ pub fn new( ++ pin: impl Peripheral

+ 'd, ++ ch: impl Peripheral

+ 'd, ++ pull: Pull, ++ ) -> Self { ++ into_ref!(pin, ch); ++ // Needed if using AnyPin+AnyChannel. ++ assert_eq!(pin.pin(), ch.number()); ++ ++ Self { ++ pin: Input::new(pin, pull), ++ } ++ } + +- #[interrupt] +- unsafe fn EXTI7_0() { +- on_irq(); +- } +- #[interrupt] +- unsafe fn EXTI15_8() { +- on_irq(); +- } +- #[interrupt] +- unsafe fn EXTI25_16() { +- on_irq(); +- } ++ pub fn is_high(&self) -> bool { ++ self.pin.is_high() ++ } + +- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { +- use crate::pac::Interrupt; ++ pub fn is_low(&self) -> bool { ++ self.pin.is_low() ++ } + +- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI25_16 as u8); +- } +-} ++ pub fn get_level(&self) -> Level { ++ self.pin.get_level() ++ } + +-#[cfg(all(gpio_v3, not(ch641)))] +-mod irq_impl { +- use super::*; ++ pub async fn wait_for_high<'a>(&'a mut self) { ++ let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false); ++ if self.is_high() { ++ return; ++ } ++ fut.await ++ } + +- #[interrupt] +- unsafe fn EXTI0() { +- on_irq(); +- } +- #[interrupt] +- unsafe fn EXTI1() { +- on_irq(); +- } +- #[interrupt] +- unsafe fn EXTI2() { +- on_irq(); +- } +- #[interrupt] +- unsafe fn EXTI3() { +- on_irq(); ++ pub async fn wait_for_low<'a>(&'a mut self) { ++ let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true); ++ if self.is_low() { ++ return; ++ } ++ fut.await ++ } ++ ++ pub async fn wait_for_rising_edge<'a>(&'a mut self) { ++ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await ++ } ++ ++ pub async fn wait_for_falling_edge<'a>(&'a mut self) { ++ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await ++ } ++ ++ pub async fn wait_for_any_edge<'a>(&'a mut self) { ++ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await ++ } + } +- #[interrupt] +- unsafe fn EXTI4() { +- on_irq(); ++ ++ pub unsafe fn on_irq() { ++ let exti = &crate::pac::EXTI; ++ ++ let bits = exti.intfr().read(); ++ ++ // We don't handle or change any EXTI lines above 24. ++ let bits = bits.0 & 0x00FFFFFF; ++ ++ // Clear pending - Clears the EXTI's line pending bits. ++ exti.intfr().write(|w| w.0 = bits); ++ ++ exti.intenr().modify(|w| w.0 = w.0 & !bits); ++ ++ // Wake the tasks ++ for pin in BitIter(bits) { ++ EXTI_WAKERS[pin as usize].wake(); ++ } + } +- #[interrupt] +- unsafe fn EXTI9_5() { +- on_irq(); ++ ++ #[must_use = "futures do nothing unless you `.await` or poll them"] ++ struct ExtiInputFuture<'a> { ++ pin: u8, ++ phantom: PhantomData<&'a mut AnyPin>, ++ } ++ ++ // EXTI0-EXTI23 Px0-Px23(x=A/B/C) ++ impl<'a> ExtiInputFuture<'a> { ++ fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self { ++ critical_section::with(|_| { ++ let exti = &crate::pac::EXTI; ++ let afio = &crate::pac::AFIO; ++ ++ let port = port as u8; ++ let pin = pin as usize; ++ ++ #[cfg(afio_v0)] ++ { ++ // AFIO_EXTICR ++ // stride: 2, len: 15, 8 lines ++ afio.exticr().modify(|w| w.set_exti(pin, port)); ++ } ++ // V1, V2, V3, L1 ++ #[cfg(any(afio_v3, afio_l1))] ++ { ++ // AFIO_EXTICRx ++ // stride: 4, len: 4, 16 lines ++ afio.exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); ++ } ++ #[cfg(afio_x0)] ++ { ++ // stride: 2, len: 15, 24 lines ++ afio.exticr(pin / 16).modify(|w| w.set_exti(pin % 16, port)); ++ } ++ #[cfg(afio_ch641)] ++ { ++ // single register ++ afio.exticr().modify(|w| w.set_exti(pin, port != 0)); ++ } ++ ++ // See-also: 7.4.3 ++ exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt ++ ++ exti.rtenr().modify(|w| w.set_tr(pin, rising)); ++ exti.ftenr().modify(|w| w.set_tr(pin, falling)); ++ }); ++ ++ Self { ++ pin, ++ phantom: PhantomData, ++ } ++ } + } +- #[interrupt] +- unsafe fn EXTI15_10() { +- on_irq(); ++ ++ impl<'a> Drop for ExtiInputFuture<'a> { ++ fn drop(&mut self) { ++ critical_section::with(|_| { ++ let exti = &crate::pac::EXTI; ++ let pin = self.pin; ++ exti.intenr().modify(|w| w.0 = w.0 & !(1 << pin)); ++ }); ++ } + } + +- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { +- use crate::pac::Interrupt; ++ impl<'a> Future for ExtiInputFuture<'a> { ++ type Output = (); + +- qingke::pfic::enable_interrupt(Interrupt::EXTI0 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI1 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI2 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI3 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI4 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI9_5 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI15_10 as u8); +- } +-} ++ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { ++ let exti = &crate::pac::EXTI; + +-#[cfg(gpio_v0)] +-mod irq_impl { +- use super::*; ++ EXTI_WAKERS[self.pin as usize].register(cx.waker()); + +- #[interrupt] +- unsafe fn EXTI7_0() { +- on_irq(); ++ if exti.intenr().read().mr(self.pin as _) == false { ++ // intenr cleared by on_irq, then we can assume it is triggered ++ Poll::Ready(()) ++ } else { ++ Poll::Pending ++ } ++ } + } + +- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { +- use crate::pac::Interrupt; ++ /* ++ EXTI0 ++ EXTI1 ++ EXTI2 ++ EXTI3 ++ EXTI4 ++ EXTI9_5 ++ EXTI15_10 ++ EXTI7_0 ++ EXTI15_8 ++ EXTI25_16 ++ */ ++ ++ /// safety: must be called only once ++ #[cfg(gpio_x0)] ++ mod irq_impl { ++ use super::*; ++ ++ #[interrupt] ++ unsafe fn EXTI7_0() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI15_8() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI25_16() { ++ on_irq(); ++ } ++ ++ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { ++ use crate::pac::Interrupt; + +- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI25_16 as u8); ++ } + } +-} + +-#[cfg(all(gpio_v3, ch641))] +-mod irq_impl { +- use super::*; ++ #[cfg(all(gpio_v3, not(ch641)))] ++ mod irq_impl { ++ use super::*; ++ ++ #[interrupt] ++ unsafe fn EXTI0() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI1() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI2() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI3() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI4() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI9_5() { ++ on_irq(); ++ } ++ #[interrupt] ++ unsafe fn EXTI15_10() { ++ on_irq(); ++ } + +- #[interrupt] +- unsafe fn EXTI7_0() { +- on_irq(); ++ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { ++ use crate::pac::Interrupt; ++ ++ qingke::pfic::enable_interrupt(Interrupt::EXTI0 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI1 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI2 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI3 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI4 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI9_5 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI15_10 as u8); ++ } + } + +- #[interrupt] +- unsafe fn EXTI15_8() { +- on_irq(); ++ #[cfg(gpio_v0)] ++ mod irq_impl { ++ use super::*; ++ ++ #[interrupt] ++ unsafe fn EXTI7_0() { ++ on_irq(); ++ } ++ ++ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { ++ use crate::pac::Interrupt; ++ ++ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); ++ } + } + +- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { +- use crate::pac::Interrupt; ++ #[cfg(all(gpio_v3, ch641))] ++ mod irq_impl { ++ use super::*; ++ ++ #[interrupt] ++ unsafe fn EXTI7_0() { ++ on_irq(); ++ } ++ ++ #[interrupt] ++ unsafe fn EXTI15_8() { ++ on_irq(); ++ } ++ ++ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) { ++ use crate::pac::Interrupt; + +- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); +- qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); ++ qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8); ++ } + } ++ pub(crate) use irq_impl::*; + } +- +-pub(crate) use irq_impl::*; +diff --git a/src/lib.rs b/src/lib.rs +index 997da34..b451a55 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -140,6 +140,7 @@ pub fn init(config: Config) -> Peripherals { + ::critical_section::with(|cs| unsafe { + gpio::init(cs); + dma::init(cs, config.dma_interrupt_priority); ++ #[cfg(feature = "exti")] + exti::init(cs); + }); + + diff --git a/ch32v-insert-coin/ext/qingke b/ch32v-insert-coin/ext/qingke new file mode 160000 index 0000000..2443891 --- /dev/null +++ b/ch32v-insert-coin/ext/qingke @@ -0,0 +1 @@ +Subproject commit 2443891811d7351d62e78085bcf60fa78c063c08 diff --git a/ch32v-insert-coin/ext/wavetable-synth b/ch32v-insert-coin/ext/wavetable-synth new file mode 160000 index 0000000..30033e1 --- /dev/null +++ b/ch32v-insert-coin/ext/wavetable-synth @@ -0,0 +1 @@ +Subproject commit 30033e1438c25aed4ea506cdbd69cc4ceffa0b4e diff --git a/ch32v-insert-coin/init.sh b/ch32v-insert-coin/init.sh new file mode 100755 index 0000000..bf063fc --- /dev/null +++ b/ch32v-insert-coin/init.sh @@ -0,0 +1,10 @@ +#!/bin/bash +DIR=${PWD} +mkdir ${PWD}/tmp +cd tmp +git clone ssh://git@git.glyphs.tech:222/taproot-tech/docker-devtools.git +cd docker-devtools/ch32 +ls +./build.sh +cd $DIR +rm -rf tmp diff --git a/ch32v-insert-coin/launch.sh b/ch32v-insert-coin/launch.sh new file mode 100755 index 0000000..ae7aa33 --- /dev/null +++ b/ch32v-insert-coin/launch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +podman run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-rust:latest /bin/bash diff --git a/ch32v-insert-coin/riscv32ec-unknown-none-elf.json b/ch32v-insert-coin/riscv32ec-unknown-none-elf.json new file mode 100644 index 0000000..b2022a5 --- /dev/null +++ b/ch32v-insert-coin/riscv32ec-unknown-none-elf.json @@ -0,0 +1,18 @@ +{ + "arch": "riscv32", + "atomic-cas": false, + "cpu": "generic-rv32", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+e,+c,+forced-atomics", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "riscv32", + "llvm-abiname": "ilp32e", + "max-atomic-width": 32, + "panic-strategy": "abort", + "relocation-model": "static", + "target-pointer-width": 32 +} diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs new file mode 100644 index 0000000..a8fdbb8 --- /dev/null +++ b/ch32v-insert-coin/src/app.rs @@ -0,0 +1,705 @@ +#[derive(Default, Clone, Copy)] +pub enum State { + // system is asleep, waiting for wake from coin insertion + DeepSleep, + // system is in low-power mode, dimmed lights, waiting for interaction + Idle, + // system is active. on entry: play coin sound. on button press: play different sound + #[default] + Active, +} + +pub mod settings { + + #[derive(Debug, Default, Clone, Copy)] + pub enum Level { + Off, + Low, + #[default] + Medium, + High, + Maximum, + } + + impl Level { + pub fn next(&mut self) { + *self = match self { + Self::Off => Self::Low, + Self::Low => Self::Medium, + Self::Medium => Self::High, + Self::High => Self::Maximum, + Self::Maximum => Self::Off, + }; + } + } + + // volume control + impl Level { + pub fn as_volume_divisor(&self) -> u8 { + match self { + Self::Off => u8::MAX, + Self::Low => 4, + Self::Medium => 3, + Self::High => 2, + Self::Maximum => 1, + } + } + + pub fn as_brightness_divisor(&self) -> u8 { + match self { + Self::Off => u8::MAX, + Self::Low => 8, + Self::Medium => 6, + Self::High => 4, + Self::Maximum => 2, + } + } + } + + #[derive(Clone, Copy)] + pub struct Settings { + pub brightness: Level, + pub volume: Level, + pub button_sound_index: usize, + } + + impl Default for Settings { + fn default() -> Self { + Self { + brightness: Level::Medium, + volume: Level::Medium, + button_sound_index: 0, + } + } + } +} + +pub mod sequencer { + pub struct BasicSequence<'a> { + sequence: &'a [u8], + index: usize, + } + impl<'a> BasicSequence<'a> { + pub fn new(sequence: &'a [u8]) -> Self { + Self { sequence, index: 0 } + } + pub fn next(&mut self) { + self.index += 1; + if self.index > self.sequence.len() - 1 { + self.index = 0; + } + } + pub fn get_value(&self) -> u8 { + self.sequence[self.index] + } + } + + pub struct SequenceEntry { + pub frequency_hz: u16, + pub duration_ms: u16, + } + + pub struct DynamicSequence<'a> { + sequence: &'a [SequenceEntry], + index: usize, + + // timer stuff + system_tick_rate_hz: usize, + ticks_remaining: usize, + + // playback stuff + num_loops: usize, + loop_count: usize, + + // misc + enabled: bool, + } + + impl<'a> DynamicSequence<'a> { + pub fn new(sequence: &'a [SequenceEntry], system_tick_rate_hz: usize) -> Self { + Self { + sequence, + index: 0, + system_tick_rate_hz, + ticks_remaining: 0, + num_loops: 1, + loop_count: 0, + enabled: false, + } + } + + pub fn play_sequence(&mut self, sequence: &'a [SequenceEntry], num_loops: usize) { + self.sequence = sequence; + self.num_loops = num_loops; + self.loop_count = 0; + self.enabled = true; + self.index = 0; + self.ticks_remaining = 0; + } + + pub fn enable(&mut self) { + self.enabled = true; + } + + pub fn disable(&mut self) { + self.enabled = false; + } + + pub fn tick(&mut self) { + if self.enabled { + self.ticks_remaining = self.ticks_remaining.saturating_sub(1); + } + } + + pub fn need_service(&self) -> bool { + self.enabled && self.ticks_remaining == 0 + } + + pub fn service(&mut self) -> Option { + let entry = &self.sequence[self.index]; + self.ticks_remaining = self.system_tick_rate_hz * (entry.duration_ms as usize) / 1000; + self.index = self.index.saturating_add(1); + + let out = if self.loop_count > self.num_loops { + None + } else { + Some(entry.frequency_hz) + }; + + if self.index > self.sequence.len() - 1 { + self.index = 0; + self.loop_count = self.loop_count.saturating_add(1); + } + + out + } + } +} + +use crate::insert_coin::{ + DacService, LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, +}; +use crate::synthesizer::SynthesizerService; + +pub use settings::Settings; + +// #[cfg(feature = "enable_print")] +use ch32_hal::println; + +pub struct TimerConfig { + pub sp_timer_ms: usize, + pub lp_timer_ms: usize, + pub batt_adc_timer_ms: usize, + pub usb_adc_timer_ms: usize, + pub led0_timer_ms: usize, + pub led1_timer_ms: usize, + pub shutdown_timer_s: usize, + // pub led2_timer_ms: usize, +} + +pub struct Timers { + sp_timer: TickTimerService, + lp_timer: TickTimerService, + batt_adc_timer: TickTimerService, + usb_adc_timer: TickTimerService, + led0_timer: TickTimerService, + led1_timer: TickTimerService, + shutdown_timer: TickTimerService, + pps_timer: TickTimerService, + // led2_timer: TickTimerService, +} + +impl Timers { + pub fn new(config: TimerConfig, system_tick_rate_hz: usize) -> Self { + Self { + sp_timer: TickTimerService::new( + TickServiceData::new(config.sp_timer_ms * system_tick_rate_hz / 1000), + false, + ), + lp_timer: TickTimerService::new( + TickServiceData::new(config.lp_timer_ms * system_tick_rate_hz / 1000), + false, + ), + batt_adc_timer: TickTimerService::new( + TickServiceData::new(config.batt_adc_timer_ms * system_tick_rate_hz / 1000), + false, + ), + usb_adc_timer: TickTimerService::new( + TickServiceData::new(config.usb_adc_timer_ms * system_tick_rate_hz / 1000), + false, + ), + led0_timer: TickTimerService::new( + TickServiceData::new(config.led0_timer_ms * system_tick_rate_hz / 1000), + true, + ), + led1_timer: TickTimerService::new( + TickServiceData::new(config.led1_timer_ms * system_tick_rate_hz / 1000), + true, + ), + shutdown_timer: TickTimerService::new( + TickServiceData::new(config.shutdown_timer_s), + false, + ), + pps_timer: TickTimerService::new(TickServiceData::new(system_tick_rate_hz), true), + // led2_timer: TickTimerService::new( + // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), + // true, + // ), + } + } + pub fn tick(&mut self) { + self.sp_timer.tick(); + self.lp_timer.tick(); + self.batt_adc_timer.tick(); + self.usb_adc_timer.tick(); + self.led0_timer.tick(); + self.led1_timer.tick(); + self.pps_timer.tick(); + // self.led2_timer.tick(); + } + pub fn need_service(&self) -> bool { + self.sp_timer.need_service() + | self.lp_timer.need_service() + | self.batt_adc_timer.need_service() + | self.usb_adc_timer.need_service() + | self.led0_timer.need_service() + | self.led1_timer.need_service() + | self.shutdown_timer.need_service() + | self.pps_timer.need_service() + // | self.led2_timer.need_service() + } + pub fn init(&mut self) { + self.led0_timer.reset(); + self.led0_timer.enable(true); + } +} + +// pub struct ServiceConfigs { + +// } +// things that sort of don't touch hardware but also do? +// TODO: make this merged with the interfaces maybe +// but also maybe not, since these are things that require servicing +pub struct Services { + pub led0: LedService, + pub led1: LedService, + // pub led2: LedService, + pub synth0: SynthesizerService, + pub sample_player: DacService<'static>, + pub sequencer: sequencer::DynamicSequence<'static>, +} + +impl Services { + pub fn tick(&mut self) { + self.synth0.tick(); + self.sample_player.tick(); + self.sequencer.tick(); + } +} + +pub struct Config { + pub system_tick_rate_hz: usize, + pub timers: TimerConfig, +} + +pub struct Sequences { + pub led0: sequencer::BasicSequence<'static>, + pub led1: sequencer::BasicSequence<'static>, + // pub led2: sequencer::BasicSequence<'static>, + pub audio: &'static [(&'static [sequencer::SequenceEntry], usize)], +} + +// things that touch hardware +pub struct Interfaces { + pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, + pub adc_core: crate::AdcCore, + pub amp: crate::Amplifier, + pub usb: crate::Usb, +} + +pub struct App { + state: State, + pub settings: Settings, + timers: Timers, + services: Services, + sequences: Sequences, + interfaces: Interfaces, +} + +use settings::Level; +impl App { + pub fn new( + config: Config, + services: Services, + sequences: Sequences, + interfaces: Interfaces, + settings: Settings, + ) -> Self { + Self { + state: State::default(), + settings, + timers: Timers::new(config.timers, config.system_tick_rate_hz), + services, + sequences, + interfaces, + } + } + + pub fn init(&mut self) { + // self.timers.init(); + self.interfaces.amp.enable(); + + self.timers.batt_adc_timer.reset(); + self.timers.batt_adc_timer.enable(true); + + self.timers.usb_adc_timer.reset(); + self.timers.usb_adc_timer.enable(true); + + self.timers.led0_timer.reset(); + self.timers.led0_timer.enable(true); + + self.timers.led1_timer.reset(); + self.timers.led1_timer.enable(true); + + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + + self.timers.pps_timer.reset(); + self.timers.pps_timer.enable(true); + + // self.timers.led2_timer.reset(); + // self.timers.led2_timer.enable(true); + + // self.services.synth0.set_freq(1); + self.services.synth0.disable(); + self.services.sequencer.disable(); + + crate::riscv::asm::delay(2_500_000); + } + + pub fn set_state(&mut self, state: State) { + self.state = state + } + + pub fn state(&self) -> State { + self.state + } + + pub fn settings(&self) -> Settings { + self.settings + } + + pub fn tick(&mut self) { + self.timers.tick(); + self.services.tick(); + } + + pub fn service(&mut self) { + // timers + if self.timers.sp_timer.need_service() { + self.timers.sp_timer.service(); + #[cfg(feature = "enable_print")] + println!("sp service"); + self.timers.sp_timer.reset(); + self.main_button_short_press(); + } + if self.timers.lp_timer.need_service() { + self.timers.lp_timer.service(); + #[cfg(feature = "enable_print")] + println!("lp service"); + self.timers.lp_timer.reset(); + self.main_button_long_press(); + } + if self.timers.batt_adc_timer.need_service() { + self.timers.batt_adc_timer.service(); + if !self.interfaces.usb.powered() { + let bv = self.interfaces.adc_core.get_battery_voltage(); + let avg = self.interfaces.adc_core.get_average(); + // #[cfg(feature = "enable_print")] + // println!("batt adc service: {bv}, {avg}"); + // println!("none USB"); + if avg < 421 { + self.set_state(State::DeepSleep); + } + } + } + if self.timers.usb_adc_timer.need_service() { + self.timers.usb_adc_timer.service(); + #[cfg(feature = "enable_print")] + println!("usb adc service"); + } + if self.timers.led0_timer.need_service() { + let out = match self.settings.brightness { + Level::Off => 0, + Level::Low => 5, + Level::Medium => 25, + Level::High => 75, + Level::Maximum => { + self.sequences.led0.next(); + self.sequences.led0.get_value() / 6 + } + }; + + self.timers.led0_timer.service(); + self.services.led0.set_amplitude(out); + // #[cfg(feature = "enable_print")] + // println!("led0 sevice {}", self.sequences.led0.get_value()); + } + if self.timers.led1_timer.need_service() { + let out = match self.settings.brightness { + Level::Off => 0, + Level::Low => 5, + Level::Medium => 25, + Level::High => 75, + Level::Maximum => { + self.sequences.led1.next(); + self.sequences.led1.get_value() / 6 + } + }; + self.timers.led1_timer.service(); + self.services.led1.set_amplitude(out); + + // #[cfg(feature = "enable_print")] + // println!("led1 service"); + } + if self.timers.pps_timer.need_service() { + self.timers.pps_timer.service(); + self.timers.shutdown_timer.tick(); + } + if self.timers.shutdown_timer.need_service() { + self.timers.shutdown_timer.service(); + self.timers.shutdown_timer.reset(); + // println!("eepy"); + self.set_state(State::DeepSleep); + } + // if self.timers.led2_timer.need_service() { + // let out = match self.settings.brightness { + // Level::Off => 0, + // Level::Low => 5, + // Level::Medium => 25, + // Level::High => 75, + // Level::Maximum => { + // self.sequences.led2.next(); + // self.sequences.led2.get_value() / 6 + // } + // }; + // self.timers.led2_timer.service(); + // self.services.led2.set_amplitude(out); + + // // #[cfg(feature = "enable_print")] + // // println!("led2 service"); + // } + + // services + if self.services.led0.need_service() { + self.interfaces + .pwm_core + .write_amplitude(self.services.led0.channel, self.services.led0.amplitude); + self.services.led0.service(); + } + if self.services.led1.need_service() { + self.interfaces + .pwm_core + .write_amplitude(self.services.led1.channel, self.services.led1.amplitude); + self.services.led1.service(); + } + // if self.services.led2.need_service() { + // self.interfaces + // .pwm_core + // .write_amplitude(self.services.led2.channel, self.services.led2.amplitude); + // self.services.led2.service(); + // } + + // TODO: disable when you get to the end automatically + // in the sequencer, not here + if self.services.sequencer.need_service() { + if let Some(out) = self.services.sequencer.service() { + if out == 0 { + self.services.synth0.disable(); + } else { + self.services.synth0.enable(); + self.services.synth0.set_freq(out.into()); + } + } else { + self.services.sequencer.disable(); + self.services.synth0.disable(); + } + } + + if self.services.synth0.need_service() { + let out = match self.services.synth0.service() { + Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), + None => 0, + }; + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out); + } + + if self.services.sample_player.need_service() { + self.services.sample_player.service(); + let out = self.services.sample_player.get_amplitude() / 2; + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); + } + } +} + +// interfaces to the app (for buttons, etc.) +impl App { + pub fn shut_down(&mut self) { + self.interfaces + .pwm_core + .write_amplitude(self.services.led0.channel, 0); + self.interfaces + .pwm_core + .write_amplitude(self.services.led1.channel, 0); + crate::riscv::asm::delay(10_000_000); + + self.interfaces.pwm_core.pwm.borrow().shutdown(); + self.interfaces.adc_core.shutdown(); + + self.interfaces.amp.disable(); + } + pub fn volume_button(&mut self) { + self.settings.volume.next(); + #[cfg(feature = "enable_print")] + println!("new volume: {:?}", self.settings.volume); + self.services + .sequencer + .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } + pub fn brightness_button(&mut self) { + self.settings.brightness.next(); + #[cfg(feature = "enable_print")] + println!("new brightness: {:?}", self.settings.brightness); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } + pub fn main_button_press(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("main button press"); + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); + self.timers.shutdown_timer.reset(); + self.timers.sp_timer.enable(true); + self.timers.lp_timer.enable(true); + self.timers.shutdown_timer.enable(true); + self.main_button_click(); + } + pub fn main_button_release(&mut self) { + // TODO + // #[cfg(feature = "enable_print")] + // println!("main button release"); + // let timers = ( + // self.timers.sp_timer.is_enabled(), + // self.timers.lp_timer.is_enabled(), + // ); + + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); + // match timers { + // // click + // (true, true) => self.main_button_click(), + // // short press + // (false, true) => self.main_button_short_press(), + // // long press + // (false, false) => self.main_button_long_press(), + // // anything else is not possible + // _ => {} + // } + } + pub fn coin_detect(&mut self) { + #[cfg(feature = "enable_print")] + println!("coin detect"); + // self.services.sample_player.play_sample(); + self.services + .sequencer + .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } +} + +// Events +impl App { + pub fn main_button_click(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("click"); + let data = self.sequences.audio[self.settings.button_sound_index]; + self.services.sequencer.play_sequence(data.0, data.1); + + self.settings.button_sound_index += 1; + if self.settings.button_sound_index > self.sequences.audio.len() - 1 { + self.settings.button_sound_index = 0; + } + + self.services.synth0.enable(); + + // TODO: + // this is a hack to stop the coin thing from playing. + self.services.sample_player.disable(); + } + fn main_button_short_press(&self) { + // TODO + #[cfg(feature = "enable_print")] + println!("short press"); + } + fn main_button_long_press(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("long press"); + self.set_state(State::DeepSleep); + } +} + +// Getters +impl App { + pub fn get_state(&self) -> State { + self.state + } + pub fn get_settings(&self) -> Settings { + self.settings + } + pub fn should_wake(&self) -> bool { + if self.interfaces.usb.powered() { + return true; + } else { + if self.interfaces.adc_core.get_average() >= 421 { + return true; + } + } + return false; + } +} + +// TODO LIST +// BROKEN: +// 1. audio scaling causes crash +// 2. popping on sample playback +// 3. actual app sequence (start at idle?) +// +// AUDIO: +// 3. amp_en control +// +// LED: +// +// INTERFACE: +// 1. short press handling +// 2. long press handling +// +// SYSTEM: +// 1. deep sleep +// 2. battery voltage monitoring +// 3. battery voltage cutoff +// +// STRETCH TODO LIST +// 1. clean up edge detector +// 2. better handling for pwm scaling (brightness / volume) +// 3. better interrupt handling structs +// 4. led DynamicSequence diff --git a/ch32v-insert-coin/src/debounced_gpio.rs b/ch32v-insert-coin/src/debounced_gpio.rs new file mode 100644 index 0000000..784c3fd --- /dev/null +++ b/ch32v-insert-coin/src/debounced_gpio.rs @@ -0,0 +1,66 @@ +use crate::insert_coin::{TickService, TickServiceData, TickTimerService}; +use ch32_hal::gpio::{AnyPin, Input, Pull}; + +pub struct DebouncedGPIO<'a> { + input: Input<'a>, + // value of the GPIO + value: bool, + // GPIO is ready (debounced) + ready: bool, + // debouncer is active + active: bool, + // debounce timer + timer: TickTimerService, +} + +impl<'a> DebouncedGPIO<'a> { + pub fn new(pin: AnyPin, system_tick_rate_hz: usize, debounce_time_ms: usize) -> Self { + // coin debounce timer (100ms) + Self { + input: Input::new(pin, Pull::None), + value: false, + ready: false, + active: false, + timer: TickTimerService::new( + TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), + false, + ), + } + } + + pub fn ready(&self) -> bool { + self.ready + } + + pub fn active(&self) -> bool { + self.active + } + + pub fn value(&self) -> bool { + self.value + } + + pub fn begin(&mut self) { + self.reset(); + self.timer.enable(true); + self.active = true; + } + + pub fn reset(&mut self) { + self.timer.reset(); + self.ready = false; + self.active = false; + } + + pub fn service(&mut self) { + self.timer.tick(); + if self.timer.need_service() { + self.timer.reset(); + self.value = self.input.is_high(); + self.ready = true; + } + } + pub fn is_high_immediate(&self) -> bool { + self.input.is_high() + } +} diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs new file mode 100644 index 0000000..8c0a606 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -0,0 +1,183 @@ +use ch32_hal::timer::simple_pwm::SimplePwm; +use ch32_hal::timer::Channel; +use ch32_hal::timer::GeneralInstance16bit; + +use crate::insert_coin::services::{DacService, LedService, Service, TickService, TickServiceData}; + +// static mut led0_index: usize = 0; +// static LED0: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; + +// static mut LED1_INDEX: usize = 0; +// static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8]; + +pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { + pub pwm: core::cell::RefCell>, +} + +impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { + pub fn new(pwm: SimplePwm<'d, T>) -> Self { + Self { + pwm: core::cell::RefCell::new(pwm), + } + } + + // pub fn get_handle(&'d self, ch: Channel) -> SimplePwmHandle<'d, '_, T> { + // SimplePwmHandle { + // core: &self, + // channel: ch, + // } + // } + + pub fn write_amplitude(&self, ch: Channel, amplitude: u8) { + if !self.pwm.borrow().is_enabled(ch) { + self.pwm.borrow_mut().enable(ch); + } + let max_duty = self.pwm.borrow().get_max_duty(); + let dc = amplitude as u32 * max_duty / 100; + self.pwm.borrow_mut().set_duty(ch, dc); + } + + pub fn disable(&self, ch: Channel) { + self.pwm.borrow_mut().disable(ch); + } +} + +// pub struct SimplePwmHandle { +// core: &'static SimplePwmCore<'_, T: GeneralInstance16Bit>, +// channel: Channel, +// } + +// impl SimplePwmHandle { +// pub fn set_amplitude(&self, amplitude: u8) {} +// } + +pub struct CoreConfig { + pub tick_rate_hz: usize, +} +impl CoreConfig { + pub fn new(tick_rate_hz: usize) -> Self { + Self { tick_rate_hz } + } +} + +#[derive(Default)] +struct Core { + _tick: usize, + active: bool, +} + +pub struct InsertCoin<'a, T: GeneralInstance16bit> { + core: Core, + pub config: CoreConfig, + pwm_core: SimplePwmCore<'a, T>, + pub led0: LedService, + pub led1: LedService, + // led2: LedService, + pub dac: DacService<'a>, +} + +impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { + pub fn new(config: CoreConfig, pwm_core: SimplePwmCore<'a, T>) -> Self { + // LED0 servicer setup + let led0 = LedService::new(ch32_hal::timer::Channel::Ch3); + + // LED1 servicer setup + let led1 = LedService::new(ch32_hal::timer::Channel::Ch1); + + // DAC servicer setup + let dac_sample_rate_hz = 4000; + let dac_tick_per_service = config.tick_rate_hz / (dac_sample_rate_hz); + let dac_service_data = TickServiceData::new(dac_tick_per_service); + let dac = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); + + Self { + config, + core: Default::default(), + pwm_core, + led0, + led1, + // led2, + dac, + } + } + + /// takes self reference and runs + pub fn service(&mut self) { + if self.is_active() { + self.dac.tick(); + + if self.led0.need_service() { + self.pwm_core + .write_amplitude(self.led0.channel, self.led0.amplitude); + self.led0.service(); + } + + if self.led1.need_service() { + self.pwm_core + .write_amplitude(self.led1.channel, self.led1.amplitude); + self.led1.service(); + } + + if self.dac.need_service() { + self.dac.service(); + // TODO: adpcm-pwm-dac:e4c811653781e69e40b63fd27a8c1e20 + self.pwm_core + .write_amplitude(self.dac.channel, self.dac.get_amplitude() as u8); + } + } + } + + // /// consumes self and runs + // pub fn run(mut self) -> ! { + // let mut delay = Delay; + // let tick_interval_us = 1000000/self.config.tick_rate_hz; + + // let mut led0_index = 0; + // let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; + + // let mut led1_index = 0; + // let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; + + // loop { + // self.dac.tick(); + + // if(self.led0.need_service()) { + // self.led0.set_amplitude(led0_dcs[led0_index]); + // self.pwm_core.write_amplitude(self.led0.channel, self.led0.amplitude); + + // led0_index += 1; + // if led0_index > led0_dcs.len() - 1 { + // led0_index = 0; + // } + // self.led0.service(); + // } + + // if(self.led1.need_service()) { + // self.led1.set_amplitude(led1_dcs[led1_index]); + // self.pwm_core.write_amplitude(self.led1.channel, self.led1.amplitude); + + // led1_index += 1; + // if led1_index > led1_dcs.len() - 1 { + // led1_index = 0; + // } + // self.led1.service(); + // } + + // if(self.dac.need_service()) { + // self.dac.service(); + // // TODO: adpcm-pwm-dac:e4c811653781e69e40b63fd27a8c1e20 + // self.pwm_core.write_amplitude(self.dac.channel, self.dac.get_amplitude() as u8); + // } + + // delay.delay_us(tick_interval_us as u32); + // } + // } + + pub fn is_active(&self) -> bool { + self.core.active + } + + pub fn set_active(&mut self, active: bool) { + self.core.active = active; + } +} diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs new file mode 100644 index 0000000..766dcca --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -0,0 +1,7 @@ +mod insert_coin; +mod services; + +pub use services::{DacService, LedService, Service, TickTimerService}; + +pub use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; +pub use services::{TickService, TickServiceData}; diff --git a/ch32v-insert-coin/src/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs new file mode 100644 index 0000000..0a3b364 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -0,0 +1,68 @@ +use crate::insert_coin::services::{TickService, TickServiceData}; + +use adpcm_pwm_dac::dac::DpcmDecoder; +use ch32_hal::timer::Channel; + +pub struct DacService<'a> { + service_data: core::cell::RefCell, + dpcm_decoder: core::cell::RefCell>, + amplitude: core::cell::RefCell, + pub channel: Channel, + enabled: bool, +} + +impl<'a> DacService<'a> { + pub fn new(channel: Channel, service_data: TickServiceData) -> Self { + Self { + service_data: core::cell::RefCell::new(service_data), + dpcm_decoder: core::cell::RefCell::new(DpcmDecoder::new()), + amplitude: core::cell::RefCell::new(0), + channel, + enabled: false, + } + } + + pub fn play_sample(&mut self) { + self.dpcm_decoder.borrow_mut().seek_to_sample(0); + self.enabled = true; + } + + pub fn disable(&mut self) { + self.enabled = false; + } + + pub fn load_data(&self, data: &'a [u8]) { + self.dpcm_decoder.borrow_mut().load_data(data); + self.dpcm_decoder.borrow_mut().seek_to_sample(0); + } + + pub fn set_amplitude(&self, amplitude: usize) { + self.amplitude.replace(amplitude); + } + pub fn get_amplitude(&self) -> usize { + *self.amplitude.borrow() + } +} + +impl<'a> TickService for DacService<'a> { + fn tick(&self) { + if self.enabled { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + } + } + + fn need_service(&self) -> bool { + self.enabled && self.service_data.borrow().ticks_remaining == 0 + } + + fn service(&self) { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_per_service; + if (self.dpcm_decoder.borrow().is_done()) { + self.set_amplitude(0); + } else { + self.set_amplitude(self.dpcm_decoder.borrow_mut().output_next()); + } + } +} diff --git a/ch32v-insert-coin/src/insert_coin/services/led.rs b/ch32v-insert-coin/src/insert_coin/services/led.rs new file mode 100644 index 0000000..c1bf890 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -0,0 +1,36 @@ +use ch32_hal::timer::Channel; + +use crate::insert_coin::services::Service; + +pub struct LedService { + // need_service: core::cell::RefCell, + need_service: bool, + pub channel: Channel, + pub amplitude: u8, +} + +impl LedService { + pub fn new(channel: Channel) -> Self { + Self { + // service_data: core::cell::RefCell::new(service_data), + need_service: false, + channel, + amplitude: 0, + } + } + + pub fn set_amplitude(&mut self, amplitude: u8) { + self.amplitude = amplitude; + self.need_service = true; + } +} + +impl Service for LedService { + fn need_service(&self) -> bool { + self.need_service + } + + fn service(&mut self) { + self.need_service = false; + } +} diff --git a/ch32v-insert-coin/src/insert_coin/services/mod.rs b/ch32v-insert-coin/src/insert_coin/services/mod.rs new file mode 100644 index 0000000..a761548 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/mod.rs @@ -0,0 +1,11 @@ +mod services; +pub use services::{TickService, TickServiceData, Service}; + +mod led; +pub use led::LedService; + +mod dac; +pub use dac::DacService; + +mod tick_timer; +pub use tick_timer::TickTimerService; \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/services.rs b/ch32v-insert-coin/src/insert_coin/services/services.rs new file mode 100644 index 0000000..e2c86e2 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/services.rs @@ -0,0 +1,31 @@ +pub struct TickServiceData { + pub ticks_per_service: usize, + pub ticks_remaining: usize, +} + +impl TickServiceData { + pub fn new(ticks_per_service: usize) -> Self { + Self { + ticks_per_service, + ticks_remaining: ticks_per_service, + } + } +} + +pub trait TickService { + /// indicate to the service that a tick has occurred + fn tick(&self); + + /// return true if service needs the service() routine run + fn need_service(&self) -> bool; + + /// service routine - handle what needs to be done (non blocking) when + /// the service needs to be serviced here + fn service(&self); +} + + +pub trait Service { + fn need_service(&self) -> bool; + fn service(&mut self); +} \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs new file mode 100644 index 0000000..863314a --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs @@ -0,0 +1,49 @@ +use crate::insert_coin::services::{TickService, TickServiceData}; + +pub struct TickTimerService { + service_data: core::cell::RefCell, + _auto_reset: bool, + enabled: bool, +} + +impl TickTimerService { + pub fn new(service_data: TickServiceData, auto_reset: bool) -> Self { + Self { + service_data: core::cell::RefCell::new(service_data), + _auto_reset: auto_reset, + enabled: false, + } + } + + pub fn is_enabled(&self) -> bool { + self.enabled + } + + pub fn reset(&mut self) { + let mut sd = self.service_data.borrow_mut(); + sd.ticks_remaining = sd.ticks_per_service; + self.enabled = false; + } + + pub fn enable(&mut self, enable: bool) { + self.enabled = enable; + } +} + +impl TickService for TickTimerService { + fn tick(&self) { + if self.enabled { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + } + } + + fn need_service(&self) -> bool { + self.enabled && self.service_data.borrow().ticks_remaining == 0 + } + + fn service(&self) { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_per_service; + } +} diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs new file mode 100644 index 0000000..694ce75 --- /dev/null +++ b/ch32v-insert-coin/src/main.rs @@ -0,0 +1,648 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +// app stuff +mod insert_coin; + +// system stuff +mod system; + +mod debounced_gpio; +use debounced_gpio::DebouncedGPIO; + +// synthesizer :3 +mod synthesizer; +use synthesizer::SynthesizerService; + +// sequences +mod sequences; +use sequences::SEQUENCE_LIST; + +mod app; +use app::{ + sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig, +}; + +use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; +use insert_coin::{CoreConfig, DacService, InsertCoin, LedService, SimplePwmCore}; + +use ch32_hal as hal; +use hal::bind_interrupts; +use hal::delay::Delay; +use hal::gpio::{AnyPin, Input, Level, Output, OutputOpenDrain, Pin, Pull}; +use hal::time::Hertz; +use hal::timer::low_level::CountingMode; +use hal::timer::simple_pwm::{PwmPin, SimplePwm}; + +use hal::println; + +use qingke::riscv; + +use crate::app::sequencer::{DynamicSequence, SequenceEntry}; + +static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; + +pub struct Usb { + usb_pin: Input<'static>, +} + +impl Usb { + pub fn new(usb_pin: Input<'static>) -> Self { + Self { usb_pin } + } + pub fn powered(&self) -> bool { + self.usb_pin.is_high() + } + // pub fn enable(&mut self) { + // self.usb_pin.set_as_input(Pull::Up); + // } + // pub fn disable(&mut self) { + // self.usb_pin.set_as_input(Pull::None); + // } +} + +pub struct Amplifier { + amp_en: OutputOpenDrain<'static>, +} + +impl Amplifier { + pub fn new(amp_en: OutputOpenDrain<'static>) -> Self { + let mut amp = Self { amp_en }; + amp.disable(); + amp + } + pub fn enable(&mut self) { + self.amp_en.set_low(); + } + pub fn disable(&mut self) { + self.amp_en.set_high(); + } + pub fn enabled(&self) -> bool { + !self.amp_en.is_set_high() + } +} + +use hal::adc::Adc; +use hal::peripherals::{ADC1, PD4}; + +pub struct AdcCore { + adc: Adc<'static, ADC1>, + battery_pin: PD4, + batt_values: [u16; 10], + index: usize, +} + +impl AdcCore { + pub fn new(mut adc: Adc<'static, ADC1>, pin: PD4) -> Self { + riscv::asm::delay(20_000); + adc.calibrate(); + Self { + adc, + battery_pin: pin, + batt_values: [1024; 10], + index: 0, + } + } + + // TODO make this a float or something + pub fn get_battery_voltage(&mut self) -> u16 { + let val = self + .adc + .convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241); + self.batt_values[self.index] = val; + self.index += 1; + if self.index > &self.batt_values.len() - 1 { + self.index = 0; + } + val + } + + pub fn get_average(&self) -> u16 { + let mut sum = 0; + for value in &self.batt_values { + sum += value; + } + sum / self.batt_values.len() as u16 + } + + pub fn shutdown(&mut self) { + self.adc.shutdown() + } +} + +#[derive(Debug)] +struct Flag { + value: bool, +} +impl Flag { + pub fn active(&self) -> bool { + unsafe { core::ptr::read_volatile(&raw const self.value as *const bool) } + } + pub fn set(&mut self) { + unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, true) } + } + pub fn clear(&mut self) { + unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, false) } + } +} + +#[derive(Debug)] +struct InputFlags { + sense_coin_flag: Flag, + main_btn_flag: Flag, + volume_btn_flag: bool, + light_ctrl_btn_flag: bool, + systick_flag: Flag, +} + +impl Default for InputFlags { + fn default() -> Self { + Self { + sense_coin_flag: Flag { value: false }, + main_btn_flag: Flag { value: false }, + volume_btn_flag: false, + light_ctrl_btn_flag: false, + systick_flag: Flag { value: false }, + } + } +} + +static mut INPUT_FLAGS: InputFlags = InputFlags { + sense_coin_flag: Flag { value: false }, + main_btn_flag: Flag { value: false }, + volume_btn_flag: false, + light_ctrl_btn_flag: false, + systick_flag: Flag { value: false }, +}; + +struct Test {} +impl Handler for Test { + unsafe fn on_interrupt() { + // #[cfg(feature = "enable_print")] + // println!("on_interrupt()"); + critical_section::with(|_| unsafe { + let flags = system::clear_interrupt(2, 6); + if flags[0] { + // safe because single-threaded + #[allow(static_mut_refs)] + INPUT_FLAGS.sense_coin_flag.set(); + } + if flags[1] { + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.set(); + } + }); + } +} + +#[qingke_rt::interrupt(core)] +fn SysTick() { + let r = &ch32_hal::pac::SYSTICK; + + // Clear interrupt flag + r.sr().write(|w| w.set_cntif(false)); + + unsafe { + // safe because single-threaded + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.set(); + } +} + +fn systick_init(tick_freq_hz: usize) { + let r = &ch32_hal::pac::SYSTICK; + + // Calculate counts per millisecond using HCLK/8 as clock source + // HCLK/8 = 48MHz/8 = 6MHz + // For tick_freq_hz interrupt: 6MHz / tick_freq_hz + let systick_per_tick = (48_000_000 / 8 / tick_freq_hz) as u32; + + // Reset SysTick + r.ctlr().write(|w| { + // Start with everything disabled + }); + + // Set compare register and reset counter + r.cmp().write_value(systick_per_tick - 1); + r.cnt().write_value(0); + + // Clear interrupt flag + r.sr().write(|w| w.set_cntif(false)); + + // Configure and start SysTick + r.ctlr().write(|w| { + w.set_ste(true); // Enable counter + w.set_stie(true); // Enable interrupt + w.set_stre(true); // Auto reload enable + w.set_stclk(ch32_hal::pac::systick::vals::Stclk::HCLK_DIV8); // HCLK/8 clock source + }); +} +fn systick_stop() { + let r = &ch32_hal::pac::SYSTICK; + // Reset SysTick + r.ctlr().write(|w| { + // Start with everything disabled + }); +} + +bind_interrupts!(struct Irqs { + EXTI7_0 => Test; +}); + +// TODO: remove +use app::settings::Settings; +use insert_coin::TickTimerService; +use insert_coin::{TickService, TickServiceData}; + +fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { + // initialize ADC core first, and exit if battery is too low + let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); + let mut batt_monitor_pin = p.PD4; + let mut adc_core = AdcCore::new(adc, batt_monitor_pin); + + let mut usb_detect_pin = p.PD5; + let usb_detect_input = Input::new(usb_detect_pin, Pull::Up); + let usb = Usb::new(usb_detect_input); + + let bv = adc_core.get_battery_voltage(); + + // if we don't have USB power, and the batt ADC reads under 421, don't wake + if !usb.powered() && bv < 421 { + adc_core.shutdown(); + return app_settings; + } + // === output setup === + + // LED0 output setup + let led0_pin = PwmPin::new_ch3::<0>(p.PC3); + let led0_ch = hal::timer::Channel::Ch3; + + // LED1 output setup + let led1_pin = PwmPin::new_ch1::<0>(p.PD2); + let led1_ch = hal::timer::Channel::Ch1; + + // DAC output setup + let dac_pin = PwmPin::new_ch4::<0>(p.PC4); + // let dac_ch = hal::timer::Channel::Ch4; + + // PWM timer setup + let mut pwm = SimplePwm::new( + p.TIM1, + Some(led1_pin), + // Some(led2_pin), + None, + Some(led0_pin), + Some(dac_pin), + Hertz::khz(200), + CountingMode::default(), + ); + + pwm.set_polarity(led0_ch, OutputPolarity::ActiveHigh); + pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); + let mut pwm_core = SimplePwmCore::new(pwm); + pwm_core.write_amplitude(led0_ch, 0); + pwm_core.write_amplitude(led1_ch, 0); + + // pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow); + + let tick_rate_hz = 50000; + + let core_config = CoreConfig::new(tick_rate_hz); + + // === input setup === + + // adc + // let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); + // let mut batt_monitor_pin = p.PD4; + // let adc_core = AdcCore::new(adc, batt_monitor_pin); + + // adc2 + // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); + + // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); + + // #[cfg(feature = "enable_print")] + // println!("ADC calibration value: {}", adc_cal); + + // definitions + let sense_coin_pin = p.PC2; + let main_btn_pin = p.PD6; + let volume_btn_pin = p.PC6; + let light_ctrl_btn_pin = p.PC7; + let amp_en = p.PC5; + // let extra_io_1 = p.PD0; + // let extra_io_2 = p.PD3; + + let mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default()); + let amp = Amplifier::new(amp_en_output); + + // set up interrupts + unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) }; + unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) }; + + // coin debouncer (100ms) + let mut sense_coin_input = + DebouncedGPIO::new(sense_coin_pin.degrade(), core_config.tick_rate_hz, 20); + // main button debouncer (100ms) + let mut main_btn_input = + DebouncedGPIO::new(main_btn_pin.degrade(), core_config.tick_rate_hz, 20); + // button debouncer (100ms) + let mut volume_btn_input = + DebouncedGPIO::new(volume_btn_pin.degrade(), core_config.tick_rate_hz, 100); + // button debouncer (100ms) + let mut light_ctrl_btn_input = + DebouncedGPIO::new(light_ctrl_btn_pin.degrade(), core_config.tick_rate_hz, 100); + + let timer_config = TimerConfig { + sp_timer_ms: 1000, + lp_timer_ms: 3000, + batt_adc_timer_ms: 1000, + usb_adc_timer_ms: 10000, + led0_timer_ms: 100, + led1_timer_ms: 100, + // 4 hours: + // shutdown_timer_s: 1, + shutdown_timer_s: 4 * 60 * 60 * 110 / 100, + // led2_timer_ms: 100, + }; + + let app_config = Config { + system_tick_rate_hz: tick_rate_hz, + timers: timer_config, + }; + + // DAC servicer setup + let dac_sample_rate_hz = 16000; + let dac_tick_per_service = tick_rate_hz / dac_sample_rate_hz; + let dac_service_data = TickServiceData::new(dac_tick_per_service); + + // let coin_sound = include_bytes!("../audio/coin5.raw"); + // let coin_sound = include_bytes!("../audio/coin2.raw"); + + let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); + // sample_player.load_data(coin_sound); + + let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz); + + let app_services = Services { + led0: LedService::new(led0_ch), + led1: LedService::new(led1_ch), + // led2: LedService::new(led2_ch), + synth0: SynthesizerService::new(tick_rate_hz), + sample_player, + sequencer, + }; + + let app_sequences = Sequences { + led0: BasicSequence::new(&LED0_SEQ), + led1: BasicSequence::new(&LED0_SEQ), + // led2: BasicSequence::new(&LED0_SEQ), + audio: &SEQUENCE_LIST, + }; + + let app_interfaces = Interfaces { + pwm_core, + adc_core, + amp, + usb, + }; + + let mut app = App::new( + app_config, + app_services, + app_sequences, + app_interfaces, + app_settings, + ); + + let need_sound = unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.main_btn_flag.active() { + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.clear(); + true + } else { + false + } + }; + + // init systick + systick_init(tick_rate_hz); + + // set up interrupts + unsafe { + use hal::pac::Interrupt; + use qingke::interrupt::Priority; + use qingke_rt::CoreInterrupt; + + system::clear_interrupt(2, 6); + + qingke::pfic::set_priority(CoreInterrupt::SysTick as u8, Priority::P15 as u8); + + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); + } + + // MAIN APPLICATION + // process + // -depress big button (insert coin button) and it wakes up and turns on led one led at a fixed brightness + // -we will want one sound, the coin insert sound, to play when coin button is pressed. (This is when they insert a coin) + // -We will want a different sound or potentially multiple different sounds played in a rotating fashion when someone presses the button. Probably do some led blinking as well.(This is when they depress the big insert to play button) + // -depress the big button for approx 2s and it puts the led into low light mode. Just making it dimmer than usual. + // -depress the big button for 5s and it goes into deep sleep, everything off with the low sleep current draw + + // #[cfg(feature = "enable_print")] + // println!("begin"); + let mut volume_btn_prev = volume_btn_input.is_high_immediate(); + let mut light_ctrl_btn_prev = light_ctrl_btn_input.is_high_immediate(); + + app.init(); + if need_sound { + app.main_button_click(); + } + loop { + // system servicing + + // volume edge detector + if !volume_btn_input.active() { + let volume_btn_curr = volume_btn_input.is_high_immediate(); + if volume_btn_prev != volume_btn_curr { + volume_btn_input.begin(); + volume_btn_prev = volume_btn_curr; + } + } + + volume_btn_input.service(); + if volume_btn_input.ready() { + // #[cfg(feature = "enable_print")] + // println!("volume btn value: {}", volume_btn_input.value()); + if !volume_btn_input.value() { + app.volume_button(); + } + volume_btn_input.reset(); + } + + // brightness edge detector + if !light_ctrl_btn_input.active() { + let light_ctrl_btn_curr = light_ctrl_btn_input.is_high_immediate(); + if light_ctrl_btn_prev != light_ctrl_btn_curr { + light_ctrl_btn_input.begin(); + light_ctrl_btn_prev = light_ctrl_btn_curr; + } + } + + light_ctrl_btn_input.service(); + if light_ctrl_btn_input.ready() { + #[cfg(feature = "enable_print")] + println!("brightness btn value: {}", light_ctrl_btn_input.value()); + if !light_ctrl_btn_input.value() { + app.brightness_button(); + } + light_ctrl_btn_input.reset(); + } + + // coin_detect interrupt + + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.sense_coin_flag.active() { + #[allow(static_mut_refs)] + INPUT_FLAGS.sense_coin_flag.clear(); + sense_coin_input.begin(); + } + sense_coin_input.service(); + if sense_coin_input.ready() { + sense_coin_input.reset(); + app.coin_detect(); + } + } + + // main button handling + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.main_btn_flag.active() { + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.clear(); + main_btn_input.begin(); + } + } + main_btn_input.service(); + if main_btn_input.ready() { + let value = main_btn_input.value(); + main_btn_input.reset(); + // #[cfg(feature = "enable_print")] + // println!("main button", value); + + match value { + true => app.main_button_press(), + false => app.main_button_release(), + } + } + + // systick tick + if unsafe { + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.active() + } { + unsafe { + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.clear(); + } + // app tick + app.tick(); + } + + app.service(); + + match app.get_state() { + // enter standby + app::State::DeepSleep => { + app.shut_down(); + return app.get_settings(); + } + _ => {} + } + } +} + +use ch32_hal::timer::low_level::{OutputCompareMode, Timer}; +use ch32_hal::timer::Channel; + +// fn shutdown_main(p: Peripherals) { +fn shutdown_main(p: hal::Peripherals) { + systick_stop(); + // LED0 output setup + let led0_pin = OutputOpenDrain::new(p.PC3, Level::Low, Default::default()); + let led1_pin = OutputOpenDrain::new(p.PD2, Level::High, Default::default()); + let led2_pin = OutputOpenDrain::new(p.PA1, Level::High, Default::default()); + let dac_pin = OutputOpenDrain::new(p.PC4, Level::Low, Default::default()); + let mut amp_pin = OutputOpenDrain::new(p.PC5, Level::Low, Default::default()); + amp_pin.set_high(); + let volume_btn_pin = OutputOpenDrain::new(p.PC6, Level::Low, Default::default()); + let light_ctrl_btn_pin = OutputOpenDrain::new(p.PC7, Level::Low, Default::default()); + let usb_detect_input = OutputOpenDrain::new(p.PD5, Level::Low, Default::default()); + + let sense_coin_pin = p.PC2; + let main_btn_pin = p.PD6; + + unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) }; + unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, false) }; + + let sense_coin_pin = Input::new(sense_coin_pin, Pull::None); + let main_btn_pin = Input::new(main_btn_pin, Pull::None); + + riscv::asm::delay(1_000_000); + + loop { + unsafe { system::enter_standby() }; + riscv::asm::wfi(); + unsafe { + #[allow(static_mut_refs)] + if (INPUT_FLAGS.sense_coin_flag.active() || INPUT_FLAGS.main_btn_flag.active()) + // && app.should_wake() + { + break; + } + } + } +} + +#[qingke_rt::entry] +fn main() -> ! { + #[cfg(feature = "enable_print")] + hal::debug::SDIPrint::enable(); + + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + let mut p = hal::init(config); + + // delay to let the debugger attach + // println!("pre"); + riscv::asm::delay(20_000_000); + // println!("post"); + // debug_main(p); + + let mut app_settings = Settings::default(); + + loop { + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + app_settings = app_main(p, app_settings); + + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + shutdown_main(p); + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // println!("panic: {info:?}"); + loop {} +} diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs new file mode 100644 index 0000000..4cf6e4c --- /dev/null +++ b/ch32v-insert-coin/src/sequences.rs @@ -0,0 +1,346 @@ +use crate::app::sequencer::SequenceEntry; + +pub static TEST_SEQ: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 1000, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 1000, + }, +]; + +pub static TEST_SEQ1: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 100, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 100, + }, +]; + +// play twice +pub static WAHWAH: [SequenceEntry; 9] = [ + SequenceEntry { + frequency_hz: 100, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 25, + }, +]; + +pub static DECENDING_TONES: [SequenceEntry; 21] = [ + SequenceEntry { + frequency_hz: 1100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1050, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 950, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 850, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 750, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 650, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 550, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 100, + duration_ms: 50, + }, +]; + +// PLAY ONCE +pub static COIN_CHIRP: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 1000, + duration_ms: 100, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 500, + }, +]; + +// PLAY ONCE +pub static SOUND_8_BIT_GAME_4: [SequenceEntry; 4] = [ + SequenceEntry { + frequency_hz: 220, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 660, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 880, + duration_ms: 50, + }, +]; + +// PLAY TWICE +// TODO: this isn't working quite right with the 0hz "non note" for a rest... +pub static SOUND_8_BIT_GAME_1: [SequenceEntry; 3] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 0, + duration_ms: 50, + }, +]; + +// PLAY 3 TIMES +pub static SOUND_8_BIT_GAME_3: [SequenceEntry; 3] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 660, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, +]; + +// PLAY 1 TIMES +pub static ASCENDING_TONES: [SequenceEntry; 29] = [ + SequenceEntry { + frequency_hz: 100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 550, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 650, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 750, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 850, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 950, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1050, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 1000, + }, +]; + +pub static SEQUENCE_LIST: [(&'static [SequenceEntry], usize); 7] = [ + (&SOUND_8_BIT_GAME_3, 2), + (&SOUND_8_BIT_GAME_4, 0), + (&ASCENDING_TONES, 0), + (&COIN_CHIRP, 0), + (&WAHWAH, 5), + (&DECENDING_TONES, 0), + (&SOUND_8_BIT_GAME_1, 1), + // &TEST_SEQ, +]; diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs new file mode 100644 index 0000000..810336e --- /dev/null +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -0,0 +1,66 @@ +use crate::insert_coin::SimplePwmCore; +use ch32_hal::println; +use ch32_hal::timer::GeneralInstance16bit; +use wavetable_synth::{synthesizer::SimpleWavetableSynthesizer, wavetable::SimpleWavetable}; + +const SQUARE_WAVETABLE: [u8; 2] = [0, 100]; +// const SQUARE_WAVETABLE: [u8; 32] = [ +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, +// 100, 100, 100, 100, 100, 100, 100, +// ]; + +pub struct SynthesizerService { + pub synth: SimpleWavetableSynthesizer>, + pub enabled: bool, + pub need_service: bool, +} + +impl SynthesizerService { + pub fn new(clock_freq_hz: usize) -> Self { + let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); + let synth = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); + + Self { + synth, + enabled: true, + need_service: false, + } + } + + pub fn tick(&mut self) { + if self.enabled { + self.synth.tick(); + } + } + + pub fn need_service(&self) -> bool { + self.need_service || (self.enabled && self.synth.has_new_output()) + } + + pub fn service(&mut self) -> Option { + self.need_service = false; + if self.enabled { + Some(self.synth.get_output()) + } else { + None + } + } +} + +impl SynthesizerService { + pub fn set_freq(&mut self, freq_hz: usize) { + self.synth.set_freq(freq_hz); + } + + pub fn enable(&mut self) { + self.enabled = true; + self.need_service = true; + // TODO: write the enable function + } + + pub fn disable(&mut self) { + self.enabled = false; + self.need_service = true; + // TODO: write the disable function + } +} diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs new file mode 100644 index 0000000..484d513 --- /dev/null +++ b/ch32v-insert-coin/src/system.rs @@ -0,0 +1,134 @@ +use ch32_hal as hal; +use hal::println; + +pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { + critical_section::with(|_| { + #[cfg(feature = "enable_print")] + println!("init_gpio_irq {pin}:{port}"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; + + let port = port as u8; + let pin = pin as usize; + + // let b = afio.exticr().read(); + afio.exticr().modify(|w| w.set_exti(pin, port)); + + exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt + exti.rtenr().modify(|w| w.set_tr(pin, rising)); + exti.ftenr().modify(|w| w.set_tr(pin, falling)); + }); +} + +pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> [bool; 2] { + let mut output = [false, false]; + + let exti = &hal::pac::EXTI; + + let coin_pin = coin_pin as usize; + let button_pin = button_pin as usize; + + exti.intenr().modify(|w| w.set_mr(coin_pin, false)); // disable interrupt + exti.intenr().modify(|w| w.set_mr(button_pin, false)); // disable interrupt + + let bits = exti.intfr().read(); + + // We don't handle or change any EXTI lines above 24. + let bits = bits.0 & 0x00FFFFFF; + #[cfg(feature = "enable_print")] + println!("bits: {bits:08x}"); + + // coin_flag + if (bits & (0x1 << coin_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("coin irq!"); + output[0] = true; + } + + // button_flag + if (bits & (0x1 << button_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("main_btn irq!"); + output[1] = true; + } + + // Clear pending - Clears the EXTI's line pending bits. + exti.intfr().write(|w| w.0 = bits); + + use hal::pac::Interrupt; + unsafe { + qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); + }; + + // exti.intenr().modify(|w| w.0 = w.0 & !bits); + exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt + exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt + + output +} + +/// enter standby (SLEEPDEEP) mode, with WFE enabled. +/// from CH32V003 reference manual 2.3.1: +/// entry: +/// 1. configure core register control bit: SLEEPDEEP=1 | PDDS = 1 +/// 2. configure external interrupt register to mask all but EXTI7_0 +/// 3. execute WFI or WFE (optionally SEVONPEND) and SLEEPONEXIT +/// * (probably WFI?) +/// exit: +/// 1. any interrupt/event (set in external interrupt register) +pub unsafe fn enter_standby() { + critical_section::with(|_| { + use hal::pac::Interrupt; + use qingke_rt::CoreInterrupt; + + // configure core register control bit (SLEEPDEEP=1 | PDDS=1) + let pfic = &hal::pac::PFIC; + pfic.sctlr().modify(|w| { + // we only want to wake on enabled interrupts + w.set_sevonpend(false); + // we want to enable deep sleep + w.set_sleepdeep(true); + w.set_wfitowfe(false); + w.set_sleeponexit(false); + }); + + // set PDDS=1: + // get current value of PWR_CTLR + let mut reg: u32 = 0x4000_7000; + let mut val: u32 = unsafe { (reg as *mut u32).read_volatile() }; + // modify PDDS + val |= 1 << 1; // PWR_CTLR[1] -> PDDS + unsafe { + (reg as *mut u32).write_volatile(val); + } + + // // disable all exti interrupts + let exti = &hal::pac::EXTI; + // exti.intenr().write(| w| { + // w.0 = 0x00000000; + // // w.set_mr(pin, true); + // // w.set_mr(pin, true); + // }); + + // clear all pending exti interrupts + let bits = 0xFFFFFFFF; + exti.intfr().write(|w| w.0 = bits); + + // enable all exti interrupts + let exti = &hal::pac::EXTI; + exti.intenr().write(|w| { + w.0 = 0x00FFFFFF; + // w.set_mr(pin, true); + // w.set_mr(pin, true); + }); + + unsafe { + qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + } + + // execute WFI + #[cfg(feature = "enable_print")] + println!("WFI CONFIGURED HOPEFULLY"); + }); +} diff --git a/d5a78ca0e0e0591dbd312378abcc82a1/description b/d5a78ca0e0e0591dbd312378abcc82a1/description deleted file mode 100644 index 3ca9329..0000000 --- a/d5a78ca0e0e0591dbd312378abcc82a1/description +++ /dev/null @@ -1,5 +0,0 @@ -embassy hangs - -noted that embassy hangs on the ch32v003 - even on the templated example. - -for now, we will not use embassy. diff --git a/d5a78ca0e0e0591dbd312378abcc82a1/tags b/d5a78ca0e0e0591dbd312378abcc82a1/tags deleted file mode 100644 index 93f371e..0000000 --- a/d5a78ca0e0e0591dbd312378abcc82a1/tags +++ /dev/null @@ -1 +0,0 @@ -bug diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/dependencies/8b98f887e6f027890908edf066debce1 b/e9e2d173359f7a1ef441f8d5cc9af0c1/dependencies/8b98f887e6f027890908edf066debce1 deleted file mode 100644 index e69de29..0000000 diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/description b/e9e2d173359f7a1ef441f8d5cc9af0c1/description deleted file mode 100644 index 67d6a92..0000000 --- a/e9e2d173359f7a1ef441f8d5cc9af0c1/description +++ /dev/null @@ -1,9 +0,0 @@ -phase 1: init - -REQUIREMENTS -* PWM DAC -* GPIO input -* interrupts -* PWM outputs - * bit-banged - * hardware diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/state b/e9e2d173359f7a1ef441f8d5cc9af0c1/state deleted file mode 100644 index 9e3b09d..0000000 --- a/e9e2d173359f7a1ef441f8d5cc9af0c1/state +++ /dev/null @@ -1 +0,0 @@ -wontdo \ No newline at end of file diff --git a/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description b/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description deleted file mode 100644 index 57d10ef..0000000 --- a/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description +++ /dev/null @@ -1 +0,0 @@ -was able to compile the blinky.rs example out of the ch32v-hal repository diff --git a/f00aa7a5559d50ec2b47b76369adde1b/description b/f00aa7a5559d50ec2b47b76369adde1b/description deleted file mode 100644 index 7064c22..0000000 --- a/f00aa7a5559d50ec2b47b76369adde1b/description +++ /dev/null @@ -1 +0,0 @@ -compile code diff --git a/f00aa7a5559d50ec2b47b76369adde1b/state b/f00aa7a5559d50ec2b47b76369adde1b/state deleted file mode 100644 index 348ebd9..0000000 --- a/f00aa7a5559d50ec2b47b76369adde1b/state +++ /dev/null @@ -1 +0,0 @@ -done \ No newline at end of file diff --git a/f00aa7a5559d50ec2b47b76369adde1b/tags b/f00aa7a5559d50ec2b47b76369adde1b/tags deleted file mode 100644 index 970be2b..0000000 --- a/f00aa7a5559d50ec2b47b76369adde1b/tags +++ /dev/null @@ -1 +0,0 @@ -phase-0 diff --git a/f311060330813a8833ab4bc82835aefa/description b/f311060330813a8833ab4bc82835aefa/description deleted file mode 100644 index d2ede00..0000000 --- a/f311060330813a8833ab4bc82835aefa/description +++ /dev/null @@ -1 +0,0 @@ -enter deep sleep when adc battery voltage reading is low diff --git a/f311060330813a8833ab4bc82835aefa/tags b/f311060330813a8833ab4bc82835aefa/tags deleted file mode 100644 index a7453f0..0000000 --- a/f311060330813a8833ab4bc82835aefa/tags +++ /dev/null @@ -1 +0,0 @@ -feature diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..65e9ae5 --- /dev/null +++ b/notes.md @@ -0,0 +1,97 @@ +# ENVIRONMENT +there is a docker image that contains the entire toolchain + flashing utility. first, you need to build the base docker image with the following script located in the ch32v-insert-coin directory: +```shell +$ ./init.sh +``` +once built, you won't need to build this again. for the remainder of your development, you can use the following script to launch the environment shell: +```shell +$ ./launch.sh +``` + +this will launch the docker image and give you a shell which has all of the toolchains and flashing utilities installed. to build the firmware image and flash it to the ch32 (assuming you have a wch-linke attached) run the following from inside the env shell: + +```shell +$ ./build-run.sh +``` +this will build the firmware image, and attempt to upload it to the board using `wlink`. once uploaded, it will attach a serial debugger. + +to exit the serial debugger simply use `ctrl-c`. to exit the environment shell use: + +```shell +$ exit +``` + +# FLASHING +flashing is done using the [`wlink`](https://github.com/ch32-rs/wlink?tab=readme-ov-file#install) utility. `probe-rs` also works, but can be flaky, and does not support SDI prints very well. + +the `wlink` utility will automatically detect the correct chip, but if it doesn't you can specify it with an additional argument. + +`wlink --help` lists all the options to the `wlink` utility. + +## DEVELOPMENT +when building using the rust toolchain, you can simply add the following to your `.cargo/config.toml`: +`runner = "wlink -v flash` + +if you want to monitor prints via SDI, you can use the following instead: +`runner = "wlink -v flash --enable-sdi-print --watch-serial"` + +## STANDALONE +when flashing standalone with the `wlink` utility, you can simply run the following: +`wlink -v flash ` + + +# CH32V MISC NOTES +## SLEEP / DEEP SLEEP +it turns out you can not put the chip into deep sleep and have it wake up correctly until you power cycle it. WHAT THE FUCK. see page 36 of the [qingkev3 processor manual](https://www.wch-ic.com/download/file?id=368) at the bottom of section 6.1 Enter Sleep + + +# EVAL BOARD NOTES +## PINOUT +WCHLinkE SWDIO -> PD1 + + + +# AUDIO NOTES +## LIMITS +we have ~9-10kB of space remaining on the chip. +i can likely squeeze out _maybe_ another kB by dropping every print, but +that appears to cause some lockup, i think because the SDI peripheral is +trying to attach, but unable to. + +for how things sit currently, we have the following (conservative) limits +to fit within 9kB: + +16ksps, 4b depth -> 64kbps -> 1.125s +8ksps, 4b depth -> 32kbps -> 2.25s +6ksps, 4b depth -> 24kbps -> 3.0s +4ksps, 4b depth -> 16kbps -> 4.5s + +in reality, the numbers i have seen are a little smaller than this, likely due +to compiler optimizations when the dac is unloaded / there's no audio. + +a 16ksps file has an _experimental_ max of 0.75s. applying that offset +everywhere, we get empirical limits of: + +16ksps, 4b depth: 0.75s +8ksps, 4b depth: 1.875s +6ksps, 4b depth: 2.625s +4ksps, 4b depth: 4.225s + +## FFMPEG +use the following command to convert an input .wav to a raw mono pcm_u8 with 8ksps: +```shell +ffmpeg -i input.wav -f u8 -c:a pcm_u8 -ar 8000 -ac 1 output.raw +``` + + +# PINOUTS +LED0: PC3 +LED1: PD2 +DAC: PC4 +COIN SWITCH: PC2 +BUTTON SWITCH: PD6 +ADC: PD4 +GPIO: any remaining pin + +## FORBIDDEN: +PD1