From 299613f9397164644448c54c6375dc5fbacc9f7d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:34:36 -0600 Subject: [PATCH 001/148] create entomologist issue branch --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bd9d23 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This branch is used by entomologist to track issues. \ No newline at end of file From 88fb3d5757a83fca79a4c2019d89e9894d87c815 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:44:37 -0600 Subject: [PATCH 002/148] create new issue 777077169fce2ffba439b0af82e0f574 --- 777077169fce2ffba439b0af82e0f574/description | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 777077169fce2ffba439b0af82e0f574/description diff --git a/777077169fce2ffba439b0af82e0f574/description b/777077169fce2ffba439b0af82e0f574/description new file mode 100644 index 0000000..186f7d2 --- /dev/null +++ b/777077169fce2ffba439b0af82e0f574/description @@ -0,0 +1,37 @@ +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 From 3ef1481eff4512c4eb37e6c3cf837166d1f2cd03 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:50:10 -0600 Subject: [PATCH 003/148] create new issue 4b5cce76398043576a0f9a6e29492127 --- 4b5cce76398043576a0f9a6e29492127/description | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 4b5cce76398043576a0f9a6e29492127/description diff --git a/4b5cce76398043576a0f9a6e29492127/description b/4b5cce76398043576a0f9a6e29492127/description new file mode 100644 index 0000000..dc791fd --- /dev/null +++ b/4b5cce76398043576a0f9a6e29492127/description @@ -0,0 +1,6 @@ +phase 0: bootstrap + +REQUIREMENTS: +* set up toolchain for CH32V development +* flash CH32V board +* blink an LED From 252b41b7433c53a8006ad4cb4a1a2f51c822496a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:52:22 -0600 Subject: [PATCH 004/148] create new issue e9e2d173359f7a1ef441f8d5cc9af0c1 --- e9e2d173359f7a1ef441f8d5cc9af0c1/description | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 e9e2d173359f7a1ef441f8d5cc9af0c1/description diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/description b/e9e2d173359f7a1ef441f8d5cc9af0c1/description new file mode 100644 index 0000000..cb19ac9 --- /dev/null +++ b/e9e2d173359f7a1ef441f8d5cc9af0c1/description @@ -0,0 +1,8 @@ +phase 1: init + +REQUIREMENTS +* GPIO input +* interrupts +* PWM outputs + * bit-banged + * hardware From b19e3b920f253629fcbfa97a831ef8605b4dc39f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:54:12 -0600 Subject: [PATCH 005/148] create new issue 2ff2ce2cae3914fbb8382372498abdd5 --- 2ff2ce2cae3914fbb8382372498abdd5/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 2ff2ce2cae3914fbb8382372498abdd5/description diff --git a/2ff2ce2cae3914fbb8382372498abdd5/description b/2ff2ce2cae3914fbb8382372498abdd5/description new file mode 100644 index 0000000..a0ed520 --- /dev/null +++ b/2ff2ce2cae3914fbb8382372498abdd5/description @@ -0,0 +1 @@ +set up toolchain for CH32V development From 94bd5af3ef2f68df1a874aff91544096ef87e9c9 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:54:40 -0600 Subject: [PATCH 006/148] issue 2ff2ce2cae3914fbb8382372498abdd5 add tag phase-0 --- 2ff2ce2cae3914fbb8382372498abdd5/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 2ff2ce2cae3914fbb8382372498abdd5/tags diff --git a/2ff2ce2cae3914fbb8382372498abdd5/tags b/2ff2ce2cae3914fbb8382372498abdd5/tags new file mode 100644 index 0000000..970be2b --- /dev/null +++ b/2ff2ce2cae3914fbb8382372498abdd5/tags @@ -0,0 +1 @@ +phase-0 From d2f3c656ac623616d41692851f38432f13c8b415 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:54:50 -0600 Subject: [PATCH 007/148] create new issue 1892880782c8c31990d591c7b05fa6c3 --- 1892880782c8c31990d591c7b05fa6c3/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 1892880782c8c31990d591c7b05fa6c3/description diff --git a/1892880782c8c31990d591c7b05fa6c3/description b/1892880782c8c31990d591c7b05fa6c3/description new file mode 100644 index 0000000..cd8422a --- /dev/null +++ b/1892880782c8c31990d591c7b05fa6c3/description @@ -0,0 +1 @@ +flash CH32V board From 1e4406e22154a21a5efde0e4d19808ac50b85f22 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:54:59 -0600 Subject: [PATCH 008/148] issue 1892880782c8c31990d591c7b05fa6c3 add tag phase-0 --- 1892880782c8c31990d591c7b05fa6c3/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 1892880782c8c31990d591c7b05fa6c3/tags diff --git a/1892880782c8c31990d591c7b05fa6c3/tags b/1892880782c8c31990d591c7b05fa6c3/tags new file mode 100644 index 0000000..970be2b --- /dev/null +++ b/1892880782c8c31990d591c7b05fa6c3/tags @@ -0,0 +1 @@ +phase-0 From 571959bed78dcbe685d8bc59d4b32d9d553f20fe Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:55:06 -0600 Subject: [PATCH 009/148] create new issue f00aa7a5559d50ec2b47b76369adde1b --- f00aa7a5559d50ec2b47b76369adde1b/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 f00aa7a5559d50ec2b47b76369adde1b/description diff --git a/f00aa7a5559d50ec2b47b76369adde1b/description b/f00aa7a5559d50ec2b47b76369adde1b/description new file mode 100644 index 0000000..7064c22 --- /dev/null +++ b/f00aa7a5559d50ec2b47b76369adde1b/description @@ -0,0 +1 @@ +compile code From 636e254edd59a8e029fd40160fc2c2e052ea5617 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:55:12 -0600 Subject: [PATCH 010/148] issue f00aa7a5559d50ec2b47b76369adde1b add tag phase-0 --- f00aa7a5559d50ec2b47b76369adde1b/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 f00aa7a5559d50ec2b47b76369adde1b/tags diff --git a/f00aa7a5559d50ec2b47b76369adde1b/tags b/f00aa7a5559d50ec2b47b76369adde1b/tags new file mode 100644 index 0000000..970be2b --- /dev/null +++ b/f00aa7a5559d50ec2b47b76369adde1b/tags @@ -0,0 +1 @@ +phase-0 From 68a84bf95fd4ec231d71c62ef0e173eae3abb259 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:55:38 -0600 Subject: [PATCH 011/148] create new issue 0acf72abc9e4641d0683e1bb5f2cf829 --- 0acf72abc9e4641d0683e1bb5f2cf829/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 0acf72abc9e4641d0683e1bb5f2cf829/description diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/description b/0acf72abc9e4641d0683e1bb5f2cf829/description new file mode 100644 index 0000000..85a5ead --- /dev/null +++ b/0acf72abc9e4641d0683e1bb5f2cf829/description @@ -0,0 +1 @@ +blink an LED From e2c07df1841955988c2140675d988cdd0dc8439b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:55:50 -0600 Subject: [PATCH 012/148] issue 0acf72abc9e4641d0683e1bb5f2cf829 add tag phase-0 --- 0acf72abc9e4641d0683e1bb5f2cf829/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 0acf72abc9e4641d0683e1bb5f2cf829/tags diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/tags b/0acf72abc9e4641d0683e1bb5f2cf829/tags new file mode 100644 index 0000000..970be2b --- /dev/null +++ b/0acf72abc9e4641d0683e1bb5f2cf829/tags @@ -0,0 +1 @@ +phase-0 From 466415eceb11426311378ca5b50b99037f069de6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:56:25 -0600 Subject: [PATCH 013/148] change state of issue 777077169fce2ffba439b0af82e0f574, new -> inprogress --- 777077169fce2ffba439b0af82e0f574/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 777077169fce2ffba439b0af82e0f574/state diff --git a/777077169fce2ffba439b0af82e0f574/state b/777077169fce2ffba439b0af82e0f574/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/777077169fce2ffba439b0af82e0f574/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From d9e1d91ca686ae645a7449ab947b5df7c277e746 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:56:36 -0600 Subject: [PATCH 014/148] change state of issue 4b5cce76398043576a0f9a6e29492127, new -> backlog --- 4b5cce76398043576a0f9a6e29492127/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 4b5cce76398043576a0f9a6e29492127/state diff --git a/4b5cce76398043576a0f9a6e29492127/state b/4b5cce76398043576a0f9a6e29492127/state new file mode 100644 index 0000000..b6fe829 --- /dev/null +++ b/4b5cce76398043576a0f9a6e29492127/state @@ -0,0 +1 @@ +backlog \ No newline at end of file From 719d44ab4994042497cb036bf30ffcbbe4af6d96 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:56:40 -0600 Subject: [PATCH 015/148] change assignee of issue 4b5cce76398043576a0f9a6e29492127, None -> sigil-03 --- 4b5cce76398043576a0f9a6e29492127/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 4b5cce76398043576a0f9a6e29492127/assignee diff --git a/4b5cce76398043576a0f9a6e29492127/assignee b/4b5cce76398043576a0f9a6e29492127/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/4b5cce76398043576a0f9a6e29492127/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 696a04f23cf09c1ef148c8997f7d86f2187f2210 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:56:58 -0600 Subject: [PATCH 016/148] change assignee of issue 777077169fce2ffba439b0af82e0f574, None -> sigil-03 --- 777077169fce2ffba439b0af82e0f574/assignee | 1 + 1 file changed, 1 insertion(+) create mode 100644 777077169fce2ffba439b0af82e0f574/assignee diff --git a/777077169fce2ffba439b0af82e0f574/assignee b/777077169fce2ffba439b0af82e0f574/assignee new file mode 100644 index 0000000..284bb3b --- /dev/null +++ b/777077169fce2ffba439b0af82e0f574/assignee @@ -0,0 +1 @@ +sigil-03 \ No newline at end of file From 04a3989d49c472aff7fe61088bc1df7c3743eb7b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:57:20 -0600 Subject: [PATCH 017/148] issue 4b5cce76398043576a0f9a6e29492127 add tag phase-0 --- 4b5cce76398043576a0f9a6e29492127/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 4b5cce76398043576a0f9a6e29492127/tags diff --git a/4b5cce76398043576a0f9a6e29492127/tags b/4b5cce76398043576a0f9a6e29492127/tags new file mode 100644 index 0000000..970be2b --- /dev/null +++ b/4b5cce76398043576a0f9a6e29492127/tags @@ -0,0 +1 @@ +phase-0 From e5966814117ade54df36c2cef73ebb00bb4420a8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 17 Jul 2025 21:59:40 -0600 Subject: [PATCH 018/148] edit description of issue 777077169fce2ffba439b0af82e0f574 --- 777077169fce2ffba439b0af82e0f574/description | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/777077169fce2ffba439b0af82e0f574/description b/777077169fce2ffba439b0af82e0f574/description index 186f7d2..b5f019d 100644 --- a/777077169fce2ffba439b0af82e0f574/description +++ b/777077169fce2ffba439b0af82e0f574/description @@ -35,3 +35,7 @@ LM386/SC8002B amplifier * 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) From ba699e255c1f0160e6dbe8a2e06bf2c9af41a974 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 19 Jul 2025 00:17:37 -0600 Subject: [PATCH 019/148] add comment 93feac46f17a40fa41f543c56b5eafb2 on issue 2ff2ce2cae3914fbb8382372498abdd5 --- .../description | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description diff --git a/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description b/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description new file mode 100644 index 0000000..1fa60ea --- /dev/null +++ b/2ff2ce2cae3914fbb8382372498abdd5/comments/93feac46f17a40fa41f543c56b5eafb2/description @@ -0,0 +1,21 @@ +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... From f8f3aaf034e6bc79cba935f653cf1da9b4eca7fd Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:36:29 -0600 Subject: [PATCH 020/148] add comment b0ff36f6102eaa30463133423f4d7ffe on issue 2ff2ce2cae3914fbb8382372498abdd5 --- .../comments/b0ff36f6102eaa30463133423f4d7ffe/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description diff --git a/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description b/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description new file mode 100644 index 0000000..c8fcba0 --- /dev/null +++ b/2ff2ce2cae3914fbb8382372498abdd5/comments/b0ff36f6102eaa30463133423f4d7ffe/description @@ -0,0 +1 @@ +this is done - required getting a wch-linkE programmer, which was able to interface with probe-rs and program the ch32v003 chip. From 40c4bbf14b1c39cf45ef56c2ea0c784496d74f71 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:36:56 -0600 Subject: [PATCH 021/148] add comment b90b4b71d0559508b16931dcf8250859 on issue 1892880782c8c31990d591c7b05fa6c3 --- .../comments/b90b4b71d0559508b16931dcf8250859/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description diff --git a/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description b/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description new file mode 100644 index 0000000..71e5f82 --- /dev/null +++ b/1892880782c8c31990d591c7b05fa6c3/comments/b90b4b71d0559508b16931dcf8250859/description @@ -0,0 +1 @@ +flashing successful using wch linkE programmer From 4584ad60ae10de77252461a5cf097ef57a57567e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:37:45 -0600 Subject: [PATCH 022/148] add comment c89eb1f378c37bfd6c9b369e69b1219a on issue f00aa7a5559d50ec2b47b76369adde1b --- .../comments/c89eb1f378c37bfd6c9b369e69b1219a/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description diff --git a/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description b/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description new file mode 100644 index 0000000..57d10ef --- /dev/null +++ b/f00aa7a5559d50ec2b47b76369adde1b/comments/c89eb1f378c37bfd6c9b369e69b1219a/description @@ -0,0 +1 @@ +was able to compile the blinky.rs example out of the ch32v-hal repository From a6271be987afdb5841f7e9a518a6ac37d67324f8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:37:55 -0600 Subject: [PATCH 023/148] change state of issue f00aa7a5559d50ec2b47b76369adde1b, new -> done --- f00aa7a5559d50ec2b47b76369adde1b/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 f00aa7a5559d50ec2b47b76369adde1b/state diff --git a/f00aa7a5559d50ec2b47b76369adde1b/state b/f00aa7a5559d50ec2b47b76369adde1b/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/f00aa7a5559d50ec2b47b76369adde1b/state @@ -0,0 +1 @@ +done \ No newline at end of file From 2dc82522db379f0f643bfc940b3f23f5fbbb5b92 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:38:21 -0600 Subject: [PATCH 024/148] add comment 6eb67d2adaef9836f4fde15755ec8c80 on issue 0acf72abc9e4641d0683e1bb5f2cf829 --- .../comments/6eb67d2adaef9836f4fde15755ec8c80/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description b/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description new file mode 100644 index 0000000..57ce453 --- /dev/null +++ b/0acf72abc9e4641d0683e1bb5f2cf829/comments/6eb67d2adaef9836f4fde15755ec8c80/description @@ -0,0 +1 @@ +modified blinky.rs to have 3 heartbeat blinks, was able to flash and observe the update. From 137f332a23000c78bd96575d07e5d1176eb456cb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:38:28 -0600 Subject: [PATCH 025/148] change state of issue 0acf72abc9e4641d0683e1bb5f2cf829, new -> done --- 0acf72abc9e4641d0683e1bb5f2cf829/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 0acf72abc9e4641d0683e1bb5f2cf829/state diff --git a/0acf72abc9e4641d0683e1bb5f2cf829/state b/0acf72abc9e4641d0683e1bb5f2cf829/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/0acf72abc9e4641d0683e1bb5f2cf829/state @@ -0,0 +1 @@ +done \ No newline at end of file From b56aefa8ae8aad5da72f04351e5e0868922da41d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:39:40 -0600 Subject: [PATCH 026/148] change state of issue 1892880782c8c31990d591c7b05fa6c3, new -> done --- 1892880782c8c31990d591c7b05fa6c3/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 1892880782c8c31990d591c7b05fa6c3/state diff --git a/1892880782c8c31990d591c7b05fa6c3/state b/1892880782c8c31990d591c7b05fa6c3/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/1892880782c8c31990d591c7b05fa6c3/state @@ -0,0 +1 @@ +done \ No newline at end of file From 6c5fe86b11d75eab47b5ffccfbae771c28de1794 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:39:51 -0600 Subject: [PATCH 027/148] change state of issue 2ff2ce2cae3914fbb8382372498abdd5, new -> done --- 2ff2ce2cae3914fbb8382372498abdd5/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 2ff2ce2cae3914fbb8382372498abdd5/state diff --git a/2ff2ce2cae3914fbb8382372498abdd5/state b/2ff2ce2cae3914fbb8382372498abdd5/state new file mode 100644 index 0000000..348ebd9 --- /dev/null +++ b/2ff2ce2cae3914fbb8382372498abdd5/state @@ -0,0 +1 @@ +done \ No newline at end of file From 059f0afe77de29514a8ed0c53e4103ed3ea500ad Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:40:14 -0600 Subject: [PATCH 028/148] change state of issue 4b5cce76398043576a0f9a6e29492127, backlog -> done --- 4b5cce76398043576a0f9a6e29492127/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4b5cce76398043576a0f9a6e29492127/state b/4b5cce76398043576a0f9a6e29492127/state index b6fe829..348ebd9 100644 --- a/4b5cce76398043576a0f9a6e29492127/state +++ b/4b5cce76398043576a0f9a6e29492127/state @@ -1 +1 @@ -backlog \ No newline at end of file +done \ No newline at end of file From 4c5ada838c871a1927c72ac326e93fe7e12631e1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 12:40:33 -0600 Subject: [PATCH 029/148] change state of issue e9e2d173359f7a1ef441f8d5cc9af0c1, new -> inprogress --- e9e2d173359f7a1ef441f8d5cc9af0c1/state | 1 + 1 file changed, 1 insertion(+) create mode 100644 e9e2d173359f7a1ef441f8d5cc9af0c1/state diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/state b/e9e2d173359f7a1ef441f8d5cc9af0c1/state new file mode 100644 index 0000000..505c028 --- /dev/null +++ b/e9e2d173359f7a1ef441f8d5cc9af0c1/state @@ -0,0 +1 @@ +inprogress \ No newline at end of file From a22b67b875188cf8604708404b0787b1c5e9f05b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:08:10 -0600 Subject: [PATCH 030/148] edit description of issue e9e2d173359f7a1ef441f8d5cc9af0c1 --- e9e2d173359f7a1ef441f8d5cc9af0c1/description | 1 + 1 file changed, 1 insertion(+) diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/description b/e9e2d173359f7a1ef441f8d5cc9af0c1/description index cb19ac9..67d6a92 100644 --- a/e9e2d173359f7a1ef441f8d5cc9af0c1/description +++ b/e9e2d173359f7a1ef441f8d5cc9af0c1/description @@ -1,6 +1,7 @@ phase 1: init REQUIREMENTS +* PWM DAC * GPIO input * interrupts * PWM outputs From 595c8e0ea5eabbf8168f0b1788f2894f66a46ab6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:09:52 -0600 Subject: [PATCH 031/148] create new issue 8b98f887e6f027890908edf066debce1 --- 8b98f887e6f027890908edf066debce1/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 8b98f887e6f027890908edf066debce1/description diff --git a/8b98f887e6f027890908edf066debce1/description b/8b98f887e6f027890908edf066debce1/description new file mode 100644 index 0000000..c04f29d --- /dev/null +++ b/8b98f887e6f027890908edf066debce1/description @@ -0,0 +1,5 @@ +AUDIO PLAYER + +requirements: +* DAC using PWM output +* read ADPCM data and export to DAC From b62a538a4cb774055d8a73ae2d07b23321ac6fcb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:10:42 -0600 Subject: [PATCH 032/148] edit description of issue 8b98f887e6f027890908edf066debce1 --- 8b98f887e6f027890908edf066debce1/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8b98f887e6f027890908edf066debce1/description b/8b98f887e6f027890908edf066debce1/description index c04f29d..92ca179 100644 --- a/8b98f887e6f027890908edf066debce1/description +++ b/8b98f887e6f027890908edf066debce1/description @@ -1,4 +1,4 @@ -AUDIO PLAYER +audio player requirements: * DAC using PWM output From 616f03413dd34a7db3c8898ccdcc8554438a1eeb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:11:42 -0600 Subject: [PATCH 033/148] add dep 8b98f887e6f027890908edf066debce1 to issue e9e2d173359f7a1ef441f8d5cc9af0c1 --- .../dependencies/8b98f887e6f027890908edf066debce1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 e9e2d173359f7a1ef441f8d5cc9af0c1/dependencies/8b98f887e6f027890908edf066debce1 diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/dependencies/8b98f887e6f027890908edf066debce1 b/e9e2d173359f7a1ef441f8d5cc9af0c1/dependencies/8b98f887e6f027890908edf066debce1 new file mode 100644 index 0000000..e69de29 From fa2ac63df0ab333062e9b765cea43cdbe5cef78d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:14:52 -0600 Subject: [PATCH 034/148] create new issue c6c969915b74005bc0b7f0c4b182243e --- c6c969915b74005bc0b7f0c4b182243e/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 c6c969915b74005bc0b7f0c4b182243e/description diff --git a/c6c969915b74005bc0b7f0c4b182243e/description b/c6c969915b74005bc0b7f0c4b182243e/description new file mode 100644 index 0000000..afc67c6 --- /dev/null +++ b/c6c969915b74005bc0b7f0c4b182243e/description @@ -0,0 +1,4 @@ +pwm audio output + +REQUIREMENTS: +create an interface that accepts an input amplitude and outputs a PWM signal From 5883add312323be188c07ae5f65442bae27a872d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:15:41 -0600 Subject: [PATCH 035/148] create new issue 2207e89d7201efbdceadd1c528d95341 --- 2207e89d7201efbdceadd1c528d95341/description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 2207e89d7201efbdceadd1c528d95341/description diff --git a/2207e89d7201efbdceadd1c528d95341/description b/2207e89d7201efbdceadd1c528d95341/description new file mode 100644 index 0000000..2bf140f --- /dev/null +++ b/2207e89d7201efbdceadd1c528d95341/description @@ -0,0 +1,4 @@ +adpcm decoder + +REQUIREMENTS: +* write a module which takes as input adpcm data and outputs amplitudes From c5bb494248adf3524510d97c78b2e3186aa14ece Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:16:04 -0600 Subject: [PATCH 036/148] edit description of issue c6c969915b74005bc0b7f0c4b182243e --- c6c969915b74005bc0b7f0c4b182243e/description | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c6c969915b74005bc0b7f0c4b182243e/description b/c6c969915b74005bc0b7f0c4b182243e/description index afc67c6..734f5ad 100644 --- a/c6c969915b74005bc0b7f0c4b182243e/description +++ b/c6c969915b74005bc0b7f0c4b182243e/description @@ -1,4 +1,4 @@ -pwm audio output +pwm dac REQUIREMENTS: -create an interface that accepts an input amplitude and outputs a PWM signal +* create an interface that accepts an input amplitude and outputs a PWM signal From 1a59079d70fc7c7204fad40ae6d7e4c0c67245d5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:16:13 -0600 Subject: [PATCH 037/148] add dep c6c969915b74005bc0b7f0c4b182243e to issue 8b98f887e6f027890908edf066debce1 --- .../dependencies/c6c969915b74005bc0b7f0c4b182243e | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 8b98f887e6f027890908edf066debce1/dependencies/c6c969915b74005bc0b7f0c4b182243e diff --git a/8b98f887e6f027890908edf066debce1/dependencies/c6c969915b74005bc0b7f0c4b182243e b/8b98f887e6f027890908edf066debce1/dependencies/c6c969915b74005bc0b7f0c4b182243e new file mode 100644 index 0000000..e69de29 From 2ea6cc07b21855a54a41be722643b3aeed496439 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 26 Jul 2025 13:16:22 -0600 Subject: [PATCH 038/148] add dep 2207e89d7201efbdceadd1c528d95341 to issue 8b98f887e6f027890908edf066debce1 --- .../dependencies/2207e89d7201efbdceadd1c528d95341 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 8b98f887e6f027890908edf066debce1/dependencies/2207e89d7201efbdceadd1c528d95341 diff --git a/8b98f887e6f027890908edf066debce1/dependencies/2207e89d7201efbdceadd1c528d95341 b/8b98f887e6f027890908edf066debce1/dependencies/2207e89d7201efbdceadd1c528d95341 new file mode 100644 index 0000000..e69de29 From 6d02dadceca59a25594323178c68c1ebaabf0d71 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 13:06:53 -0600 Subject: [PATCH 039/148] create new issue d5a78ca0e0e0591dbd312378abcc82a1 --- d5a78ca0e0e0591dbd312378abcc82a1/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 d5a78ca0e0e0591dbd312378abcc82a1/description diff --git a/d5a78ca0e0e0591dbd312378abcc82a1/description b/d5a78ca0e0e0591dbd312378abcc82a1/description new file mode 100644 index 0000000..3ca9329 --- /dev/null +++ b/d5a78ca0e0e0591dbd312378abcc82a1/description @@ -0,0 +1,5 @@ +embassy hangs + +noted that embassy hangs on the ch32v003 - even on the templated example. + +for now, we will not use embassy. From faa1622857e0aa6c66191a716458902bf2321247 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 13:10:23 -0600 Subject: [PATCH 040/148] initial commit of heartbeat on ch32v --- .gitignore | 1 + .gitmodules | 6 + ch32v-insert-coin/.cargo/config.toml | 27 + ch32v-insert-coin/.gitignore | 1 + ch32v-insert-coin/Cargo.lock | 614 ++++++++++++++++++ ch32v-insert-coin/Cargo.toml | 36 + ch32v-insert-coin/ext/adpcm-pwm-dac | 1 + ch32v-insert-coin/ext/ch32-hal | 1 + .../riscv32ec-unknown-none-elf.json | 18 + ch32v-insert-coin/src/main.rs | 45 ++ 10 files changed, 750 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 ch32v-insert-coin/.cargo/config.toml create mode 100644 ch32v-insert-coin/.gitignore create mode 100644 ch32v-insert-coin/Cargo.lock create mode 100644 ch32v-insert-coin/Cargo.toml create mode 160000 ch32v-insert-coin/ext/adpcm-pwm-dac create mode 160000 ch32v-insert-coin/ext/ch32-hal create mode 100644 ch32v-insert-coin/riscv32ec-unknown-none-elf.json create mode 100644 ch32v-insert-coin/src/main.rs 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..c6e7965 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "adpcm-pwm-dac"] + path = ch32v-insert-coin/ext/adpcm-pwm-dac + url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git +[submodule "ch32v-insert-coin/ext/ch32-hal"] + path = ch32v-insert-coin/ext/ch32-hal + url = git@github.com:ch32-rs/ch32-hal.git diff --git a/ch32v-insert-coin/.cargo/config.toml b/ch32v-insert-coin/.cargo/config.toml new file mode 100644 index 0000000..57920f5 --- /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..3624407 --- /dev/null +++ b/ch32v-insert-coin/Cargo.lock @@ -0,0 +1,614 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[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", + "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 = [ + "ch32-hal", + "embassy-executor", + "embassy-time", + "embedded-hal 1.0.0", + "panic-halt", + "qingke-rt", +] + +[[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" +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", + "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" diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml new file mode 100644 index 0000000..b7a1a06 --- /dev/null +++ b/ch32v-insert-coin/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "ch32v-insert-coin" +version = "0.1.0" +edition = "2024" + +[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 + #"nightly", +] } + +embassy-time = "0.4.0" +panic-halt = "1.0" + +embedded-hal = "1.0.0" + +qingke-rt = { version = "*", features = ["highcode"] } + + + +[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 \ 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..d06329a --- /dev/null +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -0,0 +1 @@ +Subproject commit d06329a7259f126987355045281cbdf292258710 diff --git a/ch32v-insert-coin/ext/ch32-hal b/ch32v-insert-coin/ext/ch32-hal new file mode 160000 index 0000000..2b8e1c8 --- /dev/null +++ b/ch32v-insert-coin/ext/ch32-hal @@ -0,0 +1 @@ +Subproject commit 2b8e1c864ba5545ee65b1c77dcb17c86a471b70c 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..e394eb4 --- /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" +} \ No newline at end of file diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs new file mode 100644 index 0000000..7fc4b30 --- /dev/null +++ b/ch32v-insert-coin/src/main.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +use hal::delay::Delay; +use hal::gpio::{AnyPin, Level, Output, Pin}; +use {ch32_hal as hal}; + + +fn blink(pin: AnyPin, interval_ms: u64) { + let mut led = Output::new(pin, Level::Low, Default::default()); + + let mut delay = Delay; + + loop { + let hb_count = 3; + let hb_period_ms = 1000; + for _ in 0..hb_count { + led.set_low(); + delay.delay_ms((interval_ms/2) as u32); + led.set_high(); + delay.delay_ms((interval_ms/2) as u32); + } + delay.delay_ms((hb_period_ms - (interval_ms * hb_count)) as u32); + } +} + +#[qingke_rt::entry] +fn main() -> ! { + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; + let p = hal::init(config); + + let mut delay = Delay; + + blink(p.PD6.degrade(), 100); + loop{}; +} + + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + loop {} +} \ No newline at end of file From 0ac166bf5e1ad1776725f0b2ca1cb861224b3b81 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 14:58:39 -0600 Subject: [PATCH 041/148] initial working demo of PWM dac w/ sinusoidal data --- ch32v-insert-coin/Cargo.lock | 6 ++- ch32v-insert-coin/Cargo.toml | 5 +- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- ch32v-insert-coin/src/main.rs | 72 ++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock index 3624407..028f1cb 100644 --- a/ch32v-insert-coin/Cargo.lock +++ b/ch32v-insert-coin/Cargo.lock @@ -2,6 +2,10 @@ # 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" @@ -66,9 +70,9 @@ dependencies = [ name = "ch32v-insert-coin" version = "0.1.0" dependencies = [ + "adpcm-pwm-dac", "ch32-hal", "embassy-executor", - "embassy-time", "embedded-hal 1.0.0", "panic-halt", "qingke-rt", diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml index b7a1a06..566357b 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -11,21 +11,20 @@ ch32-hal = { path = "ext/ch32-hal/", features = [ "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 - #"nightly", ] } -embassy-time = "0.4.0" panic-halt = "1.0" embedded-hal = "1.0.0" qingke-rt = { version = "*", features = ["highcode"] } - +adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" } [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index d06329a..8502ed7 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit d06329a7259f126987355045281cbdf292258710 +Subproject commit 8502ed75bf8d3e8df1eb5f1b5576079f55c6e13e diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 7fc4b30..3ff3278 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -6,7 +6,30 @@ use hal::delay::Delay; use hal::gpio::{AnyPin, Level, Output, Pin}; use {ch32_hal as hal}; +use hal::time::Hertz; +use hal::timer::low_level::CountingMode; +use hal::timer::simple_pwm::{PwmPin, SimplePwm}; +use hal::timer::{Channel, GeneralInstance16bit}; +// const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80]; +const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; + +struct SimplePwmDacPin<'d, T: GeneralInstance16bit>{ + pin: SimplePwm<'d, T>, + ch: Channel, +} + + +use adpcm_pwm_dac::{interface::DacInterface, dac::Dac}; + +impl DacInterface for SimplePwmDacPin<'_, T> +where T: GeneralInstance16bit { + fn write_amplitude(&mut self, amplitude: u8) { + let max_duty = self.pin.get_max_duty(); + let dc = amplitude as u32 * max_duty / 100; + self.pin.set_duty(self.ch, dc); + } +} fn blink(pin: AnyPin, interval_ms: u64) { let mut led = Output::new(pin, Level::Low, Default::default()); @@ -26,14 +49,61 @@ fn blink(pin: AnyPin, interval_ms: u64) { } } +fn pwm_blink(mut pwm_dac_pin: SimplePwmDacPin<'_, T>) { + let mut delay = Delay; + + + + let interval_ms = 1000u32; + loop { + pwm_dac_pin.write_amplitude(75); + delay.delay_ms((interval_ms/2) as u32); + pwm_dac_pin.write_amplitude(50); + delay.delay_ms((interval_ms/2) as u32); + pwm_dac_pin.write_amplitude(25); + delay.delay_ms((interval_ms/2) as u32); + } +} + +fn dac_run(mut dac: Dac<'_, T>, sample_rate: usize) { + let mut delay = Delay; + + + dac.load_data(&DAC_DATA); + + let interval_us = 1000000/sample_rate as u32; + loop { + dac.output_next(); + delay.delay_us(interval_us); + } +} + #[qingke_rt::entry] fn main() -> ! { let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); + - let mut delay = Delay; + let pin = PwmPin::new_ch4::<0>(p.PC4); + let ch = hal::timer::Channel::Ch4; + let mut pwm = SimplePwm::new( + p.TIM1, + None, + None, + None, + Some(pin), + Hertz::khz(24), + CountingMode::default(), + ); + pwm.enable(ch); + + let mut pwm_dac_pin = SimplePwmDacPin{pin: pwm, ch}; + let mut dac = Dac::new(pwm_dac_pin); + let sample_rate_hz = 440 * DAC_DATA.len(); + // pwm_blink(pwm_dac_pin); + dac_run(dac, sample_rate_hz); blink(p.PD6.degrade(), 100); loop{}; } From 144cdf776ec386e30c5570991fb5293300928481 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:48:48 -0600 Subject: [PATCH 042/148] create new issue 95dbcb73dd29dafe84621252289a4179 --- 95dbcb73dd29dafe84621252289a4179/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 95dbcb73dd29dafe84621252289a4179/description diff --git a/95dbcb73dd29dafe84621252289a4179/description b/95dbcb73dd29dafe84621252289a4179/description new file mode 100644 index 0000000..5a93718 --- /dev/null +++ b/95dbcb73dd29dafe84621252289a4179/description @@ -0,0 +1 @@ +wake from deep sleep on rising edge From 5747fc9650f7d27da4659b69b313fa2cbc3e8c2a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:48:58 -0600 Subject: [PATCH 043/148] create new issue 3eb0b69b70c8853f9f533c6daa6acd1f --- 3eb0b69b70c8853f9f533c6daa6acd1f/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 3eb0b69b70c8853f9f533c6daa6acd1f/description diff --git a/3eb0b69b70c8853f9f533c6daa6acd1f/description b/3eb0b69b70c8853f9f533c6daa6acd1f/description new file mode 100644 index 0000000..bca79ec --- /dev/null +++ b/3eb0b69b70c8853f9f533c6daa6acd1f/description @@ -0,0 +1 @@ +ADC battery voltage reading From db590a13c4e55a8c947d2a1e93b36428786d6785 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:49:11 -0600 Subject: [PATCH 044/148] create new issue f311060330813a8833ab4bc82835aefa --- f311060330813a8833ab4bc82835aefa/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 f311060330813a8833ab4bc82835aefa/description diff --git a/f311060330813a8833ab4bc82835aefa/description b/f311060330813a8833ab4bc82835aefa/description new file mode 100644 index 0000000..d2ede00 --- /dev/null +++ b/f311060330813a8833ab4bc82835aefa/description @@ -0,0 +1 @@ +enter deep sleep when adc battery voltage reading is low From 0c5a8d3c3e9039ac88cc85e8382ebe5e9ec8be01 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:49:28 -0600 Subject: [PATCH 045/148] change state of issue e9e2d173359f7a1ef441f8d5cc9af0c1, inprogress -> wontdo --- e9e2d173359f7a1ef441f8d5cc9af0c1/state | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e9e2d173359f7a1ef441f8d5cc9af0c1/state b/e9e2d173359f7a1ef441f8d5cc9af0c1/state index 505c028..9e3b09d 100644 --- a/e9e2d173359f7a1ef441f8d5cc9af0c1/state +++ b/e9e2d173359f7a1ef441f8d5cc9af0c1/state @@ -1 +1 @@ -inprogress \ No newline at end of file +wontdo \ No newline at end of file From 0fcb68558af83049dcd2f51bd608630db63e99ba Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:49:52 -0600 Subject: [PATCH 046/148] issue f311060330813a8833ab4bc82835aefa add tag feature --- f311060330813a8833ab4bc82835aefa/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 f311060330813a8833ab4bc82835aefa/tags diff --git a/f311060330813a8833ab4bc82835aefa/tags b/f311060330813a8833ab4bc82835aefa/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/f311060330813a8833ab4bc82835aefa/tags @@ -0,0 +1 @@ +feature From 7e7a476b0c4ec26d64060a23c8d6d06671ba56fa Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:49:58 -0600 Subject: [PATCH 047/148] issue 3eb0b69b70c8853f9f533c6daa6acd1f add tag feature --- 3eb0b69b70c8853f9f533c6daa6acd1f/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 3eb0b69b70c8853f9f533c6daa6acd1f/tags diff --git a/3eb0b69b70c8853f9f533c6daa6acd1f/tags b/3eb0b69b70c8853f9f533c6daa6acd1f/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/3eb0b69b70c8853f9f533c6daa6acd1f/tags @@ -0,0 +1 @@ +feature From 16edd6ce028e4e964d08ccba9186eb9ea06f5194 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:50:05 -0600 Subject: [PATCH 048/148] issue 95dbcb73dd29dafe84621252289a4179 add tag feature --- 95dbcb73dd29dafe84621252289a4179/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 95dbcb73dd29dafe84621252289a4179/tags diff --git a/95dbcb73dd29dafe84621252289a4179/tags b/95dbcb73dd29dafe84621252289a4179/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/95dbcb73dd29dafe84621252289a4179/tags @@ -0,0 +1 @@ +feature From 661a42d88d36e34c7a124399187a64086fb8950a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:50:31 -0600 Subject: [PATCH 049/148] create new issue 02175f27333ffe6b0c61218fae3142b6 --- 02175f27333ffe6b0c61218fae3142b6/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 02175f27333ffe6b0c61218fae3142b6/description diff --git a/02175f27333ffe6b0c61218fae3142b6/description b/02175f27333ffe6b0c61218fae3142b6/description new file mode 100644 index 0000000..c2ce541 --- /dev/null +++ b/02175f27333ffe6b0c61218fae3142b6/description @@ -0,0 +1 @@ +enter deep sleep after long button press From 44b2872f4b46bd61409d8561210752b3a47308b8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:50:39 -0600 Subject: [PATCH 050/148] issue 02175f27333ffe6b0c61218fae3142b6 add tag feature --- 02175f27333ffe6b0c61218fae3142b6/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 02175f27333ffe6b0c61218fae3142b6/tags diff --git a/02175f27333ffe6b0c61218fae3142b6/tags b/02175f27333ffe6b0c61218fae3142b6/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/02175f27333ffe6b0c61218fae3142b6/tags @@ -0,0 +1 @@ +feature From 3bbf665bd1fc0f778ca8971b5e6a08b314ed62bb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:51:19 -0600 Subject: [PATCH 051/148] create new issue a05a70c9994ab6c8e3f9c69c7d536952 --- a05a70c9994ab6c8e3f9c69c7d536952/description | 1 + 1 file changed, 1 insertion(+) create mode 100644 a05a70c9994ab6c8e3f9c69c7d536952/description diff --git a/a05a70c9994ab6c8e3f9c69c7d536952/description b/a05a70c9994ab6c8e3f9c69c7d536952/description new file mode 100644 index 0000000..e8fd071 --- /dev/null +++ b/a05a70c9994ab6c8e3f9c69c7d536952/description @@ -0,0 +1 @@ +enter deep sleep after inactivity timeout From 577993b085d1a7b3b1ec822206b8fbd4f7826dba Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:51:26 -0600 Subject: [PATCH 052/148] issue a05a70c9994ab6c8e3f9c69c7d536952 add tag feature --- a05a70c9994ab6c8e3f9c69c7d536952/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 a05a70c9994ab6c8e3f9c69c7d536952/tags diff --git a/a05a70c9994ab6c8e3f9c69c7d536952/tags b/a05a70c9994ab6c8e3f9c69c7d536952/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/a05a70c9994ab6c8e3f9c69c7d536952/tags @@ -0,0 +1 @@ +feature From 6183e4f3a464b2e791db8efaa08ee4e61978ce82 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:51:35 -0600 Subject: [PATCH 053/148] issue d5a78ca0e0e0591dbd312378abcc82a1 add tag bug --- d5a78ca0e0e0591dbd312378abcc82a1/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 d5a78ca0e0e0591dbd312378abcc82a1/tags diff --git a/d5a78ca0e0e0591dbd312378abcc82a1/tags b/d5a78ca0e0e0591dbd312378abcc82a1/tags new file mode 100644 index 0000000..93f371e --- /dev/null +++ b/d5a78ca0e0e0591dbd312378abcc82a1/tags @@ -0,0 +1 @@ +bug From 0be46bb7655b36cf67bd4136cb05467b643446ae Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:52:54 -0600 Subject: [PATCH 054/148] create new issue 49260fa4a2467b55f7b4d197825683a2 --- 49260fa4a2467b55f7b4d197825683a2/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 49260fa4a2467b55f7b4d197825683a2/description diff --git a/49260fa4a2467b55f7b4d197825683a2/description b/49260fa4a2467b55f7b4d197825683a2/description new file mode 100644 index 0000000..e0501bb --- /dev/null +++ b/49260fa4a2467b55f7b4d197825683a2/description @@ -0,0 +1,5 @@ +gpio inputs + +* 1-2 of these wake from sleep interrupt + * 1 for button press + * 1 for coin detect From 9e7e50ba09dc81641ff2c35a69aa3d3b69edd798 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:53:07 -0600 Subject: [PATCH 055/148] issue 49260fa4a2467b55f7b4d197825683a2 add tag feature --- 49260fa4a2467b55f7b4d197825683a2/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 49260fa4a2467b55f7b4d197825683a2/tags diff --git a/49260fa4a2467b55f7b4d197825683a2/tags b/49260fa4a2467b55f7b4d197825683a2/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/49260fa4a2467b55f7b4d197825683a2/tags @@ -0,0 +1 @@ +feature From 8fb48231fed54dee644edfb06aadb9e59f2fe073 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:53:16 -0600 Subject: [PATCH 056/148] edit description of issue 49260fa4a2467b55f7b4d197825683a2 --- 49260fa4a2467b55f7b4d197825683a2/description | 2 ++ 1 file changed, 2 insertions(+) diff --git a/49260fa4a2467b55f7b4d197825683a2/description b/49260fa4a2467b55f7b4d197825683a2/description index e0501bb..f44cd6d 100644 --- a/49260fa4a2467b55f7b4d197825683a2/description +++ b/49260fa4a2467b55f7b4d197825683a2/description @@ -1,5 +1,7 @@ gpio inputs +4x GPIO inputs + * 1-2 of these wake from sleep interrupt * 1 for button press * 1 for coin detect From b967b2e96f2b4f5f3dd8889a31776c6308dea486 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:54:01 -0600 Subject: [PATCH 057/148] create new issue b0682a96fb4d2cb7c6d06141b5d36849 --- b0682a96fb4d2cb7c6d06141b5d36849/description | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 b0682a96fb4d2cb7c6d06141b5d36849/description diff --git a/b0682a96fb4d2cb7c6d06141b5d36849/description b/b0682a96fb4d2cb7c6d06141b5d36849/description new file mode 100644 index 0000000..a41443b --- /dev/null +++ b/b0682a96fb4d2cb7c6d06141b5d36849/description @@ -0,0 +1,5 @@ +LED control + +3 channels of LEDs + +* need PWM-ish dimming, but can be bit-banged / slow From bf2a2370f83e96b68e44c4991d3cda981fcc5692 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:54:19 -0600 Subject: [PATCH 058/148] issue b0682a96fb4d2cb7c6d06141b5d36849 add tag feature --- b0682a96fb4d2cb7c6d06141b5d36849/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 b0682a96fb4d2cb7c6d06141b5d36849/tags diff --git a/b0682a96fb4d2cb7c6d06141b5d36849/tags b/b0682a96fb4d2cb7c6d06141b5d36849/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/b0682a96fb4d2cb7c6d06141b5d36849/tags @@ -0,0 +1 @@ +feature From 428f2b74a636223022bb9596ad3751e6d9622891 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:54:27 -0600 Subject: [PATCH 059/148] issue 8b98f887e6f027890908edf066debce1 add tag feature --- 8b98f887e6f027890908edf066debce1/tags | 1 + 1 file changed, 1 insertion(+) create mode 100644 8b98f887e6f027890908edf066debce1/tags diff --git a/8b98f887e6f027890908edf066debce1/tags b/8b98f887e6f027890908edf066debce1/tags new file mode 100644 index 0000000..a7453f0 --- /dev/null +++ b/8b98f887e6f027890908edf066debce1/tags @@ -0,0 +1 @@ +feature From 977eb0b98a326639026f19b4fe11141dfbdb547d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:54:40 -0600 Subject: [PATCH 060/148] edit description of issue 8b98f887e6f027890908edf066debce1 --- 8b98f887e6f027890908edf066debce1/description | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8b98f887e6f027890908edf066debce1/description b/8b98f887e6f027890908edf066debce1/description index 92ca179..d2095a8 100644 --- a/8b98f887e6f027890908edf066debce1/description +++ b/8b98f887e6f027890908edf066debce1/description @@ -2,4 +2,4 @@ audio player requirements: * DAC using PWM output -* read ADPCM data and export to DAC +* read DPCM data and export to DAC From 8898d09e7eb559fc926db0b306d35d46572f1d96 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 12:55:42 -0600 Subject: [PATCH 061/148] add comment ce3b8bdb2b09e0b7610e363bef7fc146 on issue 8b98f887e6f027890908edf066debce1 --- .../comments/ce3b8bdb2b09e0b7610e363bef7fc146/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description diff --git a/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description b/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description new file mode 100644 index 0000000..a0497af --- /dev/null +++ b/8b98f887e6f027890908edf066debce1/comments/ce3b8bdb2b09e0b7610e363bef7fc146/description @@ -0,0 +1,3 @@ +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 From 85077d4f1e63847e9b991e32de0818165a1c9657 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 13:09:47 -0600 Subject: [PATCH 062/148] add DPCM dac + LED servicing --- ch32v-insert-coin/Cargo.lock | 42 +++++++++++ ch32v-insert-coin/Cargo.toml | 3 +- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- ch32v-insert-coin/src/main.rs | 106 +++++++++++++++++++++++----- 4 files changed, 133 insertions(+), 20 deletions(-) diff --git a/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock index 028f1cb..348625b 100644 --- a/ch32v-insert-coin/Cargo.lock +++ b/ch32v-insert-coin/Cargo.lock @@ -18,6 +18,27 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitfields" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdbce6688e3ab66aff2ab413b762ccde9f37990e27bba0bb38a4b2ad1b5d877" +dependencies = [ + "bitfields-impl", +] + +[[package]] +name = "bitfields-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57413e4b276d883b77fb368b7b33ae6a5eb97692852d49a5394d4f72ba961827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "thiserror", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -71,6 +92,7 @@ name = "ch32v-insert-coin" version = "0.1.0" dependencies = [ "adpcm-pwm-dac", + "bitfields", "ch32-hal", "embassy-executor", "embedded-hal 1.0.0", @@ -593,6 +615,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml index 566357b..b4fc9c7 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -25,6 +25,7 @@ embedded-hal = "1.0.0" qingke-rt = { version = "*", features = ["highcode"] } adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" } +bitfields = "1.0.0" [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. @@ -32,4 +33,4 @@ lto = true opt-level = "s" # Optimize for size. [profile.dev] -overflow-checks = false \ No newline at end of file +overflow-checks = false diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index 8502ed7..e6114d2 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit 8502ed75bf8d3e8df1eb5f1b5576079f55c6e13e +Subproject commit e6114d2c521ef3a769bd2a52411e3a938b566908 diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3ff3278..97f7bc3 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -3,6 +3,7 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +use adpcm_pwm_dac::dac::DpcmDac; use hal::delay::Delay; use hal::gpio::{AnyPin, Level, Output, Pin}; use {ch32_hal as hal}; @@ -14,6 +15,22 @@ use hal::timer::{Channel, GeneralInstance16bit}; // const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80]; const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; +// DPCS DATA +// step size: 5 +// 0 +// + 25 -> [1, 0, 1, 1] -> 0xB +// + 25 -> 0xB +// + 25 -> 0xB +// + 25 -> 0xB +// - 25 -> 0xA +// - 25 -> 0xA +// - 25 -> 0xA +const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; + +// const DATA2: [u8; 1] = [0x00u8]; + + + struct SimplePwmDacPin<'d, T: GeneralInstance16bit>{ pin: SimplePwm<'d, T>, ch: Channel, @@ -65,26 +82,47 @@ fn pwm_blink(mut pwm_dac_pin: SimplePwmDacPin<'_, T>) { } } -fn dac_run(mut dac: Dac<'_, T>, sample_rate: usize) { - let mut delay = Delay; +// // fn dac_run(mut dac: Dac<'_, T>, sample_rate: usize) { +// fn dac_run(mut dac: DpcmDac<'_, T>, sample_rate: usize) { + +// dac.load_data(data); +// // dac.load_data(&DAC_DATA); - dac.load_data(&DAC_DATA); +// let interval_us = 1000000/sample_rate as u32; +// loop { - let interval_us = 1000000/sample_rate as u32; - loop { - dac.output_next(); - delay.delay_us(interval_us); - } -} +// } +// } #[qingke_rt::entry] fn main() -> ! { let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); + + + // let led_pin = PwmPin::new_ch4::<0>(p.PD0); + // let ch = hal::timer::Channel::Ch4; + // let mut pwm = SimplePwm::new( + // p.TIM1, + // None, + // None, + // None, + // Some(pin), + // Hertz::khz(100), + // CountingMode::default(), + // ); + // pwm.enable(ch); + + // + + // LED output setup + let mut led = Output::new(p.PD0, Level::Low, Default::default()); + + // PWM DAC output pin setup let pin = PwmPin::new_ch4::<0>(p.PC4); let ch = hal::timer::Channel::Ch4; let mut pwm = SimplePwm::new( @@ -93,19 +131,51 @@ fn main() -> ! { None, None, Some(pin), - Hertz::khz(24), + Hertz::khz(100), CountingMode::default(), ); pwm.enable(ch); - - let mut pwm_dac_pin = SimplePwmDacPin{pin: pwm, ch}; - let mut dac = Dac::new(pwm_dac_pin); - let sample_rate_hz = 440 * DAC_DATA.len(); - // pwm_blink(pwm_dac_pin); - dac_run(dac, sample_rate_hz); - blink(p.PD6.degrade(), 100); - loop{}; + + + let mut delay = Delay; + + + + // DAC setup + let mut dac = DpcmDac::new(pwm_dac_pin); + let data = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); + dac.load_data(data); + + + // DAC servicer computations + let mut dac_active = true; + let sample_rate_hz = 16000; + let dac_tick_per_service = 5; + let tick_rate_hz = sample_rate_hz * dac_tick_per_service; + let tick_interval = 1000000/(tick_rate_hz); + + // LED servicer computations + let mut led_active = true; + let led_blink_rate_hz = 3; + let led_tick_per_service = (tick_rate_hz/(led_blink_rate_hz * 2)); + + + // pwm_blink(pwm_dac_pin); + let mut tick: usize = 0; + loop{ + let dac_need_service = ((tick % dac_tick_per_service) == 0); + if(dac_need_service && dac_active) { + dac_active = dac.output_next(); + } + + let led_need_service = ((tick % led_tick_per_service) == 0); + if(led_need_service && led_active) { + led.toggle(); + } + tick = tick.wrapping_add(1); + delay.delay_us(tick_interval as u32); + }; } From 5be93cc32e987eb5da5c2b42d7c78566098ae060 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 14:32:58 -0600 Subject: [PATCH 063/148] work towards interrupts --- ch32v-insert-coin/src/main.rs | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 97f7bc3..3f9e9ef 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -4,9 +4,11 @@ #![feature(impl_trait_in_assoc_type)] use adpcm_pwm_dac::dac::DpcmDac; -use hal::delay::Delay; -use hal::gpio::{AnyPin, Level, Output, Pin}; use {ch32_hal as hal}; +use hal::peripherals::EXTI4; +use hal::{bind_interrupts, interrupt}; +use hal::delay::Delay; +use hal::gpio::{AnyPin, Level, Input, Output, Pin, Pull}; use hal::time::Hertz; use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; @@ -30,6 +32,7 @@ const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; // const DATA2: [u8; 1] = [0x00u8]; +static mut IRQ1_FLAG: bool = false; struct SimplePwmDacPin<'d, T: GeneralInstance16bit>{ pin: SimplePwm<'d, T>, @@ -95,6 +98,15 @@ fn pwm_blink(mut pwm_dac_pin: SimplePwmDacPin<'_, T>) { // } // } + +#[qingke_rt::interrupt] +fn EXTI4(){ + unsafe{IRQ1_FLAG = true;}; +} +// bind_interrupts!(struct Irqs { +// EXTI4 => button_press(); +// }); + #[qingke_rt::entry] fn main() -> ! { let mut config = hal::Config::default(); @@ -102,6 +114,12 @@ fn main() -> ! { let p = hal::init(config); + // button 1 setup + // let b1 = Input::new(p.PA2, Pull::Up); + + // p.EXTI4 + let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); + // let led_pin = PwmPin::new_ch4::<0>(p.PD0); // let ch = hal::timer::Channel::Ch4; // let mut pwm = SimplePwm::new( @@ -120,6 +138,8 @@ fn main() -> ! { // LED output setup let mut led = Output::new(p.PD0, Level::Low, Default::default()); + // LED1 output setup + let mut led1 = Output::new(p.PD6, Level::High, Default::default()); // PWM DAC output pin setup @@ -160,10 +180,22 @@ fn main() -> ! { let led_blink_rate_hz = 3; let led_tick_per_service = (tick_rate_hz/(led_blink_rate_hz * 2)); + // LED1 servicer vars + let mut led1_need_service = false; + // pwm_blink(pwm_dac_pin); let mut tick: usize = 0; loop{ + // handle IRQ flags + unsafe { + if(IRQ1_FLAG) { + led1_need_service = true; + // led_active = true; + IRQ1_FLAG = false; + } + } + let dac_need_service = ((tick % dac_tick_per_service) == 0); if(dac_need_service && dac_active) { dac_active = dac.output_next(); @@ -173,6 +205,12 @@ fn main() -> ! { if(led_need_service && led_active) { led.toggle(); } + + if led1_need_service { + led1.set_low(); + led1_need_service = false; + } + tick = tick.wrapping_add(1); delay.delay_us(tick_interval as u32); }; @@ -182,4 +220,5 @@ fn main() -> ! { #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { loop {} -} \ No newline at end of file +} + From c212831760c4f6f55f2e7818cdfe2e0b4d02124f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 17:55:11 -0600 Subject: [PATCH 064/148] bump adpcm pwm dac --- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index e6114d2..714715b 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit e6114d2c521ef3a769bd2a52411e3a938b566908 +Subproject commit 714715b4aae512d1384b7387e3d2f695ea9b348e From 67d5817940b0fff735f1adaad87167baefbf9407 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 11 Aug 2025 18:28:52 -0600 Subject: [PATCH 065/148] update impl to enable pwm ch --- ch32v-insert-coin/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3f9e9ef..3ff6126 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -45,10 +45,17 @@ use adpcm_pwm_dac::{interface::DacInterface, dac::Dac}; impl DacInterface for SimplePwmDacPin<'_, T> where T: GeneralInstance16bit { fn write_amplitude(&mut self, amplitude: u8) { + if !self.pin.is_enabled(self.ch) { + self.pin.enable(self.ch); + } let max_duty = self.pin.get_max_duty(); let dc = amplitude as u32 * max_duty / 100; self.pin.set_duty(self.ch, dc); } + + fn disable(&mut self) { + self.pin.disable(self.ch); + } } fn blink(pin: AnyPin, interval_ms: u64) { From 0496b6e103e8266f5041665b9ba6d1bb0333dec4 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 11 Aug 2025 18:46:39 -0600 Subject: [PATCH 066/148] add notes --- notes.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 notes.md diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..8883403 --- /dev/null +++ b/notes.md @@ -0,0 +1,18 @@ +# 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 ` + From 77188a3eaad667d702fca0f78734216b92b92ad7 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 11 Aug 2025 18:46:54 -0600 Subject: [PATCH 067/148] update main to do a repeated sweep --- ch32v-insert-coin/src/main.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3ff6126..5d05bcc 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -125,7 +125,7 @@ fn main() -> ! { // let b1 = Input::new(p.PA2, Pull::Up); // p.EXTI4 - let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); + // let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); // let led_pin = PwmPin::new_ch4::<0>(p.PD0); // let ch = hal::timer::Channel::Ch4; @@ -195,17 +195,18 @@ fn main() -> ! { let mut tick: usize = 0; loop{ // handle IRQ flags - unsafe { - if(IRQ1_FLAG) { - led1_need_service = true; - // led_active = true; - IRQ1_FLAG = false; - } - } + // unsafe { + // if(IRQ1_FLAG) { + // led1_need_service = true; + // // led_active = true; + // IRQ1_FLAG = false; + // } + // } let dac_need_service = ((tick % dac_tick_per_service) == 0); if(dac_need_service && dac_active) { - dac_active = dac.output_next(); + // dac_active = dac.output_next(); + dac.output_next(); } let led_need_service = ((tick % led_tick_per_service) == 0); From 51af5c334337d4a59221a9d86a545fa8ec789f4d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 17:03:29 -0600 Subject: [PATCH 068/148] move to modular servicer design --- ch32v-insert-coin/.cargo/config.toml | 4 +- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- .../src/insert_coin/insert_coin.rs | 165 +++++++++++++ ch32v-insert-coin/src/insert_coin/mod.rs | 5 + .../src/insert_coin/services/dac.rs | 51 ++++ .../src/insert_coin/services/led.rs | 42 ++++ .../src/insert_coin/services/mod.rs | 8 + .../src/insert_coin/services/services.rs | 25 ++ ch32v-insert-coin/src/main.rs | 223 ++++++------------ 9 files changed, 371 insertions(+), 154 deletions(-) create mode 100644 ch32v-insert-coin/src/insert_coin/insert_coin.rs create mode 100644 ch32v-insert-coin/src/insert_coin/mod.rs create mode 100644 ch32v-insert-coin/src/insert_coin/services/dac.rs create mode 100644 ch32v-insert-coin/src/insert_coin/services/led.rs create mode 100644 ch32v-insert-coin/src/insert_coin/services/mod.rs create mode 100644 ch32v-insert-coin/src/insert_coin/services/services.rs diff --git a/ch32v-insert-coin/.cargo/config.toml b/ch32v-insert-coin/.cargo/config.toml index 57920f5..cf4d2cd 100644 --- a/ch32v-insert-coin/.cargo/config.toml +++ b/ch32v-insert-coin/.cargo/config.toml @@ -6,12 +6,12 @@ target = "riscv32ec-unknown-none-elf.json" # 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" # 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" +# runner = "probe-rs run --chip ch32v003" [unstable] build-std = ["core"] diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index 714715b..c8d33b3 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit 714715b4aae512d1384b7387e3d2f695ea9b348e +Subproject commit c8d33b3c41bf4b53ff76837a9557f7960f508df5 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..b67200d --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -0,0 +1,165 @@ +use adpcm_pwm_dac::dac; +use ch32_hal::timer::GeneralInstance16bit; +use ch32_hal::timer::simple_pwm::SimplePwm; +use ch32_hal::timer::Channel; +use ch32_hal::delay::Delay; + +use crate::insert_coin::services::{DacService, LedService, Service, ServiceData}; + + +pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { + 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 CoreConfig { + tick_rate_hz: usize, +} +impl CoreConfig { + pub fn new(tick_rate_hz: usize) -> Self { + Self { + tick_rate_hz, + } + } +} + +#[derive(Default)] +struct Core { + tick: usize, +} + +pub struct InsertCoin<'a, T: GeneralInstance16bit> { + core: Core, + config: CoreConfig, + pwm_core: SimplePwmCore<'a, T>, + led0: LedService, + led1: LedService, + // led2: LedService, + + 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_blink_rate_hz = 9; + let led0_tick_per_service = (config.tick_rate_hz/(led0_blink_rate_hz * 2)); + let led0_service_data = ServiceData::new(led0_tick_per_service); + let led0 = LedService::new(ch32_hal::timer::Channel::Ch3, led0_service_data); + + // LED1 servicer setup + let led1_blink_rate_hz = 3; + let led1_tick_per_service = (config.tick_rate_hz/(led1_blink_rate_hz * 2)); + let led1_service_data = ServiceData::new(led1_tick_per_service); + let led1 = LedService::new(ch32_hal::timer::Channel::Ch1, led1_service_data); + + + // DAC servicer setup + let dac_sample_rate_hz = 16000; + let dac_tick_per_service = (config.tick_rate_hz/(dac_sample_rate_hz)); + let dac_service_data = ServiceData::new(dac_tick_per_service); + let dac = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); + let data = include_bytes!("../../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); + dac.load_data(data); + + Self { + config, + core: Default::default(), + pwm_core, + led0, + led1, + // led2, + dac, + // led1: Led { + // channel: hal::timer::Channel::Ch1, + // amplitude: 0, + // need_service: false, + // }, + } + } + + /// 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.led0.tick(); + self.led1.tick(); + 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); + } + } +} \ No newline at end of file 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..a4bc002 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -0,0 +1,5 @@ +mod insert_coin; +mod services; +use services::LedService; + +pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore}; \ No newline at end of file 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..ec2ee85 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -0,0 +1,51 @@ +use crate::insert_coin::services::{ServiceData, Service}; + + +use adpcm_pwm_dac::{dac::{DpcmDac, DpcmDecoder}, interface::DacInterface}; +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, +} + +impl<'a> DacService<'a> { + pub fn new(channel: Channel, service_data: ServiceData) -> 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, + } + } + + pub fn load_data(&self, data: &'a [u8]) { + self.dpcm_decoder.borrow_mut().load_data(data); + } + + pub fn set_amplitude(&self, amplitude: usize) { + self.amplitude.replace(amplitude); + } + pub fn get_amplitude(&self) -> usize { + *self.amplitude.borrow() + } +} + +impl<'a> Service for DacService<'a> { + fn tick(&self) { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + } + + fn need_service(&self) -> bool { + 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; + 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..f63934b --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -0,0 +1,42 @@ +use ch32_hal::timer::Channel; + +use crate::insert_coin::services::{ServiceData, Service}; + +pub struct LedService { + service_data: core::cell::RefCell, + pub channel: Channel, + pub amplitude: u8, +} + +impl LedService { + pub fn new(channel: Channel, service_data: ServiceData) -> Self { + Self { + service_data: core::cell::RefCell::new(service_data), + channel, + amplitude: 0, + } + } + + pub fn set_amplitude(&mut self, amplitude: u8) { + self.amplitude = amplitude; + } +} + + +impl Service for LedService { + fn tick(&self) { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + } + + fn need_service(&self) -> bool { + 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/insert_coin/services/mod.rs b/ch32v-insert-coin/src/insert_coin/services/mod.rs new file mode 100644 index 0000000..ba70192 --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/mod.rs @@ -0,0 +1,8 @@ +mod services; +pub use services::{Service, ServiceData}; + +mod led; +pub use led::LedService; + +mod dac; +pub use dac::DacService; \ 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..002edca --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/services.rs @@ -0,0 +1,25 @@ +pub struct ServiceData { + pub ticks_per_service: usize, + pub ticks_remaining: usize, +} + +impl ServiceData { + pub fn new(ticks_per_service: usize) -> Self { + Self { + ticks_per_service, + ticks_remaining: 0, + } + } +} + +pub trait Service { + /// 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); +} diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 5d05bcc..a2131af 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -3,6 +3,10 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +mod insert_coin; +use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig}; + + use adpcm_pwm_dac::dac::DpcmDac; use {ch32_hal as hal}; use hal::peripherals::EXTI4; @@ -14,6 +18,15 @@ use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; use hal::timer::{Channel, GeneralInstance16bit}; +// #[qingke_rt::interrupt] +// fn EXTI4(){ +// unsafe{IRQ1_FLAG = true;}; +// } +// bind_interrupts!(struct Irqs { +// EXTI4 => button_press(); +// }); + + // const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80]; const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; @@ -29,90 +42,47 @@ const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; // - 25 -> 0xA const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; -// const DATA2: [u8; 1] = [0x00u8]; - - static mut IRQ1_FLAG: bool = false; -struct SimplePwmDacPin<'d, T: GeneralInstance16bit>{ - pin: SimplePwm<'d, T>, - ch: Channel, -} +// struct SimplePwmHandle<'a, 'c, T: GeneralInstance16bit>{ +// core: &'a SimplePwmCore<'c, T>, +// channel: Channel, +// } -use adpcm_pwm_dac::{interface::DacInterface, dac::Dac}; - -impl DacInterface for SimplePwmDacPin<'_, T> -where T: GeneralInstance16bit { - fn write_amplitude(&mut self, amplitude: u8) { - if !self.pin.is_enabled(self.ch) { - self.pin.enable(self.ch); - } - let max_duty = self.pin.get_max_duty(); - let dc = amplitude as u32 * max_duty / 100; - self.pin.set_duty(self.ch, dc); - } - - fn disable(&mut self) { - self.pin.disable(self.ch); - } -} - -fn blink(pin: AnyPin, interval_ms: u64) { - let mut led = Output::new(pin, Level::Low, Default::default()); - - let mut delay = Delay; - - loop { - let hb_count = 3; - let hb_period_ms = 1000; - for _ in 0..hb_count { - led.set_low(); - delay.delay_ms((interval_ms/2) as u32); - led.set_high(); - delay.delay_ms((interval_ms/2) as u32); - } - delay.delay_ms((hb_period_ms - (interval_ms * hb_count)) as u32); - } -} - -fn pwm_blink(mut pwm_dac_pin: SimplePwmDacPin<'_, T>) { - let mut delay = Delay; - - - - let interval_ms = 1000u32; - loop { - pwm_dac_pin.write_amplitude(75); - delay.delay_ms((interval_ms/2) as u32); - pwm_dac_pin.write_amplitude(50); - delay.delay_ms((interval_ms/2) as u32); - pwm_dac_pin.write_amplitude(25); - delay.delay_ms((interval_ms/2) as u32); - } -} - -// // fn dac_run(mut dac: Dac<'_, T>, sample_rate: usize) { -// fn dac_run(mut dac: DpcmDac<'_, T>, sample_rate: usize) { - - -// dac.load_data(data); -// // dac.load_data(&DAC_DATA); - -// let interval_us = 1000000/sample_rate as u32; -// loop { +// impl<'a, 'c, T: GeneralInstance16bit> SimplePwmHandle<'a, 'c, T> { +// pub fn write_amplitude(&'a self, amplitude: u8) { +// self.core.write_amplitude(self.channel, amplitude); +// } +// pub fn disable(&'a self) { +// self.core.disable(self.channel); // } // } +// struct SimplePwmDacHandle<'d, T: GeneralInstance16bit>{ +// pin: core::cell::RefCell>, +// ch: Channel, +// } -#[qingke_rt::interrupt] -fn EXTI4(){ - unsafe{IRQ1_FLAG = true;}; -} -// bind_interrupts!(struct Irqs { -// EXTI4 => button_press(); -// }); + +// use adpcm_pwm_dac::{interface::DacInterface, dac::Dac}; + +// impl DacInterface for SimplePwmDacHandle<'_, T> +// where T: GeneralInstance16bit { +// fn write_amplitude(&mut self, amplitude: u8) { +// if !self.pin.borrow().is_enabled(self.ch) { +// self.pin.get_mut().enable(self.ch); +// } +// let max_duty = self.pin.borrow().get_max_duty(); +// let dc = amplitude as u32 * max_duty / 100; +// self.pin.get_mut().set_duty(self.ch, dc); +// } + +// fn disable(&mut self) { +// self.pin.get_mut().disable(self.ch); +// } +// } #[qingke_rt::entry] fn main() -> ! { @@ -120,108 +90,59 @@ fn main() -> ! { config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); - // button 1 setup // let b1 = Input::new(p.PA2, Pull::Up); // p.EXTI4 // let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); - // let led_pin = PwmPin::new_ch4::<0>(p.PD0); - // let ch = hal::timer::Channel::Ch4; - // let mut pwm = SimplePwm::new( - // p.TIM1, - // None, - // None, - // None, - // Some(pin), - // Hertz::khz(100), - // CountingMode::default(), - // ); - // pwm.enable(ch); - - // - // LED output setup - let mut led = Output::new(p.PD0, Level::Low, Default::default()); + // LED0 output setup + let mut led0_pin = PwmPin::new_ch3::<0>(p.PC3); + let led0_ch = hal::timer::Channel::Ch3; // LED1 output setup - let mut led1 = Output::new(p.PD6, Level::High, Default::default()); + let mut led1_pin = PwmPin::new_ch1::<0>(p.PD2); + let led1_ch = hal::timer::Channel::Ch1; + + + // LED2 output setup + + // DAC output setup + let dac_pin = PwmPin::new_ch4::<0>(p.PC4); + let dac_ch = hal::timer::Channel::Ch4; + - // PWM DAC output pin setup - let pin = PwmPin::new_ch4::<0>(p.PC4); - let ch = hal::timer::Channel::Ch4; + // PWM timer setup let mut pwm = SimplePwm::new( p.TIM1, + Some(led1_pin), None, - None, - None, - Some(pin), + Some(led0_pin), + Some(dac_pin), Hertz::khz(100), CountingMode::default(), ); - pwm.enable(ch); - let mut pwm_dac_pin = SimplePwmDacPin{pin: pwm, ch}; - - let mut delay = Delay; - - - - // DAC setup - let mut dac = DpcmDac::new(pwm_dac_pin); - let data = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); - dac.load_data(data); - - - // DAC servicer computations - let mut dac_active = true; let sample_rate_hz = 16000; let dac_tick_per_service = 5; let tick_rate_hz = sample_rate_hz * dac_tick_per_service; - let tick_interval = 1000000/(tick_rate_hz); - // LED servicer computations - let mut led_active = true; - let led_blink_rate_hz = 3; - let led_tick_per_service = (tick_rate_hz/(led_blink_rate_hz * 2)); + let config = CoreConfig::new(tick_rate_hz); - // LED1 servicer vars - let mut led1_need_service = false; + let pwm_core = SimplePwmCore::new(pwm); + let app = InsertCoin::new(config, pwm_core); - // pwm_blink(pwm_dac_pin); - let mut tick: usize = 0; - loop{ - // handle IRQ flags - // unsafe { - // if(IRQ1_FLAG) { - // led1_need_service = true; - // // led_active = true; - // IRQ1_FLAG = false; - // } - // } + // insert_coin.init(); + + app.run(); - let dac_need_service = ((tick % dac_tick_per_service) == 0); - if(dac_need_service && dac_active) { - // dac_active = dac.output_next(); - dac.output_next(); - } - - let led_need_service = ((tick % led_tick_per_service) == 0); - if(led_need_service && led_active) { - led.toggle(); - } - - if led1_need_service { - led1.set_low(); - led1_need_service = false; - } - - tick = tick.wrapping_add(1); - delay.delay_us(tick_interval as u32); - }; + // // DAC servicer setup + // let mut pwm_dac_interface = SimplePwmDacHandle{pin: core::cell::RefCell::new(pwm), ch: dac_ch}; + // let mut dac = DpcmDac::new(pwm_dac_interface); + // let mut dac_active = true; } From 1a714bd90720ff01ed1c888a6f6c1556fe75338c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 19:37:31 -0600 Subject: [PATCH 069/148] add tick timer and re-factor for app vs interface --- .../src/insert_coin/insert_coin.rs | 59 ++++++++++++++--- ch32v-insert-coin/src/insert_coin/mod.rs | 3 + .../src/insert_coin/services/dac.rs | 8 +-- .../src/insert_coin/services/led.rs | 8 +-- .../src/insert_coin/services/mod.rs | 7 +- .../src/insert_coin/services/services.rs | 6 +- .../src/insert_coin/services/tick_timer.rs | 34 ++++++++++ ch32v-insert-coin/src/main.rs | 66 +++++++++++++++++-- 8 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 ch32v-insert-coin/src/insert_coin/services/tick_timer.rs diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index b67200d..6487029 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -4,9 +4,15 @@ use ch32_hal::timer::simple_pwm::SimplePwm; use ch32_hal::timer::Channel; use ch32_hal::delay::Delay; -use crate::insert_coin::services::{DacService, LedService, Service, ServiceData}; +use crate::insert_coin::services::{DacService, LedService, 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> { pwm: core::cell::RefCell>, } @@ -43,7 +49,7 @@ impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { pub struct CoreConfig { - tick_rate_hz: usize, + pub tick_rate_hz: usize, } impl CoreConfig { pub fn new(tick_rate_hz: usize) -> Self { @@ -56,14 +62,15 @@ impl CoreConfig { #[derive(Default)] struct Core { tick: usize, + active: bool, } pub struct InsertCoin<'a, T: GeneralInstance16bit> { core: Core, - config: CoreConfig, + pub config: CoreConfig, pwm_core: SimplePwmCore<'a, T>, - led0: LedService, - led1: LedService, + pub led0: LedService, + pub led1: LedService, // led2: LedService, dac: DacService<'a>, @@ -77,20 +84,20 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // LED0 servicer setup let led0_blink_rate_hz = 9; let led0_tick_per_service = (config.tick_rate_hz/(led0_blink_rate_hz * 2)); - let led0_service_data = ServiceData::new(led0_tick_per_service); + let led0_service_data = TickServiceData::new(led0_tick_per_service); let led0 = LedService::new(ch32_hal::timer::Channel::Ch3, led0_service_data); // LED1 servicer setup let led1_blink_rate_hz = 3; let led1_tick_per_service = (config.tick_rate_hz/(led1_blink_rate_hz * 2)); - let led1_service_data = ServiceData::new(led1_tick_per_service); + let led1_service_data = TickServiceData::new(led1_tick_per_service); let led1 = LedService::new(ch32_hal::timer::Channel::Ch1, led1_service_data); // DAC servicer setup let dac_sample_rate_hz = 16000; let dac_tick_per_service = (config.tick_rate_hz/(dac_sample_rate_hz)); - let dac_service_data = ServiceData::new(dac_tick_per_service); + let dac_service_data = TickServiceData::new(dac_tick_per_service); let dac = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); let data = include_bytes!("../../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); dac.load_data(data); @@ -111,6 +118,34 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { } } + /// takes self reference and runs + pub fn service(&mut self) { + + if self.is_active() { + self.led0.tick(); + self.led1.tick(); + 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; @@ -162,4 +197,12 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { 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; + } } \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index a4bc002..c97fd60 100644 --- a/ch32v-insert-coin/src/insert_coin/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -2,4 +2,7 @@ mod insert_coin; mod services; use services::LedService; +pub use services::TickTimerService; + +pub use services::{TickService, TickServiceData}; pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore}; \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index ec2ee85..22681de 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -1,18 +1,18 @@ -use crate::insert_coin::services::{ServiceData, Service}; +use crate::insert_coin::services::{TickServiceData, TickService}; use adpcm_pwm_dac::{dac::{DpcmDac, DpcmDecoder}, interface::DacInterface}; use ch32_hal::timer::Channel; pub struct DacService<'a> { - service_data: core::cell::RefCell, + service_data: core::cell::RefCell, dpcm_decoder: core::cell::RefCell>, amplitude: core::cell::RefCell, pub channel: Channel, } impl<'a> DacService<'a> { - pub fn new(channel: Channel, service_data: ServiceData) -> Self { + 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()), @@ -33,7 +33,7 @@ impl<'a> DacService<'a> { } } -impl<'a> Service for DacService<'a> { +impl<'a> TickService for DacService<'a> { fn tick(&self) { let mut tc = self.service_data.borrow_mut(); tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); diff --git a/ch32v-insert-coin/src/insert_coin/services/led.rs b/ch32v-insert-coin/src/insert_coin/services/led.rs index f63934b..b11e9d8 100644 --- a/ch32v-insert-coin/src/insert_coin/services/led.rs +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -1,15 +1,15 @@ use ch32_hal::timer::Channel; -use crate::insert_coin::services::{ServiceData, Service}; +use crate::insert_coin::services::{TickServiceData, TickService}; pub struct LedService { - service_data: core::cell::RefCell, + service_data: core::cell::RefCell, pub channel: Channel, pub amplitude: u8, } impl LedService { - pub fn new(channel: Channel, service_data: ServiceData) -> Self { + pub fn new(channel: Channel, service_data: TickServiceData) -> Self { Self { service_data: core::cell::RefCell::new(service_data), channel, @@ -23,7 +23,7 @@ impl LedService { } -impl Service for LedService { +impl TickService for LedService { fn tick(&self) { let mut tc = self.service_data.borrow_mut(); tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); diff --git a/ch32v-insert-coin/src/insert_coin/services/mod.rs b/ch32v-insert-coin/src/insert_coin/services/mod.rs index ba70192..730c651 100644 --- a/ch32v-insert-coin/src/insert_coin/services/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/services/mod.rs @@ -1,8 +1,11 @@ mod services; -pub use services::{Service, ServiceData}; +pub use services::{TickService, TickServiceData}; mod led; pub use led::LedService; mod dac; -pub use dac::DacService; \ No newline at end of file +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 index 002edca..45e0ac0 100644 --- a/ch32v-insert-coin/src/insert_coin/services/services.rs +++ b/ch32v-insert-coin/src/insert_coin/services/services.rs @@ -1,9 +1,9 @@ -pub struct ServiceData { +pub struct TickServiceData { pub ticks_per_service: usize, pub ticks_remaining: usize, } -impl ServiceData { +impl TickServiceData { pub fn new(ticks_per_service: usize) -> Self { Self { ticks_per_service, @@ -12,7 +12,7 @@ impl ServiceData { } } -pub trait Service { +pub trait TickService { /// indicate to the service that a tick has occurred fn tick(&self); 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..c740c6b --- /dev/null +++ b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs @@ -0,0 +1,34 @@ +use ch32_hal::timer::Channel; + +use crate::insert_coin::services::{TickServiceData, TickService}; + +pub struct TickTimerService { + service_data: core::cell::RefCell, +} + +impl TickTimerService { + pub fn new(service_data: TickServiceData) -> Self { + Self { + service_data: core::cell::RefCell::new(service_data), + } + } +} + + +impl TickService for TickTimerService { + fn tick(&self) { + let mut tc = self.service_data.borrow_mut(); + tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + } + + fn need_service(&self) -> bool { + 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 index a2131af..aaab104 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -84,14 +84,18 @@ static mut IRQ1_FLAG: bool = false; // } // } +// TODO: remove +use insert_coin::{TickService, TickServiceData}; +use insert_coin::TickTimerService; + #[qingke_rt::entry] fn main() -> ! { let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); - // button 1 setup - // let b1 = Input::new(p.PA2, Pull::Up); + // coin button input setup + let b1 = Input::new(p.PD4, Pull::Up); // p.EXTI4 // let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); @@ -133,12 +137,62 @@ fn main() -> ! { let pwm_core = SimplePwmCore::new(pwm); - let app = InsertCoin::new(config, pwm_core); - + let mut interfaces = InsertCoin::new(config, pwm_core); + interfaces.set_active(true); // insert_coin.init(); - - app.run(); + 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]; + + // tick timer 0 + let tt0_fire_rate_hz = 9; + let tt0_tick_per_service = (interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2)); + let tt0_service_data = TickServiceData::new(tt0_tick_per_service); + let tt0 = TickTimerService::new(tt0_service_data); + + // tick timer 1 + let tt1_fire_rate_hz = 3; + let tt1_tick_per_service = (interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2)); + let tt1_service_data = TickServiceData::new(tt1_tick_per_service); + let tt1 = TickTimerService::new(tt1_service_data); + + + let mut delay = Delay; + let tick_interval_us = 1000000/interfaces.config.tick_rate_hz; + + loop { + + tt0.tick(); + tt1.tick(); + // TODO: timer + // if app.led0.need_service() { + if tt0.need_service() { + interfaces.led0.set_amplitude(led0_dcs[led0_index]); + led0_index += 1; + if led0_index > led0_dcs.len() - 1 { + led0_index = 0; + } + tt0.service(); + } + // } + + // if app.led1.need_service() { + if tt1.need_service() { + interfaces.led1.set_amplitude(led1_dcs[led1_index]); + led1_index += 1; + if led1_index > led1_dcs.len() - 1 { + led1_index = 0; + } + tt1.service() + } + + interfaces.service(); + + delay.delay_us(tick_interval_us as u32); + } // // DAC servicer setup // let mut pwm_dac_interface = SimplePwmDacHandle{pin: core::cell::RefCell::new(pwm), ch: dac_ch}; // let mut dac = DpcmDac::new(pwm_dac_interface); From e3235b34a4ec52267c61a5741eb75a6c7e205edf Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 21:40:39 -0600 Subject: [PATCH 070/148] initial demo --- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- .../src/insert_coin/insert_coin.rs | 6 +- ch32v-insert-coin/src/insert_coin/mod.rs | 2 +- .../src/insert_coin/services/led.rs | 22 ++- .../src/insert_coin/services/mod.rs | 2 +- .../src/insert_coin/services/services.rs | 8 +- .../src/insert_coin/services/tick_timer.rs | 28 +++- ch32v-insert-coin/src/main.rs | 148 +++++++++++++++--- 8 files changed, 167 insertions(+), 51 deletions(-) diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index c8d33b3..ba25b7c 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit c8d33b3c41bf4b53ff76837a9557f7960f508df5 +Subproject commit ba25b7c89f4deb52426d97fd35eb13496f183775 diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index 6487029..fe69780 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -4,7 +4,7 @@ use ch32_hal::timer::simple_pwm::SimplePwm; use ch32_hal::timer::Channel; use ch32_hal::delay::Delay; -use crate::insert_coin::services::{DacService, LedService, TickService, TickServiceData}; +use crate::insert_coin::services::{DacService, LedService, TickService, TickServiceData, Service}; // static mut led0_index: usize = 0; @@ -122,8 +122,6 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { pub fn service(&mut self) { if self.is_active() { - self.led0.tick(); - self.led1.tick(); self.dac.tick(); @@ -159,8 +157,6 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; loop { - self.led0.tick(); - self.led1.tick(); self.dac.tick(); diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index c97fd60..e13b953 100644 --- a/ch32v-insert-coin/src/insert_coin/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -4,5 +4,5 @@ use services::LedService; pub use services::TickTimerService; -pub use services::{TickService, TickServiceData}; +pub use services::{TickService, TickServiceData, Service}; pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore}; \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/led.rs b/ch32v-insert-coin/src/insert_coin/services/led.rs index b11e9d8..d248ab6 100644 --- a/ch32v-insert-coin/src/insert_coin/services/led.rs +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -1,9 +1,10 @@ use ch32_hal::timer::Channel; -use crate::insert_coin::services::{TickServiceData, TickService}; +use crate::insert_coin::services::{Service, TickService, TickServiceData}; pub struct LedService { - service_data: core::cell::RefCell, + // need_service: core::cell::RefCell, + need_service: bool, pub channel: Channel, pub amplitude: u8, } @@ -11,7 +12,8 @@ pub struct LedService { impl LedService { pub fn new(channel: Channel, service_data: TickServiceData) -> Self { Self { - service_data: core::cell::RefCell::new(service_data), + // service_data: core::cell::RefCell::new(service_data), + need_service: false, channel, amplitude: 0, } @@ -19,23 +21,19 @@ impl LedService { pub fn set_amplitude(&mut self, amplitude: u8) { self.amplitude = amplitude; + self.need_service = true; } } -impl TickService for LedService { - fn tick(&self) { - let mut tc = self.service_data.borrow_mut(); - tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); - } +impl Service for LedService { fn need_service(&self) -> bool { - self.service_data.borrow().ticks_remaining == 0 + self.need_service } - fn service(&self) { - let mut tc = self.service_data.borrow_mut(); - tc.ticks_remaining = tc.ticks_per_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 index 730c651..a761548 100644 --- a/ch32v-insert-coin/src/insert_coin/services/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/services/mod.rs @@ -1,5 +1,5 @@ mod services; -pub use services::{TickService, TickServiceData}; +pub use services::{TickService, TickServiceData, Service}; mod led; pub use led::LedService; diff --git a/ch32v-insert-coin/src/insert_coin/services/services.rs b/ch32v-insert-coin/src/insert_coin/services/services.rs index 45e0ac0..e2c86e2 100644 --- a/ch32v-insert-coin/src/insert_coin/services/services.rs +++ b/ch32v-insert-coin/src/insert_coin/services/services.rs @@ -7,7 +7,7 @@ impl TickServiceData { pub fn new(ticks_per_service: usize) -> Self { Self { ticks_per_service, - ticks_remaining: 0, + ticks_remaining: ticks_per_service, } } } @@ -23,3 +23,9 @@ pub trait TickService { /// 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 index c740c6b..90e0188 100644 --- a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs +++ b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs @@ -4,25 +4,45 @@ use crate::insert_coin::services::{TickServiceData, TickService}; pub struct TickTimerService { service_data: core::cell::RefCell, + auto_reset: bool, + enabled: bool, } impl TickTimerService { - pub fn new(service_data: TickServiceData) -> Self { + pub fn new(service_data: TickServiceData, auto_reset: bool) -> Self { Self { service_data: core::cell::RefCell::new(service_data), + 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_per_service = sd.ticks_remaining; + self.enabled = false; } + + pub fn enable(&mut self, enable: bool) { + self.enabled = enable; + } } impl TickService for TickTimerService { fn tick(&self) { - let mut tc = self.service_data.borrow_mut(); - tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + 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.service_data.borrow().ticks_remaining == 0 + self.enabled && self.service_data.borrow().ticks_remaining == 0 } fn service(&self) { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index aaab104..59515b9 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -95,7 +95,7 @@ fn main() -> ! { let p = hal::init(config); // coin button input setup - let b1 = Input::new(p.PD4, Pull::Up); + let coin_button = Input::new(p.PD4, Pull::Up); // p.EXTI4 // let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); @@ -151,47 +151,143 @@ fn main() -> ! { let tt0_fire_rate_hz = 9; let tt0_tick_per_service = (interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2)); let tt0_service_data = TickServiceData::new(tt0_tick_per_service); - let tt0 = TickTimerService::new(tt0_service_data); + let mut tt0 = TickTimerService::new(tt0_service_data, true); // tick timer 1 let tt1_fire_rate_hz = 3; let tt1_tick_per_service = (interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2)); let tt1_service_data = TickServiceData::new(tt1_tick_per_service); - let tt1 = TickTimerService::new(tt1_service_data); + let mut tt1 = TickTimerService::new(tt1_service_data, true); + // debounce timer + // let db_ticks = interfaces.config.tick_rate_hz / 100; + let db_ticks = interfaces.config.tick_rate_hz / 100; + let db_timer_data = TickServiceData::new(db_ticks); + let mut db_timer = TickTimerService::new(db_timer_data, false); + db_timer.reset(); + + // short press timer + let sp_ticks = 2 * interfaces.config.tick_rate_hz; + let sp_timer_data = TickServiceData::new(sp_ticks); + let mut sp_timer = TickTimerService::new(sp_timer_data, false); + sp_timer.reset(); + + // long press timer + let lp_ticks = 5 * interfaces.config.tick_rate_hz; + let lp_timer_data = TickServiceData::new(lp_ticks); + let mut lp_timer = TickTimerService::new(lp_timer_data, false); + lp_timer.reset(); let mut delay = Delay; let tick_interval_us = 1000000/interfaces.config.tick_rate_hz; - + + interfaces.led0.set_amplitude(100); + interfaces.led1.set_amplitude(100); + + loop { - - tt0.tick(); - tt1.tick(); - // TODO: timer - // if app.led0.need_service() { - if tt0.need_service() { - interfaces.led0.set_amplitude(led0_dcs[led0_index]); - led0_index += 1; - if led0_index > led0_dcs.len() - 1 { - led0_index = 0; + // wait for button to be released if it's pressed + if (coin_button.is_low()) { + loop { + if coin_button.is_high() && !db_timer.is_enabled() { + db_timer.enable(true); + } + if db_timer.need_service() { + db_timer.reset(); + if coin_button.is_high() { + break; + } + } + db_timer.tick(); + delay.delay_us(tick_interval_us as u32); } - tt0.service(); } - // } - - // if app.led1.need_service() { - if tt1.need_service() { - interfaces.led1.set_amplitude(led1_dcs[led1_index]); - led1_index += 1; - if led1_index > led1_dcs.len() - 1 { - led1_index = 0; + // wait for button input + loop { + if coin_button.is_low() && !db_timer.is_enabled() { + db_timer.enable(true); } - tt1.service() + if db_timer.need_service() { + db_timer.reset(); + if coin_button.is_low() { + break; + } + } + db_timer.tick(); + delay.delay_us(tick_interval_us as u32); } - interfaces.service(); + let mut active = true; + tt0.enable(true); + tt1.enable(true); + loop { + if active { + tt0.tick(); + tt1.tick(); - delay.delay_us(tick_interval_us as u32); + if tt0.need_service() { + interfaces.led0.set_amplitude(led0_dcs[led0_index]); + led0_index += 1; + if led0_index > led0_dcs.len() - 1 { + led0_index = 0; + } + tt0.service(); + } + + if tt1.need_service() { + interfaces.led1.set_amplitude(led1_dcs[led1_index]); + led1_index += 1; + if led1_index > led1_dcs.len() - 1 { + led1_index = 0; + } + tt1.service() + } + + } + + // buttons + + // SP + if coin_button.is_low() && !sp_timer.is_enabled() { + sp_timer.reset(); + sp_timer.enable(true); + } + if sp_timer.need_service() { + sp_timer.reset(); + if coin_button.is_low() { + active = false; + // TODO: fix polarity + interfaces.led0.set_amplitude(90); + interfaces.led1.set_amplitude(90); + } + } + if coin_button.is_high() && sp_timer.is_enabled() { + sp_timer.reset(); + } + sp_timer.tick(); + + + // LP + if coin_button.is_low() && !lp_timer.is_enabled() { + lp_timer.reset(); + lp_timer.enable(true); + } + if lp_timer.need_service() { + lp_timer.reset(); + if coin_button.is_low() { + interfaces.led0.set_amplitude(100); + interfaces.led1.set_amplitude(100); + // break; + } + } + if coin_button.is_high() && lp_timer.is_enabled() { + lp_timer.reset(); + } + lp_timer.tick(); + + interfaces.service(); + delay.delay_us(tick_interval_us as u32); + } } // // DAC servicer setup // let mut pwm_dac_interface = SimplePwmDacHandle{pin: core::cell::RefCell::new(pwm), ch: dac_ch}; From a71c7986e6e59bcb4eba0887b06783f03e264e70 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 21 Aug 2025 11:08:26 -0600 Subject: [PATCH 071/148] add interrupts for most handlers --- .gitmodules | 5 +- ch32v-insert-coin/.cargo/config.toml | 4 +- ch32v-insert-coin/Cargo.lock | 56 +- ch32v-insert-coin/Cargo.toml | 5 +- ch32v-insert-coin/audio/sweep_dpcm_u4.raw | Bin 0 -> 3992 bytes ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- ch32v-insert-coin/ext/ch32-hal | 2 +- .../ext/patches/optional-exti.patch | 652 ++++++++++++++++++ ch32v-insert-coin/ext/qingke | 1 + .../src/insert_coin/insert_coin.rs | 105 ++- ch32v-insert-coin/src/insert_coin/mod.rs | 3 +- .../src/insert_coin/services/dac.rs | 3 +- .../src/insert_coin/services/led.rs | 4 +- .../src/insert_coin/services/tick_timer.rs | 14 +- ch32v-insert-coin/src/main.rs | 452 +++++++----- 15 files changed, 1028 insertions(+), 280 deletions(-) create mode 100644 ch32v-insert-coin/audio/sweep_dpcm_u4.raw create mode 100644 ch32v-insert-coin/ext/patches/optional-exti.patch create mode 160000 ch32v-insert-coin/ext/qingke diff --git a/.gitmodules b/.gitmodules index c6e7965..b695a5d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,7 @@ url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git [submodule "ch32v-insert-coin/ext/ch32-hal"] path = ch32v-insert-coin/ext/ch32-hal - url = git@github.com:ch32-rs/ch32-hal.git + url = git@github.com:sigil-03/ch32-hal.git +[submodule "ch32v-insert-coin/ext/qingke"] + path = ch32v-insert-coin/ext/qingke + url = git@github.com:ch32-rs/qingke.git diff --git a/ch32v-insert-coin/.cargo/config.toml b/ch32v-insert-coin/.cargo/config.toml index cf4d2cd..68ad23c 100644 --- a/ch32v-insert-coin/.cargo/config.toml +++ b/ch32v-insert-coin/.cargo/config.toml @@ -6,9 +6,9 @@ target = "riscv32ec-unknown-none-elf.json" # 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" -# runner = "wlink -v flash --enable-sdi-print --watch-serial" +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" diff --git a/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock index 348625b..dc8468c 100644 --- a/ch32v-insert-coin/Cargo.lock +++ b/ch32v-insert-coin/Cargo.lock @@ -18,27 +18,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" -[[package]] -name = "bitfields" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdbce6688e3ab66aff2ab413b762ccde9f37990e27bba0bb38a4b2ad1b5d877" -dependencies = [ - "bitfields-impl", -] - -[[package]] -name = "bitfields-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57413e4b276d883b77fb368b7b33ae6a5eb97692852d49a5394d4f72ba961827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "thiserror", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -71,7 +50,7 @@ dependencies = [ "futures", "nb 1.1.0", "proc-macro2", - "qingke", + "qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "qingke-rt", "quote", "rand_core", @@ -92,11 +71,12 @@ name = "ch32v-insert-coin" version = "0.1.0" dependencies = [ "adpcm-pwm-dac", - "bitfields", "ch32-hal", + "critical-section", "embassy-executor", "embedded-hal 1.0.0", "panic-halt", + "qingke 0.5.0", "qingke-rt", ] @@ -487,6 +467,14 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qingke" +version = "0.5.0" +dependencies = [ + "bit_field", + "riscv 0.12.1", +] + [[package]] name = "qingke" version = "0.5.0" @@ -504,7 +492,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b955c60adac70c6d40205b1dbe9f57e1151d06aa842069cdbaef7bc07ad283fd" dependencies = [ - "qingke", + "qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "qingke-rt-macros", ] @@ -615,26 +603,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml index b4fc9c7..1e46f37 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -24,8 +24,11 @@ embedded-hal = "1.0.0" qingke-rt = { version = "*", features = ["highcode"] } +qingke = {path = "ext/qingke"} + adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" } -bitfields = "1.0.0" + +critical-section = { version = "1.2.0" } [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. 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 0000000000000000000000000000000000000000..1adc4941f1b07961bce9b2958be16ba9abcb8700 GIT binary patch literal 3992 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 L-Z1^B;5Pm@L7`ta literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index ba25b7c..e4bb93e 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit ba25b7c89f4deb52426d97fd35eb13496f183775 +Subproject commit e4bb93e0399f27024434adf2558a893574fbfef3 diff --git a/ch32v-insert-coin/ext/ch32-hal b/ch32v-insert-coin/ext/ch32-hal index 2b8e1c8..412b9f5 160000 --- a/ch32v-insert-coin/ext/ch32-hal +++ b/ch32v-insert-coin/ext/ch32-hal @@ -1 +1 @@ -Subproject commit 2b8e1c864ba5545ee65b1c77dcb17c86a471b70c +Subproject commit 412b9f5ee3a3708de8602d6103ec83c6dd436b63 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/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index fe69780..3d0dacb 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -1,8 +1,6 @@ -use adpcm_pwm_dac::dac; use ch32_hal::timer::GeneralInstance16bit; use ch32_hal::timer::simple_pwm::SimplePwm; use ch32_hal::timer::Channel; -use ch32_hal::delay::Delay; use crate::insert_coin::services::{DacService, LedService, TickService, TickServiceData, Service}; @@ -40,9 +38,9 @@ impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { self.pwm.borrow_mut().set_duty(ch, dc); } - pub fn disable(&self, ch: Channel) { - self.pwm.borrow_mut().disable(ch); - } + // pub fn disable(&self, ch: Channel) { + // self.pwm.borrow_mut().disable(ch); + // } } @@ -61,7 +59,7 @@ impl CoreConfig { #[derive(Default)] struct Core { - tick: usize, + _tick: usize, active: bool, } @@ -73,7 +71,7 @@ pub struct InsertCoin<'a, T: GeneralInstance16bit> { pub led1: LedService, // led2: LedService, - dac: DacService<'a>, + pub dac: DacService<'a>, } impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { @@ -82,25 +80,17 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // LED0 servicer setup - let led0_blink_rate_hz = 9; - let led0_tick_per_service = (config.tick_rate_hz/(led0_blink_rate_hz * 2)); - let led0_service_data = TickServiceData::new(led0_tick_per_service); - let led0 = LedService::new(ch32_hal::timer::Channel::Ch3, led0_service_data); + let led0 = LedService::new(ch32_hal::timer::Channel::Ch3); // LED1 servicer setup - let led1_blink_rate_hz = 3; - let led1_tick_per_service = (config.tick_rate_hz/(led1_blink_rate_hz * 2)); - let led1_service_data = TickServiceData::new(led1_tick_per_service); - let led1 = LedService::new(ch32_hal::timer::Channel::Ch1, led1_service_data); + let led1 = LedService::new(ch32_hal::timer::Channel::Ch1); // DAC servicer setup let dac_sample_rate_hz = 16000; - let dac_tick_per_service = (config.tick_rate_hz/(dac_sample_rate_hz)); + 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); - let data = include_bytes!("../../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); - dac.load_data(data); Self { config, @@ -110,11 +100,6 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { led1, // led2, dac, - // led1: Led { - // channel: hal::timer::Channel::Ch1, - // amplitude: 0, - // need_service: false, - // }, } } @@ -144,55 +129,53 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { } - /// consumes self and runs - pub fn run(mut self) -> ! { - let mut delay = Delay; - let tick_interval_us = 1000000/self.config.tick_rate_hz; + // /// 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 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]; + // let mut led1_index = 0; + // let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; - loop { - self.dac.tick(); + // 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); + // 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(); - } + // 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); + // 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(); - } + // 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); - } + // 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); - } - } + // delay.delay_us(tick_interval_us as u32); + // } + // } pub fn is_active(&self) -> bool { self.core.active diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index e13b953..e7c42d8 100644 --- a/ch32v-insert-coin/src/insert_coin/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -1,8 +1,7 @@ mod insert_coin; mod services; -use services::LedService; pub use services::TickTimerService; -pub use services::{TickService, TickServiceData, Service}; +pub use services::{TickService, TickServiceData}; pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore}; \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index 22681de..4cdaec0 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -1,7 +1,7 @@ use crate::insert_coin::services::{TickServiceData, TickService}; -use adpcm_pwm_dac::{dac::{DpcmDac, DpcmDecoder}, interface::DacInterface}; +use adpcm_pwm_dac::dac::DpcmDecoder; use ch32_hal::timer::Channel; pub struct DacService<'a> { @@ -23,6 +23,7 @@ impl<'a> DacService<'a> { 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) { diff --git a/ch32v-insert-coin/src/insert_coin/services/led.rs b/ch32v-insert-coin/src/insert_coin/services/led.rs index d248ab6..b7e2056 100644 --- a/ch32v-insert-coin/src/insert_coin/services/led.rs +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -1,6 +1,6 @@ use ch32_hal::timer::Channel; -use crate::insert_coin::services::{Service, TickService, TickServiceData}; +use crate::insert_coin::services::{Service}; pub struct LedService { // need_service: core::cell::RefCell, @@ -10,7 +10,7 @@ pub struct LedService { } impl LedService { - pub fn new(channel: Channel, service_data: TickServiceData) -> Self { + pub fn new(channel: Channel) -> Self { Self { // service_data: core::cell::RefCell::new(service_data), need_service: false, diff --git a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs index 90e0188..a10ab74 100644 --- a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs +++ b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs @@ -1,10 +1,8 @@ -use ch32_hal::timer::Channel; - use crate::insert_coin::services::{TickServiceData, TickService}; pub struct TickTimerService { service_data: core::cell::RefCell, - auto_reset: bool, + _auto_reset: bool, enabled: bool, } @@ -12,18 +10,18 @@ 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: auto_reset, enabled: false, } } - pub fn is_enabled(&self) -> bool { - self.enabled - } + // pub fn is_enabled(&self) -> bool { + // self.enabled + // } pub fn reset(&mut self) { let mut sd = self.service_data.borrow_mut(); - sd.ticks_per_service = sd.ticks_remaining; + sd.ticks_remaining = sd.ticks_per_service; self.enabled = false; } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 59515b9..ac2877e 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -4,85 +4,167 @@ #![feature(impl_trait_in_assoc_type)] mod insert_coin; +use ch32_hal::{interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig}; -use adpcm_pwm_dac::dac::DpcmDac; use {ch32_hal as hal}; -use hal::peripherals::EXTI4; -use hal::{bind_interrupts, interrupt}; +use hal::{bind_interrupts}; use hal::delay::Delay; -use hal::gpio::{AnyPin, Level, Input, Output, Pin, Pull}; +use hal::gpio::{AnyPin, Input, Pin, Pull}; use hal::time::Hertz; use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; -use hal::timer::{Channel, GeneralInstance16bit}; -// #[qingke_rt::interrupt] -// fn EXTI4(){ -// unsafe{IRQ1_FLAG = true;}; -// } -// bind_interrupts!(struct Irqs { -// EXTI4 => button_press(); -// }); +use hal::println; + +use qingke::riscv; -// const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80]; -const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; +struct DebouncedGPIO<'a> { + input: Input<'a>, + // value of the GPIO + value: bool, + // GPIO is ready (debounced) + ready: bool, + // debounce timer + timer: TickTimerService, +} -// DPCS DATA -// step size: 5 -// 0 -// + 25 -> [1, 0, 1, 1] -> 0xB -// + 25 -> 0xB -// + 25 -> 0xB -// + 25 -> 0xB -// - 25 -> 0xA -// - 25 -> 0xA -// - 25 -> 0xA -const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; +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::Up), + value: false, + ready: false, + timer: TickTimerService::new(TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), false), + } + } -static mut IRQ1_FLAG: bool = false; + pub fn ready(&self) -> bool { + self.ready + } + + pub fn value(&self) -> bool { + self.value + } + + pub fn begin(&mut self) { + self.reset(); + self.timer.enable(true); + } + + pub fn reset(&mut self) { + self.timer.reset(); + self.ready = 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; + } + } +} -// struct SimplePwmHandle<'a, 'c, T: GeneralInstance16bit>{ -// core: &'a SimplePwmCore<'c, T>, -// channel: Channel, -// } +// DeepSleep --coin button irq--> Active +// Active --2s button--> Idle +// Idle/Active --5s button--> DeepSleep -// impl<'a, 'c, T: GeneralInstance16bit> SimplePwmHandle<'a, 'c, T> { -// pub fn write_amplitude(&'a self, amplitude: u8) { -// self.core.write_amplitude(self.channel, amplitude); -// } +pub enum SystemState { + // 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 + Active, +} -// pub fn disable(&'a self) { -// self.core.disable(self.channel); -// } -// } +unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { + critical_section::with(|_| { + println!("init_gpio_irq"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; -// struct SimplePwmDacHandle<'d, T: GeneralInstance16bit>{ -// pin: core::cell::RefCell>, -// ch: Channel, -// } + 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)); + }); +} -// use adpcm_pwm_dac::{interface::DacInterface, dac::Dac}; +fn clear_interrupt (coin_pin: u8, button_pin: u8) { + 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; + + // coin_flag + if (bits & (0x1 << coin_pin)) != 0x0 { + println!("coin irq!"); + unsafe { INPUT_FLAGS.coin_flag = true; } + } + + + // button_flag + if (bits & (0x1 << button_pin)) != 0x0 { + println!("button irq!"); + unsafe { INPUT_FLAGS.button_flag = true; } + } + + + // 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); + exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt + exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt +} + + +#[derive(Debug)] +struct InputFlags { + coin_flag: bool, + button_flag: bool, +} + +static mut INPUT_FLAGS: InputFlags = InputFlags{coin_flag: false, button_flag: false}; + +struct Test { +} +impl Handler for Test { + unsafe fn on_interrupt() { + println!("on_interrupt()"); + critical_section::with(|_| { + clear_interrupt(4, 6); + }); + } +} + +bind_interrupts!(struct Irqs { + EXTI7_0 => Test; +}); -// impl DacInterface for SimplePwmDacHandle<'_, T> -// where T: GeneralInstance16bit { -// fn write_amplitude(&mut self, amplitude: u8) { -// if !self.pin.borrow().is_enabled(self.ch) { -// self.pin.get_mut().enable(self.ch); -// } -// let max_duty = self.pin.borrow().get_max_duty(); -// let dc = amplitude as u32 * max_duty / 100; -// self.pin.get_mut().set_duty(self.ch, dc); -// } -// fn disable(&mut self) { -// self.pin.get_mut().disable(self.ch); -// } -// } // TODO: remove use insert_coin::{TickService, TickServiceData}; @@ -90,23 +172,28 @@ use insert_coin::TickTimerService; #[qingke_rt::entry] fn main() -> ! { + hal::debug::SDIPrint::enable(); let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); - // coin button input setup - let coin_button = Input::new(p.PD4, Pull::Up); + // delay to let the debugger attach + let mut delay = Delay; + delay.delay_ms(1000); - // p.EXTI4 - // let ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up); - + // === output setup === + // LED0 output setup - let mut led0_pin = PwmPin::new_ch3::<0>(p.PC3); + let led0_pin = PwmPin::new_ch3::<0>(p.PC3); let led0_ch = hal::timer::Channel::Ch3; + // let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); + // unsafe { + // LED = Some(led0_pin); + // } // LED1 output setup - let mut led1_pin = PwmPin::new_ch1::<0>(p.PD2); + let led1_pin = PwmPin::new_ch1::<0>(p.PD2); let led1_ch = hal::timer::Channel::Ch1; @@ -114,7 +201,7 @@ fn main() -> ! { // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); - let dac_ch = hal::timer::Channel::Ch4; + // let dac_ch = hal::timer::Channel::Ch4; @@ -129,15 +216,38 @@ fn main() -> ! { CountingMode::default(), ); + pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); + pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); + let sample_rate_hz = 16000; let dac_tick_per_service = 5; let tick_rate_hz = sample_rate_hz * dac_tick_per_service; - let config = CoreConfig::new(tick_rate_hz); + let core_config = CoreConfig::new(tick_rate_hz); + + + // === input setup === + + // definitions + let coin_pin = p.PD4; + let button_pin = p.PD6; + println!("coin pin: {} | coin port: {}", coin_pin.pin(), coin_pin.port()); + println!("push pin: {} | push port: {}", button_pin.pin(), button_pin.port()); + + // set up interrupts + unsafe {init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true)}; + unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), true, true)}; + + // coin debouncer (100ms) + let mut coin_input = DebouncedGPIO::new(coin_pin.degrade(), core_config.tick_rate_hz, 100); + // button debouncer (100ms) + let mut button_input = DebouncedGPIO::new(button_pin.degrade(), core_config.tick_rate_hz, 100); + + let pwm_core = SimplePwmCore::new(pwm); - let mut interfaces = InsertCoin::new(config, pwm_core); + let mut interfaces = InsertCoin::new(core_config, pwm_core); interfaces.set_active(true); // insert_coin.init(); @@ -149,22 +259,16 @@ fn main() -> ! { // tick timer 0 let tt0_fire_rate_hz = 9; - let tt0_tick_per_service = (interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2)); + let tt0_tick_per_service = interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2); let tt0_service_data = TickServiceData::new(tt0_tick_per_service); let mut tt0 = TickTimerService::new(tt0_service_data, true); // tick timer 1 let tt1_fire_rate_hz = 3; - let tt1_tick_per_service = (interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2)); + let tt1_tick_per_service = interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2); let tt1_service_data = TickServiceData::new(tt1_tick_per_service); let mut tt1 = TickTimerService::new(tt1_service_data, true); - // debounce timer - // let db_ticks = interfaces.config.tick_rate_hz / 100; - let db_ticks = interfaces.config.tick_rate_hz / 100; - let db_timer_data = TickServiceData::new(db_ticks); - let mut db_timer = TickTimerService::new(db_timer_data, false); - db_timer.reset(); // short press timer let sp_ticks = 2 * interfaces.config.tick_rate_hz; @@ -179,49 +283,44 @@ fn main() -> ! { lp_timer.reset(); let mut delay = Delay; - let tick_interval_us = 1000000/interfaces.config.tick_rate_hz; - - interfaces.led0.set_amplitude(100); - interfaces.led1.set_amplitude(100); + let tick_interval_us = 1000000/interfaces.config.tick_rate_hz - 10; + + // dac data + let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); + let button_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); + + let mut system_state = SystemState::DeepSleep; + + interfaces.led0.set_amplitude(0); + interfaces.led1.set_amplitude(0); + interfaces.service(); + + unsafe { + use hal::pac::Interrupt; + + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 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 + + println!("begin"); loop { - // wait for button to be released if it's pressed - if (coin_button.is_low()) { - loop { - if coin_button.is_high() && !db_timer.is_enabled() { - db_timer.enable(true); - } - if db_timer.need_service() { - db_timer.reset(); - if coin_button.is_high() { - break; - } - } - db_timer.tick(); - delay.delay_us(tick_interval_us as u32); - } - } - // wait for button input - loop { - if coin_button.is_low() && !db_timer.is_enabled() { - db_timer.enable(true); - } - if db_timer.need_service() { - db_timer.reset(); - if coin_button.is_low() { - break; - } - } - db_timer.tick(); - delay.delay_us(tick_interval_us as u32); - } + match system_state { + SystemState::DeepSleep => { + // TODO: make this REALLY deep sleep + riscv::asm::wfi(); + }, + SystemState::Idle => { - let mut active = true; - tt0.enable(true); - tt1.enable(true); - loop { - if active { + }, + SystemState::Active => { tt0.tick(); tt1.tick(); @@ -243,61 +342,102 @@ fn main() -> ! { tt1.service() } - } + interfaces.service(); + }, + } - // buttons - - // SP - if coin_button.is_low() && !sp_timer.is_enabled() { - sp_timer.reset(); - sp_timer.enable(true); - } - if sp_timer.need_service() { - sp_timer.reset(); - if coin_button.is_low() { - active = false; - // TODO: fix polarity - interfaces.led0.set_amplitude(90); - interfaces.led1.set_amplitude(90); + + { + // system input servicing + unsafe { + if INPUT_FLAGS.coin_flag { + println!("coin flag active"); + INPUT_FLAGS.coin_flag = false; + coin_input.begin(); + // enter the active state + system_state = SystemState::Active; + + // todo: enter active + tt0.enable(true); + tt1.enable(true); + interfaces.dac.load_data(coin_sound); + + } + if INPUT_FLAGS.button_flag { + println!("button flag active"); + INPUT_FLAGS.button_flag = false; + button_input.begin(); } } - if coin_button.is_high() && sp_timer.is_enabled() { - sp_timer.reset(); + + // debouncer + coin_input.service(); + button_input.service(); + + + if coin_input.ready() { + println!("debounced coin_input value: {}", coin_input.value()); + coin_input.reset(); + } + if button_input.ready() { + + let value = button_input.value(); + button_input.reset(); + println!("debounced button_input value: {}", value); + + if !value { + interfaces.dac.load_data(button_sound); + + println!("reset hold timers + enable"); + sp_timer.reset(); + sp_timer.enable(true); + lp_timer.reset(); + lp_timer.enable(true); + } + else { + sp_timer.reset(); + lp_timer.reset(); + } + + } + + // timers sp_timer.tick(); - - - // LP - if coin_button.is_low() && !lp_timer.is_enabled() { - lp_timer.reset(); - lp_timer.enable(true); - } - if lp_timer.need_service() { - lp_timer.reset(); - if coin_button.is_low() { - interfaces.led0.set_amplitude(100); - interfaces.led1.set_amplitude(100); - // break; - } - } - if coin_button.is_high() && lp_timer.is_enabled() { - lp_timer.reset(); - } lp_timer.tick(); - interfaces.service(); - delay.delay_us(tick_interval_us as u32); + if sp_timer.need_service() { + println!("sp detect!"); + sp_timer.reset(); + + // todo enter idle + system_state = SystemState::Idle; + // TODO: fix polarity + interfaces.led0.set_amplitude(10); + interfaces.led1.set_amplitude(10); + interfaces.service(); + } + + if lp_timer.need_service() { + println!("lp detect!"); + lp_timer.reset(); + + // todo enter deepsleep + system_state = SystemState::DeepSleep; + // TODO: fix polarity + interfaces.led0.set_amplitude(0); + interfaces.led1.set_amplitude(0); + interfaces.service(); + } } + + delay.delay_us(tick_interval_us as u32); } - // // DAC servicer setup - // let mut pwm_dac_interface = SimplePwmDacHandle{pin: core::cell::RefCell::new(pwm), ch: dac_ch}; - // let mut dac = DpcmDac::new(pwm_dac_interface); - // let mut dac_active = true; } #[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { +fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } From 09ba2415d06e85dc0b64eb01083a5fb529f26160 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 25 Aug 2025 21:00:28 -0600 Subject: [PATCH 072/148] add build scripts + docker build env + notes on how to use --- ch32v-insert-coin/build-run.sh | 2 ++ ch32v-insert-coin/init.sh | 9 +++++++++ notes.md | 11 +++++++++++ 3 files changed, 22 insertions(+) create mode 100755 ch32v-insert-coin/build-run.sh create mode 100755 ch32v-insert-coin/init.sh diff --git a/ch32v-insert-coin/build-run.sh b/ch32v-insert-coin/build-run.sh new file mode 100755 index 0000000..ad28a11 --- /dev/null +++ b/ch32v-insert-coin/build-run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-env:latest cargo +nightly run --release diff --git a/ch32v-insert-coin/init.sh b/ch32v-insert-coin/init.sh new file mode 100755 index 0000000..193536f --- /dev/null +++ b/ch32v-insert-coin/init.sh @@ -0,0 +1,9 @@ +#!/bin/bash +DIR=${PWD} +mkdir ${PWD}/tmp +cd tmp +git clone ssh://git@git.glyphs.tech:222/sigil-03/docker-devtools.git +cd docker-devtools/ch32 +docker build --tag ch32-env . +cd $DIR +rm -rf tmp diff --git a/notes.md b/notes.md index 8883403..733a006 100644 --- a/notes.md +++ b/notes.md @@ -1,3 +1,14 @@ +# 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: +```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. + # 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. From cb73b71ffe6c44b82a83f9aa8c65bf983fd96dde Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 30 Aug 2025 11:09:37 -0600 Subject: [PATCH 073/148] update build system to be a little more flexible --- ch32v-insert-coin/build-run.sh | 2 +- ch32v-insert-coin/init.sh | 3 ++- ch32v-insert-coin/launch.sh | 2 ++ notes.md | 14 +++++++++++++- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100755 ch32v-insert-coin/launch.sh diff --git a/ch32v-insert-coin/build-run.sh b/ch32v-insert-coin/build-run.sh index ad28a11..8461b3a 100755 --- a/ch32v-insert-coin/build-run.sh +++ b/ch32v-insert-coin/build-run.sh @@ -1,2 +1,2 @@ #!/bin/bash -docker run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-env:latest cargo +nightly run --release +cargo +nightly run --release \ No newline at end of file diff --git a/ch32v-insert-coin/init.sh b/ch32v-insert-coin/init.sh index 193536f..a9c6514 100755 --- a/ch32v-insert-coin/init.sh +++ b/ch32v-insert-coin/init.sh @@ -4,6 +4,7 @@ mkdir ${PWD}/tmp cd tmp git clone ssh://git@git.glyphs.tech:222/sigil-03/docker-devtools.git cd docker-devtools/ch32 -docker build --tag ch32-env . +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..4230979 --- /dev/null +++ b/ch32v-insert-coin/launch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-rust:latest /bin/bash diff --git a/notes.md b/notes.md index 733a006..fd0c276 100644 --- a/notes.md +++ b/notes.md @@ -3,12 +3,24 @@ there is a docker image that contains the entire toolchain + flashing utility. f ```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: +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. From 83c2a64f129cecf3eb6e1d8327df760405c1ace2 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 30 Aug 2025 13:54:13 -0600 Subject: [PATCH 074/148] holy shit i think it's deep sleeping --- ch32v-insert-coin/src/main.rs | 155 ++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 36 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ac2877e..9bf1359 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -84,6 +84,73 @@ pub enum SystemState { Active, } +/// 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) +unsafe fn enter_standby(pin: usize) { + 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(true); + // we want to enable deep sleep + w.set_sleepdeep(true); + w.set_wfitowfe(true); + w.set_sleeponexit(false); + }); + + // // 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(Interrupt::EXTI7_0 as u8); + // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + } + + + // execute WFI + println!("WFI CONFIGURED HOPEFULLY"); + + // core::arch::asm!("wfi"); + + // pfic. + // qingke::pfic:: + }); + +} + unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { critical_section::with(|_| { println!("init_gpio_irq"); @@ -135,7 +202,11 @@ fn clear_interrupt (coin_pin: u8, button_pin: u8) { // 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); + 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 } @@ -297,8 +368,13 @@ fn main() -> ! { unsafe { use hal::pac::Interrupt; + // use qingke_rt::CoreInterrupt; + + // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); + clear_interrupt(4, 6); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } @@ -312,41 +388,6 @@ fn main() -> ! { println!("begin"); loop { - match system_state { - SystemState::DeepSleep => { - // TODO: make this REALLY deep sleep - riscv::asm::wfi(); - }, - SystemState::Idle => { - - }, - SystemState::Active => { - tt0.tick(); - tt1.tick(); - - if tt0.need_service() { - interfaces.led0.set_amplitude(led0_dcs[led0_index]); - led0_index += 1; - if led0_index > led0_dcs.len() - 1 { - led0_index = 0; - } - tt0.service(); - } - - if tt1.need_service() { - interfaces.led1.set_amplitude(led1_dcs[led1_index]); - led1_index += 1; - if led1_index > led1_dcs.len() - 1 { - led1_index = 0; - } - tt1.service() - } - - interfaces.service(); - }, - } - - { // system input servicing unsafe { @@ -431,6 +472,48 @@ fn main() -> ! { } } + match system_state { + SystemState::DeepSleep => { + // TODO: make this REALLY deep sleep + unsafe{enter_standby(4)}; + loop { + riscv::asm::wfi(); + unsafe{ + if INPUT_FLAGS.coin_flag { + break; + } + }; + } + }, + SystemState::Idle => { + + }, + SystemState::Active => { + tt0.tick(); + tt1.tick(); + + if tt0.need_service() { + interfaces.led0.set_amplitude(led0_dcs[led0_index]); + led0_index += 1; + if led0_index > led0_dcs.len() - 1 { + led0_index = 0; + } + tt0.service(); + } + + if tt1.need_service() { + interfaces.led1.set_amplitude(led1_dcs[led1_index]); + led1_index += 1; + if led1_index > led1_dcs.len() - 1 { + led1_index = 0; + } + tt1.service() + } + + interfaces.service(); + }, + } + delay.delay_us(tick_interval_us as u32); } } From b334b18233c82ba15c1e65e2e620eee1f65c6f3b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 30 Aug 2025 13:59:51 -0600 Subject: [PATCH 075/148] update notes with deep sleep references --- notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notes.md b/notes.md index fd0c276..3a56b55 100644 --- a/notes.md +++ b/notes.md @@ -39,3 +39,7 @@ if you want to monitor prints via SDI, you can use the following instead: 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 From 980fe9522c55b2d2ad8928fb2c2f7c8f2f0825f8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 8 Sep 2025 10:37:18 -0600 Subject: [PATCH 076/148] update notes with pinout --- notes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/notes.md b/notes.md index 3a56b55..1c3704d 100644 --- a/notes.md +++ b/notes.md @@ -43,3 +43,8 @@ when flashing standalone with the `wlink` utility, you can simply run the follow # 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 From 3f4b8112fd1b3d533ee7400611565fc2b6db902b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 12 Sep 2025 11:44:04 -0600 Subject: [PATCH 077/148] support ADC readings --- ch32v-insert-coin/ext/ch32-hal | 2 +- ch32v-insert-coin/src/main.rs | 43 ++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/ch32v-insert-coin/ext/ch32-hal b/ch32v-insert-coin/ext/ch32-hal index 412b9f5..f413367 160000 --- a/ch32v-insert-coin/ext/ch32-hal +++ b/ch32v-insert-coin/ext/ch32-hal @@ -1 +1 @@ -Subproject commit 412b9f5ee3a3708de8602d6103ec83c6dd436b63 +Subproject commit f41336744c4e2548c8f6ba9b323ae4aa39959f1d diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ac2877e..4fa1700 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -4,7 +4,7 @@ #![feature(impl_trait_in_assoc_type)] mod insert_coin; -use ch32_hal::{interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; +use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig}; @@ -155,7 +155,7 @@ impl Handler for Test { unsafe fn on_interrupt() { println!("on_interrupt()"); critical_section::with(|_| { - clear_interrupt(4, 6); + clear_interrupt(2, 6); }); } } @@ -228,12 +228,26 @@ fn main() -> ! { // === input setup === + // adc + let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); + let mut adc_pin = p.PD4; + // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); + delay.delay_ms(1000); + let adc_cal = adc.calibrate(); + println!("ADC calibration value: {}", adc_cal); + + + // definitions - let coin_pin = p.PD4; + let coin_pin = p.PC2; let button_pin = p.PD6; println!("coin pin: {} | coin port: {}", coin_pin.pin(), coin_pin.port()); println!("push pin: {} | push port: {}", button_pin.pin(), button_pin.port()); + //2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 + // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 + + // set up interrupts unsafe {init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true)}; unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), true, true)}; @@ -282,14 +296,22 @@ fn main() -> ! { let mut lp_timer = TickTimerService::new(lp_timer_data, false); lp_timer.reset(); - let mut delay = Delay; + // battery read timer + let adc1_ticks = 5 * interfaces.config.tick_rate_hz; + let adc1_timer_data = TickServiceData::new(adc1_ticks); + let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); + adc1_timer.reset(); + adc1_timer.enable(true); + + + let tick_interval_us = 1000000/interfaces.config.tick_rate_hz - 10; // dac data let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); let button_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - let mut system_state = SystemState::DeepSleep; + let mut system_state = SystemState::Active; interfaces.led0.set_amplitude(0); interfaces.led1.set_amplitude(0); @@ -405,6 +427,7 @@ fn main() -> ! { // timers sp_timer.tick(); lp_timer.tick(); + adc1_timer.tick(); if sp_timer.need_service() { println!("sp detect!"); @@ -429,6 +452,16 @@ fn main() -> ! { interfaces.led1.set_amplitude(0); interfaces.service(); } + + if adc1_timer.need_service() { + let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); + println!("ADC value: {}", val); + + adc1_timer.reset(); + adc1_timer.enable(true); + + + } } delay.delay_us(tick_interval_us as u32); From 94848d0d43c379acb8bfc24526976b37fde3c064 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 19 Oct 2025 10:51:49 -0600 Subject: [PATCH 078/148] update notes --- .gitmodules | 6 ++-- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- notes.md | 47 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index b695a5d..fb915f4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ -[submodule "adpcm-pwm-dac"] - path = ch32v-insert-coin/ext/adpcm-pwm-dac - url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git [submodule "ch32v-insert-coin/ext/ch32-hal"] path = ch32v-insert-coin/ext/ch32-hal url = git@github.com:sigil-03/ch32-hal.git [submodule "ch32v-insert-coin/ext/qingke"] path = ch32v-insert-coin/ext/qingke url = git@github.com:ch32-rs/qingke.git +[submodule "ch32v-insert-coin/ext/adpcm-pwm-dac"] + path = ch32v-insert-coin/ext/adpcm-pwm-dac + url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index e4bb93e..ba25b7c 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit e4bb93e0399f27024434adf2558a893574fbfef3 +Subproject commit ba25b7c89f4deb52426d97fd35eb13496f183775 diff --git a/notes.md b/notes.md index 1c3704d..65e9ae5 100644 --- a/notes.md +++ b/notes.md @@ -48,3 +48,50 @@ it turns out you can not put the chip into deep sleep and have it wake up correc # 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 From 8d612d1559c7b30ade2060a4423166aa65326c03 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 19 Oct 2025 10:52:44 -0600 Subject: [PATCH 079/148] update init and launch scripts --- ch32v-insert-coin/init.sh | 2 +- ch32v-insert-coin/launch.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ch32v-insert-coin/init.sh b/ch32v-insert-coin/init.sh index a9c6514..bf063fc 100755 --- a/ch32v-insert-coin/init.sh +++ b/ch32v-insert-coin/init.sh @@ -2,7 +2,7 @@ DIR=${PWD} mkdir ${PWD}/tmp cd tmp -git clone ssh://git@git.glyphs.tech:222/sigil-03/docker-devtools.git +git clone ssh://git@git.glyphs.tech:222/taproot-tech/docker-devtools.git cd docker-devtools/ch32 ls ./build.sh diff --git a/ch32v-insert-coin/launch.sh b/ch32v-insert-coin/launch.sh index 4230979..ae7aa33 100755 --- a/ch32v-insert-coin/launch.sh +++ b/ch32v-insert-coin/launch.sh @@ -1,2 +1,2 @@ #!/bin/bash -docker run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-rust:latest /bin/bash +podman run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-rust:latest /bin/bash From fba0fbec6112381122076f28923cc87722b4a6af Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 19 Oct 2025 10:53:58 -0600 Subject: [PATCH 080/148] minor formatting cleanups --- ch32v-insert-coin/audio/coin.raw | Bin 0 -> 6224 bytes ch32v-insert-coin/audio/coin8ksps.raw | Bin 0 -> 3112 bytes .../audio/coinMixTest1_dpcm_u4.raw | Bin 0 -> 5999 bytes ch32v-insert-coin/bins.zip | Bin 0 -> 42532 bytes ch32v-insert-coin/bins/README.md | 4 + ch32v-insert-coin/bins/coin_sound_16ksps.bin | Bin 0 -> 38180 bytes ch32v-insert-coin/bins/coin_sound_6ksps.bin | Bin 0 -> 34684 bytes ch32v-insert-coin/bins/coin_sound_8ksps.bin | Bin 0 -> 38780 bytes .../riscv32ec-unknown-none-elf.json | 4 +- .../src/insert_coin/insert_coin.rs | 2 +- ch32v-insert-coin/src/main.rs | 193 ++++++++---------- 11 files changed, 92 insertions(+), 111 deletions(-) create mode 100644 ch32v-insert-coin/audio/coin.raw create mode 100644 ch32v-insert-coin/audio/coin8ksps.raw create mode 100644 ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw create mode 100644 ch32v-insert-coin/bins.zip create mode 100644 ch32v-insert-coin/bins/README.md create mode 100755 ch32v-insert-coin/bins/coin_sound_16ksps.bin create mode 100755 ch32v-insert-coin/bins/coin_sound_6ksps.bin create mode 100755 ch32v-insert-coin/bins/coin_sound_8ksps.bin diff --git a/ch32v-insert-coin/audio/coin.raw b/ch32v-insert-coin/audio/coin.raw new file mode 100644 index 0000000000000000000000000000000000000000..2a0af5699e573fb38c337f4d3c71c2e6fe78f198 GIT binary patch literal 6224 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 S)8oJM=Rd0W@8bWAz&`-gNY!Kj literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/audio/coin8ksps.raw b/ch32v-insert-coin/audio/coin8ksps.raw new file mode 100644 index 0000000000000000000000000000000000000000..72caab0d06e0ea2e307c4629878c9a28ab011f09 GIT binary patch literal 3112 zcmds3!D<{i5LH17Jcob^)X)R@7ztWn9}E(xp#%AtF=Su|)-VGdm_t5h51WGzh6KFe zgFj~9Q;)M*vg{@7WqUk5ty0yidZj0|RsUy~e9y;yKj1TF#W^+V)Jfh=!zMTw;?%*8 zIZXJiKa|ego#NNur>AJmb@`N>)y=T+Mg|j#e%S%44*^Je3X^wH)fl)MwJsQEdU%Nm zCTJ=SHaxnT@arT$JKk&uPpjE|*XK#oKVqUPNRU@EHCKi-n$5o65c*m=QRx6&d?o49 zSuY7yic5$KH_eqq@R5c^-uRL!7l3o*6#EMn^R_0o#4Us+T`E}%Y}~$;1Qc9rO(e#C zGZ(we4^x01vSP{EP80(;+%K(ap|8kH``z{T(V8cR(%DYU!?AvLzrNc+JoD6rG%aHy zx6aBEm%CGkWFdpl;sPQ~NWJg->hElU61{fT6t0lRY~M=4hVUvjG&YVt?5!&>R2E2Y zR_p9oNPt`g5=Q&hl~vyd>vgxIzC2povq%-$BSs0E#c z{EL?g^-)1ElZrLoB=V!6v7?1soqeMK!C)ng5c(h-J8SJp>jfSe3Vd9luw9H}!?8~= zLPwz?w%R}dgvAMf%ojkc2b5fx5OEE9ZStkn4XUkzG3(kgk|}{nccy_xQD_?pXn_vr zkpn_v4Y*e17QvEG#r%tVJbm5B^Yi(*`2GHm{e7A>pO2%7j}LD`{rL9fIY@fk+-l7) z^Ko$dhx^05Ii8<}IfTc@vN6l~?U&~da{u}MMoN8|a~JJH)DWJ{`z2p*51WnZ(=hn% z!F)}w_eWR9^S=LVZ=nZY9o9BQ(Kp`bb9DY-S)2N8aQl?3_%B1A#g);@9;iD>v_3ya=1HelP`HFuE;J~#N?OiZFC6${%Nq@NyGs^jRgx-%y>}U2JDTetAyng zjSffrX~uyl0r^c;pHEjf{U4_;+3@UT$-h~gOwMF9ImdwN?CdA)T*5BWn{k=0>c?>1 zN2sZ|&QF4+PS`$GOK=)>iXJZvE*S(DO);JaHFGR*?})zJBm#%%g5+tcF_3 z)K48@0c-=PQmhHt=E`&}9}W+c;^Bff1?pIz@zTJqX@3wp)z7MI1aowP=Qa0+ap*X@ zO)E$O5wlE}=)^IFhQGA|ja~7%Q@8JN65;f45Dr-)ooE%vuM_KyJ)b+mszQ-~$sAPz z?xBUYzga>1cLk*JgbeIz%g!UV_{8)X_-&^m2lKG376>wEKXSW_bDD$@50BP*daY2% zKnmz9a$vf>Bxyl1_T<~ksztQbViuHifuae`Al#atc0TcX{ar1Lxy+Ic6~(I78$cRI zxHhB)`HJxuA}uWChwC-f4-?Jsiq?Qy4==T_#7lMG9RC^Or>*~9;ZMW=X8?Z!9``-y literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36cc103862e2faf5fbc6c0416bebe4b0925d1adf GIT binary patch literal 5999 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 literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/bins.zip b/ch32v-insert-coin/bins.zip new file mode 100644 index 0000000000000000000000000000000000000000..b77a19472ecdde2ae3f2cde08d2e7e7a7431858f GIT binary patch literal 42532 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 literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/bins/README.md b/ch32v-insert-coin/bins/README.md new file mode 100644 index 0000000..c548cd9 --- /dev/null +++ b/ch32v-insert-coin/bins/README.md @@ -0,0 +1,4 @@ +TO FLASH / RUN: + +`wlink -v flash --enable-sdi-print --watch-serial bins/coin_sound_8ksps.bin` + diff --git a/ch32v-insert-coin/bins/coin_sound_16ksps.bin b/ch32v-insert-coin/bins/coin_sound_16ksps.bin new file mode 100755 index 0000000000000000000000000000000000000000..c003c2a6ea54f4dba7bcf3ba1581d84b7f44627d GIT binary patch literal 38180 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

Y4jn9UtT& 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 literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/bins/coin_sound_6ksps.bin b/ch32v-insert-coin/bins/coin_sound_6ksps.bin new file mode 100755 index 0000000000000000000000000000000000000000..9796caf8c7bb95d54477ed8ecfb15d269669b815 GIT binary patch literal 34684 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 InsertCoin<'a, T> { pub fn set_active(&mut self, active: bool) { self.core.active = active; } -} \ No newline at end of file +} diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 81d8d09..a50cda8 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -5,11 +5,10 @@ mod insert_coin; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; -use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig}; +use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; - -use {ch32_hal as hal}; -use hal::{bind_interrupts}; +use ch32_hal as hal; +use hal::bind_interrupts; use hal::delay::Delay; use hal::gpio::{AnyPin, Input, Pin, Pull}; use hal::time::Hertz; @@ -20,7 +19,6 @@ use hal::println; use qingke::riscv; - struct DebouncedGPIO<'a> { input: Input<'a>, // value of the GPIO @@ -28,7 +26,7 @@ struct DebouncedGPIO<'a> { // GPIO is ready (debounced) ready: bool, // debounce timer - timer: TickTimerService, + timer: TickTimerService, } impl<'a> DebouncedGPIO<'a> { @@ -38,7 +36,10 @@ impl<'a> DebouncedGPIO<'a> { input: Input::new(pin, Pull::Up), value: false, ready: false, - timer: TickTimerService::new(TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), false), + timer: TickTimerService::new( + TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), + false, + ), } } @@ -70,10 +71,9 @@ impl<'a> DebouncedGPIO<'a> { } } - // DeepSleep --coin button irq--> Active -// Active --2s button--> Idle -// Idle/Active --5s button--> DeepSleep +// Active --2s button--> Idle +// Idle/Active --5s button--> DeepSleep pub enum SystemState { // system is asleep, waiting for wake from coin insertion @@ -84,7 +84,7 @@ pub enum SystemState { Active, } -/// enter standby (SLEEPDEEP) mode, with WFE enabled. +/// 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 @@ -95,7 +95,6 @@ pub enum SystemState { /// 1. any interrupt/event (set in external interrupt register) unsafe fn enter_standby(pin: usize) { critical_section::with(|_| { - use hal::pac::Interrupt; use qingke_rt::CoreInterrupt; @@ -122,7 +121,6 @@ unsafe fn enter_standby(pin: usize) { // let bits = 0xFFFFFFFF; // exti.intfr().write(|w| w.0 = bits); - // // enable all exti interrupts // let exti = &hal::pac::EXTI; @@ -133,14 +131,13 @@ unsafe fn enter_standby(pin: usize) { // }); unsafe { - // qingke::pfic::disable_interrupt(Interrupt::EXTI7_0 as u8); + // qingke::pfic::disable_interrupt(Interrupt::EXTI7_0 as u8); // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); - } + } - - // execute WFI + // execute WFI println!("WFI CONFIGURED HOPEFULLY"); // core::arch::asm!("wfi"); @@ -148,29 +145,27 @@ unsafe fn enter_standby(pin: usize) { // pfic. // qingke::pfic:: }); - } unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { critical_section::with(|_| { - println!("init_gpio_irq"); - let exti = &hal::pac::EXTI; - let afio = &hal::pac::AFIO; + println!("init_gpio_irq"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; - let port = port as u8; - let pin = pin as usize; + let port = port as u8; + let pin = pin as usize; - // let b = afio.exticr().read(); - afio.exticr().modify(|w| w.set_exti(pin, port)); + // 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)); + 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)); }); } - -fn clear_interrupt (coin_pin: u8, button_pin: u8) { +fn clear_interrupt(coin_pin: u8, button_pin: u8) { let exti = &hal::pac::EXTI; let coin_pin = coin_pin as usize; @@ -179,7 +174,6 @@ fn clear_interrupt (coin_pin: u8, button_pin: u8) { 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. @@ -188,40 +182,44 @@ fn clear_interrupt (coin_pin: u8, button_pin: u8) { // coin_flag if (bits & (0x1 << coin_pin)) != 0x0 { println!("coin irq!"); - unsafe { INPUT_FLAGS.coin_flag = true; } + unsafe { + INPUT_FLAGS.coin_flag = true; + } } - // button_flag if (bits & (0x1 << button_pin)) != 0x0 { println!("button irq!"); - unsafe { INPUT_FLAGS.button_flag = true; } + unsafe { + INPUT_FLAGS.button_flag = 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); }; - + 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 } - #[derive(Debug)] struct InputFlags { coin_flag: bool, button_flag: bool, } -static mut INPUT_FLAGS: InputFlags = InputFlags{coin_flag: false, button_flag: false}; +static mut INPUT_FLAGS: InputFlags = InputFlags { + coin_flag: false, + button_flag: false, +}; -struct Test { -} +struct Test {} impl Handler for Test { unsafe fn on_interrupt() { println!("on_interrupt()"); @@ -235,11 +233,9 @@ bind_interrupts!(struct Irqs { EXTI7_0 => Test; }); - - // TODO: remove -use insert_coin::{TickService, TickServiceData}; use insert_coin::TickTimerService; +use insert_coin::{TickService, TickServiceData}; #[qingke_rt::entry] fn main() -> ! { @@ -252,7 +248,6 @@ fn main() -> ! { let mut delay = Delay; delay.delay_ms(1000); - // === output setup === // LED0 output setup @@ -267,15 +262,12 @@ fn main() -> ! { let led1_pin = PwmPin::new_ch1::<0>(p.PD2); let led1_ch = hal::timer::Channel::Ch1; - // LED2 output setup // 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, @@ -296,7 +288,6 @@ fn main() -> ! { let core_config = CoreConfig::new(tick_rate_hz); - // === input setup === // adc @@ -307,29 +298,32 @@ fn main() -> ! { let adc_cal = adc.calibrate(); println!("ADC calibration value: {}", adc_cal); - - // definitions let coin_pin = p.PC2; let button_pin = p.PD6; - println!("coin pin: {} | coin port: {}", coin_pin.pin(), coin_pin.port()); - println!("push pin: {} | push port: {}", button_pin.pin(), button_pin.port()); + // println!( + // "coin pin: {} | coin port: {}", + // coin_pin.pin(), + // coin_pin.port() + // ); + // println!( + // "push pin: {} | push port: {}", + // button_pin.pin(), + // button_pin.port() + // ); //2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 - // set up interrupts - unsafe {init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true)}; - unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), true, true)}; + unsafe { init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true) }; + unsafe { init_gpio_irq(button_pin.pin(), button_pin.port(), true, true) }; // coin debouncer (100ms) let mut coin_input = DebouncedGPIO::new(coin_pin.degrade(), core_config.tick_rate_hz, 100); // button debouncer (100ms) let mut button_input = DebouncedGPIO::new(button_pin.degrade(), core_config.tick_rate_hz, 100); - - let pwm_core = SimplePwmCore::new(pwm); let mut interfaces = InsertCoin::new(core_config, pwm_core); @@ -341,65 +335,60 @@ fn main() -> ! { let mut led1_index = 0; let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; - + // tick timer 0 - let tt0_fire_rate_hz = 9; - let tt0_tick_per_service = interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2); + let tt0_fire_rate_hz = 9; + let tt0_tick_per_service = interfaces.config.tick_rate_hz / (tt0_fire_rate_hz * 2); let tt0_service_data = TickServiceData::new(tt0_tick_per_service); let mut tt0 = TickTimerService::new(tt0_service_data, true); // tick timer 1 - let tt1_fire_rate_hz = 3; - let tt1_tick_per_service = interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2); + let tt1_fire_rate_hz = 3; + let tt1_tick_per_service = interfaces.config.tick_rate_hz / (tt1_fire_rate_hz * 2); let tt1_service_data = TickServiceData::new(tt1_tick_per_service); let mut tt1 = TickTimerService::new(tt1_service_data, true); - - // short press timer + // short press timer let sp_ticks = 2 * interfaces.config.tick_rate_hz; let sp_timer_data = TickServiceData::new(sp_ticks); let mut sp_timer = TickTimerService::new(sp_timer_data, false); sp_timer.reset(); - // long press timer + // long press timer let lp_ticks = 5 * interfaces.config.tick_rate_hz; let lp_timer_data = TickServiceData::new(lp_ticks); let mut lp_timer = TickTimerService::new(lp_timer_data, false); lp_timer.reset(); - // battery read timer + // battery read timer let adc1_ticks = 5 * interfaces.config.tick_rate_hz; let adc1_timer_data = TickServiceData::new(adc1_ticks); let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); adc1_timer.reset(); adc1_timer.enable(true); - - - let tick_interval_us = 1000000/interfaces.config.tick_rate_hz - 10; + let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; // dac data - let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - let button_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); + let coin_sound = include_bytes!("../audio/coin.raw"); + // let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); let mut system_state = SystemState::Active; interfaces.led0.set_amplitude(0); interfaces.led1.set_amplitude(0); interfaces.service(); - + unsafe { use hal::pac::Interrupt; // use qingke_rt::CoreInterrupt; - // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); clear_interrupt(4, 6); - qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); - // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick 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 @@ -424,7 +413,6 @@ fn main() -> ! { tt0.enable(true); tt1.enable(true); interfaces.dac.load_data(coin_sound); - } if INPUT_FLAGS.button_flag { println!("button flag active"); @@ -434,35 +422,30 @@ fn main() -> ! { } // debouncer - coin_input.service(); + coin_input.service(); button_input.service(); - if coin_input.ready() { println!("debounced coin_input value: {}", coin_input.value()); coin_input.reset(); - } if button_input.ready() { - let value = button_input.value(); button_input.reset(); println!("debounced button_input value: {}", value); - + if !value { - interfaces.dac.load_data(button_sound); - + // interfaces.dac.load_data(button_sound); + println!("reset hold timers + enable"); sp_timer.reset(); sp_timer.enable(true); lp_timer.reset(); lp_timer.enable(true); - } - else { + } else { sp_timer.reset(); lp_timer.reset(); } - } // timers @@ -486,7 +469,7 @@ fn main() -> ! { println!("lp detect!"); lp_timer.reset(); - // todo enter deepsleep + // todo enter deepsleep system_state = SystemState::DeepSleep; // TODO: fix polarity interfaces.led0.set_amplitude(0); @@ -500,27 +483,23 @@ fn main() -> ! { adc1_timer.reset(); adc1_timer.enable(true); - - } } match system_state { SystemState::DeepSleep => { - // TODO: make this REALLY deep sleep - unsafe{enter_standby(4)}; - loop { - riscv::asm::wfi(); - unsafe{ - if INPUT_FLAGS.coin_flag { - break; - } - }; - } - }, - SystemState::Idle => { - - }, + // TODO: make this REALLY deep sleep + unsafe { enter_standby(4) }; + loop { + riscv::asm::wfi(); + unsafe { + if INPUT_FLAGS.coin_flag { + break; + } + }; + } + } + SystemState::Idle => {} SystemState::Active => { tt0.tick(); tt1.tick(); @@ -544,16 +523,14 @@ fn main() -> ! { } interfaces.service(); - }, + } } delay.delay_us(tick_interval_us as u32); } } - #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } - From 1a420018d1b326452d2d11740205be58c4558cb3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 12 Sep 2025 15:47:45 -0600 Subject: [PATCH 081/148] deep sleep and wakeup working! --- ch32v-insert-coin/Cargo.toml | 3 + ch32v-insert-coin/src/main.rs | 154 +++++++++++++++++++++++----------- 2 files changed, 109 insertions(+), 48 deletions(-) diff --git a/ch32v-insert-coin/Cargo.toml b/ch32v-insert-coin/Cargo.toml index 1e46f37..facb4f1 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -3,6 +3,9 @@ name = "ch32v-insert-coin" version = "0.1.0" edition = "2024" +[features] +enable_print = [] + [dependencies] ch32-hal = { path = "ext/ch32-hal/", features = [ "ch32v003f4u6", diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index a50cda8..6d22219 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -102,56 +102,53 @@ unsafe fn enter_standby(pin: usize) { let pfic = &hal::pac::PFIC; pfic.sctlr().modify(|w| { // we only want to wake on enabled interrupts - w.set_sevonpend(true); + w.set_sevonpend(false); // we want to enable deep sleep w.set_sleepdeep(true); - w.set_wfitowfe(true); + w.set_wfitowfe(false); w.set_sleeponexit(false); }); // // disable all exti interrupts - // let exti = &hal::pac::EXTI; + 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 + // clear all pending exti interrupts + let bits = 0xFFFFFFFF; + exti.intfr().write(|w| w.0 = bits); + - // 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); - // }); + // 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(Interrupt::EXTI7_0 as u8); - // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); - // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); } - // execute WFI + + // execute WFI + #[cfg(feature="enable_print")] println!("WFI CONFIGURED HOPEFULLY"); - // core::arch::asm!("wfi"); - - // pfic. - // qingke::pfic:: }); } unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { critical_section::with(|_| { - println!("init_gpio_irq"); - let exti = &hal::pac::EXTI; - let afio = &hal::pac::AFIO; + #[cfg(features="enable_print")] + println!("init_gpio_irq"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; let port = port as u8; let pin = pin as usize; @@ -181,6 +178,7 @@ fn clear_interrupt(coin_pin: u8, button_pin: u8) { // coin_flag if (bits & (0x1 << coin_pin)) != 0x0 { + #[cfg(feature="enable_print")] println!("coin irq!"); unsafe { INPUT_FLAGS.coin_flag = true; @@ -189,6 +187,7 @@ fn clear_interrupt(coin_pin: u8, button_pin: u8) { // button_flag if (bits & (0x1 << button_pin)) != 0x0 { + #[cfg(feature="enable_print")] println!("button irq!"); unsafe { INPUT_FLAGS.button_flag = true; @@ -222,6 +221,7 @@ static mut INPUT_FLAGS: InputFlags = InputFlags { struct Test {} impl Handler for Test { unsafe fn on_interrupt() { + #[cfg(feature="enable_print")] println!("on_interrupt()"); critical_section::with(|_| { clear_interrupt(2, 6); @@ -237,16 +237,37 @@ bind_interrupts!(struct Irqs { use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -#[qingke_rt::entry] -fn main() -> ! { - hal::debug::SDIPrint::enable(); - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; - let p = hal::init(config); +fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { + // LED0 output setup + use hal::gpio::{Output, Level}; + let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); + + // button pin setup + let button_pin = p.PD6; + unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), false, true)}; + let mut button_input = Input::new(button_pin, Pull::Up); - // delay to let the debugger attach - let mut delay = Delay; delay.delay_ms(1000); + + unsafe{enter_standby(4)}; + delay.delay_ms(1000); + riscv::asm::wfi(); + + // get the clocks re-initialized + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { hal::rcc::init(config.rcc); } + + #[cfg(feature="enable_print")] + println!("begin loop"); + + loop { + led0_pin.toggle(); + delay.delay_ms(100); + } +} + +fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // === output setup === @@ -296,6 +317,8 @@ fn main() -> ! { // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); delay.delay_ms(1000); let adc_cal = adc.calibrate(); + + #[cfg(feature="enable_print")] println!("ADC calibration value: {}", adc_cal); // definitions @@ -384,9 +407,9 @@ fn main() -> ! { // use qingke_rt::CoreInterrupt; // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); - clear_interrupt(4, 6); - qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); - // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); + clear_interrupt(2, 6); + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } // MAIN APPLICATION @@ -397,12 +420,14 @@ fn main() -> ! { // -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"); loop { { // system input servicing unsafe { if INPUT_FLAGS.coin_flag { + #[cfg(feature="enable_print")] println!("coin flag active"); INPUT_FLAGS.coin_flag = false; coin_input.begin(); @@ -415,6 +440,7 @@ fn main() -> ! { interfaces.dac.load_data(coin_sound); } if INPUT_FLAGS.button_flag { + #[cfg(feature="enable_print")] println!("button flag active"); INPUT_FLAGS.button_flag = false; button_input.begin(); @@ -426,17 +452,20 @@ fn main() -> ! { button_input.service(); if coin_input.ready() { + #[cfg(feature="enable_print")] println!("debounced coin_input value: {}", coin_input.value()); coin_input.reset(); } if button_input.ready() { let value = button_input.value(); button_input.reset(); + #[cfg(feature="enable_print")] println!("debounced button_input value: {}", value); if !value { // interfaces.dac.load_data(button_sound); + #[cfg(feature="enable_print")] println!("reset hold timers + enable"); sp_timer.reset(); sp_timer.enable(true); @@ -454,6 +483,7 @@ fn main() -> ! { adc1_timer.tick(); if sp_timer.need_service() { + #[cfg(feature="enable_print")] println!("sp detect!"); sp_timer.reset(); @@ -466,6 +496,7 @@ fn main() -> ! { } if lp_timer.need_service() { + #[cfg(feature="enable_print")] println!("lp detect!"); lp_timer.reset(); @@ -479,6 +510,7 @@ fn main() -> ! { if adc1_timer.need_service() { let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); + #[cfg(feature="enable_print")] println!("ADC value: {}", val); adc1_timer.reset(); @@ -488,18 +520,24 @@ fn main() -> ! { match system_state { SystemState::DeepSleep => { - // TODO: make this REALLY deep sleep - unsafe { enter_standby(4) }; - loop { - riscv::asm::wfi(); - unsafe { - if INPUT_FLAGS.coin_flag { - break; - } - }; - } - } - SystemState::Idle => {} + // TODO: make this REALLY deep sleep + unsafe{enter_standby(4)}; + loop { + riscv::asm::wfi(); + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { hal::rcc::init(config.rcc); } + unsafe{ + if INPUT_FLAGS.coin_flag { + system_state = SystemState::Active; + break; + } + }; + } + }, + SystemState::Idle => { + + }, SystemState::Active => { tt0.tick(); tt1.tick(); @@ -530,6 +568,26 @@ fn main() -> ! { } } +#[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 + let mut delay = Delay; + delay.delay_ms(1000); + + debug_main(p, delay); + + + +} + + #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} From 885c7746b3fff3f10acef55505ce3b17d1b0235f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 12 Sep 2025 16:19:26 -0600 Subject: [PATCH 082/148] deep sleep with PDDS is working --- ch32v-insert-coin/src/main.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 6d22219..2db5988 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -109,6 +109,15 @@ unsafe fn enter_standby(pin: usize) { w.set_sleeponexit(false); }); + // set PDDS=1: + // get current value of PWR_CTLR + let mut reg: u32 = 0x4000_7000; + let mut val: u32 = 0; + unsafe { val = (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| { @@ -144,8 +153,8 @@ unsafe fn enter_standby(pin: usize) { } unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { - critical_section::with(|_| { - #[cfg(features="enable_print")] + critical_section::with(|_| { + #[cfg(feature="enable_print")] println!("init_gpio_irq"); let exti = &hal::pac::EXTI; let afio = &hal::pac::AFIO; From 8c88456fcb6e537558876124cd61462ad61dd469 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 25 Oct 2025 14:09:54 -0600 Subject: [PATCH 083/148] add second ADC pin --- ch32v-insert-coin/src/main.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 2db5988..40b71dc 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -293,7 +293,9 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let led1_ch = hal::timer::Channel::Ch1; // LED2 output setup - + let led2_pin = PwmPin::new_ch2::<0>(p.PA1); + let led2_ch = hal::timer::Channel::Ch2; + // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); // let dac_ch = hal::timer::Channel::Ch4; @@ -302,10 +304,10 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let mut pwm = SimplePwm::new( p.TIM1, Some(led1_pin), - None, + Some(led2_pin), Some(led0_pin), Some(dac_pin), - Hertz::khz(100), + Hertz::khz(200), CountingMode::default(), ); @@ -323,6 +325,11 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // adc let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); let mut adc_pin = p.PD4; + + // adc2 + // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); + let mut usb_adc_pin = p.PD5; + // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); delay.delay_ms(1000); let adc_cal = adc.calibrate(); @@ -519,6 +526,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { if adc1_timer.need_service() { let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); + let val = adc.convert(&mut usb_adc_pin, hal::adc::SampleTime::CYCLES241); #[cfg(feature="enable_print")] println!("ADC value: {}", val); From 08d7289c936d74d3b836d9bde7539ae9d46c5cfa Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 07:55:11 -0600 Subject: [PATCH 084/148] formatting --- ch32v-insert-coin/src/main.rs | 134 +++++++++++++++++----------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 40b71dc..cecf6da 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -113,10 +113,14 @@ unsafe fn enter_standby(pin: usize) { // get current value of PWR_CTLR let mut reg: u32 = 0x4000_7000; let mut val: u32 = 0; - unsafe { val = (reg as *mut u32).read_volatile(); } + unsafe { + val = (reg as *mut u32).read_volatile(); + } // modify PDDS val |= 1 << 1; // PWR_CTLR[1] -> PDDS - unsafe { (reg as *mut u32).write_volatile(val); } + unsafe { + (reg as *mut u32).write_volatile(val); + } // // disable all exti interrupts let exti = &hal::pac::EXTI; @@ -129,7 +133,6 @@ unsafe fn enter_standby(pin: usize) { // clear all pending exti interrupts let bits = 0xFFFFFFFF; exti.intfr().write(|w| w.0 = bits); - // enable all exti interrupts let exti = &hal::pac::EXTI; @@ -144,20 +147,18 @@ unsafe fn enter_standby(pin: usize) { qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); } - - // execute WFI - #[cfg(feature="enable_print")] + // execute WFI + #[cfg(feature = "enable_print")] println!("WFI CONFIGURED HOPEFULLY"); - }); } unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { - critical_section::with(|_| { - #[cfg(feature="enable_print")] - println!("init_gpio_irq"); - let exti = &hal::pac::EXTI; - let afio = &hal::pac::AFIO; + critical_section::with(|_| { + #[cfg(feature = "enable_print")] + println!("init_gpio_irq"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; let port = port as u8; let pin = pin as usize; @@ -187,7 +188,7 @@ fn clear_interrupt(coin_pin: u8, button_pin: u8) { // coin_flag if (bits & (0x1 << coin_pin)) != 0x0 { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("coin irq!"); unsafe { INPUT_FLAGS.coin_flag = true; @@ -196,7 +197,7 @@ fn clear_interrupt(coin_pin: u8, button_pin: u8) { // button_flag if (bits & (0x1 << button_pin)) != 0x0 { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("button irq!"); unsafe { INPUT_FLAGS.button_flag = true; @@ -230,7 +231,7 @@ static mut INPUT_FLAGS: InputFlags = InputFlags { struct Test {} impl Handler for Test { unsafe fn on_interrupt() { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("on_interrupt()"); critical_section::with(|_| { clear_interrupt(2, 6); @@ -247,27 +248,29 @@ use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { - // LED0 output setup - use hal::gpio::{Output, Level}; + // LED0 output setup + use hal::gpio::{Level, Output}; let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); // button pin setup - let button_pin = p.PD6; - unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), false, true)}; + let button_pin = p.PD6; + unsafe { init_gpio_irq(button_pin.pin(), button_pin.port(), false, true) }; let mut button_input = Input::new(button_pin, Pull::Up); delay.delay_ms(1000); - - unsafe{enter_standby(4)}; + + unsafe { enter_standby(4) }; delay.delay_ms(1000); riscv::asm::wfi(); // get the clocks re-initialized let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { hal::rcc::init(config.rcc); } + unsafe { + hal::rcc::init(config.rcc); + } - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("begin loop"); loop { @@ -277,7 +280,6 @@ fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { } fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { - // === output setup === // LED0 output setup @@ -294,8 +296,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // LED2 output setup let led2_pin = PwmPin::new_ch2::<0>(p.PA1); - let led2_ch = hal::timer::Channel::Ch2; - + let led2_ch = hal::timer::Channel::Ch2; + // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); // let dac_ch = hal::timer::Channel::Ch4; @@ -329,26 +331,26 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // adc2 // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); let mut usb_adc_pin = p.PD5; - + // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); delay.delay_ms(1000); let adc_cal = adc.calibrate(); - - #[cfg(feature="enable_print")] + + #[cfg(feature = "enable_print")] println!("ADC calibration value: {}", adc_cal); // definitions let coin_pin = p.PC2; let button_pin = p.PD6; // println!( - // "coin pin: {} | coin port: {}", - // coin_pin.pin(), - // coin_pin.port() + // "coin pin: {} | coin port: {}", + // coin_pin.pin(), + // coin_pin.port() // ); // println!( - // "push pin: {} | push port: {}", - // button_pin.pin(), - // button_pin.port() + // "push pin: {} | push port: {}", + // button_pin.pin(), + // button_pin.port() // ); //2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 @@ -424,8 +426,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); clear_interrupt(2, 6); - qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); - // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } // MAIN APPLICATION @@ -436,14 +438,14 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // -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")] + #[cfg(feature = "enable_print")] println!("begin"); loop { { // system input servicing unsafe { if INPUT_FLAGS.coin_flag { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("coin flag active"); INPUT_FLAGS.coin_flag = false; coin_input.begin(); @@ -456,7 +458,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { interfaces.dac.load_data(coin_sound); } if INPUT_FLAGS.button_flag { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("button flag active"); INPUT_FLAGS.button_flag = false; button_input.begin(); @@ -468,20 +470,20 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { button_input.service(); if coin_input.ready() { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("debounced coin_input value: {}", coin_input.value()); coin_input.reset(); } if button_input.ready() { let value = button_input.value(); button_input.reset(); - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("debounced button_input value: {}", value); if !value { // interfaces.dac.load_data(button_sound); - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("reset hold timers + enable"); sp_timer.reset(); sp_timer.enable(true); @@ -499,7 +501,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { adc1_timer.tick(); if sp_timer.need_service() { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("sp detect!"); sp_timer.reset(); @@ -512,7 +514,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { } if lp_timer.need_service() { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("lp detect!"); lp_timer.reset(); @@ -527,7 +529,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { if adc1_timer.need_service() { let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); let val = adc.convert(&mut usb_adc_pin, hal::adc::SampleTime::CYCLES241); - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] println!("ADC value: {}", val); adc1_timer.reset(); @@ -537,24 +539,24 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { match system_state { SystemState::DeepSleep => { - // TODO: make this REALLY deep sleep - unsafe{enter_standby(4)}; - loop { - riscv::asm::wfi(); - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { hal::rcc::init(config.rcc); } - unsafe{ - if INPUT_FLAGS.coin_flag { - system_state = SystemState::Active; - break; - } - }; + // TODO: make this REALLY deep sleep + unsafe { enter_standby(4) }; + loop { + riscv::asm::wfi(); + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { + hal::rcc::init(config.rcc); } - }, - SystemState::Idle => { - - }, + unsafe { + if INPUT_FLAGS.coin_flag { + system_state = SystemState::Active; + break; + } + }; + } + } + SystemState::Idle => {} SystemState::Active => { tt0.tick(); tt1.tick(); @@ -587,7 +589,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { #[qingke_rt::entry] fn main() -> ! { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] hal::debug::SDIPrint::enable(); let mut config = hal::Config::default(); @@ -599,12 +601,8 @@ fn main() -> ! { delay.delay_ms(1000); debug_main(p, delay); - - - } - #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} From 3eb410c796cba623f53f83d6312c5afdc350234f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 08:20:19 -0600 Subject: [PATCH 085/148] move most of the system functions out of main.rs --- ch32v-insert-coin/src/main.rs | 160 ++++---------------------------- ch32v-insert-coin/src/system.rs | 145 +++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 143 deletions(-) create mode 100644 ch32v-insert-coin/src/system.rs diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index cecf6da..19c3869 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -3,7 +3,12 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +// app stuff mod insert_coin; + +// system stuff +mod system; + use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; @@ -84,139 +89,6 @@ pub enum SystemState { Active, } -/// 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) -unsafe fn enter_standby(pin: usize) { - 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 = 0; - unsafe { - val = (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"); - }); -} - -unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { - critical_section::with(|_| { - #[cfg(feature = "enable_print")] - println!("init_gpio_irq"); - 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)); - }); -} - -fn clear_interrupt(coin_pin: u8, button_pin: u8) { - 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; - - // coin_flag - if (bits & (0x1 << coin_pin)) != 0x0 { - #[cfg(feature = "enable_print")] - println!("coin irq!"); - unsafe { - INPUT_FLAGS.coin_flag = true; - } - } - - // button_flag - if (bits & (0x1 << button_pin)) != 0x0 { - #[cfg(feature = "enable_print")] - println!("button irq!"); - unsafe { - INPUT_FLAGS.button_flag = 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 -} - #[derive(Debug)] struct InputFlags { coin_flag: bool, @@ -233,8 +105,8 @@ impl Handler for Test { unsafe fn on_interrupt() { #[cfg(feature = "enable_print")] println!("on_interrupt()"); - critical_section::with(|_| { - clear_interrupt(2, 6); + critical_section::with(|_| unsafe { + INPUT_FLAGS = system::clear_interrupt(2, 6); }); } } @@ -254,12 +126,12 @@ fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // button pin setup let button_pin = p.PD6; - unsafe { init_gpio_irq(button_pin.pin(), button_pin.port(), false, true) }; + unsafe { system::init_gpio_irq(button_pin.pin(), button_pin.port(), false, true) }; let mut button_input = Input::new(button_pin, Pull::Up); delay.delay_ms(1000); - unsafe { enter_standby(4) }; + unsafe { system::enter_standby(4) }; delay.delay_ms(1000); riscv::asm::wfi(); @@ -357,8 +229,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 // set up interrupts - unsafe { init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true) }; - unsafe { init_gpio_irq(button_pin.pin(), button_pin.port(), true, true) }; + unsafe { system::init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true) }; + unsafe { system::init_gpio_irq(button_pin.pin(), button_pin.port(), true, true) }; // coin debouncer (100ms) let mut coin_input = DebouncedGPIO::new(coin_pin.degrade(), core_config.tick_rate_hz, 100); @@ -411,7 +283,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; // dac data - let coin_sound = include_bytes!("../audio/coin.raw"); + // let coin_sound = include_bytes!("../audio/coin.raw"); + let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); // let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); let mut system_state = SystemState::Active; @@ -425,7 +298,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // use qingke_rt::CoreInterrupt; // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); - clear_interrupt(2, 6); + system::clear_interrupt(2, 6); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } @@ -540,7 +413,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { match system_state { SystemState::DeepSleep => { // TODO: make this REALLY deep sleep - unsafe { enter_standby(4) }; + unsafe { system::enter_standby(4) }; loop { riscv::asm::wfi(); let mut config = hal::Config::default(); @@ -600,7 +473,8 @@ fn main() -> ! { let mut delay = Delay; delay.delay_ms(1000); - debug_main(p, delay); + // debug_main(p, delay); + app_main(p, delay); } #[panic_handler] diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs new file mode 100644 index 0000000..0a3212b --- /dev/null +++ b/ch32v-insert-coin/src/system.rs @@ -0,0 +1,145 @@ +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"); + 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)); + }); +} + +// FIXME: should return a vec of the interrupts +pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { + let mut input_flags = crate::InputFlags { + coin_flag: false, + button_flag: 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; + + // coin_flag + if (bits & (0x1 << coin_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("coin irq!"); + input_flags.coin_flag = true; + // unsafe { + // INPUT_FLAGS.coin_flag = true; + // } + } + + // button_flag + if (bits & (0x1 << button_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("button irq!"); + input_flags.button_flag = true; + // unsafe { + // INPUT_FLAGS.button_flag = 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 + + input_flags +} + +/// 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(pin: usize) { + 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 = 0; + unsafe { + val = (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"); + }); +} From 73e4b482a63997aac1922f861cf64232f9bb5307 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 13:31:18 -0600 Subject: [PATCH 086/148] add settings struct --- ch32v-insert-coin/src/main.rs | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 19c3869..c1b4631 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -24,6 +24,42 @@ use hal::println; use qingke::riscv; +enum Level { + Off, + Low, + 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, + }; + } +} + +struct Settings { + brightness: Level, + volume: Level, + button_sound_index: u8, +} + +impl Default for Settings { + fn default() -> Self { + Self { + brightness: Level::Medium, + volume: Level::Medium, + button_sound_index: 0, + } + } +} + struct DebouncedGPIO<'a> { input: Input<'a>, // value of the GPIO From b1d7574a80087023da01fc52cbeea84490dfd634 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 14:00:29 -0600 Subject: [PATCH 087/148] add additional InputFlags and minor refactor --- ch32v-insert-coin/src/main.rs | 89 +++++++++++++++++++++++---------- ch32v-insert-coin/src/system.rs | 9 ++-- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c1b4631..8929c69 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -127,13 +127,28 @@ pub enum SystemState { #[derive(Debug)] struct InputFlags { - coin_flag: bool, - button_flag: bool, + sense_coin_flag: bool, + main_btn_flag: bool, + volume_btn_flag: bool, + light_ctrl_btn_flag: bool, +} + +impl Default for InputFlags { + fn default() -> Self { + Self { + sense_coin_flag: false, + main_btn_flag: false, + volume_btn_flag: false, + light_ctrl_btn_flag: false, + } + } } static mut INPUT_FLAGS: InputFlags = InputFlags { - coin_flag: false, - button_flag: false, + sense_coin_flag: false, + main_btn_flag: false, + volume_btn_flag: false, + light_ctrl_btn_flag: false, }; struct Test {} @@ -248,8 +263,12 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { println!("ADC calibration value: {}", adc_cal); // definitions - let coin_pin = p.PC2; - let button_pin = p.PD6; + 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; + // println!( // "coin pin: {} | coin port: {}", // coin_pin.pin(), @@ -265,13 +284,30 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 // set up interrupts - unsafe { system::init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true) }; - unsafe { system::init_gpio_irq(button_pin.pin(), button_pin.port(), true, true) }; + unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), false, true) }; + unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) }; + unsafe { system::init_gpio_irq(volume_btn_pin.pin(), volume_btn_pin.port(), true, true) }; + unsafe { + system::init_gpio_irq( + light_ctrl_btn_pin.pin(), + light_ctrl_btn_pin.port(), + true, + true, + ) + }; // coin debouncer (100ms) - let mut coin_input = DebouncedGPIO::new(coin_pin.degrade(), core_config.tick_rate_hz, 100); + let mut sense_coin_input = + DebouncedGPIO::new(sense_coin_pin.degrade(), core_config.tick_rate_hz, 100); + // main button debouncer (100ms) + let mut main_btn_input = + DebouncedGPIO::new(main_btn_pin.degrade(), core_config.tick_rate_hz, 100); // button debouncer (100ms) - let mut button_input = DebouncedGPIO::new(button_pin.degrade(), core_config.tick_rate_hz, 100); + 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 pwm_core = SimplePwmCore::new(pwm); @@ -321,6 +357,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // dac data // let coin_sound = include_bytes!("../audio/coin.raw"); let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); + let button_sounds = [include_bytes!("../audio/sweep_dpcm_u4.raw")]; // let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); let mut system_state = SystemState::Active; @@ -353,11 +390,11 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { { // system input servicing unsafe { - if INPUT_FLAGS.coin_flag { + if INPUT_FLAGS.sense_coin_flag { #[cfg(feature = "enable_print")] println!("coin flag active"); - INPUT_FLAGS.coin_flag = false; - coin_input.begin(); + INPUT_FLAGS.sense_coin_flag = false; + sense_coin_input.begin(); // enter the active state system_state = SystemState::Active; @@ -366,31 +403,31 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { tt1.enable(true); interfaces.dac.load_data(coin_sound); } - if INPUT_FLAGS.button_flag { + if INPUT_FLAGS.main_btn_flag { #[cfg(feature = "enable_print")] println!("button flag active"); - INPUT_FLAGS.button_flag = false; - button_input.begin(); + INPUT_FLAGS.main_btn_flag = false; + main_btn_input.begin(); } } // debouncer - coin_input.service(); - button_input.service(); + sense_coin_input.service(); + main_btn_input.service(); - if coin_input.ready() { + if sense_coin_input.ready() { #[cfg(feature = "enable_print")] - println!("debounced coin_input value: {}", coin_input.value()); - coin_input.reset(); + println!("debounced coin_input value: {}", sense_coin_input.value()); + sense_coin_input.reset(); } - if button_input.ready() { - let value = button_input.value(); - button_input.reset(); + if main_btn_input.ready() { + let value = main_btn_input.value(); + main_btn_input.reset(); #[cfg(feature = "enable_print")] println!("debounced button_input value: {}", value); if !value { - // interfaces.dac.load_data(button_sound); + interfaces.dac.load_data(button_sounds[0]); #[cfg(feature = "enable_print")] println!("reset hold timers + enable"); @@ -458,7 +495,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { hal::rcc::init(config.rcc); } unsafe { - if INPUT_FLAGS.coin_flag { + if INPUT_FLAGS.sense_coin_flag { system_state = SystemState::Active; break; } diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs index 0a3212b..d1cbb43 100644 --- a/ch32v-insert-coin/src/system.rs +++ b/ch32v-insert-coin/src/system.rs @@ -22,10 +22,7 @@ pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { // FIXME: should return a vec of the interrupts pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { - let mut input_flags = crate::InputFlags { - coin_flag: false, - button_flag: false, - }; + let mut input_flags = crate::InputFlags::default(); let exti = &hal::pac::EXTI; @@ -44,7 +41,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { if (bits & (0x1 << coin_pin)) != 0x0 { #[cfg(feature = "enable_print")] println!("coin irq!"); - input_flags.coin_flag = true; + input_flags.sense_coin_flag = true; // unsafe { // INPUT_FLAGS.coin_flag = true; // } @@ -54,7 +51,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { if (bits & (0x1 << button_pin)) != 0x0 { #[cfg(feature = "enable_print")] println!("button irq!"); - input_flags.button_flag = true; + input_flags.main_btn_flag = true; // unsafe { // INPUT_FLAGS.button_flag = true; // } From 485f617515fc07c2bc95aba62151d009f16cdbb7 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 15:44:35 -0600 Subject: [PATCH 088/148] cursed input flags stuff --- ch32v-insert-coin/src/main.rs | 43 ++++++++++++++++++--------------- ch32v-insert-coin/src/system.rs | 40 ++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 8929c69..ba13aee 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -144,6 +144,21 @@ impl Default for InputFlags { } } +impl InputFlags { + pub fn set_sense_coin_flag(&mut self, val: bool) { + self.sense_coin_flag = val; + } + pub fn set_main_btn_flag(&mut self, val: bool) { + self.main_btn_flag = val; + } + pub fn set_volume_btn_flag(&mut self, val: bool) { + self.volume_btn_flag = val; + } + pub fn set_light_ctrl_btn_flag(&mut self, val: bool) { + self.light_ctrl_btn_flag = val; + } +} + static mut INPUT_FLAGS: InputFlags = InputFlags { sense_coin_flag: false, main_btn_flag: false, @@ -157,7 +172,9 @@ impl Handler for Test { #[cfg(feature = "enable_print")] println!("on_interrupt()"); critical_section::with(|_| unsafe { - INPUT_FLAGS = system::clear_interrupt(2, 6); + let flags = system::clear_interrupt(2, 6); + INPUT_FLAGS.sense_coin_flag = flags.sense_coin_flag; + INPUT_FLAGS.main_btn_flag = flags.main_btn_flag; }); } } @@ -249,11 +266,11 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // adc let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); - let mut adc_pin = p.PD4; + let mut batt_monitor_pin = p.PD4; // adc2 // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); - let mut usb_adc_pin = p.PD5; + let mut usb_detect_pin = p.PD5; // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); delay.delay_ms(1000); @@ -268,20 +285,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let volume_btn_pin = p.PC6; let light_ctrl_btn_pin = p.PC7; let amp_en = p.PC5; - - // println!( - // "coin pin: {} | coin port: {}", - // coin_pin.pin(), - // coin_pin.port() - // ); - // println!( - // "push pin: {} | push port: {}", - // button_pin.pin(), - // button_pin.port() - // ); - - //2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 - // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 + let extra_io_1 = p.PD0; + let extra_io_2 = p.PD3; // set up interrupts unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), false, true) }; @@ -473,8 +478,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { } if adc1_timer.need_service() { - let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); - let val = adc.convert(&mut usb_adc_pin, hal::adc::SampleTime::CYCLES241); + let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); + let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); #[cfg(feature = "enable_print")] println!("ADC value: {}", val); diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs index d1cbb43..5266894 100644 --- a/ch32v-insert-coin/src/system.rs +++ b/ch32v-insert-coin/src/system.rs @@ -4,7 +4,7 @@ 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"); + println!("init_gpio_irq {pin}:{port}"); let exti = &hal::pac::EXTI; let afio = &hal::pac::AFIO; @@ -20,7 +20,6 @@ pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { }); } -// FIXME: should return a vec of the interrupts pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { let mut input_flags = crate::InputFlags::default(); @@ -36,27 +35,52 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { // We don't handle or change any EXTI lines above 24. let bits = bits.0 & 0x00FFFFFF; + println!("bits: {bits:08x}"); // coin_flag if (bits & (0x1 << coin_pin)) != 0x0 { #[cfg(feature = "enable_print")] println!("coin irq!"); input_flags.sense_coin_flag = true; - // unsafe { - // INPUT_FLAGS.coin_flag = true; - // } } // button_flag if (bits & (0x1 << button_pin)) != 0x0 { #[cfg(feature = "enable_print")] - println!("button irq!"); - input_flags.main_btn_flag = true; + println!("main_btn irq!"); + // CHECK PC6 // unsafe { - // INPUT_FLAGS.button_flag = true; + // let mut val = 0; + // let reg: u32 = 0x40011008; + // val = (reg as *mut u32).read_volatile(); + // if ((val >> 0x6) & 0x1) == 0x0 { + // input_flags.volume_btn_flag = true; + // #[cfg(feature = "enable_print")] + // println!("volume irq!"); + // println!("CBANK: {val:08x}"); + // } + // } + // // CHECK PD6 + // unsafe { + // let mut val = 0; + // let reg: u32 = 0x40011408; + // val = (reg as *mut u32).read_volatile(); + // // if ((val >> 0x6) & 0x1) == 0x0 { + // input_flags.main_btn_flag = true; + // #[cfg(feature = "enable_print")] + // println!("main_btn irq!"); + // println!("DBANK: {val:08x}"); + // // } // } } + // light_ctrl_btn_flag + // if (bits & (0x1 << light_ctrl_btn_pin)) != 0x0 { + // #[cfg(feature = "enable_print")] + // println!("light ctrl btn irq!"); + // input_flags.light_ctrl_btn_flag = true; + // } + // Clear pending - Clears the EXTI's line pending bits. exti.intfr().write(|w| w.0 = bits); From 144d24cf598ce1f42ce5565d4f822942f7c433f6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 16:13:59 -0600 Subject: [PATCH 089/148] initial volume and light control button support w/ debouncer --- ch32v-insert-coin/src/main.rs | 54 +++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ba13aee..9ad174c 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -66,6 +66,8 @@ struct DebouncedGPIO<'a> { value: bool, // GPIO is ready (debounced) ready: bool, + // debouncer is active + active: bool, // debounce timer timer: TickTimerService, } @@ -77,6 +79,7 @@ impl<'a> DebouncedGPIO<'a> { input: Input::new(pin, Pull::Up), value: false, ready: false, + active: false, timer: TickTimerService::new( TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), false, @@ -88,6 +91,10 @@ impl<'a> DebouncedGPIO<'a> { self.ready } + pub fn active(&self) -> bool { + self.active + } + pub fn value(&self) -> bool { self.value } @@ -95,11 +102,13 @@ impl<'a> DebouncedGPIO<'a> { 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) { @@ -110,6 +119,9 @@ impl<'a> DebouncedGPIO<'a> { self.ready = true; } } + pub fn is_high_immediate(&self) -> bool { + self.input.is_high() + } } // DeepSleep --coin button irq--> Active @@ -291,15 +303,15 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // set up interrupts unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), false, true) }; unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) }; - unsafe { system::init_gpio_irq(volume_btn_pin.pin(), volume_btn_pin.port(), true, true) }; - unsafe { - system::init_gpio_irq( - light_ctrl_btn_pin.pin(), - light_ctrl_btn_pin.port(), - true, - true, - ) - }; + // unsafe { system::init_gpio_irq(volume_btn_pin.pin(), volume_btn_pin.port(), true, true) }; + // unsafe { + // system::init_gpio_irq( + // light_ctrl_btn_pin.pin(), + // light_ctrl_btn_pin.port(), + // true, + // true, + // ) + // }; // coin debouncer (100ms) let mut sense_coin_input = @@ -414,11 +426,25 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { INPUT_FLAGS.main_btn_flag = false; main_btn_input.begin(); } + if !volume_btn_input.active() { + if !volume_btn_input.is_high_immediate() && !volume_btn_input.active() { + #[cfg(feature = "enable_print")] + println!("volume btn triggered: {}", volume_btn_input.active()); + volume_btn_input.begin(); + } + } + if !light_ctrl_btn_input.is_high_immediate() && !light_ctrl_btn_input.active() { + #[cfg(feature = "enable_print")] + println!("light ctrl btn triggered!"); + light_ctrl_btn_input.begin(); + } } // debouncer sense_coin_input.service(); main_btn_input.service(); + volume_btn_input.service(); + light_ctrl_btn_input.service(); if sense_coin_input.ready() { #[cfg(feature = "enable_print")] @@ -445,6 +471,16 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { lp_timer.reset(); } } + if volume_btn_input.ready() { + #[cfg(feature = "enable_print")] + println!("volume btn value: {}", volume_btn_input.value()); + volume_btn_input.reset(); + } + if light_ctrl_btn_input.ready() { + #[cfg(feature = "enable_print")] + println!("light_ctrl_btn value: {}", sense_coin_input.value()); + sense_coin_input.reset(); + } // timers sp_timer.tick(); From 9f12502a3df5dddd58de1d42972ce8e867dbd92d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 17:26:34 -0600 Subject: [PATCH 090/148] add edge detecting inputs for volume and brightness settings --- ch32v-insert-coin/src/main.rs | 87 +++++++++++++++++++++++---------- ch32v-insert-coin/src/system.rs | 7 +-- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9ad174c..71b3cc4 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -47,7 +47,7 @@ impl Level { struct Settings { brightness: Level, volume: Level, - button_sound_index: u8, + button_sound_index: usize, } impl Default for Settings { @@ -156,20 +156,20 @@ impl Default for InputFlags { } } -impl InputFlags { - pub fn set_sense_coin_flag(&mut self, val: bool) { - self.sense_coin_flag = val; - } - pub fn set_main_btn_flag(&mut self, val: bool) { - self.main_btn_flag = val; - } - pub fn set_volume_btn_flag(&mut self, val: bool) { - self.volume_btn_flag = val; - } - pub fn set_light_ctrl_btn_flag(&mut self, val: bool) { - self.light_ctrl_btn_flag = val; - } -} +// impl InputFlags { +// pub fn set_sense_coin_flag(&mut self, val: bool) { +// self.sense_coin_flag = val; +// } +// pub fn set_main_btn_flag(&mut self, val: bool) { +// self.main_btn_flag = val; +// } +// pub fn set_volume_btn_flag(&mut self, val: bool) { +// self.volume_btn_flag = val; +// } +// pub fn set_light_ctrl_btn_flag(&mut self, val: bool) { +// self.light_ctrl_btn_flag = val; +// } +// } static mut INPUT_FLAGS: InputFlags = InputFlags { sense_coin_flag: false, @@ -211,7 +211,7 @@ fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { delay.delay_ms(1000); - unsafe { system::enter_standby(4) }; + unsafe { system::enter_standby() }; delay.delay_ms(1000); riscv::asm::wfi(); @@ -332,6 +332,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { interfaces.set_active(true); // insert_coin.init(); + let mut settings = Settings::default(); + let mut led0_index = 0; let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; @@ -403,7 +405,30 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { #[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(); loop { + // 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_prev = volume_btn_curr; + unsafe { + INPUT_FLAGS.volume_btn_flag = true; + } + } + } + + 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_prev = light_ctrl_btn_curr; + unsafe { + INPUT_FLAGS.light_ctrl_btn_flag = true; + } + } + } { // system input servicing unsafe { @@ -426,16 +451,16 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { INPUT_FLAGS.main_btn_flag = false; main_btn_input.begin(); } - if !volume_btn_input.active() { - if !volume_btn_input.is_high_immediate() && !volume_btn_input.active() { - #[cfg(feature = "enable_print")] - println!("volume btn triggered: {}", volume_btn_input.active()); - volume_btn_input.begin(); - } + if INPUT_FLAGS.volume_btn_flag { + #[cfg(feature = "enable_print")] + println!("volume btn triggered"); + INPUT_FLAGS.volume_btn_flag = false; + volume_btn_input.begin(); } - if !light_ctrl_btn_input.is_high_immediate() && !light_ctrl_btn_input.active() { + if INPUT_FLAGS.light_ctrl_btn_flag { #[cfg(feature = "enable_print")] println!("light ctrl btn triggered!"); + INPUT_FLAGS.light_ctrl_btn_flag = false; light_ctrl_btn_input.begin(); } } @@ -458,8 +483,14 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { println!("debounced button_input value: {}", value); if !value { - interfaces.dac.load_data(button_sounds[0]); + interfaces + .dac + .load_data(button_sounds[settings.button_sound_index]); + settings.button_sound_index += 1; + if settings.button_sound_index > button_sounds.len() - 1 { + settings.button_sound_index = 0; + } #[cfg(feature = "enable_print")] println!("reset hold timers + enable"); sp_timer.reset(); @@ -474,12 +505,14 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { if volume_btn_input.ready() { #[cfg(feature = "enable_print")] println!("volume btn value: {}", volume_btn_input.value()); + settings.volume.next(); volume_btn_input.reset(); } if light_ctrl_btn_input.ready() { #[cfg(feature = "enable_print")] - println!("light_ctrl_btn value: {}", sense_coin_input.value()); - sense_coin_input.reset(); + println!("light_ctrl_btn value: {}", light_ctrl_btn_input.value()); + settings.brightness.next(); + light_ctrl_btn_input.reset(); } // timers @@ -527,7 +560,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { match system_state { SystemState::DeepSleep => { // TODO: make this REALLY deep sleep - unsafe { system::enter_standby(4) }; + unsafe { system::enter_standby() }; loop { riscv::asm::wfi(); let mut config = hal::Config::default(); diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs index 5266894..97b43e4 100644 --- a/ch32v-insert-coin/src/system.rs +++ b/ch32v-insert-coin/src/system.rs @@ -105,7 +105,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { /// * (probably WFI?) /// exit: /// 1. any interrupt/event (set in external interrupt register) -pub unsafe fn enter_standby(pin: usize) { +pub unsafe fn enter_standby() { critical_section::with(|_| { use hal::pac::Interrupt; use qingke_rt::CoreInterrupt; @@ -124,10 +124,7 @@ pub unsafe fn enter_standby(pin: usize) { // set PDDS=1: // get current value of PWR_CTLR let mut reg: u32 = 0x4000_7000; - let mut val: u32 = 0; - unsafe { - val = (reg as *mut u32).read_volatile(); - } + let mut val: u32 = unsafe { (reg as *mut u32).read_volatile() }; // modify PDDS val |= 1 << 1; // PWR_CTLR[1] -> PDDS unsafe { From 17c82e5a6ce7371bde31beae416bcd04df5137fb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 18:08:26 -0600 Subject: [PATCH 091/148] add some button noises --- ch32v-insert-coin/audio/button_1.raw | Bin 0 -> 1209 bytes ch32v-insert-coin/audio/button_2.raw | Bin 0 -> 990 bytes ch32v-insert-coin/audio/button_3.raw | Bin 0 -> 2133 bytes ch32v-insert-coin/src/main.rs | 24 +++++++++++++++++------- 4 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 ch32v-insert-coin/audio/button_1.raw create mode 100644 ch32v-insert-coin/audio/button_2.raw create mode 100644 ch32v-insert-coin/audio/button_3.raw diff --git a/ch32v-insert-coin/audio/button_1.raw b/ch32v-insert-coin/audio/button_1.raw new file mode 100644 index 0000000000000000000000000000000000000000..ade05bd67582cd95872cf3b14a1af1c4c356ea69 GIT binary patch literal 1209 zcmZ`(;cDwT47S0B1kOML8xm;W#fBIX=)eXCQpkCi1R7Z36cT74h3#GPqZig6g9TRD zVCP+~bI;}eyEam+&ypj{k`M{tIw+xpAO!Ib5JV(oU9UI-r9q@&KMcC+g)JI%={m6Z z-T<4IhexQPS8;kP`~JK+JxcTbDH6R5gG;_^s*RBdKtw-PRoUKfjB)&Y7s-Yw9>1Mp ztUlhqpS_~-g2w@23Sw=wxh>`K=mvQIo&=$^J8~0zc922In4?Ul8ZZ+B&7>qSstk4J zN+L>B9GO5R_A)rH0H_N0asw)&GUtdw$+NNWJ>Pw8f1MwGEZf(9d+l8$wpaiAD%@PX zu;?;Z%N+RTrQLn$Hndf#?DhaWw`t_~>OT@8DQat2dFbP0yn2~`H5EP$9lXp-yt@-$gg>9tUt?GA zugIhQ>qqh9Li&f@Li{#cT9Ias1{apuoAcvlzq`C*D+)+=m&4MX!y^Bhm%pai?z4Mq zb+XMb*L%>Ye?DF2p-UvY90u*z6hJNFbta}xA>O2!j4q7tN>*m0f_zY8=33VbbZBe? zdf>M4+G@*vYbj9A1r-u$k_riN)L7}XYX}(?W+>#qS>ve-MkE`kWHLiYiT1`*FrWtQ zn9GoqgeH>c>GVM^xtU@Y@w&q%h0{lS&%4PihwY|&Z}ywhG=D|KbJyqFs_UDyKAq=F zWTvi6;z~PWLPj~)BAAiQ)^Six?~Gv7%1#h&sZOM{npQ429z5p}$B^YrIz}OIwT1+# zAacl!^@XnS;hfFGpi!v3%PH8nsc1N;v7qH@l5zA#duJ?bN~bW`h9g70XdIxSr6;o- z6V;72QryCU%fMvmMJvmSbj~K+04UBY&@m=xq>Hcs%P3t72<5Gl%Ve#&#p%A@JWGCa z4&FaWb1PzT{`Q%9J96JYSBl>ZKcDMK7dIn)UwdudI86P<8cMCNKlt^SKJErI^TSb_ zxHYEGyyIGS$WcRxg10`9fbbO_0EPRPq(aA|k~B7sh}9xo+##|uWb``2dlKT5Cjk!S eCxaD-kRWU8_y=0s$l6~5h2Z>0{}27q2K*cL?=4dR literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/audio/button_2.raw b/ch32v-insert-coin/audio/button_2.raw new file mode 100644 index 0000000000000000000000000000000000000000..16b9b362e005c17b879e4650db5ff78a2b9caf3f GIT binary patch literal 990 zcmX|=VaKyj9L9@A(KvrPbg1uAG#ZU_=up&mS#0d4QJg61yKHRirqN+zvA)Z5zk04$ z*Y)yu@dNlD6#*#!*Z-obs?S?HON-f5?KHuj<0dMfzctL`nh|Xf(cb~$>sjMQ!AvF{-vxdv<4Q?+I z#`0BQ(O0hD5d&u3ESJkb?U_8OW)*VHaLA!t{H&JtP&2}+cAj#=+nU!##r4HAS32uWN z)n=TuSTK@{&YTQxd=1J7p0GXp`NKNoyuX~JGZlFMaTu0JQ%PJukCsz&|FU|q3|4Vc zVaZ$XgSSmV@6`3&Yq-)Gf+xBK^((!4mY1gOUHil4@}UK`(EtjAF0cLA)4`f2?E&3{ zh4o0eRY!3)5R~5718=$}x@-9g)Zj{842_~EE_6wN(iA_dLo|2!JsQ;oZ`2@Ooc>|EDS$ zbkDdPhjys5OE}*LOyvoTHy{6wa#8-UAH78Kk*yu648Nz^8VtfmhQwLQM?JRSyj6Na zOFJ}~7C_5}-uxsjJ1xH@w(yoNreJ>_R4%y3(68RyY9j5i?~Htv=${Y6_uWY&$ek+1cm#aFxpJM t*AI~YqQqI&(7iNePuR2@71JIVG0Q^896;#0#nzpF&>U7XP`p2v{{bNyV$lEq literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/audio/button_3.raw b/ch32v-insert-coin/audio/button_3.raw new file mode 100644 index 0000000000000000000000000000000000000000..eb62b2dd1e0a6a0e78d8639ce93c155f8d332ecb GIT binary patch literal 2133 zcma)+YmTEZ5QJ@sk@!hVM1tEa5(h}6?A$wX{_P1)qK z+;cb0_pkb7ZsRok-A&t%uiZQbJgD)2@a`MMfb@v?U7TKtDZ5&u}{ z;!l6t9@fW~fzcoiWEhT4(wb}yC~cLweht;0{;c2Odf*PF z#!ApILuEC`PV^SNHmclA1`EKVdxqIx(d`hDzGM*ttwk-Zs&bzxp-1_2_?uV29_L=uQcd15uZsS1W^vS3T3L8(=&#u(J@mc z8E&iAwwi$3wpK)ZVjTf9l~R{M7d7<_h-?xY^K8=4DFqfiwAYOwVE6_r3!)R&lHB!x z0`>;N709fHh1xTl-;^IA$mcNMD{^PjQwZU2SmGBTM&qrVxr;Xl9jsxkQP&=QN`F#nA|j_G`ww$TX(#{y literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 71b3cc4..54ce5dd 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -375,9 +375,12 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // dac data // let coin_sound = include_bytes!("../audio/coin.raw"); - let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - let button_sounds = [include_bytes!("../audio/sweep_dpcm_u4.raw")]; - // let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); + // let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); + + let coin_sound = include_bytes!("../audio/button_1.raw"); + let button_sound_1 = include_bytes!("../audio/button_1.raw"); + let button_sound_2 = include_bytes!("../audio/button_2.raw"); + let button_sound_3 = include_bytes!("../audio/button_3.raw"); let mut system_state = SystemState::Active; @@ -483,12 +486,19 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { println!("debounced button_input value: {}", value); if !value { - interfaces - .dac - .load_data(button_sounds[settings.button_sound_index]); + interfaces.dac.load_data(match settings.button_sound_index { + 0 => button_sound_1, + 1 => button_sound_2, + 2 => button_sound_3, + _ => button_sound_1, + }); + // interfaces + // .dac + // .load_data(button_sounds[settings.button_sound_index]); settings.button_sound_index += 1; - if settings.button_sound_index > button_sounds.len() - 1 { + if settings.button_sound_index > 2 { + // if settings.button_sound_index > button_sounds.len() - 1 { settings.button_sound_index = 0; } #[cfg(feature = "enable_print")] From 3ea7aac1f414fabfcec7704f9a987fda77b6f42a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 26 Oct 2025 18:56:52 -0600 Subject: [PATCH 092/148] add other audio --- ch32v-insert-coin/audio/button_3.raw | Bin 2133 -> 1413 bytes ch32v-insert-coin/audio/coin2.raw | Bin 0 -> 3871 bytes .../src/insert_coin/insert_coin.rs | 45 +++++++----------- ch32v-insert-coin/src/main.rs | 7 +-- ch32v-insert-coin/src/system.rs | 1 + 5 files changed, 21 insertions(+), 32 deletions(-) create mode 100644 ch32v-insert-coin/audio/coin2.raw diff --git a/ch32v-insert-coin/audio/button_3.raw b/ch32v-insert-coin/audio/button_3.raw index eb62b2dd1e0a6a0e78d8639ce93c155f8d332ecb..0477e753eb20c05749decf3a1665218f4d1b953e 100644 GIT binary patch literal 1413 zcma)(QEr1U5Ja&=B>KlnM1tD{i30?w+{fG?+_>|0O-V@AHZr!Eot>E_X64t~b=t;m zIJUf{eqdYjeDSSW^8GR`!hbsFOW>z7&qz~R zZry;92-{erfz4j7G2P3-r|iML`E;M7Q~0R+mnGj=n@z{UyE!jI z#~m57Uc_+depQaO+M&kX92yF%c|d%DdhZ1?SYxgKDdY}^9m+`~t0;0>oK|jXIQ4^(h*4Fo1;VS1RjEqYh?_#<6>tyYfn9e+_;Yni=_aNX3G*`W^^-22~Y@!BAdp zY2DpEM*5kl*q2xG^APv^WMNq?escfof&0&gIAhli)|e6(6F^4ykSMJcW5HrB2OdUS z(kBR(GQGF8vx}$RPOL`L=T=f%(Rc49J>?A$wX{_P1)qK z+;cb0_pkb7ZsRok-A&t%uiZQbJgD)2@a`MMfb@v?U7TKtDZ5&u}{ z;!l6t9@fW~fzcoiWEhT4(wb}yC~cLweht;0{;c2Odf*PF z#!ApILuEC`PV^SNHmclA1`EKVdxqIx(d`hDzGM*ttwk-Zs&bzxp-1_2_?uV29_L=uQcd15uZsS1W^vS3T3L8(=&#u(J@mc z8E&iAwwi$3wpK)ZVjTf9l~R{M7d7<_h-?xY^K8=4DFqfiwAYOwVE6_r3!)R&lHB!x z0`>;N709fHh1xTl-;^IA$mcNMD{^PjQwZU2SmGBTM&qrVxr;Xl9jsxkQP&=QN`F#nA|j_G`ww$TX(#{y diff --git a/ch32v-insert-coin/audio/coin2.raw b/ch32v-insert-coin/audio/coin2.raw new file mode 100644 index 0000000000000000000000000000000000000000..1caabb9a9d26122a2c50fdd4ccd50ecb1cbeabda GIT binary patch literal 3871 zcmXw6;cDYL6P3V%7`C7R2^>h?MFI=fumuZbaG?7xHq@|zdm)AdT*!TwEp%b;kHQ98 z$U?t&`OYYNn_w%_Xmn=g%xFqN6TVU=NkkZEGEuq0CMksEpF)}_$7q+`ICq=2zxygm z=dFzM`FtX+Kdt@4Ki_usbT8`kDUNn|ZnwjwJ__%);$doLQ`vA1VXj*-sc@)>%sl$_ zp7w7q&AeF`YB&|5c|3^^FYPLNOXQ-9!Q9t#)dtrYAEhb;{-l1ZNqTG6Rp}B@(K!H| zhbW~Eq)h85Xe|I7(4~t!5JYVuuxH;&(bfv4O_3-m*E*OeNDJW~ z*6OfI`?#dBA>A5MqR_|su57QBzoWhiud*{hORX_7`sVQF*Q?9QE0Ngq(_HPlm9|35 z-i{};a7+>ws8BFalB#T5kW51LurY+VIAN>i=tvcaCQQ?B+ODr((vP8uhn4VUbr@z@ ztqp}ATiM#<*Vl&?>AjHcX8nB^E9-+zizgf^q>rG3bQZb@uFMf&=MsR3B;Z+1USSAX z2?O~V3mIS!7AXF=SxV_keR$B_;j4c>m@j3S-aqX;?S74y$9O1YcU_I$WMgfA?0gdQ zDztTHlBaU%hvxpJ`)25+Q&+tmBlV32BP1=*D-%sk+pB*zO?4-qrt?}eE2cS_sVq#Gt46ff zad%fC2WYep=*!J+VSfU{Y(w<#f(Qby*xFM6A z8PTg%R|ql|1VCbgJ)$i*Avl4AVBI)^*+EW0En`ArM9vimK@gI_z$M|&1bo#FN`!Y3gnfifyc0?z3sP(p%w0Ve0{l;9I~;7n-67pGt~bCQRcU}@4Y35kXkOyU5G5q81} z013Ym94sbq9Qn?i4v8ZHW3%P#A-08)vsoOSJi*f#LM2XwESB=&JdsuZd1mqKIi6r& zV+N{OIxB=C=Bzdr>5C{$I>PO&Mh1wmJxG5H7z3DyFNXs&96HoA8w5%Ko5FfAmydyZ zhyg2%opG{HzpirRTE08+p9S@Ke|{=U%#!-zq-Pi%{FxOaQa-{C)@%n+Wt;YFH|r7WMbXba(C!{ zJ`I;u`t&qT+aA6Rn-R5^v_+BiEpF#2b%kzLrE&AhqJ+0j85dD9kiUo)XC~i91))e& zbYNn39-IY`%zspM6e&l|5ru;yc#5=QRZ(Lk7 zijWMDf~P@C5G|pyaiKsYK@Xgp=%O$mx0p(f{A3D%(|AWTf`!1RZzTXN-W z!6r^3P824If>!nf4?rDBR!rz zLh;mJKF!~E-aP4bwJGN?&?-ge$`z@rM@0soJazDVK%R4gMJ5rB6Pk%I{9YYR`6QTYGs$V7uz51jM|*gQN(ZC;b9DA-H5l% z;rXt%sU427-oM8Fa8{~=;=$@;WhX0>#$}Xh8?0KEKA>lebqq&iLTJofsi|~_s(7XO z0sSp5TeFY2c?&hZSNR^mjV@!2t9>lVlxT$_Dp}N~Gkinxg@(cnR{4gKpT$F%b#0B^ z#{z~x*3f<~B!Z^-(uy$X=wLVQWL!jTkcK)mM@bNuE|is;yI1BfF8X9T4`)==iQdlx zrR-joYNlL0TN3b zY~E^-)@a5f#t06q%aKKV!8tTA9A>^W@bf={;c!5afMJyD9QI@}PHbf3tu8RT&=MrL zOr=7*I7QsBaG0DO(xI#Ook3DGEbq!n@fny87TN^53zg{xD6q>K)HTb90Z5|N#ZXgW zb^n^;Nt|ar%Ly8e!c6ES{#_r>fh&y(d-SZjSJT|1#TYwTpoP*P$TTl?b1gsKc?$eW+gbY4vxD?p0H0SA?U3b2R7-`rB+l~q7DqKeKE zohL$_#h7tW2MC}IUSL2zJHJ3!d@$QSu8j@chQckXM-R#OFrUkNJ#HM%^jt!?QRO)E z_5BAo(-dH!i7CdD%+cEmL+ { +pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { pwm: core::cell::RefCell>, } @@ -43,17 +42,12 @@ impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { // } } - - - pub struct CoreConfig { pub tick_rate_hz: usize, } impl CoreConfig { pub fn new(tick_rate_hz: usize) -> Self { - Self { - tick_rate_hz, - } + Self { tick_rate_hz } } } @@ -70,25 +64,20 @@ pub struct InsertCoin<'a, T: GeneralInstance16bit> { 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 = 16000; - let dac_tick_per_service = config.tick_rate_hz/(dac_sample_rate_hz); + 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); @@ -105,28 +94,28 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { /// takes self reference and runs pub fn service(&mut self) { - - if self.is_active() { + if self.is_active() { self.dac.tick(); - if self.led0.need_service() { - self.pwm_core.write_amplitude(self.led0.channel, self.led0.amplitude); + 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.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); + self.pwm_core + .write_amplitude(self.dac.channel, self.dac.get_amplitude() as u8); } } - } // /// consumes self and runs @@ -134,7 +123,6 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // 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]; @@ -143,12 +131,11 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // 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; @@ -159,7 +146,7 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // 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; @@ -178,7 +165,7 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { // } pub fn is_active(&self) -> bool { - self.core.active + self.core.active } pub fn set_active(&mut self, active: bool) { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 54ce5dd..d59d44f 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -268,7 +268,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); - let sample_rate_hz = 16000; + let sample_rate_hz = 4000; + // let sample_rate_hz = 16000; let dac_tick_per_service = 5; let tick_rate_hz = sample_rate_hz * dac_tick_per_service; @@ -374,10 +375,10 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; // dac data - // let coin_sound = include_bytes!("../audio/coin.raw"); + // let coin_sound = include_bytes!("../audio/coin2.raw"); // let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - let coin_sound = include_bytes!("../audio/button_1.raw"); + let button_sound_1 = include_bytes!("../audio/button_1.raw"); let button_sound_2 = include_bytes!("../audio/button_2.raw"); let button_sound_3 = include_bytes!("../audio/button_3.raw"); diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs index 97b43e4..0ee18a6 100644 --- a/ch32v-insert-coin/src/system.rs +++ b/ch32v-insert-coin/src/system.rs @@ -48,6 +48,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { if (bits & (0x1 << button_pin)) != 0x0 { #[cfg(feature = "enable_print")] println!("main_btn irq!"); + input_flags.main_btn_flag = true; // CHECK PC6 // unsafe { // let mut val = 0; From 58579ae6c24fd5ea0097cbd05c7cce8e9fab10f2 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 21:10:36 -0600 Subject: [PATCH 093/148] first wavetable synth demo --- .gitmodules | 3 ++ ch32v-insert-coin/Cargo.lock | 5 +++ ch32v-insert-coin/Cargo.toml | 2 + ch32v-insert-coin/ext/wavetable-synth | 1 + ch32v-insert-coin/src/main.rs | 57 +++++++++++++++++---------- ch32v-insert-coin/src/synthesizer.rs | 36 +++++++++++++++++ 6 files changed, 84 insertions(+), 20 deletions(-) create mode 160000 ch32v-insert-coin/ext/wavetable-synth create mode 100644 ch32v-insert-coin/src/synthesizer.rs diff --git a/.gitmodules b/.gitmodules index fb915f4..e953eec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "ch32v-insert-coin/ext/adpcm-pwm-dac"] path = ch32v-insert-coin/ext/adpcm-pwm-dac url = ssh://git@git.glyphs.tech:222/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/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock index dc8468c..7fd7006 100644 --- a/ch32v-insert-coin/Cargo.lock +++ b/ch32v-insert-coin/Cargo.lock @@ -78,6 +78,7 @@ dependencies = [ "panic-halt", "qingke 0.5.0", "qingke-rt", + "wavetable-synth", ] [[package]] @@ -626,3 +627,7 @@ 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 index facb4f1..e09776c 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -31,6 +31,8 @@ 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] diff --git a/ch32v-insert-coin/ext/wavetable-synth b/ch32v-insert-coin/ext/wavetable-synth new file mode 160000 index 0000000..9417e6e --- /dev/null +++ b/ch32v-insert-coin/ext/wavetable-synth @@ -0,0 +1 @@ +Subproject commit 9417e6edbf590f3fe777e39ad76efc21a024e01d diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index d59d44f..ac9bac5 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -9,6 +9,10 @@ mod insert_coin; // system stuff mod system; +// synthesizer :3 +mod synthesizer; +use synthesizer::AppSynthesizers; + use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; @@ -204,30 +208,40 @@ fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { use hal::gpio::{Level, Output}; let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); - // button pin setup - let button_pin = p.PD6; - unsafe { system::init_gpio_irq(button_pin.pin(), button_pin.port(), false, true) }; - let mut button_input = Input::new(button_pin, Pull::Up); - delay.delay_ms(1000); - unsafe { system::enter_standby() }; - delay.delay_ms(1000); - riscv::asm::wfi(); + let tick_rate_hz = 100000; // 100khz + let tick_interval_us = 1000000 / tick_rate_hz; - // get the clocks re-initialized - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { - hal::rcc::init(config.rcc); - } + // 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, + None, + None, + None, + Some(dac_pin), + Hertz::khz(200), + CountingMode::default(), + ); + + let pwm_core = SimplePwmCore::new(pwm); + + // === synthesizer setup === + let mut synthesizer = AppSynthesizers::new(tick_rate_hz, pwm_core); + synthesizer.square.set_freq(440); #[cfg(feature = "enable_print")] println!("begin loop"); + led0_pin.toggle(); + loop { - led0_pin.toggle(); - delay.delay_ms(100); + synthesizer.service(); + delay.delay_us(tick_interval_us as u32); } } @@ -275,6 +289,11 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let core_config = CoreConfig::new(tick_rate_hz); + let pwm_core = SimplePwmCore::new(pwm); + + // === synthesizer setup === + // let synthesizer = AppSynthesizers::new(core_config.tick_rate_hz, pwm_core); + // === input setup === // adc @@ -327,8 +346,6 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let mut light_ctrl_btn_input = DebouncedGPIO::new(light_ctrl_btn_pin.degrade(), core_config.tick_rate_hz, 100); - let pwm_core = SimplePwmCore::new(pwm); - let mut interfaces = InsertCoin::new(core_config, pwm_core); interfaces.set_active(true); // insert_coin.init(); @@ -631,8 +648,8 @@ fn main() -> ! { let mut delay = Delay; delay.delay_ms(1000); - // debug_main(p, delay); - app_main(p, delay); + debug_main(p, delay); + // app_main(p, delay); } #[panic_handler] diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs new file mode 100644 index 0000000..ab50fe6 --- /dev/null +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -0,0 +1,36 @@ +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; 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 AppSynthesizers<'a, T: GeneralInstance16bit> { + pub square: SimpleWavetableSynthesizer>, + output: SimplePwmCore<'a, T>, +} + +impl<'a, T: GeneralInstance16bit> AppSynthesizers<'a, T> { + pub fn new(clock_freq_hz: usize, output: SimplePwmCore<'a, T>) -> Self { + let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); + let square = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); + + Self { square, output } + } + + pub fn service(&mut self) { + self.square.tick(); + if self.square.has_new_output() { + let out = self.square.get_output(); + // println!("OUTPUT: {out}"); + self.output.write_amplitude( + ch32_hal::timer::Channel::Ch4, + out / 2, + // (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8, + ); + } + } +} From 0836fc58df769776ea140deeb9235506604f6c4e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 28 Oct 2025 15:34:36 -0600 Subject: [PATCH 094/148] use systick event system to drive synthesis --- ch32v-insert-coin/audio/sequences | 18 +++ ch32v-insert-coin/ext/wavetable-synth | 2 +- ch32v-insert-coin/src/main.rs | 210 +++++++++++++++++++++++--- ch32v-insert-coin/src/synthesizer.rs | 30 +++- 4 files changed, 229 insertions(+), 31 deletions(-) create mode 100644 ch32v-insert-coin/audio/sequences 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/ext/wavetable-synth b/ch32v-insert-coin/ext/wavetable-synth index 9417e6e..e6ca9c7 160000 --- a/ch32v-insert-coin/ext/wavetable-synth +++ b/ch32v-insert-coin/ext/wavetable-synth @@ -1 +1 @@ -Subproject commit 9417e6edbf590f3fe777e39ad76efc21a024e01d +Subproject commit e6ca9c7ed8b9af193333be793983df5e48cb961a diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ac9bac5..ed5e986 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -195,6 +195,107 @@ impl Handler for Test { } } +static mut LED: Option> = None; +static mut SYSTICK_COUNT: u32 = 0; +static mut SYNTHESIZERS: Option> = None; +static mut TOGGLE_COUNT: u32 = 0; +static mut TICK_FLAG: bool = false; +static mut SYNTH_TICK_FLAG: bool = false; +static mut NOTE_TICK_FLAG: bool = false; +static mut FREQ: [usize; 2] = [220, 440]; +static mut INDEX: usize = 0; + +fn flag() -> bool { + unsafe { core::ptr::read_volatile(&raw const TICK_FLAG as *const bool) } +} + +fn clear_flag() { + unsafe { + TICK_FLAG = false; + } +} + +fn synth_flag() -> bool { + unsafe { core::ptr::read_volatile(&raw const SYNTH_TICK_FLAG as *const bool) } +} + +fn clear_synth_flag() { + unsafe { + SYNTH_TICK_FLAG = false; + } +} + +fn note_flag() -> bool { + unsafe { core::ptr::read_volatile(&raw const NOTE_TICK_FLAG as *const bool) } +} + +fn clear_note_flag() { + unsafe { + NOTE_TICK_FLAG = false; + } +} + +#[qingke_rt::interrupt(core)] +fn SysTick() { + let r = &ch32_hal::pac::SYSTICK; + + // Clear interrupt flag + r.sr().write(|w| w.set_cntif(false)); + + unsafe { + // if let Some(ref mut synthesizers) = SYNTHESIZERS { + // println!("tick"); + // synthesizers.tick(); + // } + SYNTH_TICK_FLAG = true; + + if SYSTICK_COUNT % 5000 == 0 { + NOTE_TICK_FLAG = true; + } + + if SYSTICK_COUNT % 50000 == 0 { + TICK_FLAG = true; + // if let Some(ref mut led) = LED { + // let toggle = TOGGLE_COUNT; + // // println!("TOGGLE {toggle}"); + // TOGGLE_COUNT += 1; + // led.toggle(); + // } + // println!("HERE"); + } + SYSTICK_COUNT = SYSTICK_COUNT.wrapping_add(1); + } +} + +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 + }); +} + bind_interrupts!(struct Irqs { EXTI7_0 => Test; }); @@ -203,15 +304,20 @@ bind_interrupts!(struct Irqs { use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { +fn debug_main(mut p: hal::Peripherals) -> ! { // LED0 output setup use hal::gpio::{Level, Output}; - let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); + let mut led = Output::new(p.PC3, Level::High, Default::default()); - delay.delay_ms(1000); + // unsafe { + // LED = Some(led); + // } - let tick_rate_hz = 100000; // 100khz - let tick_interval_us = 1000000 / tick_rate_hz; + println!("pre"); + riscv::asm::delay(20_000_000); + println!("post2"); + + let tick_rate_hz = 100000; // 50khz // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); @@ -224,24 +330,76 @@ fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { None, None, Some(dac_pin), - Hertz::khz(200), + Hertz::khz(500), CountingMode::default(), ); let pwm_core = SimplePwmCore::new(pwm); + unsafe { + use qingke_rt::CoreInterrupt; + + qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + } // === synthesizer setup === - let mut synthesizer = AppSynthesizers::new(tick_rate_hz, pwm_core); - synthesizer.square.set_freq(440); + // unsafe { + // let mut synth = AppSynthesizers::new(tick_rate_hz, pwm_core); + // synth.square.set_freq(440); + // SYNTHESIZERS = Some(synth); + // } + let mut synth = AppSynthesizers::new(tick_rate_hz, pwm_core); + synth.square.set_freq(440); #[cfg(feature = "enable_print")] println!("begin loop"); - led0_pin.toggle(); + systick_init(tick_rate_hz); + + unsafe { + use qingke::interrupt::Priority; + use qingke_rt::CoreInterrupt; + qingke::pfic::set_priority(CoreInterrupt::SysTick as u8, Priority::P15 as u8); + qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); + } + + let mut index = 0; + let freqs = [1567, 1396, 1318, 1174, 1046, 987, 880, 783, 698]; + // let freqs = [100, 200, 300, 400, 500, 600, 700, 800, 900]; + // let freqs = [ + // 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, + // ]; loop { - synthesizer.service(); - delay.delay_us(tick_interval_us as u32); + if flag() { + clear_flag(); + led.toggle(); + } + if synth_flag() { + clear_synth_flag(); + synth.tick(); + } + if note_flag() { + clear_note_flag(); + synth.square.set_freq(freqs[index]); + // println!("{}", { freqs[index] }); + // println!("{}", freqs[index]); + index += 1; + if index > freqs.len() - 1 { + index = 0; + } + } + // if (unsafe { TICK_FLAG == true }) { + // println!("TICK!"); + // led.toggle(); + // unsafe { + // TICK_FLAG = false; + // } + // } } } @@ -394,11 +552,11 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // dac data // let coin_sound = include_bytes!("../audio/coin2.raw"); // let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - let coin_sound = include_bytes!("../audio/button_1.raw"); + // let coin_sound = include_bytes!("../audio/button_1.raw"); - let button_sound_1 = include_bytes!("../audio/button_1.raw"); - let button_sound_2 = include_bytes!("../audio/button_2.raw"); - let button_sound_3 = include_bytes!("../audio/button_3.raw"); + // let button_sound_1 = include_bytes!("../audio/button_1.raw"); + // let button_sound_2 = include_bytes!("../audio/button_2.raw"); + // let button_sound_3 = include_bytes!("../audio/button_3.raw"); let mut system_state = SystemState::Active; @@ -464,7 +622,7 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // todo: enter active tt0.enable(true); tt1.enable(true); - interfaces.dac.load_data(coin_sound); + // interfaces.dac.load_data(coin_sound); } if INPUT_FLAGS.main_btn_flag { #[cfg(feature = "enable_print")] @@ -504,12 +662,12 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { println!("debounced button_input value: {}", value); if !value { - interfaces.dac.load_data(match settings.button_sound_index { - 0 => button_sound_1, - 1 => button_sound_2, - 2 => button_sound_3, - _ => button_sound_1, - }); + // interfaces.dac.load_data(match settings.button_sound_index { + // 0 => button_sound_1, + // 1 => button_sound_2, + // 2 => button_sound_3, + // _ => button_sound_1, + // }); // interfaces // .dac // .load_data(button_sounds[settings.button_sound_index]); @@ -645,14 +803,16 @@ fn main() -> ! { let mut p = hal::init(config); // delay to let the debugger attach - let mut delay = Delay; - delay.delay_ms(1000); + println!("pre"); + riscv::asm::delay(20_000_000); + println!("post"); + debug_main(p); - debug_main(p, delay); // app_main(p, delay); } #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { + // println!("panic: {info:?}"); loop {} } diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs index ab50fe6..ad265c1 100644 --- a/ch32v-insert-coin/src/synthesizer.rs +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -3,10 +3,11 @@ use ch32_hal::println; use ch32_hal::timer::GeneralInstance16bit; use wavetable_synth::{synthesizer::SimpleWavetableSynthesizer, wavetable::SimpleWavetable}; -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, -]; +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 AppSynthesizers<'a, T: GeneralInstance16bit> { pub square: SimpleWavetableSynthesizer>, @@ -21,11 +22,30 @@ impl<'a, T: GeneralInstance16bit> AppSynthesizers<'a, T> { Self { square, output } } - pub fn service(&mut self) { + pub fn tick(&mut self) { self.square.tick(); if self.square.has_new_output() { let out = self.square.get_output(); // println!("OUTPUT: {out}"); + + // println!("new out"); + self.output.write_amplitude( + ch32_hal::timer::Channel::Ch4, + out / 2, + // (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8, + ); + } + + // println!("{}{}", self.square.counter, self.square.has_new_output()); + } + + pub fn service(&mut self) { + // println!("HERE"); + if self.square.has_new_output() { + let out = self.square.get_output(); + // println!("OUTPUT: {out}"); + + // println!("new out"); self.output.write_amplitude( ch32_hal::timer::Channel::Ch4, out / 2, From 008bf334a4645248cc8ec63d9f3031a4e0f7291f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 2 Nov 2025 09:14:59 -0700 Subject: [PATCH 095/148] move appdata out to new module --- ch32v-insert-coin/src/app.rs | 66 +++++++++++++++++++++++++++++++++++ ch32v-insert-coin/src/main.rs | 52 ++++++--------------------- 2 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 ch32v-insert-coin/src/app.rs diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs new file mode 100644 index 0000000..81caeee --- /dev/null +++ b/ch32v-insert-coin/src/app.rs @@ -0,0 +1,66 @@ +#[derive(Default)] +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, +} + +mod settings { + pub enum Level { + Off, + Low, + 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, + }; + } + } + + 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 use settings::Settings; + +pub struct App { + state: State, + pub settings: Settings, + // TODO: make this the "sound module" or whatever. + // synthesizers: AppSynthesizers, +} + +impl Default for App { + fn default() -> Self { + Self { + state: State::default(), + settings: Settings::default(), + } + } +} diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ed5e986..f044b57 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -13,6 +13,9 @@ mod system; mod synthesizer; use synthesizer::AppSynthesizers; +mod app; +use app::App; + use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; @@ -28,42 +31,6 @@ use hal::println; use qingke::riscv; -enum Level { - Off, - Low, - 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, - }; - } -} - -struct Settings { - brightness: Level, - volume: Level, - button_sound_index: usize, -} - -impl Default for Settings { - fn default() -> Self { - Self { - brightness: Level::Medium, - volume: Level::Medium, - button_sound_index: 0, - } - } -} - struct DebouncedGPIO<'a> { input: Input<'a>, // value of the GPIO @@ -508,7 +475,8 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { interfaces.set_active(true); // insert_coin.init(); - let mut settings = Settings::default(); + // let mut settings = Se::default(); + let mut app = App::default(); let mut led0_index = 0; let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; @@ -672,10 +640,10 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // .dac // .load_data(button_sounds[settings.button_sound_index]); - settings.button_sound_index += 1; - if settings.button_sound_index > 2 { + app.settings.button_sound_index += 1; + if app.settings.button_sound_index > 2 { // if settings.button_sound_index > button_sounds.len() - 1 { - settings.button_sound_index = 0; + app.settings.button_sound_index = 0; } #[cfg(feature = "enable_print")] println!("reset hold timers + enable"); @@ -691,13 +659,13 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { if volume_btn_input.ready() { #[cfg(feature = "enable_print")] println!("volume btn value: {}", volume_btn_input.value()); - settings.volume.next(); + app.settings.volume.next(); volume_btn_input.reset(); } if light_ctrl_btn_input.ready() { #[cfg(feature = "enable_print")] println!("light_ctrl_btn value: {}", light_ctrl_btn_input.value()); - settings.brightness.next(); + app.settings.brightness.next(); light_ctrl_btn_input.reset(); } From 791d5db4c82af79021590d03a2cae209b3c45037 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 2 Nov 2025 10:59:10 -0700 Subject: [PATCH 096/148] convert app to tick servicing --- ch32v-insert-coin/src/app.rs | 170 ++++++++- ch32v-insert-coin/src/main.rs | 542 ++++++++++++++------------- ch32v-insert-coin/src/synthesizer.rs | 17 +- 3 files changed, 460 insertions(+), 269 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 81caeee..5af4510 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -1,4 +1,4 @@ -#[derive(Default)] +#[derive(Default, Clone, Copy)] pub enum State { // system is asleep, waiting for wake from coin insertion DeepSleep, @@ -10,9 +10,12 @@ pub enum State { } mod settings { + + #[derive(Default, Clone, Copy)] pub enum Level { Off, Low, + #[default] Medium, High, Maximum, @@ -30,6 +33,7 @@ mod settings { } } + #[derive(Clone, Copy)] pub struct Settings { pub brightness: Level, pub volume: Level, @@ -47,20 +51,180 @@ mod settings { } } +use crate::insert_coin::{TickService, TickServiceData, TickTimerService}; 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 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, + 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, + ), + 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.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.led2_timer.need_service() + } + pub fn init(&mut self) { + self.led0_timer.reset(); + self.led0_timer.enable(true); + } +} + +pub struct Config { + pub system_tick_rate_hz: usize, + pub timers: TimerConfig, +} + pub struct App { state: State, pub settings: Settings, + timers: Timers, // TODO: make this the "sound module" or whatever. // synthesizers: AppSynthesizers, } -impl Default for App { - fn default() -> Self { +impl App { + pub fn new(config: Config) -> Self { Self { state: State::default(), settings: Settings::default(), + timers: Timers::new(config.timers, config.system_tick_rate_hz), + } + } + + pub fn init(&mut self) { + // self.timers.init(); + 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.led2_timer.reset(); + self.timers.led2_timer.enable(true); + } + + 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(); + } + + pub fn service(&mut self) { + if self.timers.sp_timer.need_service() { + self.timers.batt_adc_timer.service(); + #[cfg(feature = "enable_print")] + println!("sp service"); + } + if self.timers.lp_timer.need_service() { + self.timers.batt_adc_timer.service(); + #[cfg(feature = "enable_print")] + println!("lp service"); + } + if self.timers.batt_adc_timer.need_service() { + self.timers.batt_adc_timer.service(); + #[cfg(feature = "enable_print")] + println!("batt adc service"); + } + 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() { + self.timers.led0_timer.service(); + #[cfg(feature = "enable_print")] + println!("led0 service"); + } + if self.timers.led1_timer.need_service() { + self.timers.led1_timer.service(); + #[cfg(feature = "enable_print")] + println!("led1 service"); + } + if self.timers.led2_timer.need_service() { + self.timers.led2_timer.service(); + #[cfg(feature = "enable_print")] + println!("led2 service"); } } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index f044b57..f72658c 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -14,7 +14,7 @@ mod synthesizer; use synthesizer::AppSynthesizers; mod app; -use app::App; +use app::{App, Config, State, TimerConfig}; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; @@ -108,14 +108,36 @@ pub enum SystemState { Active, } +#[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: bool, main_btn_flag: bool, volume_btn_flag: bool, light_ctrl_btn_flag: bool, + systick_flag: Flag, + // synth_tick_flag: bool, } +// impl InputFlags { +// pub fn sense_coin_flag(&self) -> bool {} +// } + impl Default for InputFlags { fn default() -> Self { Self { @@ -123,6 +145,7 @@ impl Default for InputFlags { main_btn_flag: false, volume_btn_flag: false, light_ctrl_btn_flag: false, + systick_flag: Flag { value: false }, } } } @@ -147,6 +170,7 @@ static mut INPUT_FLAGS: InputFlags = InputFlags { main_btn_flag: false, volume_btn_flag: false, light_ctrl_btn_flag: false, + systick_flag: Flag { value: false }, }; struct Test {} @@ -216,20 +240,24 @@ fn SysTick() { // } SYNTH_TICK_FLAG = true; - if SYSTICK_COUNT % 5000 == 0 { - NOTE_TICK_FLAG = true; - } + // safe because single-threaded + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.set(); - if SYSTICK_COUNT % 50000 == 0 { - TICK_FLAG = true; - // if let Some(ref mut led) = LED { - // let toggle = TOGGLE_COUNT; - // // println!("TOGGLE {toggle}"); - // TOGGLE_COUNT += 1; - // led.toggle(); - // } - // println!("HERE"); - } + // if SYSTICK_COUNT % 5000 == 0 { + // NOTE_TICK_FLAG = true; + // } + + // if SYSTICK_COUNT % 50000 == 0 { + // TICK_FLAG = true; + // // if let Some(ref mut led) = LED { + // // let toggle = TOGGLE_COUNT; + // // // println!("TOGGLE {toggle}"); + // // TOGGLE_COUNT += 1; + // // led.toggle(); + // // } + // // println!("HERE"); + // } SYSTICK_COUNT = SYSTICK_COUNT.wrapping_add(1); } } @@ -370,7 +398,7 @@ fn debug_main(mut p: hal::Peripherals) -> ! { } } -fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { +fn app_main(mut p: hal::Peripherals) -> ! { // === output setup === // LED0 output setup @@ -430,7 +458,6 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let mut usb_detect_pin = p.PD5; // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); - delay.delay_ms(1000); let adc_cal = adc.calibrate(); #[cfg(feature = "enable_print")] @@ -473,10 +500,6 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let mut interfaces = InsertCoin::new(core_config, pwm_core); interfaces.set_active(true); - // insert_coin.init(); - - // let mut settings = Se::default(); - let mut app = App::default(); let mut led0_index = 0; let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; @@ -484,39 +507,32 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { let mut led1_index = 0; let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; - // tick timer 0 - let tt0_fire_rate_hz = 9; - let tt0_tick_per_service = interfaces.config.tick_rate_hz / (tt0_fire_rate_hz * 2); - let tt0_service_data = TickServiceData::new(tt0_tick_per_service); - let mut tt0 = TickTimerService::new(tt0_service_data, true); - - // tick timer 1 - let tt1_fire_rate_hz = 3; - let tt1_tick_per_service = interfaces.config.tick_rate_hz / (tt1_fire_rate_hz * 2); - let tt1_service_data = TickServiceData::new(tt1_tick_per_service); - let mut tt1 = TickTimerService::new(tt1_service_data, true); - - // short press timer - let sp_ticks = 2 * interfaces.config.tick_rate_hz; - let sp_timer_data = TickServiceData::new(sp_ticks); - let mut sp_timer = TickTimerService::new(sp_timer_data, false); - sp_timer.reset(); - - // long press timer - let lp_ticks = 5 * interfaces.config.tick_rate_hz; - let lp_timer_data = TickServiceData::new(lp_ticks); - let mut lp_timer = TickTimerService::new(lp_timer_data, false); - lp_timer.reset(); - - // battery read timer - let adc1_ticks = 5 * interfaces.config.tick_rate_hz; - let adc1_timer_data = TickServiceData::new(adc1_ticks); - let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); - adc1_timer.reset(); - adc1_timer.enable(true); + // // battery read timer + // let adc1_ticks = 5 * interfaces.config.tick_rate_hz; + // let adc1_timer_data = TickServiceData::new(adc1_ticks); + // let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); + // adc1_timer.reset(); + // adc1_timer.enable(true); let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; + let timer_config = TimerConfig { + sp_timer_ms: 2000, + lp_timer_ms: 5000, + batt_adc_timer_ms: 10000, + usb_adc_timer_ms: 10000, + led0_timer_ms: 1000, + led1_timer_ms: 300, + led2_timer_ms: 1100, + }; + + let app_config = Config { + system_tick_rate_hz: tick_rate_hz, + timers: timer_config, + }; + + let mut app = App::new(app_config); + // dac data // let coin_sound = include_bytes!("../audio/coin2.raw"); // let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); @@ -526,20 +542,25 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { // let button_sound_2 = include_bytes!("../audio/button_2.raw"); // let button_sound_3 = include_bytes!("../audio/button_3.raw"); - let mut system_state = SystemState::Active; - interfaces.led0.set_amplitude(0); interfaces.led1.set_amplitude(0); interfaces.service(); + // init systick + systick_init(tick_rate_hz); + + // set up interrupts unsafe { use hal::pac::Interrupt; - // use qingke_rt::CoreInterrupt; + use qingke::interrupt::Priority; + use qingke_rt::CoreInterrupt; - // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); 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); + qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } // MAIN APPLICATION @@ -555,210 +576,227 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { 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(); loop { - // 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_prev = volume_btn_curr; - unsafe { - INPUT_FLAGS.volume_btn_flag = true; - } - } - } + // system servicing - 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_prev = light_ctrl_btn_curr; - unsafe { - INPUT_FLAGS.light_ctrl_btn_flag = true; - } - } - } - { - // system input servicing + // systick tick + if unsafe { + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.active() + } { unsafe { - if INPUT_FLAGS.sense_coin_flag { - #[cfg(feature = "enable_print")] - println!("coin flag active"); - INPUT_FLAGS.sense_coin_flag = false; - sense_coin_input.begin(); - // enter the active state - system_state = SystemState::Active; - - // todo: enter active - tt0.enable(true); - tt1.enable(true); - // interfaces.dac.load_data(coin_sound); - } - if INPUT_FLAGS.main_btn_flag { - #[cfg(feature = "enable_print")] - println!("button flag active"); - INPUT_FLAGS.main_btn_flag = false; - main_btn_input.begin(); - } - if INPUT_FLAGS.volume_btn_flag { - #[cfg(feature = "enable_print")] - println!("volume btn triggered"); - INPUT_FLAGS.volume_btn_flag = false; - volume_btn_input.begin(); - } - if INPUT_FLAGS.light_ctrl_btn_flag { - #[cfg(feature = "enable_print")] - println!("light ctrl btn triggered!"); - INPUT_FLAGS.light_ctrl_btn_flag = false; - light_ctrl_btn_input.begin(); - } + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.clear(); } + // #[cfg(feature = "enable_print")] + // println!("tick!"); - // debouncer - sense_coin_input.service(); - main_btn_input.service(); - volume_btn_input.service(); - light_ctrl_btn_input.service(); + // app tick + app.tick(); - if sense_coin_input.ready() { - #[cfg(feature = "enable_print")] - println!("debounced coin_input value: {}", sense_coin_input.value()); - sense_coin_input.reset(); - } - if main_btn_input.ready() { - let value = main_btn_input.value(); - main_btn_input.reset(); - #[cfg(feature = "enable_print")] - println!("debounced button_input value: {}", value); - - if !value { - // interfaces.dac.load_data(match settings.button_sound_index { - // 0 => button_sound_1, - // 1 => button_sound_2, - // 2 => button_sound_3, - // _ => button_sound_1, - // }); - // interfaces - // .dac - // .load_data(button_sounds[settings.button_sound_index]); - - app.settings.button_sound_index += 1; - if app.settings.button_sound_index > 2 { - // if settings.button_sound_index > button_sounds.len() - 1 { - app.settings.button_sound_index = 0; - } - #[cfg(feature = "enable_print")] - println!("reset hold timers + enable"); - sp_timer.reset(); - sp_timer.enable(true); - lp_timer.reset(); - lp_timer.enable(true); - } else { - sp_timer.reset(); - lp_timer.reset(); - } - } - if volume_btn_input.ready() { - #[cfg(feature = "enable_print")] - println!("volume btn value: {}", volume_btn_input.value()); - app.settings.volume.next(); - volume_btn_input.reset(); - } - if light_ctrl_btn_input.ready() { - #[cfg(feature = "enable_print")] - println!("light_ctrl_btn value: {}", light_ctrl_btn_input.value()); - app.settings.brightness.next(); - light_ctrl_btn_input.reset(); - } - - // timers - sp_timer.tick(); - lp_timer.tick(); - adc1_timer.tick(); - - if sp_timer.need_service() { - #[cfg(feature = "enable_print")] - println!("sp detect!"); - sp_timer.reset(); - - // todo enter idle - system_state = SystemState::Idle; - // TODO: fix polarity - interfaces.led0.set_amplitude(10); - interfaces.led1.set_amplitude(10); - interfaces.service(); - } - - if lp_timer.need_service() { - #[cfg(feature = "enable_print")] - println!("lp detect!"); - lp_timer.reset(); - - // todo enter deepsleep - system_state = SystemState::DeepSleep; - // TODO: fix polarity - interfaces.led0.set_amplitude(0); - interfaces.led1.set_amplitude(0); - interfaces.service(); - } - - if adc1_timer.need_service() { - let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); - let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); - #[cfg(feature = "enable_print")] - println!("ADC value: {}", val); - - adc1_timer.reset(); - adc1_timer.enable(true); - } + // interfaces } - match system_state { - SystemState::DeepSleep => { - // TODO: make this REALLY deep sleep - unsafe { system::enter_standby() }; - loop { - riscv::asm::wfi(); - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { - hal::rcc::init(config.rcc); - } - unsafe { - if INPUT_FLAGS.sense_coin_flag { - system_state = SystemState::Active; - break; - } - }; - } - } - SystemState::Idle => {} - SystemState::Active => { - tt0.tick(); - tt1.tick(); - - if tt0.need_service() { - interfaces.led0.set_amplitude(led0_dcs[led0_index]); - led0_index += 1; - if led0_index > led0_dcs.len() - 1 { - led0_index = 0; - } - tt0.service(); - } - - if tt1.need_service() { - interfaces.led1.set_amplitude(led1_dcs[led1_index]); - led1_index += 1; - if led1_index > led1_dcs.len() - 1 { - led1_index = 0; - } - tt1.service() - } - - interfaces.service(); - } - } - - delay.delay_us(tick_interval_us as u32); + app.service(); + interfaces.service(); } + + // // 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_prev = volume_btn_curr; + // unsafe { + // INPUT_FLAGS.volume_btn_flag = true; + // } + // } + // } + + // 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_prev = light_ctrl_btn_curr; + // unsafe { + // INPUT_FLAGS.light_ctrl_btn_flag = true; + // } + // } + // } + // { + // // system input servicing + // unsafe { + // if INPUT_FLAGS.sense_coin_flag { + // #[cfg(feature = "enable_print")] + // println!("coin flag active"); + // INPUT_FLAGS.sense_coin_flag = false; + // sense_coin_input.begin(); + // // enter the active state + // app.set_state(State::Active); + + // // todo: enter active + // // tt0.enable(true); + // // tt1.enable(true); + // // interfaces.dac.load_data(coin_sound); + // } + // if INPUT_FLAGS.main_btn_flag { + // #[cfg(feature = "enable_print")] + // println!("button flag active"); + // INPUT_FLAGS.main_btn_flag = false; + // main_btn_input.begin(); + // } + // if INPUT_FLAGS.volume_btn_flag { + // #[cfg(feature = "enable_print")] + // println!("volume btn triggered"); + // INPUT_FLAGS.volume_btn_flag = false; + // volume_btn_input.begin(); + // } + // if INPUT_FLAGS.light_ctrl_btn_flag { + // #[cfg(feature = "enable_print")] + // println!("light ctrl btn triggered!"); + // INPUT_FLAGS.light_ctrl_btn_flag = false; + // light_ctrl_btn_input.begin(); + // } + // } + + // // debouncer + // sense_coin_input.service(); + // main_btn_input.service(); + // volume_btn_input.service(); + // light_ctrl_btn_input.service(); + + // if sense_coin_input.ready() { + // #[cfg(feature = "enable_print")] + // println!("debounced coin_input value: {}", sense_coin_input.value()); + // sense_coin_input.reset(); + // } + // if main_btn_input.ready() { + // let value = main_btn_input.value(); + // main_btn_input.reset(); + // #[cfg(feature = "enable_print")] + // println!("debounced button_input value: {}", value); + + // if !value { + // // interfaces.dac.load_data(match settings.button_sound_index { + // // 0 => button_sound_1, + // // 1 => button_sound_2, + // // 2 => button_sound_3, + // // _ => button_sound_1, + // // }); + // // interfaces + // // .dac + // // .load_data(button_sounds[settings.button_sound_index]); + + // app.settings.button_sound_index += 1; + // if app.settings.button_sound_index > 2 { + // // if settings.button_sound_index > button_sounds.len() - 1 { + // app.settings.button_sound_index = 0; + // } + // #[cfg(feature = "enable_print")] + // println!("TODO reset hold timers + enable"); + // // sp_timer.reset(); + // // sp_timer.enable(true); + // // lp_timer.reset(); + // // lp_timer.enable(true); + // } else { + // // sp_timer.reset(); + // // lp_timer.reset(); + // } + // } + // if volume_btn_input.ready() { + // #[cfg(feature = "enable_print")] + // println!("volume btn value: {}", volume_btn_input.value()); + // app.settings.volume.next(); + // volume_btn_input.reset(); + // } + // if light_ctrl_btn_input.ready() { + // #[cfg(feature = "enable_print")] + // println!("light_ctrl_btn value: {}", light_ctrl_btn_input.value()); + // app.settings.brightness.next(); + // light_ctrl_btn_input.reset(); + // } + + // // if sp_timer.need_service() { + // // #[cfg(feature = "enable_print")] + // // println!("sp detect!"); + // // sp_timer.reset(); + + // // // todo enter idle + // // app.set_state(State::Idle); + // // // TODO: fix polarity + // // interfaces.led0.set_amplitude(10); + // // interfaces.led1.set_amplitude(10); + // // interfaces.service(); + // // } + + // // if lp_timer.need_service() { + // // #[cfg(feature = "enable_print")] + // // println!("lp detect!"); + // // lp_timer.reset(); + + // // // todo enter deepsleep + // // app.set_state(State::DeepSleep); + // // // TODO: fix polarity + // // interfaces.led0.set_amplitude(0); + // // interfaces.led1.set_amplitude(0); + // // interfaces.service(); + // // } + + // // if adc1_timer.need_service() { + // // let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); + // // let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); + // // #[cfg(feature = "enable_print")] + // // println!("ADC value: {}", val); + + // // adc1_timer.reset(); + // // adc1_timer.enable(true); + // // } + // } + + // match app.state() { + // State::DeepSleep => { + // // TODO: make this REALLY deep sleep + // unsafe { system::enter_standby() }; + // loop { + // riscv::asm::wfi(); + // let mut config = hal::Config::default(); + // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + // unsafe { + // hal::rcc::init(config.rcc); + // } + // unsafe { + // if INPUT_FLAGS.sense_coin_flag { + // app.set_state(State::Active); + // break; + // } + // }; + // } + // } + // State::Idle => {} + // State::Active => { + // // tt0.tick(); + // // tt1.tick(); + + // // if tt0.need_service() { + // // interfaces.led0.set_amplitude(led0_dcs[led0_index]); + // // led0_index += 1; + // // if led0_index > led0_dcs.len() - 1 { + // // led0_index = 0; + // // } + // // tt0.service(); + // // } + + // // if tt1.need_service() { + // // interfaces.led1.set_amplitude(led1_dcs[led1_index]); + // // led1_index += 1; + // // if led1_index > led1_dcs.len() - 1 { + // // led1_index = 0; + // // } + // // tt1.service() + // // } + // } + // } + // } } #[qingke_rt::entry] @@ -774,9 +812,9 @@ fn main() -> ! { println!("pre"); riscv::asm::delay(20_000_000); println!("post"); - debug_main(p); + // debug_main(p); - // app_main(p, delay); + app_main(p); } #[panic_handler] diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs index ad265c1..a85d978 100644 --- a/ch32v-insert-coin/src/synthesizer.rs +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -26,31 +26,20 @@ impl<'a, T: GeneralInstance16bit> AppSynthesizers<'a, T> { self.square.tick(); if self.square.has_new_output() { let out = self.square.get_output(); - // println!("OUTPUT: {out}"); - - // println!("new out"); self.output.write_amplitude( ch32_hal::timer::Channel::Ch4, + // TODO: set level here. or maybe use dac? out / 2, - // (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8, ); } - - // println!("{}{}", self.square.counter, self.square.has_new_output()); } pub fn service(&mut self) { // println!("HERE"); if self.square.has_new_output() { let out = self.square.get_output(); - // println!("OUTPUT: {out}"); - - // println!("new out"); - self.output.write_amplitude( - ch32_hal::timer::Channel::Ch4, - out / 2, - // (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8, - ); + self.output + .write_amplitude(ch32_hal::timer::Channel::Ch4, out / 2); } } } From e1943accd24f15e6be6f3d5b8241c05c98be8f0d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 2 Nov 2025 13:41:46 -0700 Subject: [PATCH 097/148] move led services inside app --- ch32v-insert-coin/src/app.rs | 89 ++++++++++++++++++- ch32v-insert-coin/src/insert_coin/mod.rs | 4 +- .../src/insert_coin/services/led.rs | 6 +- ch32v-insert-coin/src/main.rs | 69 ++++++-------- 4 files changed, 117 insertions(+), 51 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 5af4510..e7c5eaf 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -51,7 +51,30 @@ mod settings { } } -use crate::insert_coin::{TickService, TickServiceData, TickTimerService}; +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] + } + } +} + +use crate::insert_coin::{ + LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, +}; pub use settings::Settings; #[cfg(feature = "enable_print")] @@ -134,25 +157,52 @@ impl Timers { } } +pub struct Services { + pub led0: LedService, + pub led1: LedService, + pub led2: LedService, +} + 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 struct Interfaces { + pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, +} + pub struct App { state: State, pub settings: Settings, timers: Timers, + services: Services, + sequences: Sequences, + interfaces: Interfaces, // TODO: make this the "sound module" or whatever. // synthesizers: AppSynthesizers, } impl App { - pub fn new(config: Config) -> Self { + pub fn new( + config: Config, + services: Services, + sequences: Sequences, + interfaces: Interfaces, + ) -> Self { Self { state: State::default(), settings: Settings::default(), timers: Timers::new(config.timers, config.system_tick_rate_hz), + services, + sequences, + interfaces, } } @@ -191,6 +241,7 @@ impl App { } pub fn service(&mut self) { + // timers if self.timers.sp_timer.need_service() { self.timers.batt_adc_timer.service(); #[cfg(feature = "enable_print")] @@ -213,18 +264,50 @@ impl App { } if self.timers.led0_timer.need_service() { self.timers.led0_timer.service(); + self.sequences.led0.next(); + self.services + .led0 + .set_amplitude(self.sequences.led0.get_value()); #[cfg(feature = "enable_print")] - println!("led0 service"); + println!("led0 sevice {}", self.sequences.led0.get_value()); } if self.timers.led1_timer.need_service() { self.timers.led1_timer.service(); + self.sequences.led1.next(); + self.services + .led1 + .set_amplitude(self.sequences.led1.get_value()); #[cfg(feature = "enable_print")] println!("led1 service"); } if self.timers.led2_timer.need_service() { self.timers.led2_timer.service(); + self.sequences.led2.next(); + self.services + .led2 + .set_amplitude(self.sequences.led2.get_value()); #[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(); + } } } diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index e7c42d8..461dc22 100644 --- a/ch32v-insert-coin/src/insert_coin/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -1,7 +1,7 @@ mod insert_coin; mod services; -pub use services::TickTimerService; +pub use services::{LedService, Service, TickTimerService}; +pub use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; pub use services::{TickService, TickServiceData}; -pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore}; \ No newline at end of file diff --git a/ch32v-insert-coin/src/insert_coin/services/led.rs b/ch32v-insert-coin/src/insert_coin/services/led.rs index b7e2056..c1bf890 100644 --- a/ch32v-insert-coin/src/insert_coin/services/led.rs +++ b/ch32v-insert-coin/src/insert_coin/services/led.rs @@ -1,6 +1,6 @@ use ch32_hal::timer::Channel; -use crate::insert_coin::services::{Service}; +use crate::insert_coin::services::Service; pub struct LedService { // need_service: core::cell::RefCell, @@ -25,9 +25,7 @@ impl LedService { } } - impl Service for LedService { - fn need_service(&self) -> bool { self.need_service } @@ -36,5 +34,3 @@ impl Service for LedService { self.need_service = false; } } - - diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index f72658c..a208ea8 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -14,10 +14,12 @@ mod synthesizer; use synthesizer::AppSynthesizers; mod app; -use app::{App, Config, State, TimerConfig}; +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, InsertCoin, SimplePwmCore}; +use insert_coin::{CoreConfig, InsertCoin, LedService, SimplePwmCore}; use ch32_hal as hal; use hal::bind_interrupts; @@ -31,6 +33,8 @@ use hal::println; use qingke::riscv; +static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; + struct DebouncedGPIO<'a> { input: Input<'a>, // value of the GPIO @@ -498,8 +502,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { let mut light_ctrl_btn_input = DebouncedGPIO::new(light_ctrl_btn_pin.degrade(), core_config.tick_rate_hz, 100); - let mut interfaces = InsertCoin::new(core_config, pwm_core); - interfaces.set_active(true); + // let mut interfaces = InsertCoin::new(core_config, pwm_core); + // interfaces.set_active(true); let mut led0_index = 0; let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; @@ -514,14 +518,12 @@ fn app_main(mut p: hal::Peripherals) -> ! { // adc1_timer.reset(); // adc1_timer.enable(true); - let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; - let timer_config = TimerConfig { sp_timer_ms: 2000, lp_timer_ms: 5000, batt_adc_timer_ms: 10000, usb_adc_timer_ms: 10000, - led0_timer_ms: 1000, + led0_timer_ms: 100, led1_timer_ms: 300, led2_timer_ms: 1100, }; @@ -531,7 +533,21 @@ fn app_main(mut p: hal::Peripherals) -> ! { timers: timer_config, }; - let mut app = App::new(app_config); + let app_services = Services { + led0: LedService::new(led0_ch), + led1: LedService::new(led1_ch), + led2: LedService::new(led2_ch), + }; + + let app_sequences = Sequences { + led0: BasicSequence::new(&LED0_SEQ), + led1: BasicSequence::new(&LED0_SEQ), + led2: BasicSequence::new(&LED0_SEQ), + }; + + let app_interfaces = Interfaces { pwm_core }; + + let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); // dac data // let coin_sound = include_bytes!("../audio/coin2.raw"); @@ -542,9 +558,9 @@ fn app_main(mut p: hal::Peripherals) -> ! { // let button_sound_2 = include_bytes!("../audio/button_2.raw"); // let button_sound_3 = include_bytes!("../audio/button_3.raw"); - interfaces.led0.set_amplitude(0); - interfaces.led1.set_amplitude(0); - interfaces.service(); + // interfaces.led0.set_amplitude(0); + // interfaces.led1.set_amplitude(0); + // interfaces.service(); // init systick systick_init(tick_rate_hz); @@ -590,17 +606,12 @@ fn app_main(mut p: hal::Peripherals) -> ! { #[allow(static_mut_refs)] INPUT_FLAGS.systick_flag.clear(); } - // #[cfg(feature = "enable_print")] - // println!("tick!"); - // app tick app.tick(); - - // interfaces } app.service(); - interfaces.service(); + // interfaces.service(); } // // edge detector @@ -774,31 +785,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { // } // State::Idle => {} // State::Active => { - // // tt0.tick(); - // // tt1.tick(); - - // // if tt0.need_service() { - // // interfaces.led0.set_amplitude(led0_dcs[led0_index]); - // // led0_index += 1; - // // if led0_index > led0_dcs.len() - 1 { - // // led0_index = 0; - // // } - // // tt0.service(); - // // } - - // // if tt1.need_service() { - // // interfaces.led1.set_amplitude(led1_dcs[led1_index]); - // // led1_index += 1; - // // if led1_index > led1_dcs.len() - 1 { - // // led1_index = 0; - // // } - // // tt1.service() - // // } - // } - // } - // } } - #[qingke_rt::entry] fn main() -> ! { #[cfg(feature = "enable_print")] From 9e34e77e950b4f1998f16af46af779f2184cdd4f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 4 Nov 2025 11:25:39 -0700 Subject: [PATCH 098/148] remove SystemState from main --- ch32v-insert-coin/src/main.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index a208ea8..3b1ff43 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -15,7 +15,7 @@ use synthesizer::AppSynthesizers; mod app; use app::{ - sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig, + App, Config, Interfaces, Sequences, Services, State, TimerConfig, sequencer::BasicSequence, }; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; @@ -99,19 +99,6 @@ impl<'a> DebouncedGPIO<'a> { } } -// DeepSleep --coin button irq--> Active -// Active --2s button--> Idle -// Idle/Active --5s button--> DeepSleep - -pub enum SystemState { - // 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 - Active, -} - #[derive(Debug)] struct Flag { value: bool, From f17811cdceb493a949d5a06990095bd28aa80add Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 4 Nov 2025 12:05:16 -0700 Subject: [PATCH 099/148] more cleanup of dead code in main.rs --- ch32v-insert-coin/src/main.rs | 183 +--------------------------------- 1 file changed, 1 insertion(+), 182 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3b1ff43..34ece4e 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -15,7 +15,7 @@ use synthesizer::AppSynthesizers; mod app; use app::{ - App, Config, Interfaces, Sequences, Services, State, TimerConfig, sequencer::BasicSequence, + sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig, }; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; @@ -122,13 +122,8 @@ struct InputFlags { volume_btn_flag: bool, light_ctrl_btn_flag: bool, systick_flag: Flag, - // synth_tick_flag: bool, } -// impl InputFlags { -// pub fn sense_coin_flag(&self) -> bool {} -// } - impl Default for InputFlags { fn default() -> Self { Self { @@ -141,21 +136,6 @@ impl Default for InputFlags { } } -// impl InputFlags { -// pub fn set_sense_coin_flag(&mut self, val: bool) { -// self.sense_coin_flag = val; -// } -// pub fn set_main_btn_flag(&mut self, val: bool) { -// self.main_btn_flag = val; -// } -// pub fn set_volume_btn_flag(&mut self, val: bool) { -// self.volume_btn_flag = val; -// } -// pub fn set_light_ctrl_btn_flag(&mut self, val: bool) { -// self.light_ctrl_btn_flag = val; -// } -// } - static mut INPUT_FLAGS: InputFlags = InputFlags { sense_coin_flag: false, main_btn_flag: false, @@ -177,46 +157,6 @@ impl Handler for Test { } } -static mut LED: Option> = None; -static mut SYSTICK_COUNT: u32 = 0; -static mut SYNTHESIZERS: Option> = None; -static mut TOGGLE_COUNT: u32 = 0; -static mut TICK_FLAG: bool = false; -static mut SYNTH_TICK_FLAG: bool = false; -static mut NOTE_TICK_FLAG: bool = false; -static mut FREQ: [usize; 2] = [220, 440]; -static mut INDEX: usize = 0; - -fn flag() -> bool { - unsafe { core::ptr::read_volatile(&raw const TICK_FLAG as *const bool) } -} - -fn clear_flag() { - unsafe { - TICK_FLAG = false; - } -} - -fn synth_flag() -> bool { - unsafe { core::ptr::read_volatile(&raw const SYNTH_TICK_FLAG as *const bool) } -} - -fn clear_synth_flag() { - unsafe { - SYNTH_TICK_FLAG = false; - } -} - -fn note_flag() -> bool { - unsafe { core::ptr::read_volatile(&raw const NOTE_TICK_FLAG as *const bool) } -} - -fn clear_note_flag() { - unsafe { - NOTE_TICK_FLAG = false; - } -} - #[qingke_rt::interrupt(core)] fn SysTick() { let r = &ch32_hal::pac::SYSTICK; @@ -225,31 +165,9 @@ fn SysTick() { r.sr().write(|w| w.set_cntif(false)); unsafe { - // if let Some(ref mut synthesizers) = SYNTHESIZERS { - // println!("tick"); - // synthesizers.tick(); - // } - SYNTH_TICK_FLAG = true; - // safe because single-threaded #[allow(static_mut_refs)] INPUT_FLAGS.systick_flag.set(); - - // if SYSTICK_COUNT % 5000 == 0 { - // NOTE_TICK_FLAG = true; - // } - - // if SYSTICK_COUNT % 50000 == 0 { - // TICK_FLAG = true; - // // if let Some(ref mut led) = LED { - // // let toggle = TOGGLE_COUNT; - // // // println!("TOGGLE {toggle}"); - // // TOGGLE_COUNT += 1; - // // led.toggle(); - // // } - // // println!("HERE"); - // } - SYSTICK_COUNT = SYSTICK_COUNT.wrapping_add(1); } } @@ -290,105 +208,6 @@ bind_interrupts!(struct Irqs { use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -fn debug_main(mut p: hal::Peripherals) -> ! { - // LED0 output setup - use hal::gpio::{Level, Output}; - let mut led = Output::new(p.PC3, Level::High, Default::default()); - - // unsafe { - // LED = Some(led); - // } - - println!("pre"); - riscv::asm::delay(20_000_000); - println!("post2"); - - let tick_rate_hz = 100000; // 50khz - - // 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, - None, - None, - None, - Some(dac_pin), - Hertz::khz(500), - CountingMode::default(), - ); - - let pwm_core = SimplePwmCore::new(pwm); - unsafe { - use qingke_rt::CoreInterrupt; - - qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); - } - - // === synthesizer setup === - // unsafe { - // let mut synth = AppSynthesizers::new(tick_rate_hz, pwm_core); - // synth.square.set_freq(440); - // SYNTHESIZERS = Some(synth); - // } - let mut synth = AppSynthesizers::new(tick_rate_hz, pwm_core); - synth.square.set_freq(440); - - #[cfg(feature = "enable_print")] - println!("begin loop"); - - systick_init(tick_rate_hz); - - unsafe { - use qingke::interrupt::Priority; - use qingke_rt::CoreInterrupt; - qingke::pfic::set_priority(CoreInterrupt::SysTick as u8, Priority::P15 as u8); - qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); - } - - let mut index = 0; - let freqs = [1567, 1396, 1318, 1174, 1046, 987, 880, 783, 698]; - // let freqs = [100, 200, 300, 400, 500, 600, 700, 800, 900]; - // let freqs = [ - // 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, - // ]; - - loop { - if flag() { - clear_flag(); - led.toggle(); - } - if synth_flag() { - clear_synth_flag(); - synth.tick(); - } - if note_flag() { - clear_note_flag(); - synth.square.set_freq(freqs[index]); - // println!("{}", { freqs[index] }); - // println!("{}", freqs[index]); - index += 1; - if index > freqs.len() - 1 { - index = 0; - } - } - // if (unsafe { TICK_FLAG == true }) { - // println!("TICK!"); - // led.toggle(); - // unsafe { - // TICK_FLAG = false; - // } - // } - } -} - fn app_main(mut p: hal::Peripherals) -> ! { // === output setup === From b786bf174ae690b056b326b8500f98c854dda951 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 4 Nov 2025 12:14:13 -0700 Subject: [PATCH 100/148] move debounced gpio to module file --- ch32v-insert-coin/src/debounced_gpio.rs | 66 ++++++++++++++++++++ ch32v-insert-coin/src/main.rs | 83 +------------------------ 2 files changed, 69 insertions(+), 80 deletions(-) create mode 100644 ch32v-insert-coin/src/debounced_gpio.rs diff --git a/ch32v-insert-coin/src/debounced_gpio.rs b/ch32v-insert-coin/src/debounced_gpio.rs new file mode 100644 index 0000000..baec633 --- /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::Up), + 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/main.rs b/ch32v-insert-coin/src/main.rs index 34ece4e..7fdfb35 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -9,6 +9,9 @@ mod insert_coin; // system stuff mod system; +mod debounced_gpio; +use debounced_gpio::DebouncedGPIO; + // synthesizer :3 mod synthesizer; use synthesizer::AppSynthesizers; @@ -35,70 +38,6 @@ use qingke::riscv; static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; -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::Up), - 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() - } -} - #[derive(Debug)] struct Flag { value: bool, @@ -308,22 +247,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { let mut light_ctrl_btn_input = DebouncedGPIO::new(light_ctrl_btn_pin.degrade(), core_config.tick_rate_hz, 100); - // let mut interfaces = InsertCoin::new(core_config, pwm_core); - // interfaces.set_active(true); - - 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]; - - // // battery read timer - // let adc1_ticks = 5 * interfaces.config.tick_rate_hz; - // let adc1_timer_data = TickServiceData::new(adc1_ticks); - // let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); - // adc1_timer.reset(); - // adc1_timer.enable(true); - let timer_config = TimerConfig { sp_timer_ms: 2000, lp_timer_ms: 5000, From d3ccc70782d05c02ce3f42015155ec2bd9b9460a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 08:51:11 -0700 Subject: [PATCH 101/148] move synthesizer into app struct --- ch32v-insert-coin/src/app.rs | 48 +++++++++++++++++- .../src/insert_coin/insert_coin.rs | 9 ++++ ch32v-insert-coin/src/main.rs | 6 ++- ch32v-insert-coin/src/synthesizer.rs | 50 ++++++++++--------- 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index e7c5eaf..da8b9ca 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -75,6 +75,8 @@ pub mod sequencer { use crate::insert_coin::{ LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, }; +use crate::synthesizer::SynthesizerService; + pub use settings::Settings; #[cfg(feature = "enable_print")] @@ -157,10 +159,23 @@ impl Timers { } } +// 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, +} + +impl Services { + pub fn tick(&mut self) { + self.synth0.tick(); + } } pub struct Config { @@ -174,6 +189,7 @@ pub struct Sequences { pub led2: sequencer::BasicSequence<'static>, } +// things that touch hardware pub struct Interfaces { pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, } @@ -185,8 +201,6 @@ pub struct App { services: Services, sequences: Sequences, interfaces: Interfaces, - // TODO: make this the "sound module" or whatever. - // synthesizers: AppSynthesizers, } impl App { @@ -222,6 +236,8 @@ impl App { self.timers.led2_timer.reset(); self.timers.led2_timer.enable(true); + + self.services.synth0.set_freq(440); } pub fn set_state(&mut self, state: State) { @@ -238,6 +254,7 @@ impl App { pub fn tick(&mut self) { self.timers.tick(); + self.services.tick(); } pub fn service(&mut self) { @@ -309,5 +326,32 @@ impl App { .write_amplitude(self.services.led2.channel, self.services.led2.amplitude); self.services.led2.service(); } + + if self.services.synth0.need_service() { + let out = self.services.synth0.service(); + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out); + } } } + +// TODO LIST +// +// AUDIO: +// 1. volume control +// 2. dac switching +// +// LED: +// 1. brightness control +// +// INTERFACE: +// 1. short press handling +// 2. long press handling +// 3. volume press handling +// 4. brightness press handling +// +// SYSTEM: +// 1. deep sleep +// 2. battery voltage monitoring +// 3. battery voltage cutoff diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index 79d6598..426c174 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -42,6 +42,15 @@ impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { // } } +// 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, } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 7fdfb35..9ddeb15 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -14,7 +14,7 @@ use debounced_gpio::DebouncedGPIO; // synthesizer :3 mod synthesizer; -use synthesizer::AppSynthesizers; +use synthesizer::SynthesizerService; mod app; use app::{ @@ -262,10 +262,14 @@ fn app_main(mut p: hal::Peripherals) -> ! { timers: timer_config, }; + // let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); + // let square = SimpleWavetableSynthesizer::new(square_wt, 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), }; let app_sequences = Sequences { diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs index a85d978..b91da38 100644 --- a/ch32v-insert-coin/src/synthesizer.rs +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -9,37 +9,41 @@ const SQUARE_WAVETABLE: [u8; 2] = [0, 100]; // 100, 100, 100, 100, 100, 100, 100, // ]; -pub struct AppSynthesizers<'a, T: GeneralInstance16bit> { - pub square: SimpleWavetableSynthesizer>, - output: SimplePwmCore<'a, T>, +pub struct SynthesizerService { + pub synth: SimpleWavetableSynthesizer>, } -impl<'a, T: GeneralInstance16bit> AppSynthesizers<'a, T> { - pub fn new(clock_freq_hz: usize, output: SimplePwmCore<'a, T>) -> Self { +impl SynthesizerService { + pub fn new(clock_freq_hz: usize) -> Self { let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); - let square = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); + let synth = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); - Self { square, output } + Self { synth } } pub fn tick(&mut self) { - self.square.tick(); - if self.square.has_new_output() { - let out = self.square.get_output(); - self.output.write_amplitude( - ch32_hal::timer::Channel::Ch4, - // TODO: set level here. or maybe use dac? - out / 2, - ); - } + self.synth.tick(); } - pub fn service(&mut self) { - // println!("HERE"); - if self.square.has_new_output() { - let out = self.square.get_output(); - self.output - .write_amplitude(ch32_hal::timer::Channel::Ch4, out / 2); - } + pub fn need_service(&self) -> bool { + self.synth.has_new_output() + } + + pub fn service(&mut self) -> u8 { + self.synth.get_output() + } +} + +impl SynthesizerService { + pub fn set_freq(&mut self, freq_hz: usize) { + self.synth.set_freq(freq_hz); + } + + pub fn enable(&mut self) { + // TODO: write the enable function + } + + pub fn disable(&mut self) { + // TODO: write the disable function } } From 95b55f88a8439bc32147231550bc30f1272ef79e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 10:06:41 -0700 Subject: [PATCH 102/148] add basic volume control --- ch32v-insert-coin/src/app.rs | 54 +++++++++++++++++++++++++++++------ ch32v-insert-coin/src/main.rs | 30 ++++++++++++------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index da8b9ca..54a2768 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -11,7 +11,7 @@ pub enum State { mod settings { - #[derive(Default, Clone, Copy)] + #[derive(Debug, Default, Clone, Copy)] pub enum Level { Off, Low, @@ -33,6 +33,19 @@ mod settings { } } + // 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, + } + } + } + #[derive(Clone, Copy)] pub struct Settings { pub brightness: Level, @@ -285,8 +298,8 @@ impl App { self.services .led0 .set_amplitude(self.sequences.led0.get_value()); - #[cfg(feature = "enable_print")] - println!("led0 sevice {}", self.sequences.led0.get_value()); + // #[cfg(feature = "enable_print")] + // println!("led0 sevice {}", self.sequences.led0.get_value()); } if self.timers.led1_timer.need_service() { self.timers.led1_timer.service(); @@ -294,8 +307,8 @@ impl App { self.services .led1 .set_amplitude(self.sequences.led1.get_value()); - #[cfg(feature = "enable_print")] - println!("led1 service"); + // #[cfg(feature = "enable_print")] + // println!("led1 service"); } if self.timers.led2_timer.need_service() { self.timers.led2_timer.service(); @@ -303,8 +316,8 @@ impl App { self.services .led2 .set_amplitude(self.sequences.led2.get_value()); - #[cfg(feature = "enable_print")] - println!("led2 service"); + // #[cfg(feature = "enable_print")] + // println!("led2 service"); } // services @@ -328,7 +341,7 @@ impl App { } if self.services.synth0.need_service() { - let out = self.services.synth0.service(); + let out = self.services.synth0.service() / self.settings.volume.as_volume_divisor(); self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out); @@ -336,11 +349,31 @@ impl App { } } +// interfaces to the app (for buttons, etc.) +impl App { + pub fn volume_button(&mut self) { + self.settings.volume.next(); + #[cfg(feature = "enable_print")] + println!("new volume: {:?}", self.settings.volume); + } + pub fn brightness_button(&mut self) { + #[cfg(feature = "enable_print")] + println!("brightness button app handler"); + // TODO + } + pub fn main_button(&mut self) { + // TODO + } + pub fn coin_detect(&mut self) { + // TODO + } +} + // TODO LIST // // AUDIO: -// 1. volume control // 2. dac switching +// 3. amp_en control // // LED: // 1. brightness control @@ -355,3 +388,6 @@ impl App { // 1. deep sleep // 2. battery voltage monitoring // 3. battery voltage cutoff +// +// STRETCH TODO LIST +// 1. clean up edge detector diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9ddeb15..9990634 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -329,6 +329,25 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.init(); loop { // system servicing + // + // 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(); + } // systick tick if unsafe { @@ -347,17 +366,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { // interfaces.service(); } - // // 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_prev = volume_btn_curr; - // unsafe { - // INPUT_FLAGS.volume_btn_flag = true; - // } - // } - // } - // 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 { From 930d10218f9c464e892d42b667434878cd6789cb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 10:24:19 -0700 Subject: [PATCH 103/148] add basic brightness control --- ch32v-insert-coin/src/app.rs | 34 ++++++++++++++++++++++------------ ch32v-insert-coin/src/main.rs | 23 +++++++++++++++++++++-- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 54a2768..91f5b0c 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -44,6 +44,16 @@ mod settings { Self::Maximum => 1, } } + + pub fn as_brightness_divisor(&self) -> u8 { + match self { + Self::Off => u8::MAX, + Self::Low => 4, + Self::Medium => 3, + Self::High => 2, + Self::Maximum => 1, + } + } } #[derive(Clone, Copy)] @@ -295,27 +305,27 @@ impl App { if self.timers.led0_timer.need_service() { self.timers.led0_timer.service(); self.sequences.led0.next(); - self.services - .led0 - .set_amplitude(self.sequences.led0.get_value()); + self.services.led0.set_amplitude( + self.sequences.led0.get_value() / self.settings.brightness.as_brightness_divisor(), + ); // #[cfg(feature = "enable_print")] // println!("led0 sevice {}", self.sequences.led0.get_value()); } if self.timers.led1_timer.need_service() { self.timers.led1_timer.service(); self.sequences.led1.next(); - self.services - .led1 - .set_amplitude(self.sequences.led1.get_value()); + self.services.led1.set_amplitude( + self.sequences.led1.get_value() / self.settings.brightness.as_brightness_divisor(), + ); // #[cfg(feature = "enable_print")] // println!("led1 service"); } if self.timers.led2_timer.need_service() { self.timers.led2_timer.service(); self.sequences.led2.next(); - self.services - .led2 - .set_amplitude(self.sequences.led2.get_value()); + self.services.led2.set_amplitude( + self.sequences.led2.get_value() / self.settings.brightness.as_brightness_divisor(), + ); // #[cfg(feature = "enable_print")] // println!("led2 service"); } @@ -357,9 +367,9 @@ impl App { println!("new volume: {:?}", self.settings.volume); } pub fn brightness_button(&mut self) { + self.settings.brightness.next(); #[cfg(feature = "enable_print")] - println!("brightness button app handler"); - // TODO + println!("new brightness: {:?}", self.settings.brightness); } pub fn main_button(&mut self) { // TODO @@ -376,7 +386,6 @@ impl App { // 3. amp_en control // // LED: -// 1. brightness control // // INTERFACE: // 1. short press handling @@ -391,3 +400,4 @@ impl App { // // STRETCH TODO LIST // 1. clean up edge detector +// 2. better handling for pwm scaling (brightness / volume) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9990634..288203b 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -329,8 +329,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.init(); loop { // system servicing - // - // edge detector + + // 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 { @@ -349,6 +349,25 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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(); + } + // systick tick if unsafe { #[allow(static_mut_refs)] From c8c42c01940f759c2e97e5718d87daf8fbdc424e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 11:42:27 -0700 Subject: [PATCH 104/148] add coin button handling and move coin dac inside app --- ch32v-insert-coin/src/app.rs | 23 +++++-- ch32v-insert-coin/src/insert_coin/mod.rs | 2 +- .../src/insert_coin/services/dac.rs | 7 +- ch32v-insert-coin/src/main.rs | 64 +++++++++++++++---- ch32v-insert-coin/src/system.rs | 42 ++---------- 5 files changed, 81 insertions(+), 57 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 91f5b0c..e1c9bf8 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -96,7 +96,7 @@ pub mod sequencer { } use crate::insert_coin::{ - LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, + DacService, LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, }; use crate::synthesizer::SynthesizerService; @@ -193,11 +193,13 @@ pub struct Services { pub led1: LedService, pub led2: LedService, pub synth0: SynthesizerService, + pub sample_player: DacService<'static>, } impl Services { pub fn tick(&mut self) { self.synth0.tick(); + self.sample_player.tick(); } } @@ -352,9 +354,17 @@ impl App { if self.services.synth0.need_service() { let out = self.services.synth0.service() / self.settings.volume.as_volume_divisor(); + // 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(); self.interfaces .pwm_core - .write_amplitude(ch32_hal::timer::Channel::Ch4, out); + .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); } } } @@ -374,8 +384,10 @@ impl App { pub fn main_button(&mut self) { // TODO } - pub fn coin_detect(&mut self) { - // TODO + pub fn coin_detect(&self) { + #[cfg(feature = "enable_print")] + println!("coin detect"); + self.services.sample_player.play_sample(); } } @@ -390,8 +402,6 @@ impl App { // INTERFACE: // 1. short press handling // 2. long press handling -// 3. volume press handling -// 4. brightness press handling // // SYSTEM: // 1. deep sleep @@ -401,3 +411,4 @@ impl App { // STRETCH TODO LIST // 1. clean up edge detector // 2. better handling for pwm scaling (brightness / volume) +// 3. better interrupt handling structs diff --git a/ch32v-insert-coin/src/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index 461dc22..766dcca 100644 --- a/ch32v-insert-coin/src/insert_coin/mod.rs +++ b/ch32v-insert-coin/src/insert_coin/mod.rs @@ -1,7 +1,7 @@ mod insert_coin; mod services; -pub use services::{LedService, Service, TickTimerService}; +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 index 4cdaec0..1d18994 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -1,5 +1,4 @@ -use crate::insert_coin::services::{TickServiceData, TickService}; - +use crate::insert_coin::services::{TickService, TickServiceData}; use adpcm_pwm_dac::dac::DpcmDecoder; use ch32_hal::timer::Channel; @@ -21,6 +20,10 @@ impl<'a> DacService<'a> { } } + pub fn play_sample(&self) { + self.dpcm_decoder.borrow_mut().seek_to_sample(0); + } + 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); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 288203b..d8c1cc3 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -22,7 +22,7 @@ use app::{ }; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; -use insert_coin::{CoreConfig, InsertCoin, LedService, SimplePwmCore}; +use insert_coin::{CoreConfig, DacService, InsertCoin, LedService, SimplePwmCore}; use ch32_hal as hal; use hal::bind_interrupts; @@ -56,8 +56,8 @@ impl Flag { #[derive(Debug)] struct InputFlags { - sense_coin_flag: bool, - main_btn_flag: bool, + sense_coin_flag: Flag, + main_btn_flag: Flag, volume_btn_flag: bool, light_ctrl_btn_flag: bool, systick_flag: Flag, @@ -66,8 +66,8 @@ struct InputFlags { impl Default for InputFlags { fn default() -> Self { Self { - sense_coin_flag: false, - main_btn_flag: false, + 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 }, @@ -76,8 +76,8 @@ impl Default for InputFlags { } static mut INPUT_FLAGS: InputFlags = InputFlags { - sense_coin_flag: false, - main_btn_flag: false, + 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 }, @@ -90,8 +90,15 @@ impl Handler for Test { println!("on_interrupt()"); critical_section::with(|_| unsafe { let flags = system::clear_interrupt(2, 6); - INPUT_FLAGS.sense_coin_flag = flags.sense_coin_flag; - INPUT_FLAGS.main_btn_flag = flags.main_btn_flag; + 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(); + } }); } } @@ -184,10 +191,11 @@ fn app_main(mut p: hal::Peripherals) -> ! { pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); - let sample_rate_hz = 4000; + // let sample_rate_hz = 4000; // let sample_rate_hz = 16000; - let dac_tick_per_service = 5; - let tick_rate_hz = sample_rate_hz * dac_tick_per_service; + // let dac_tick_per_service = 5; + // let tick_rate_hz = sample_rate_hz * dac_tick_per_service; + let tick_rate_hz = 50000; let core_config = CoreConfig::new(tick_rate_hz); @@ -265,11 +273,22 @@ fn app_main(mut p: hal::Peripherals) -> ! { // let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); // let square = SimpleWavetableSynthesizer::new(square_wt, tick_rate_hz); + // 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/coin2.raw"); + + let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); + sample_player.load_data(coin_sound); + 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, }; let app_sequences = Sequences { @@ -368,6 +387,27 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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(); + + #[cfg(feature = "enable_print")] + println!("coin flag active"); + sense_coin_input.begin(); + } + sense_coin_input.service(); + if sense_coin_input.ready() { + #[cfg(feature = "enable_print")] + println!("debounced coin_input value: {}", sense_coin_input.value()); + sense_coin_input.reset(); + app.coin_detect(); + } + } + // systick tick if unsafe { #[allow(static_mut_refs)] diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs index 0ee18a6..484d513 100644 --- a/ch32v-insert-coin/src/system.rs +++ b/ch32v-insert-coin/src/system.rs @@ -20,8 +20,8 @@ pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { }); } -pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { - let mut input_flags = crate::InputFlags::default(); +pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> [bool; 2] { + let mut output = [false, false]; let exti = &hal::pac::EXTI; @@ -35,53 +35,23 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { // 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!"); - input_flags.sense_coin_flag = true; + output[0] = true; } // button_flag if (bits & (0x1 << button_pin)) != 0x0 { #[cfg(feature = "enable_print")] println!("main_btn irq!"); - input_flags.main_btn_flag = true; - // CHECK PC6 - // unsafe { - // let mut val = 0; - // let reg: u32 = 0x40011008; - // val = (reg as *mut u32).read_volatile(); - // if ((val >> 0x6) & 0x1) == 0x0 { - // input_flags.volume_btn_flag = true; - // #[cfg(feature = "enable_print")] - // println!("volume irq!"); - // println!("CBANK: {val:08x}"); - // } - // } - // // CHECK PD6 - // unsafe { - // let mut val = 0; - // let reg: u32 = 0x40011408; - // val = (reg as *mut u32).read_volatile(); - // // if ((val >> 0x6) & 0x1) == 0x0 { - // input_flags.main_btn_flag = true; - // #[cfg(feature = "enable_print")] - // println!("main_btn irq!"); - // println!("DBANK: {val:08x}"); - // // } - // } + output[1] = true; } - // light_ctrl_btn_flag - // if (bits & (0x1 << light_ctrl_btn_pin)) != 0x0 { - // #[cfg(feature = "enable_print")] - // println!("light ctrl btn irq!"); - // input_flags.light_ctrl_btn_flag = true; - // } - // Clear pending - Clears the EXTI's line pending bits. exti.intfr().write(|w| w.0 = bits); @@ -94,7 +64,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt - input_flags + output } /// enter standby (SLEEPDEEP) mode, with WFE enabled. From d815507bcc6ad5ded9c0ecff8f334ef2ec009b49 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 12:14:21 -0700 Subject: [PATCH 105/148] add main button press handling --- ch32v-insert-coin/src/app.rs | 9 ++++++++- ch32v-insert-coin/src/main.rs | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index e1c9bf8..03800fa 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -381,8 +381,15 @@ impl App { #[cfg(feature = "enable_print")] println!("new brightness: {:?}", self.settings.brightness); } - pub fn main_button(&mut self) { + pub fn main_button_press(&mut self) { // TODO + #[cfg(feature = "enable_print")] + println!("main button press"); + } + pub fn main_button_release(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("main button release"); } pub fn coin_detect(&self) { #[cfg(feature = "enable_print")] diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index d8c1cc3..34d5222 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -408,6 +408,30 @@ fn app_main(mut p: hal::Peripherals) -> ! { } } + // main button handling + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.main_btn_flag.active() { + #[cfg(feature = "enable_print")] + println!("button 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_release(), + false => app.main_button_press(), + } + } + // systick tick if unsafe { #[allow(static_mut_refs)] From 2d8e2ce6eeeb7547f080c9609995cfc674e6641a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 12:20:54 -0700 Subject: [PATCH 106/148] add short and long press timer integration --- ch32v-insert-coin/src/app.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 03800fa..a50aad7 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -288,11 +288,13 @@ impl App { self.timers.batt_adc_timer.service(); #[cfg(feature = "enable_print")] println!("sp service"); + self.timers.sp_timer.reset(); } if self.timers.lp_timer.need_service() { self.timers.batt_adc_timer.service(); #[cfg(feature = "enable_print")] println!("lp service"); + self.timers.lp_timer.reset(); } if self.timers.batt_adc_timer.need_service() { self.timers.batt_adc_timer.service(); @@ -385,11 +387,17 @@ impl App { // TODO #[cfg(feature = "enable_print")] println!("main button press"); + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); + self.timers.sp_timer.enable(true); + self.timers.lp_timer.enable(true); } pub fn main_button_release(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("main button release"); + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); } pub fn coin_detect(&self) { #[cfg(feature = "enable_print")] From 5aa56a244d0ea7f4563d22a0a5ef89613cfeb6fb Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 14:26:50 -0700 Subject: [PATCH 107/148] add click, short, and long press handling / detection --- ch32v-insert-coin/src/app.rs | 32 +++++++++++++++++++ .../src/insert_coin/services/tick_timer.rs | 13 +++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index a50aad7..a5d573b 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -396,6 +396,19 @@ impl App { // TODO #[cfg(feature = "enable_print")] println!("main button release"); + match ( + self.timers.sp_timer.is_enabled(), + self.timers.lp_timer.is_enabled(), + ) { + // 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 + _ => {} + } self.timers.sp_timer.reset(); self.timers.lp_timer.reset(); } @@ -406,6 +419,25 @@ impl App { } } +// Events +impl App { + fn main_button_click(&self) { + // TODO + #[cfg(feature = "enable_print")] + println!("click"); + } + fn main_button_short_press(&self) { + // TODO + #[cfg(feature = "enable_print")] + println!("short press"); + } + fn main_button_long_press(&self) { + // TODO + #[cfg(feature = "enable_print")] + println!("long press"); + } +} + // TODO LIST // // AUDIO: diff --git a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs index a10ab74..863314a 100644 --- a/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs +++ b/ch32v-insert-coin/src/insert_coin/services/tick_timer.rs @@ -1,4 +1,4 @@ -use crate::insert_coin::services::{TickServiceData, TickService}; +use crate::insert_coin::services::{TickService, TickServiceData}; pub struct TickTimerService { service_data: core::cell::RefCell, @@ -15,22 +15,21 @@ impl TickTimerService { } } - // pub fn is_enabled(&self) -> bool { - // self.enabled - // } + 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 { @@ -48,5 +47,3 @@ impl TickService for TickTimerService { tc.ticks_remaining = tc.ticks_per_service; } } - - From 7d93cd8977a5ceb9a7aee82406d076e00b5e121c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 14:52:39 -0700 Subject: [PATCH 108/148] add initial long press deep sleep handling --- ch32v-insert-coin/src/app.rs | 10 +- ch32v-insert-coin/src/main.rs | 240 +++++----------------------------- 2 files changed, 44 insertions(+), 206 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index a5d573b..9a4e69d 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -431,10 +431,18 @@ impl App { #[cfg(feature = "enable_print")] println!("short press"); } - fn main_button_long_press(&self) { + 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 } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 34d5222..e993c35 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -160,10 +160,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { // LED0 output setup let led0_pin = PwmPin::new_ch3::<0>(p.PC3); let led0_ch = hal::timer::Channel::Ch3; - // let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); - // unsafe { - // LED = Some(led0_pin); - // } // LED1 output setup let led1_pin = PwmPin::new_ch1::<0>(p.PD2); @@ -191,19 +187,12 @@ fn app_main(mut p: hal::Peripherals) -> ! { pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); - // let sample_rate_hz = 4000; - // let sample_rate_hz = 16000; - // let dac_tick_per_service = 5; - // let tick_rate_hz = sample_rate_hz * dac_tick_per_service; let tick_rate_hz = 50000; let core_config = CoreConfig::new(tick_rate_hz); let pwm_core = SimplePwmCore::new(pwm); - // === synthesizer setup === - // let synthesizer = AppSynthesizers::new(core_config.tick_rate_hz, pwm_core); - // === input setup === // adc @@ -232,15 +221,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { // set up interrupts unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), false, true) }; unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) }; - // unsafe { system::init_gpio_irq(volume_btn_pin.pin(), volume_btn_pin.port(), true, true) }; - // unsafe { - // system::init_gpio_irq( - // light_ctrl_btn_pin.pin(), - // light_ctrl_btn_pin.port(), - // true, - // true, - // ) - // }; // coin debouncer (100ms) let mut sense_coin_input = @@ -270,9 +250,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { timers: timer_config, }; - // let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); - // let square = SimpleWavetableSynthesizer::new(square_wt, tick_rate_hz); - // DAC servicer setup let dac_sample_rate_hz = 16000; let dac_tick_per_service = tick_rate_hz / dac_sample_rate_hz; @@ -301,19 +278,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); - // dac data - // let coin_sound = include_bytes!("../audio/coin2.raw"); - // let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); - // let coin_sound = include_bytes!("../audio/button_1.raw"); - - // let button_sound_1 = include_bytes!("../audio/button_1.raw"); - // let button_sound_2 = include_bytes!("../audio/button_2.raw"); - // let button_sound_3 = include_bytes!("../audio/button_3.raw"); - - // interfaces.led0.set_amplitude(0); - // interfaces.led1.set_amplitude(0); - // interfaces.service(); - // init systick systick_init(tick_rate_hz); @@ -394,15 +358,10 @@ fn app_main(mut p: hal::Peripherals) -> ! { if INPUT_FLAGS.sense_coin_flag.active() { #[allow(static_mut_refs)] INPUT_FLAGS.sense_coin_flag.clear(); - - #[cfg(feature = "enable_print")] - println!("coin flag active"); sense_coin_input.begin(); } sense_coin_input.service(); if sense_coin_input.ready() { - #[cfg(feature = "enable_print")] - println!("debounced coin_input value: {}", sense_coin_input.value()); sense_coin_input.reset(); app.coin_detect(); } @@ -412,8 +371,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { unsafe { #[allow(static_mut_refs)] if INPUT_FLAGS.main_btn_flag.active() { - #[cfg(feature = "enable_print")] - println!("button flag active"); #[allow(static_mut_refs)] INPUT_FLAGS.main_btn_flag.clear(); main_btn_input.begin(); @@ -446,170 +403,43 @@ fn app_main(mut p: hal::Peripherals) -> ! { } app.service(); - // interfaces.service(); + + match app.get_state() { + // enter standby + app::State::DeepSleep => { + unsafe { system::enter_standby() }; + loop { + riscv::asm::wfi(); + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { + hal::rcc::init(config.rcc); + } + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.sense_coin_flag.active() { + app.set_state(State::Active); + break; + } + }; + } + } + // for everything else, don't do anything + _ => {} + } } - - // 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_prev = light_ctrl_btn_curr; - // unsafe { - // INPUT_FLAGS.light_ctrl_btn_flag = true; - // } - // } - // } - // { - // // system input servicing - // unsafe { - // if INPUT_FLAGS.sense_coin_flag { - // #[cfg(feature = "enable_print")] - // println!("coin flag active"); - // INPUT_FLAGS.sense_coin_flag = false; - // sense_coin_input.begin(); - // // enter the active state - // app.set_state(State::Active); - - // // todo: enter active - // // tt0.enable(true); - // // tt1.enable(true); - // // interfaces.dac.load_data(coin_sound); - // } - // if INPUT_FLAGS.main_btn_flag { - // #[cfg(feature = "enable_print")] - // println!("button flag active"); - // INPUT_FLAGS.main_btn_flag = false; - // main_btn_input.begin(); - // } - // if INPUT_FLAGS.volume_btn_flag { - // #[cfg(feature = "enable_print")] - // println!("volume btn triggered"); - // INPUT_FLAGS.volume_btn_flag = false; - // volume_btn_input.begin(); - // } - // if INPUT_FLAGS.light_ctrl_btn_flag { - // #[cfg(feature = "enable_print")] - // println!("light ctrl btn triggered!"); - // INPUT_FLAGS.light_ctrl_btn_flag = false; - // light_ctrl_btn_input.begin(); - // } - // } - - // // debouncer - // sense_coin_input.service(); - // main_btn_input.service(); - // volume_btn_input.service(); - // light_ctrl_btn_input.service(); - - // if sense_coin_input.ready() { - // #[cfg(feature = "enable_print")] - // println!("debounced coin_input value: {}", sense_coin_input.value()); - // sense_coin_input.reset(); - // } - // if main_btn_input.ready() { - // let value = main_btn_input.value(); - // main_btn_input.reset(); - // #[cfg(feature = "enable_print")] - // println!("debounced button_input value: {}", value); - - // if !value { - // // interfaces.dac.load_data(match settings.button_sound_index { - // // 0 => button_sound_1, - // // 1 => button_sound_2, - // // 2 => button_sound_3, - // // _ => button_sound_1, - // // }); - // // interfaces - // // .dac - // // .load_data(button_sounds[settings.button_sound_index]); - - // app.settings.button_sound_index += 1; - // if app.settings.button_sound_index > 2 { - // // if settings.button_sound_index > button_sounds.len() - 1 { - // app.settings.button_sound_index = 0; - // } - // #[cfg(feature = "enable_print")] - // println!("TODO reset hold timers + enable"); - // // sp_timer.reset(); - // // sp_timer.enable(true); - // // lp_timer.reset(); - // // lp_timer.enable(true); - // } else { - // // sp_timer.reset(); - // // lp_timer.reset(); - // } - // } - // if volume_btn_input.ready() { - // #[cfg(feature = "enable_print")] - // println!("volume btn value: {}", volume_btn_input.value()); - // app.settings.volume.next(); - // volume_btn_input.reset(); - // } - // if light_ctrl_btn_input.ready() { - // #[cfg(feature = "enable_print")] - // println!("light_ctrl_btn value: {}", light_ctrl_btn_input.value()); - // app.settings.brightness.next(); - // light_ctrl_btn_input.reset(); - // } - - // // if sp_timer.need_service() { - // // #[cfg(feature = "enable_print")] - // // println!("sp detect!"); - // // sp_timer.reset(); - - // // // todo enter idle - // // app.set_state(State::Idle); - // // // TODO: fix polarity - // // interfaces.led0.set_amplitude(10); - // // interfaces.led1.set_amplitude(10); - // // interfaces.service(); - // // } - - // // if lp_timer.need_service() { - // // #[cfg(feature = "enable_print")] - // // println!("lp detect!"); - // // lp_timer.reset(); - - // // // todo enter deepsleep - // // app.set_state(State::DeepSleep); - // // // TODO: fix polarity - // // interfaces.led0.set_amplitude(0); - // // interfaces.led1.set_amplitude(0); - // // interfaces.service(); - // // } - - // // if adc1_timer.need_service() { - // // let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); - // // let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); - // // #[cfg(feature = "enable_print")] - // // println!("ADC value: {}", val); - - // // adc1_timer.reset(); - // // adc1_timer.enable(true); - // // } - // } - - // match app.state() { - // State::DeepSleep => { - // // TODO: make this REALLY deep sleep - // unsafe { system::enter_standby() }; - // loop { - // riscv::asm::wfi(); - // let mut config = hal::Config::default(); - // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - // unsafe { - // hal::rcc::init(config.rcc); - // } - // unsafe { - // if INPUT_FLAGS.sense_coin_flag { - // app.set_state(State::Active); - // break; - // } - // }; - // } - // } - // State::Idle => {} - // State::Active => { } +// // if adc1_timer.need_service() { +// // let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); +// // let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); +// // #[cfg(feature = "enable_print")] +// // println!("ADC value: {}", val); + +// // adc1_timer.reset(); +// // adc1_timer.enable(true); +// // } +// } + #[qingke_rt::entry] fn main() -> ! { #[cfg(feature = "enable_print")] From 9e67026345b39da3a2f08385cc5d30db7f7d258b Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 19:42:44 -0700 Subject: [PATCH 109/148] dynamic sequencer support --- ch32v-insert-coin/src/app.rs | 108 +++++++++++++++++++++++++-- ch32v-insert-coin/src/main.rs | 32 +++++--- ch32v-insert-coin/src/synthesizer.rs | 26 +++++-- 3 files changed, 144 insertions(+), 22 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 9a4e69d..17c2a77 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -93,6 +93,84 @@ pub mod sequencer { 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: true, + } + } + + pub fn play_sequence(&mut self, sequence: &'a [SequenceEntry], num_loops: usize) { + self.sequence = sequence; + self.num_loops = num_loops; + self.loop_count = 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.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::{ @@ -194,12 +272,14 @@ pub struct Services { 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(); } } @@ -354,19 +434,31 @@ impl App { self.services.led2.service(); } + if self.services.sequencer.need_service() { + if let Some(out) = self.services.sequencer.service() { + self.services.synth0.set_freq(out.into()); + } else { + self.services.sequencer.disable(); + self.services.synth0.disable(); + } + } + if self.services.synth0.need_service() { - let out = self.services.synth0.service() / self.settings.volume.as_volume_divisor(); - // self.interfaces - // .pwm_core - // .write_amplitude(ch32_hal::timer::Channel::Ch4, out); + let out = match self.services.synth0.service() { + Some(value) => value / 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(); - self.interfaces - .pwm_core - .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); + // let out = self.services.sample_player.get_amplitude(); + // self.interfaces + // .pwm_core + // .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); } } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index e993c35..c7e2368 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -36,7 +36,19 @@ 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]; +static TEST_SEQ: [app::sequencer::SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 1000, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 1000, + }, +]; #[derive(Debug)] struct Flag { @@ -86,8 +98,8 @@ static mut INPUT_FLAGS: InputFlags = InputFlags { struct Test {} impl Handler for Test { unsafe fn on_interrupt() { - #[cfg(feature = "enable_print")] - println!("on_interrupt()"); + // #[cfg(feature = "enable_print")] + // println!("on_interrupt()"); critical_section::with(|_| unsafe { let flags = system::clear_interrupt(2, 6); if flags[0] { @@ -206,8 +218,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); let adc_cal = adc.calibrate(); - #[cfg(feature = "enable_print")] - println!("ADC calibration value: {}", adc_cal); + // #[cfg(feature = "enable_print")] + // println!("ADC calibration value: {}", adc_cal); // definitions let sense_coin_pin = p.PC2; @@ -260,12 +272,15 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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(&TEST_SEQ, 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 { @@ -303,9 +318,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { // -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"); - + // #[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(); @@ -324,8 +338,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { volume_btn_input.service(); if volume_btn_input.ready() { - #[cfg(feature = "enable_print")] - println!("volume btn value: {}", volume_btn_input.value()); + // #[cfg(feature = "enable_print")] + // println!("volume btn value: {}", volume_btn_input.value()); if !volume_btn_input.value() { app.volume_button(); } diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs index b91da38..d3cb3a1 100644 --- a/ch32v-insert-coin/src/synthesizer.rs +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -11,6 +11,8 @@ const SQUARE_WAVETABLE: [u8; 2] = [0, 100]; pub struct SynthesizerService { pub synth: SimpleWavetableSynthesizer>, + pub enabled: bool, + pub need_service: bool, } impl SynthesizerService { @@ -18,19 +20,29 @@ impl SynthesizerService { let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); let synth = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); - Self { synth } + Self { + synth, + enabled: true, + need_service: false, + } } pub fn tick(&mut self) { - self.synth.tick(); + if self.enabled { + self.synth.tick(); + } } pub fn need_service(&self) -> bool { - self.synth.has_new_output() + self.need_service || self.synth.has_new_output() } - pub fn service(&mut self) -> u8 { - self.synth.get_output() + pub fn service(&mut self) -> Option { + if self.enabled { + Some(self.synth.get_output()) + } else { + None + } } } @@ -40,10 +52,14 @@ impl SynthesizerService { } 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 } } From 9328087e23f147fe78c223ec1cb0217827d0a445 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 5 Nov 2025 20:25:29 -0700 Subject: [PATCH 110/148] minor fixes to sequence support --- ch32v-insert-coin/src/app.rs | 27 ++++++++++++++----- .../src/insert_coin/services/dac.rs | 17 +++++++++--- ch32v-insert-coin/src/main.rs | 5 ++-- ch32v-insert-coin/src/synthesizer.rs | 3 ++- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 17c2a77..b1befab 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -124,7 +124,7 @@ pub mod sequencer { ticks_remaining: 0, num_loops: 1, loop_count: 0, - enabled: true, + enabled: false, } } @@ -132,6 +132,9 @@ pub mod sequencer { 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) { @@ -343,6 +346,7 @@ impl App { self.timers.led2_timer.enable(true); self.services.synth0.set_freq(440); + self.services.synth0.disable(); } pub fn set_state(&mut self, state: State) { @@ -455,10 +459,10 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - // let out = self.services.sample_player.get_amplitude(); - // self.interfaces - // .pwm_core - // .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); + let out = self.services.sample_player.get_amplitude(); + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); } } } @@ -504,7 +508,7 @@ impl App { self.timers.sp_timer.reset(); self.timers.lp_timer.reset(); } - pub fn coin_detect(&self) { + pub fn coin_detect(&mut self) { #[cfg(feature = "enable_print")] println!("coin detect"); self.services.sample_player.play_sample(); @@ -513,10 +517,16 @@ impl App { // Events impl App { - fn main_button_click(&self) { + fn main_button_click(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("click"); + self.services.sequencer.play_sequence(&crate::TEST_SEQ, 1); + 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 @@ -543,6 +553,8 @@ impl App { // AUDIO: // 2. dac switching // 3. amp_en control +// 4. write sequences +// 5. sample player start disabled // // LED: // @@ -559,3 +571,4 @@ impl App { // 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/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index 1d18994..d262a14 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -8,6 +8,7 @@ pub struct DacService<'a> { dpcm_decoder: core::cell::RefCell>, amplitude: core::cell::RefCell, pub channel: Channel, + enabled: bool, } impl<'a> DacService<'a> { @@ -17,11 +18,17 @@ impl<'a> DacService<'a> { dpcm_decoder: core::cell::RefCell::new(DpcmDecoder::new()), amplitude: core::cell::RefCell::new(0), channel, + enabled: false, } } - pub fn play_sample(&self) { + 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]) { @@ -39,12 +46,14 @@ impl<'a> DacService<'a> { impl<'a> TickService for DacService<'a> { fn tick(&self) { - let mut tc = self.service_data.borrow_mut(); - tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); + 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.service_data.borrow().ticks_remaining == 0 + self.enabled && self.service_data.borrow().ticks_remaining == 0 } fn service(&self) { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c7e2368..9fc0841 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -39,7 +39,7 @@ use qingke::riscv; use crate::app::sequencer::{DynamicSequence, SequenceEntry}; static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; -static TEST_SEQ: [app::sequencer::SequenceEntry; 2] = [ +pub static TEST_SEQ: [app::sequencer::SequenceEntry; 2] = [ SequenceEntry { frequency_hz: 440, duration_ms: 1000, @@ -267,7 +267,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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/coin2.raw"); + let coin_sound = include_bytes!("../audio/coin.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); diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs index d3cb3a1..810336e 100644 --- a/ch32v-insert-coin/src/synthesizer.rs +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -34,10 +34,11 @@ impl SynthesizerService { } pub fn need_service(&self) -> bool { - self.need_service || self.synth.has_new_output() + 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 { From 48b836904b32759f38c83d8f58cbfe7f6449d4dd Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 09:02:08 -0700 Subject: [PATCH 111/148] add support for multi-sequence --- ch32v-insert-coin/src/app.rs | 15 ++++++++++++--- ch32v-insert-coin/src/main.rs | 12 ++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index b1befab..47194e3 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -295,6 +295,7 @@ pub struct Sequences { pub led0: sequencer::BasicSequence<'static>, pub led1: sequencer::BasicSequence<'static>, pub led2: sequencer::BasicSequence<'static>, + pub audio: [&'static [sequencer::SequenceEntry]; 2], } // things that touch hardware @@ -345,7 +346,7 @@ impl App { self.timers.led2_timer.reset(); self.timers.led2_timer.enable(true); - self.services.synth0.set_freq(440); + self.services.synth0.set_freq(1); self.services.synth0.disable(); } @@ -521,7 +522,15 @@ impl App { // TODO #[cfg(feature = "enable_print")] println!("click"); - self.services.sequencer.play_sequence(&crate::TEST_SEQ, 1); + self.services + .sequencer + .play_sequence(self.sequences.audio[self.settings.button_sound_index], 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: @@ -554,7 +563,7 @@ impl App { // 2. dac switching // 3. amp_en control // 4. write sequences -// 5. sample player start disabled +// 6. multiple sequences // // LED: // diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9fc0841..bb0678a 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -50,6 +50,17 @@ pub static TEST_SEQ: [app::sequencer::SequenceEntry; 2] = [ }, ]; +pub static TEST_SEQ1: [app::sequencer::SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 100, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 100, + }, +]; + #[derive(Debug)] struct Flag { value: bool, @@ -288,6 +299,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { led0: BasicSequence::new(&LED0_SEQ), led1: BasicSequence::new(&LED0_SEQ), led2: BasicSequence::new(&LED0_SEQ), + audio: [&TEST_SEQ, &TEST_SEQ1], }; let app_interfaces = Interfaces { pwm_core }; From 1bdb68c0d41cb86d61aed06ed1b3d2481a0f63cd Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 10:53:57 -0700 Subject: [PATCH 112/148] add some sequences --- ch32v-insert-coin/src/app.rs | 10 +- ch32v-insert-coin/src/main.rs | 75 ++--- ch32v-insert-coin/src/sequences.rs | 505 +++++++++++++++++++++++++++++ 3 files changed, 542 insertions(+), 48 deletions(-) create mode 100644 ch32v-insert-coin/src/sequences.rs diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 47194e3..28b22f2 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -295,7 +295,7 @@ pub struct Sequences { pub led0: sequencer::BasicSequence<'static>, pub led1: sequencer::BasicSequence<'static>, pub led2: sequencer::BasicSequence<'static>, - pub audio: [&'static [sequencer::SequenceEntry]; 2], + pub audio: &'static [&'static [sequencer::SequenceEntry]], } // things that touch hardware @@ -439,9 +439,15 @@ impl App { 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() { - self.services.synth0.set_freq(out.into()); + if out == 0 { + self.services.synth0.disable(); + } else { + self.services.synth0.set_freq(out.into()); + } } else { self.services.sequencer.disable(); self.services.synth0.disable(); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index bb0678a..481fc31 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -16,6 +16,10 @@ use debounced_gpio::DebouncedGPIO; 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, @@ -39,27 +43,6 @@ use qingke::riscv; use crate::app::sequencer::{DynamicSequence, SequenceEntry}; static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; -pub static TEST_SEQ: [app::sequencer::SequenceEntry; 2] = [ - SequenceEntry { - frequency_hz: 440, - duration_ms: 1000, - }, - SequenceEntry { - frequency_hz: 220, - duration_ms: 1000, - }, -]; - -pub static TEST_SEQ1: [app::sequencer::SequenceEntry; 2] = [ - SequenceEntry { - frequency_hz: 440, - duration_ms: 100, - }, - SequenceEntry { - frequency_hz: 220, - duration_ms: 100, - }, -]; #[derive(Debug)] struct Flag { @@ -284,7 +267,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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(&TEST_SEQ, tick_rate_hz); + let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0], tick_rate_hz); let app_services = Services { led0: LedService::new(led0_ch), @@ -299,7 +282,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { led0: BasicSequence::new(&LED0_SEQ), led1: BasicSequence::new(&LED0_SEQ), led2: BasicSequence::new(&LED0_SEQ), - audio: [&TEST_SEQ, &TEST_SEQ1], + audio: &SEQUENCE_LIST, }; let app_interfaces = Interfaces { pwm_core }; @@ -431,29 +414,29 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.service(); - match app.get_state() { - // enter standby - app::State::DeepSleep => { - unsafe { system::enter_standby() }; - loop { - riscv::asm::wfi(); - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { - hal::rcc::init(config.rcc); - } - unsafe { - #[allow(static_mut_refs)] - if INPUT_FLAGS.sense_coin_flag.active() { - app.set_state(State::Active); - break; - } - }; - } - } - // for everything else, don't do anything - _ => {} - } + // match app.get_state() { + // // enter standby + // app::State::DeepSleep => { + // unsafe { system::enter_standby() }; + // loop { + // riscv::asm::wfi(); + // let mut config = hal::Config::default(); + // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + // unsafe { + // hal::rcc::init(config.rcc); + // } + // unsafe { + // #[allow(static_mut_refs)] + // if INPUT_FLAGS.sense_coin_flag.active() { + // app.set_state(State::Active); + // break; + // } + // }; + // } + // } + // // for everything else, don't do anything + // _ => {} + // } } } // // if adc1_timer.need_service() { diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs new file mode 100644 index 0000000..55cac19 --- /dev/null +++ b/ch32v-insert-coin/src/sequences.rs @@ -0,0 +1,505 @@ +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; 71] = [ + // SequenceEntry { + // frequency_hz: 1000, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 990, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 980, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 970, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 960, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 950, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 940, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 930, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 920, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 910, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 900, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 890, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 880, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 870, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 860, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 850, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 840, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 830, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 820, + // duration_ms: 50, + // }, + // SequenceEntry { + // frequency_hz: 810, + // duration_ms: 50, + // }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 790, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 780, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 770, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 760, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 750, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 740, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 730, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 720, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 710, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 690, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 680, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 670, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 660, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 650, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 640, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 630, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 620, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 610, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 590, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 580, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 570, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 560, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 550, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 540, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 530, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 520, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 510, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 490, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 480, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 470, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 460, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 430, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 420, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 410, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 390, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 380, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 370, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 360, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 340, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 330, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 320, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 310, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 290, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 280, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 270, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 260, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 240, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 230, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 210, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 190, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 180, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 170, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 160, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 140, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 130, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 120, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 110, + 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: [SequenceEntry; 3] = [ + 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; 4] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 880, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 0, + duration_ms: 1000, + }, + SequenceEntry { + frequency_hz: 880, + 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: 1500, + duration_ms: 50, + }, +]; + +pub static SEQUENCE_LIST: [&'static [SequenceEntry]; 6] = [ + &SOUND_8_BIT_GAME_3, + &SOUND_8_BIT_GAME_1, + &SOUND_8_BIT_GAME, + &COIN_CHIRP, + &WAHWAH, + &DECENDING_TONES, + // &TEST_SEQ, +]; From a1767420f2356ab752184988f21d60db68a848dc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 11:31:26 -0700 Subject: [PATCH 113/148] update sequences --- ch32v-insert-coin/src/app.rs | 7 +- ch32v-insert-coin/src/main.rs | 2 +- ch32v-insert-coin/src/sequences.rs | 466 ++++++++++------------------- 3 files changed, 158 insertions(+), 317 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 28b22f2..e2682ba 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -295,7 +295,7 @@ pub struct Sequences { pub led0: sequencer::BasicSequence<'static>, pub led1: sequencer::BasicSequence<'static>, pub led2: sequencer::BasicSequence<'static>, - pub audio: &'static [&'static [sequencer::SequenceEntry]], + pub audio: &'static [(&'static [sequencer::SequenceEntry], usize)], } // things that touch hardware @@ -528,9 +528,8 @@ impl App { // TODO #[cfg(feature = "enable_print")] println!("click"); - self.services - .sequencer - .play_sequence(self.sequences.audio[self.settings.button_sound_index], 1); + 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 { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 481fc31..b3aa5ae 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -267,7 +267,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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], tick_rate_hz); + let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz); let app_services = Services { led0: LedService::new(led0_ch), diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs index 55cac19..b6f387b 100644 --- a/ch32v-insert-coin/src/sequences.rs +++ b/ch32v-insert-coin/src/sequences.rs @@ -62,367 +62,87 @@ pub static WAHWAH: [SequenceEntry; 9] = [ }, ]; -pub static DECENDING_TONES: [SequenceEntry; 71] = [ - // SequenceEntry { - // frequency_hz: 1000, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 990, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 980, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 970, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 960, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 950, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 940, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 930, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 920, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 910, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 900, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 890, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 880, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 870, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 860, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 850, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 840, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 830, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 820, - // duration_ms: 50, - // }, - // SequenceEntry { - // frequency_hz: 810, - // duration_ms: 50, - // }, +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: 790, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 780, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 770, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 760, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 750, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 740, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 730, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 720, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 710, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 700, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 690, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 680, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 670, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 660, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 650, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 640, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 630, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 620, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 610, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 600, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 590, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 580, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 570, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 560, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 550, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 540, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 530, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 520, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 510, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 500, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 490, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 480, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 470, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 460, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 450, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 440, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 430, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 420, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 410, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 400, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 390, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 380, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 370, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 360, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 350, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 340, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 330, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 320, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 310, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 300, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 290, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 280, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 270, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 260, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 250, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 240, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 230, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 220, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 210, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 200, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 190, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 180, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 170, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 160, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 150, duration_ms: 50, }, - SequenceEntry { - frequency_hz: 140, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 130, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 120, - duration_ms: 50, - }, - SequenceEntry { - frequency_hz: 110, - duration_ms: 50, - }, SequenceEntry { frequency_hz: 100, duration_ms: 50, @@ -494,12 +214,134 @@ pub static SOUND_8_BIT_GAME_3: [SequenceEntry; 3] = [ }, ]; -pub static SEQUENCE_LIST: [&'static [SequenceEntry]; 6] = [ - &SOUND_8_BIT_GAME_3, - &SOUND_8_BIT_GAME_1, - &SOUND_8_BIT_GAME, - &COIN_CHIRP, - &WAHWAH, - &DECENDING_TONES, +// 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] = [ + (&ASCENDING_TONES, 0), + (&SOUND_8_BIT_GAME_3, 2), + // TODO: this one is broken: + (&SOUND_8_BIT_GAME_1, 1), + (&SOUND_8_BIT_GAME, 1), + (&COIN_CHIRP, 0), + (&WAHWAH, 5), + (&DECENDING_TONES, 0), // &TEST_SEQ, ]; From 88d07fa69d1f7215ba950db0a15eafef8dca273e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 11:54:17 -0700 Subject: [PATCH 114/148] fix sequence frequency stuff to allow for rest notes --- ch32v-insert-coin/src/app.rs | 5 ++++- ch32v-insert-coin/src/sequences.rs | 12 ++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index e2682ba..720cb25 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -152,7 +152,7 @@ pub mod sequencer { } pub fn need_service(&self) -> bool { - self.ticks_remaining == 0 + self.enabled && self.ticks_remaining == 0 } pub fn service(&mut self) -> Option { @@ -348,6 +348,8 @@ impl App { self.services.synth0.set_freq(1); self.services.synth0.disable(); + + self.services.sequencer.disable(); } pub fn set_state(&mut self, state: State) { @@ -446,6 +448,7 @@ impl App { if out == 0 { self.services.synth0.disable(); } else { + self.services.synth0.enable(); self.services.synth0.set_freq(out.into()); } } else { diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs index b6f387b..e629ea5 100644 --- a/ch32v-insert-coin/src/sequences.rs +++ b/ch32v-insert-coin/src/sequences.rs @@ -179,7 +179,7 @@ pub static SOUND_8_BIT_GAME: [SequenceEntry; 3] = [ // 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; 4] = [ +pub static SOUND_8_BIT_GAME_1: [SequenceEntry; 3] = [ SequenceEntry { frequency_hz: 440, duration_ms: 50, @@ -190,11 +190,7 @@ pub static SOUND_8_BIT_GAME_1: [SequenceEntry; 4] = [ }, SequenceEntry { frequency_hz: 0, - duration_ms: 1000, - }, - SequenceEntry { - frequency_hz: 880, - duration_ms: 50, + duration_ms: 100, }, ]; @@ -335,10 +331,10 @@ pub static ASCENDING_TONES: [SequenceEntry; 29] = [ ]; pub static SEQUENCE_LIST: [(&'static [SequenceEntry], usize); 7] = [ - (&ASCENDING_TONES, 0), - (&SOUND_8_BIT_GAME_3, 2), // TODO: this one is broken: (&SOUND_8_BIT_GAME_1, 1), + (&ASCENDING_TONES, 0), + (&SOUND_8_BIT_GAME_3, 2), (&SOUND_8_BIT_GAME, 1), (&COIN_CHIRP, 0), (&WAHWAH, 5), From a50895ec96d8d1657ce7fb99e9ae0a8bdd8a5c87 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 12:22:13 -0700 Subject: [PATCH 115/148] some more sequence tuning --- ch32v-insert-coin/src/sequences.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs index e629ea5..4cf6e4c 100644 --- a/ch32v-insert-coin/src/sequences.rs +++ b/ch32v-insert-coin/src/sequences.rs @@ -162,7 +162,11 @@ pub static COIN_CHIRP: [SequenceEntry; 2] = [ ]; // PLAY ONCE -pub static SOUND_8_BIT_GAME: [SequenceEntry; 3] = [ +pub static SOUND_8_BIT_GAME_4: [SequenceEntry; 4] = [ + SequenceEntry { + frequency_hz: 220, + duration_ms: 50, + }, SequenceEntry { frequency_hz: 440, duration_ms: 50, @@ -185,12 +189,12 @@ pub static SOUND_8_BIT_GAME_1: [SequenceEntry; 3] = [ duration_ms: 50, }, SequenceEntry { - frequency_hz: 880, + frequency_hz: 1500, duration_ms: 50, }, SequenceEntry { frequency_hz: 0, - duration_ms: 100, + duration_ms: 50, }, ]; @@ -205,7 +209,7 @@ pub static SOUND_8_BIT_GAME_3: [SequenceEntry; 3] = [ duration_ms: 50, }, SequenceEntry { - frequency_hz: 1500, + frequency_hz: 1000, duration_ms: 50, }, ]; @@ -331,13 +335,12 @@ pub static ASCENDING_TONES: [SequenceEntry; 29] = [ ]; pub static SEQUENCE_LIST: [(&'static [SequenceEntry], usize); 7] = [ - // TODO: this one is broken: - (&SOUND_8_BIT_GAME_1, 1), - (&ASCENDING_TONES, 0), (&SOUND_8_BIT_GAME_3, 2), - (&SOUND_8_BIT_GAME, 1), + (&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, ]; From b52d911c12bc67c3f7bcceaca26e71488504f068 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 13:47:21 -0700 Subject: [PATCH 116/148] allow no-print --- ch32v-insert-coin/src/app.rs | 12 +++++++----- ch32v-insert-coin/src/main.rs | 18 +++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 720cb25..fb7e0fc 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -459,7 +459,8 @@ impl App { if self.services.synth0.need_service() { let out = match self.services.synth0.service() { - Some(value) => value / self.settings.volume.as_volume_divisor(), + Some(value) => value / 4, + // Some(value) => value / self.settings.volume.as_volume_divisor(), None => 0, }; self.interfaces @@ -469,7 +470,7 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - let out = self.services.sample_player.get_amplitude(); + let out = self.services.sample_player.get_amplitude() / 3; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); @@ -566,12 +567,13 @@ impl App { } // TODO LIST +// BROKEN: +// 1. audio scaling causes crash +// 2. popping on sample playback +// 3. actual app sequence (start at idle?) // // AUDIO: -// 2. dac switching // 3. amp_en control -// 4. write sequences -// 6. multiple sequences // // LED: // diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index b3aa5ae..dac3140 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -31,7 +31,7 @@ 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, Pin, Pull}; +use hal::gpio::{AnyPin, Input, Level, Output, Pin, Pull}; use hal::time::Hertz; use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; @@ -190,8 +190,9 @@ fn app_main(mut p: hal::Peripherals) -> ! { CountingMode::default(), ); - pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); + pwm.set_polarity(led0_ch, OutputPolarity::ActiveHigh); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); + pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow); let tick_rate_hz = 50000; @@ -224,8 +225,11 @@ fn app_main(mut p: hal::Peripherals) -> ! { let extra_io_1 = p.PD0; let extra_io_2 = p.PD3; + let mut amp_en_output = Output::new(amp_en, Level::Low, Default::default()); + amp_en_output.set_low(); + // set up interrupts - unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), false, true) }; + 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) @@ -247,8 +251,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { batt_adc_timer_ms: 10000, usb_adc_timer_ms: 10000, led0_timer_ms: 100, - led1_timer_ms: 300, - led2_timer_ms: 1100, + led1_timer_ms: 100, + led2_timer_ms: 100, }; let app_config = Config { @@ -460,9 +464,9 @@ fn main() -> ! { let mut p = hal::init(config); // delay to let the debugger attach - println!("pre"); + // println!("pre"); riscv::asm::delay(20_000_000); - println!("post"); + // println!("post"); // debug_main(p); app_main(p); From 4e9428cb5fbee7402b0dd1bdf30740e5120a0783 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 15:10:08 -0700 Subject: [PATCH 117/148] fix debouncer + other misc fixes for prod board --- ch32v-insert-coin/src/app.rs | 10 +++++----- ch32v-insert-coin/src/main.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index fb7e0fc..00b764e 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -48,10 +48,10 @@ mod settings { pub fn as_brightness_divisor(&self) -> u8 { match self { Self::Off => u8::MAX, - Self::Low => 4, - Self::Medium => 3, - Self::High => 2, - Self::Maximum => 1, + Self::Low => 8, + Self::Medium => 6, + Self::High => 4, + Self::Maximum => 2, } } } @@ -459,7 +459,7 @@ impl App { if self.services.synth0.need_service() { let out = match self.services.synth0.service() { - Some(value) => value / 4, + Some(value) => value / 10, // Some(value) => value / self.settings.volume.as_volume_divisor(), None => 0, }; diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index dac3140..c14a77a 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -237,7 +237,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { DebouncedGPIO::new(sense_coin_pin.degrade(), core_config.tick_rate_hz, 100); // main button debouncer (100ms) let mut main_btn_input = - DebouncedGPIO::new(main_btn_pin.degrade(), core_config.tick_rate_hz, 100); + 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); @@ -398,8 +398,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { // println!("main button", value); match value { - true => app.main_button_release(), - false => app.main_button_press(), + true => app.main_button_press(), + false => app.main_button_release(), } } From 935129baede8dba930a5daabf7f3ad39873f19d0 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 16:54:46 -0700 Subject: [PATCH 118/148] insert coin noise works but crashes at end --- ch32v-insert-coin/audio/coin3.raw | Bin 0 -> 7999 bytes ch32v-insert-coin/audio/coin4.raw | Bin 0 -> 7164 bytes ch32v-insert-coin/audio/coin5.raw | Bin 0 -> 6224 bytes ch32v-insert-coin/src/app.rs | 2 +- .../src/insert_coin/services/dac.rs | 6 +++++- ch32v-insert-coin/src/main.rs | 2 +- 6 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 ch32v-insert-coin/audio/coin3.raw create mode 100644 ch32v-insert-coin/audio/coin4.raw create mode 100644 ch32v-insert-coin/audio/coin5.raw diff --git a/ch32v-insert-coin/audio/coin3.raw b/ch32v-insert-coin/audio/coin3.raw new file mode 100644 index 0000000000000000000000000000000000000000..8c590d9611af6e92db2156177783344b25d97ae2 GIT binary patch literal 7999 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 literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/audio/coin5.raw b/ch32v-insert-coin/audio/coin5.raw new file mode 100644 index 0000000000000000000000000000000000000000..922bb2cc6a704c38f152b3f470fbf90d286690a3 GIT binary patch literal 6224 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

  • g+8qH~Evu@8&aoXMuE zJ-GPAt|(e?C5UZJR0)^?7ewjkX3^t)UL>&#dNue!jl_TKjY7Xz-sTaegDZJwHGHi;w>w_rJK=f64zHJn%m@0sR{Q literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 00b764e..9703585 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -470,7 +470,7 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - let out = self.services.sample_player.get_amplitude() / 3; + let out = self.services.sample_player.get_amplitude() / 2; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); diff --git a/ch32v-insert-coin/src/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index d262a14..0a3b364 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -59,6 +59,10 @@ impl<'a> TickService for DacService<'a> { fn service(&self) { let mut tc = self.service_data.borrow_mut(); tc.ticks_remaining = tc.ticks_per_service; - self.set_amplitude(self.dpcm_decoder.borrow_mut().output_next()); + 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/main.rs b/ch32v-insert-coin/src/main.rs index c14a77a..d29f7d5 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -265,7 +265,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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/coin.raw"); + 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); From a7cd209989d0bc55f27fe9446a1e563c067a3c32 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 17:46:41 -0700 Subject: [PATCH 119/148] do interrupt handling for sleep --- ch32v-insert-coin/src/app.rs | 55 ++++++++----- .../src/insert_coin/insert_coin.rs | 6 +- ch32v-insert-coin/src/main.rs | 78 +++++++++++++------ 3 files changed, 93 insertions(+), 46 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 9703585..4cb1053 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -372,16 +372,18 @@ impl App { pub fn service(&mut self) { // timers if self.timers.sp_timer.need_service() { - self.timers.batt_adc_timer.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.batt_adc_timer.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(); @@ -470,7 +472,7 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - let out = self.services.sample_player.get_amplitude() / 2; + let out = self.services.sample_player.get_amplitude() / 5; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); @@ -480,6 +482,20 @@ impl App { // 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); + self.interfaces + .pwm_core + .write_amplitude(self.services.led2.channel, 0); + self.interfaces + .pwm_core + .disable(ch32_hal::timer::Channel::Ch4); + } pub fn volume_button(&mut self) { self.settings.volume.next(); #[cfg(feature = "enable_print")] @@ -498,26 +514,29 @@ impl App { self.timers.lp_timer.reset(); self.timers.sp_timer.enable(true); self.timers.lp_timer.enable(true); + self.main_button_click(); } pub fn main_button_release(&mut self) { // TODO - #[cfg(feature = "enable_print")] - println!("main button release"); - match ( - self.timers.sp_timer.is_enabled(), - self.timers.lp_timer.is_enabled(), - ) { - // 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 - _ => {} - } + // #[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")] diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index 426c174..d453bea 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -37,9 +37,9 @@ impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { self.pwm.borrow_mut().set_duty(ch, dc); } - // pub fn disable(&self, ch: Channel) { - // self.pwm.borrow_mut().disable(ch); - // } + pub fn disable(&self, ch: Channel) { + self.pwm.borrow_mut().disable(ch); + } } // pub struct SimplePwmHandle { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index d29f7d5..633ad7b 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -246,8 +246,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { DebouncedGPIO::new(light_ctrl_btn_pin.degrade(), core_config.tick_rate_hz, 100); let timer_config = TimerConfig { - sp_timer_ms: 2000, - lp_timer_ms: 5000, + sp_timer_ms: 1000, + lp_timer_ms: 3000, batt_adc_timer_ms: 10000, usb_adc_timer_ms: 10000, led0_timer_ms: 100, @@ -418,29 +418,57 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.service(); - // match app.get_state() { - // // enter standby - // app::State::DeepSleep => { - // unsafe { system::enter_standby() }; - // loop { - // riscv::asm::wfi(); - // let mut config = hal::Config::default(); - // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - // unsafe { - // hal::rcc::init(config.rcc); - // } - // unsafe { - // #[allow(static_mut_refs)] - // if INPUT_FLAGS.sense_coin_flag.active() { - // app.set_state(State::Active); - // break; - // } - // }; - // } - // } - // // for everything else, don't do anything - // _ => {} - // } + match app.get_state() { + // enter standby + app::State::DeepSleep => { + app.shut_down(); + + loop { + unsafe { system::enter_standby() }; + unsafe { + #[allow(static_mut_refs)] + INPUT_FLAGS.sense_coin_flag.clear(); + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.clear(); + } + riscv::asm::wfi(); + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { + hal::rcc::init(config.rcc); + } + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.sense_coin_flag.active() + || (INPUT_FLAGS.main_btn_flag.active() + && main_btn_input.is_high_immediate()) + { + 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); + } + + app.set_state(State::Active); + + break; + } + } + } + } + // for everything else, don't do anything + _ => {} + } } } // // if adc1_timer.need_service() { From 713f3882a20ee56016dc5c596aa693dfd442ca98 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 18:42:20 -0700 Subject: [PATCH 120/148] add breathing --- ch32v-insert-coin/src/app.rs | 59 +++++++++++++++++++++++++---------- ch32v-insert-coin/src/main.rs | 2 +- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 4cb1053..c48a073 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -312,6 +312,7 @@ pub struct App { interfaces: Interfaces, } +use settings::Level; impl App { pub fn new( config: Config, @@ -396,29 +397,53 @@ impl App { 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.sequences.led0.next(); - self.services.led0.set_amplitude( - self.sequences.led0.get_value() / self.settings.brightness.as_brightness_divisor(), - ); + 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.sequences.led1.next(); - self.services.led1.set_amplitude( - self.sequences.led1.get_value() / self.settings.brightness.as_brightness_divisor(), - ); + self.services.led1.set_amplitude(out); + // #[cfg(feature = "enable_print")] // println!("led1 service"); } 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.sequences.led2.next(); - self.services.led2.set_amplitude( - self.sequences.led2.get_value() / self.settings.brightness.as_brightness_divisor(), - ); + self.services.led2.set_amplitude(out); + // #[cfg(feature = "enable_print")] // println!("led2 service"); } @@ -461,8 +486,7 @@ impl App { if self.services.synth0.need_service() { let out = match self.services.synth0.service() { - Some(value) => value / 10, - // Some(value) => value / self.settings.volume.as_volume_divisor(), + Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), None => 0, }; self.interfaces @@ -472,7 +496,7 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - let out = self.services.sample_player.get_amplitude() / 5; + let out = self.services.sample_player.get_amplitude() / 2; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); @@ -541,7 +565,10 @@ impl App { pub fn coin_detect(&mut self) { #[cfg(feature = "enable_print")] println!("coin detect"); - self.services.sample_player.play_sample(); + // self.services.sample_player.play_sample(); + self.services + .sequencer + .play_sequence(&crate::sequences::COIN_CHIRP, 0); } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 633ad7b..1e9e74c 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -234,7 +234,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { // coin debouncer (100ms) let mut sense_coin_input = - DebouncedGPIO::new(sense_coin_pin.degrade(), core_config.tick_rate_hz, 100); + 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); From 3d8ddd131773fa04e70c0a3c72b6a10b754d30a6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 19:12:06 -0700 Subject: [PATCH 121/148] repo cleanup --- ch32v-insert-coin/bins.zip | Bin 42532 -> 0 bytes ch32v-insert-coin/bins/README.md | 4 ---- ch32v-insert-coin/bins/coin_sound_16ksps.bin | Bin 38180 -> 0 bytes ch32v-insert-coin/bins/coin_sound_6ksps.bin | Bin 34684 -> 0 bytes ch32v-insert-coin/bins/coin_sound_8ksps.bin | Bin 38780 -> 0 bytes ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- ch32v-insert-coin/ext/wavetable-synth | 2 +- 7 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 ch32v-insert-coin/bins.zip delete mode 100644 ch32v-insert-coin/bins/README.md delete mode 100755 ch32v-insert-coin/bins/coin_sound_16ksps.bin delete mode 100755 ch32v-insert-coin/bins/coin_sound_6ksps.bin delete mode 100755 ch32v-insert-coin/bins/coin_sound_8ksps.bin diff --git a/ch32v-insert-coin/bins.zip b/ch32v-insert-coin/bins.zip deleted file mode 100644 index b77a19472ecdde2ae3f2cde08d2e7e7a7431858f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42532 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 diff --git a/ch32v-insert-coin/bins/README.md b/ch32v-insert-coin/bins/README.md deleted file mode 100644 index c548cd9..0000000 --- a/ch32v-insert-coin/bins/README.md +++ /dev/null @@ -1,4 +0,0 @@ -TO FLASH / RUN: - -`wlink -v flash --enable-sdi-print --watch-serial bins/coin_sound_8ksps.bin` - diff --git a/ch32v-insert-coin/bins/coin_sound_16ksps.bin b/ch32v-insert-coin/bins/coin_sound_16ksps.bin deleted file mode 100755 index c003c2a6ea54f4dba7bcf3ba1581d84b7f44627d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38180 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

    Y4jn9UtT& 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 diff --git a/ch32v-insert-coin/bins/coin_sound_6ksps.bin b/ch32v-insert-coin/bins/coin_sound_6ksps.bin deleted file mode 100755 index 9796caf8c7bb95d54477ed8ecfb15d269669b815..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34684 zcmeHw3v^W1b@sV4b4LOT&@hq!*LFr4AhueM=l$SM=}4M85~*xUAYr>rB1U&cpjbkn z2ey+?>oo#776uOz#?3?G6{@y0mJCYM*pXxj1i=IdV8?Y*Y=RRvc4KS{#}BOk+xH<2 z6P$>D^204z?GUd{+_ZX+7ow(OW8JmH-lbKi& zyPeHqH_7x=g$XyE^5H29`BHfk0cIlFTN?QjPk$$iv3HP9pJ1l^iKlx5jCmKzpZil7 zqYuea<-fCBDrEdQmzN6HU!i>FWJB zp7~SyE!;oBCj~NZ!RIgV`2s$L_^7;p?Eb1E=a2CaO}!|+-k=}+9UGj{>^E?A%w&~u z_w1{2aZ3U&|EUbMcMF_oHlvu!B*q-^q<+3UwyF@m+s; zfDbh{%G`sSat!*zStE;wJ$mMs%UK3>U-So!qwi0|#?x5xvG&m&sU(BlWgK6~W+=#H z*|zzaMpwK-sWZDUE}$mW6pp7~qO3L4|ebS{=L&WxRUE~n|v4tHn2 z>n!W*lcXcplvLiQvy{~CD+VQ{y^^k^#7`C|sTV%3TV!&IeqN*J`KD(C@f!!(OddL$ zV$snVmd;ypcD~TOiR+$crkZwNB462I0JVv0oCiNNa!p0^@Y0Eq7f$YwN=1JZo}6tS(hB3^zCa!)t{(+91!RjPBO+ELOUb_3{H5oc~Z~%id_?#>aYs;@8iy{B20IsB^zfWN)|tj;1=9pa#v#_9{wy;FFHbaV zIn|Le+OmlE^Fc>O7dzEqHXZc0EQQC@iXk!qspjVra6W&J8T)TAJ&YiEh=C|N{hNT=e$P!78T6wpxZdE z)A29LB}c!=2Q|mG3E3F!mTQ(h-)d9J?nc>NI?qLRzNgWgz1=^+r^0s*-cGtT3wKZH z8R2!L=aWU6l6M1W+hj}62hr|#+J3(4rG00i&-rH}nL%?lKie2hcWq4Hc;!8pUh*$( zaFLsT?1E7r=xOJJW^UBoq>NNgn#P}8=+Se2VcgPwGaKP3lIh(iy;C!gjo{F3PFch2Ao1~Y|1|1<3uzO(mJ z&mxKM19pD>hD$r6e9BOvpKLd<;Z==Me|g=d4#+SS4#s$sxdCq+Mz)5O}<(0z1*u`{Kcs!+m?kQs_1&C$-4|k@FX%|JUk0bT?p!rFiWw(TxeGDgY2Lm^v$53NwMVlVK4FXqZ1dppvCaMg^g{K{&4ZZ{^V54Y zvxKn`BhPf^U3+iGCv~&`bZJjJS!na1YPlYPMr(?sQOfbcfTY<-Vc^4^g(H+Ju$1F; z#|V_~nv~-Myq|gAVCT&@>HTKYq0|Ask?o#LGtQi%<}=OiskF{KYcfB1_f$I7JX_8$ zEXZt^>K{{Mg$EWe^00t3B23R=%wp`*EVDUR*sw8UeaD>@#T}c62QWUy{AgRl_I9qafNhfBt_s!k=P%iT^xg4jAMbwhc`Rfo7 zHs=iJFHKy@aE~-}4CrrSr5&4xuEkFpB^TO*Mt$2zaR>Y_P~1^4QUE_Swk54wkF;uU zj}s$#k#3N0zhLC&Q|d2wVpI%L3R-+PmFD$JUonh&(zPtc^Jg-TqPl5CKyLQ~VA@JxX1eKmN&p3oYNv4;v%A$=tnnSWYj(oV9mYUIBPA zFpBRXdh&^aAl(ZN7X+nV{f~mfss#rYLQZ$UZ{DSYyjK6f_-hlf=-EfBI|lgMFs99W z5Eu83pE|!orj1H8N*mg%e}VFW7~i8ESd1B>tzxSOcg(Ye zGXl-8{GK!4+N0rx14j-W?K{2e%n#4?zO2SpBfoz3$y8)~P_MYxL^Bb=B+<|LKJAbG6Ilgv*Fe~4j z{F<(6{sEMFJoy01DYMw}=2za~+JolVJ(>fje|V;&jqF0A*NPvaf4!oEX(b;LK1LE5q2HE5@(c6w#jxPC$QRmm2Q;YRx zj?){Hv@6W|o|({L=I*4PnY)v@na!uox*mP=xxSbCUTJ={`Inj(%rilq`OHl7O{Zq= zzUk6Tb4t_9-6^=w0-lBZ*`UwHeGc#(eGk@z91+JAa?C;nuWU}1Q~s}Lh1Ybp7q!AEU5w96Iq-t4!`@d8pjI>2lOos5ztwwWhR}O- zjO)cnxK>+NeIy!re{HFl#{2aGwUCoqs1-8xi(ZThK|vcZ=y#)U!MAj!VALB@`d*O7 z`@UEDP#fuqGSE}0^LJBq9=K+mruSf9@3Il>A@LJYt{Iz%s{}<@t zmr3LCz1b(_HjIC#Z)IYAno`5j+)}&s2a#Hhh-%7*i+*0u-{ua_w zeDF*%&DEGo{8>S)iM#hAt;-qU&(ln>pJswU9=G#W-rOtC0FY^0CNP z$;S+;k449)`uo~GW;z4^zIn2@U(pIpnlB#}=R{M>1-^a8(dEIZzC~XhdtHdOw;#>D zI(O`}5IdO8;0s6R3O$1O=)sKawt@xr&KTb{!RxgGc0ZtBNr!}Cv%U$@tp`@42$3ro zUFqy~p}Hges7C0do#ou@wlz}O!--`FcPAmv!Nyn(@lnJO)z)n3!EQc?-H21!z3t^s z@jKV^=KXt5(&}DW*CUF_QD!vQd!$c^X4mqi!QtYInVV_Mt7iaptSD7G%DW@K-~(S) zBIWtdEicVJN0G?&yy`o``+wYbOwY}FlPrL>t>iOG43<2m+lJVAX0k6%_pQ9J?#wTr zJ19p!zp}Sl^|s<)k3xnRCHpG_9&@Ue`ho3$FdGD9*J!jpX1SEeUcmNbTf|! zGkMac%RlUVIR}09^?{kO^v|>Y`wp_6Z@&23*p9{QNxghNF=CyY)W7Mne_PP9qrdAA zdwzTW2m9EPuBIdNgNASNziSWncOF8oFB(k_&S<}L$ir*=g+uZjyg4w3SvA<}@N-zI zcJoNnQS=VZQZl=Vt3=^ylvcj*iEbU^j;92&2b((|M_%KgCU|mi=g@JXxpS_d&acCM zdtg_fn3HzMvc+oT_OqI){iZ3^xV~vZgMF|MYioA?R-v!WepnRuv2O?6n3oKkWyIYk z+mt2Gr8No56;21W`vP+sKHVk!u+tt~u*LjOXJ%vE&8~cx6O3HzmH3@s$e>Fmj7Nch# zGrHaEku7gOJyVYrGIF*1lB_M~ay7>LGPtGs*gQQq)biYxzAne1!_2-zY`Lg$xCU7I zYn;%scbL7s)4Z>bQA;b@P0Lq~9))$^Fh4=}3Xj&AKD{w{}W94AVS$Zlrdw~MJK3(bwH z4`;V(1%bJSk^@+0%uOz65n6Rd{rH5#HOe-oukT16Yk#pt*sjwC_h2O#&?#eNu34wk zX@zHXGe8{y^-LV~eClpqr%Mi?RgG8D#q|egrxa`(o$|8Kn+1Wwg5ScAz!oezAG4zP-Z{-QzcA zl(JaHBDQVIZM?fvme&1m%>wrECj%!7@9(%6(+6~Doz{qPPCjSOhTpyFj;bx0ho51s zW?GG0cGW+~wpsNB#YRii^UUnlWb7H~6ifX2*_i{hpVFtZw855rNyEk+ix^IDIxn)f zw?tom;LVMt$%ERxNyC_ry1yr#Rj@{$hZX)@s31RA}Xgt;1 z8pC2n*2MoWS4vX`X>ZkXc62z(i*M}J9BsTPh`)bU(=5csN9TCloWHNr{PbSb!1Kw2 zQ>U6n{-hH7R^27H!*5aA_xF}IbpL`=At`hUJ!+ zo^M8Ux)c|ko)n(%%*wy@2{k&cfA0^YliHh>R@&(<66AQF^oh8>6YEjGez6jvR_#zJ zvGhfZylmE%#a$KpQ1g}&(fdh?TQLnQFWKLs(h;73*e29HBmR<_&7eV`2 zdIQt*eVraR?RfQ{*gKnp{n11I!~9TFUnfpt8R^?{m}f1c*iY-x7wBDc(kb-GGiaml zH~+Bz4nd6Uk?f>ua~|id8PH~-q)9y^EoOBn9`;EP+E}D0K(ft{L;c$koB?}v26we` zBgRf0#}BpXKHRLV!9Q6%X}4z-yQRZ!&nR}oe5dYi?|I$7JTM$sk9k7&@kYjV%c=7d z@_p6B)Y))fTXsvbfEi_eLnc8jC!EQ@K`Y-19ebD&ipZ3>E;MsC)&>X<0#n6WjD zZQF0Ge}9fxUau3yU+LT}El1Lr_Q*om*2y;L?re;G@PXF&&=EN-H{%4cqmLbE)o#+UsLuHLq)&|b^VpKk3s04Ndh;L`m~rCL(_LLH zt$OS)VtaI-7@^&g?NR;4%a-8*p5r|F37m|ir($J0$8tD(`=uAU8e=?5SsE38H2|+f z>Uh?P>|-zShhbYX!3RiUENJVPH?n#`!ibjZZ43$$(9(B%I8DEzKbdu6@$p*(OKYra9{aGF&p(iaI3c{mn*NIm z81~P|7dziz#xvY1xAz(rUbww8HbM zENB(g*@e@RRvyGDTlZX{S?K2v`j-pMMYVy~ak8I-ll=p2v|}*_@jU9HJkF!ZcXTVM zkKIvaRvDWbd6+fNvo9jrYqG~I$J{stDIF{LV&uhGI{X-sRhMfYP7T^eHm2X9>)0}z zpVh?k*@N43?!#OC+_BTaT&%H1CeFa7)vswwe>K5jcQ4+05H?%LX7jrQM3_!3%a!{TPL)O!mMTS9>~bb&)$)o zcYmH6r&G@`^XGDHyZR;)w`i4kT7M#t;_~=AOQUv<;3!mHb$=qOb5c&lRa%q*6l56a8OS6}yD|U zu!H!7hV|=KAev}5=~+~-#)2Or7Ib}T+1_&I2ou+BPri*8bNQL};hRS?4mUj>Lqr~G z!ZWwP(&$pf+t0sDb!8tDXN=CY;8}~`pFOrKIybm0XwR3&gWvGLn^}iT0=dHLLM>X7 zb5Ztw4SD|w^8RfRWJO6%oElR?S!Ujq`!<$j42s_j>reW(`$f%4jpN`j z_B4y5w;fikxaz~qyuQ5V+$>>Nl;1yBaNv1+GS;u|=z4NU1(OuE0`J3yOqlReW zq<0e*9~%mamFH&m>%0B2cipT%sT4bkah9>Ew-qb5ppI>vXKp-$`Hk0Af85_v${a~6 z{l@wuc*DTK`r9_Ky)R}BE*a+Xw*@p~9gn9b8{bc3{pE+4VMKda%gl|#Sl701ZW?p6 zU0~V_Jr2 zqc^bf;4#yi8{a?#i1O7R(fb;wr8hQuTg3Wnbiw&~r8oyQJE+qAGZcE4htp7pg{Nx%4E|M+`2wd?=c&)9dO z&5g?3aaq3a{tx-y!noj!qj>LwdtSzftTOX;dZCF=-`yDf- zztu5^F?L^YnJMqydxFdIOP31)Q^CDOLa?;3@PV1272#>k0^BRtR@RrTURPODR$03q zgnH!9M!r~6xz>cNOyoU|JStP(P+wo8>`m1~!nOy4j`!q?T*)-N%=s>pr=cdA$AU)NB##-#GgK;loqTMeWO zmh)Hfmq%7tt_9%>$k(g&RaAvmn?hoJ<%S5ADFn@cyWxMu#l>q0kebRFQaP^pDV@ML zjw{j`QVj;i<&>7BaZJ}<`mG{^&?pq8rEk*-Tn^Kvax>Bl#eP4E&L~df@DBou`5g2; z)1@;%V}3)rf%m6!JmXS1EE^b0EkLlL+ET7Zkb=ugLxB&119Dl#}?Fchcy(I^tfv$Rx{gGPQT zMX~@XDcgd0B+XzJ5K%M9!Hwu>47^TeMW$3I-PJ19WS&FzRxoYEO0u@DhQWdqJ{rD$WhHt5FsXl51gyKssEq|lv zlvqg|q*0me^Gk9^IuGd-f8J2{uya_L4u{06f>Q!xCoXk zVZ{b?Fxd`WNji~A@QXC5Ig*M}7V?A=Qbg*sRF=b|#++h*dMbR0I*xAC9*7~0B&(o+ zl%kBBWn3}enwl;sf(=cj#U-q<*dU;lImL#p^SLy>h?VI4#U!Gb%}+g*=BJ)VPvjWV zAqoaJ@Qp&cz~ApLPS-(ZdI?_=NJp+eExnlKke>=vx`<)b578W#hCh&`D=G4GC2#^R zE!}{s(3N=IijhgBZim5((_kTtBB%=!AdO2lv6Q6lB?{5-l~arc!|jnrt%UHKk<}XXbEeEC% ziv7aNEDejems8reB`=`ijAnBFZJZzezdeoAC_z^-gTF`+Qd9Z+QA19tzc@9AIee&2uH_vo!PrI)#H4teCr>F%S%88rk=q259gZ6yZu8mg1534)Y~frCrl!w=wx6aX0IQ`b{C$0cb*3Q?{sJl#)q z(1lz?Yf8u(lQxtOQ52;pYD(&X5B30w6ei8dRjDbYFqNkw&;}fo0bv*oQYGNWm_R;} zNI1DYFohaa0a0L95|V znufJvBrGlBuR;$%!wObulr`2V3 zTRm2<)n~KXY&N^iVRPDCHn+`V^V)oNtKDX|+Z}eN-DP*%J$A3%=de0#4!gtQa5`KL zx5MM`I($y6)8@209Zsjy<#anePOsDFvbt<8yUXEnx?C=|%j5F8d~U1T=C->XZl~Mj zcDp@puiNLbdTbuM$Ki2$TpqW_Y)`f&Whym{6`i5I^^8tgv$JgOS=LrozI2$!@dQEDqD{cZq8v;)68} z^>@|Pig#62ZK%F;U2V-*5ey_?a#nbpI(BIy8<2jRBj;+^ zol|6_I7TAqZ*j_(mVF@s{}u4>AA!FXhkpQ!117cscMTN#NWr+_(y}Y~F1Q&w;!gBz7-)Xx6b=J_ z0RgyAp?^ogfVi}*2N(~9r1G>&Jr3NB7(X9W!XE&iMh_L>PV#Sqt(L~sR}B1LkpG|H zCwhm1Njk~j3j9=@{C@*pg|k-Dm&578ao~T7qkjNg#W53=CW#*b7sSyQ!WJ*f6A9>A z_IY6Rj|nP39?wdFk6}J3luB#a4q!bFXS|C39|0eSJZwIs_ANreHE8U26n;G9V(){l zw!aT};i4)03t)FT=59saUqP1u=yH2>?5n`5;&3PM8sOWomm+%(0)Gp0waWin;9l7G zcBOtNCdWb0RsG6<--*MI0pEiDQOh3$w#MP}!1n{Ih$5`g4Z!4I zDoz6)guSSJWUr;bH-CMXbR_PxAQ~#^@BjBmu zQzBjhKZY2f((#9%C(n4PJ=qGD={j~NFvTAgTY=T{UgG~71*`Ng1Jig@@hV{T9GLjO zsbH191(@Qminjqj4h*-F?E51HtMnfOQ#@7iFtB=lO!D7Wuu6Xi_@CnV-vd_9lZpSf z*)mr7GlA8$J<$sltkUlVJ{~7;Ik0-}O#B-atnxPktLM=~|3?L@^aH>YZ&myZuzD^{ z{I4olrT+)8dR|TRnR8^U(q{ovyjF24uzHS7{QpP6Dt#$1#cvho0IO%)#9yUgm0kl( z@mru%V2b}L-UqDi^@#tJf>rt%V44q9{1ULb*CT!n0zF}s zj-S*?^M#6&fz|yU@jDc((%rx`pQzXetnLMgKdfMtz8aY38x?;QSluTQf2V>~`cuF( z->CTSfz>@E@gG*ONQ67Y!xd@2E-Nx&~9;8zmxFB0(i1biU@zmb67Ou%m?;NKk_U}FN#NWcpc@GS}Wa|t*r0WV6xw^vSh5weix#{5VYfxe5G29G&ylJAYf#9t0v zZ)WUCh@nGPoTK2EN@%sD-cRZ z(azc*gFwFZQ-Qx1G*0LJ)I*N z&|}Ad{lH&R@Oj|lz<}6;YuImq={$pMG8gz0;8)}59QK~aKtG_+=K{YKM>hdKjP(ue z2dMlK;7{V{1;EX5^m5=7<@|)|UkChn9Q`q1`nxp{C+YVTa913?5BM+Q=!3xBar9S! z>F?dB<$nX*8%Nh5eig>iQ-Pn2qi0X)H2#*C^|` zRIJ|)0n`4G`hPC;9RsHGsHd^7B>W=qm*U!g9{73aLrZk(zqxQE+K+0K^4Y-Og8rl; z`IASX14{A=67by#xHtjdmw^8^0hc9UF#$iAfa?1pHP49#6nGAzrHI1Jr-FB;dOe@X`cakbv(`z@mcH{{A{}xw8H@ zz@G%fm-zabh2A1#mNhs(7i+>1W~q-n3|w0i4%LU4MgA_Yt7DeB`jGe_vxqg-)seNt zLdWTrPSqj&QwUMf_HI>$@p)HPNk4rn^($2WFD=zJhOMBwd-ng_c zE^V{2vcI{<=J^BNZH|EYBsITi*}Wb#YD1+Mv3b_mM0>cxQ66?ygu_;Eg;OByA;c-x z)JA;kLTf9<2P@aEc1bDlS5(+S;jrk%vp$dL@RbXb%uX>)BzG}#N&=QtmO`LqbK5Uddq#DkWCER ztPy(!o`Je8HqT^Z92M2|u7_$X>&Zs$3a8swQQ@-U`KR4s^G>zdjfcn$RrNMU!`g>x zL+i>aLX}lgW8LA1$798F&WNqTX>~y5&*&JJSQQD?PQ6iy>U>UD$m{Vq&>XkN;kI30 zbIF&=?vB*1j)e28YRd6&Rc-&DHkC(q-RT+Dsd$Q)ysd0asLD|*itfC^d-MK!1$DQ_ z>h!oBVQUy|aD_Yqh6-#j$?Xb9szQ(0tkSKlvU**WD?mA3IisNtGF*{xIAjZZJ8@~08J_ybv9_Vwfr2i3*}}q-g(PBOgWbB2JlA3YgT)f4tXl_}Qg%g6t#og&xfkY_ zEF=l2B4qbPe4-U!K93C@EKJ*4cJEc*GO3O2`n2)JE90|y-OiB9R#EN^!850?jDteU z^!m-={w!a##YH>YRlv7Qii3FwgJkLw(m5`O;B5iXYmNA zr`JR$1<`4;%46Okby|5%jqF09y3g{YDbEReBN*KdcLm%k1K@C2K$13729lSP#iO?&nF36)=*zrRcCX{fPUPt8ow`ygk8l%wYem*k%!k+%D*~*vY3wS zPM6i{4Tas|a711;$!5IPisq_V8V~ID6=c^)(L>dFn6)Njt4&^el!ZX7tZxX@D#T+e zM|)g$yVvG)M_ibx;#O+XdPVATrMu-}LWEYd1e8my$2 zFIZDsjWu_q)`rDlSX%19zE!Bm9=6+T5lryDu+``DB7~xr`2Wyh*O23Y80v(O*MS$> zu)d18ZFaZp%hxX9L5VPyu_$HrU}T0O5s%XfZ>2p%2cs;TOp_+{R^kAf{K2-N0 zih3|-(M|>n4X4*;_o=$b_MdJjhdILZ-*ox0#JnzkY-z5u!1Zcmb6;l-cB#O08*IPc z7CWRCPglGH8_?@Ar~~Wv>35gIb-m(!*IB@6R|;H*R?h3~GUxSbmQ|A@kqCH}Bdh9Nj53f23a;*P< zDtYxf=nuDp&Z4qXsuPp9*NPQ^SWY|ekc;a4lg;@b$beI*bvPZUEh}%Rtg6RW%Yua- zcAR048}D1Y@Y<+!pk$RN7->vTw+IY^dGor8sLnLqD%9~XU4GT~rpvDmpXu_e5n#If zY80L>zdAan%dZBQY4T5=)FnEF1ItgFuWnsLti5glSHx`>eGzZOHMs=6E;qntidF^~rqC4yYj`!J$nu_{|LbVZwQ$GB<2)|&5+{!w{pVez4;rQGjS%598 zs*0=*RmE|$!kSPxt_VSycLlZ{kvNKkWs+i0XfFRTfk}f3cjxA;Sn)tvKv=mfPbe!A zmM<$R5SHf@VlYd~ls^cX=qtC}Usy1R__dSA7sBt3y!1>1+t<%11=n;^Z~G=Yol5&W z-iX~P;%87eJQC$IXDo0M>vTyElf0^X&_cy&$4{OjE^Hs|t_Yo3DJ@hRMo%oKcX42& z4_;N5FT5kCKGUTPdR9gkUOiX%Oax8%N6&UAFSQI_nVOVG&ywHC!aLHLlWdc+@hDc} zqgPtI3-JlyT|+##RWDT@y}nHP(QCr=toWVf@^A4}K6-b7E_&^g(!@v4kW*IR*(K8I zrOL}jhANMqWfNcgMKro>_)uDTT+N6tMg-*|8hw`FgF4qK+3dUEyIlpitMUqwjx!e< x1K&G%UQbQF15BhJOwUU2p*E=355CG+g$i(2<5`=ih7Xwbx#I?X@3g&+rsq5M&reP2@{O-Y1Ow?8LpclaLhLoy0_v z$RaX>+$Pa=G81kZ`NPsAq>K4Y6c7`oy{(cy@$`4I2zeLj^z)TW=@U=CKS0R+^QF(K zS%lCJm8Hmkw@l1y>UI!v2a=z_M=R5duGEIl)+1WT=ZGF=yM{bG$l=;EBE7g z=TD#C#{E-#(jfB=eD1;LetZh>QF#B*{WV$6AL5}j<)Y6U^xDDSlEIWlPS4a3lU2gq zGjGK8b8p0^1UxwBM*6A*OcHQ+#*O@30=}58&>6kcdkgr;&%W+aoZF|qaUab*EE z)L1WZlUBc8dpK)k;jl+bI4PfHQ1cZoXc+z7cyBp+)V-L4a5+-JBppQOlWm1SG& zi^uh?na2k-#1w1R+}9egUD{M!Khl~%z!I+M9IrAgPiJBo*NCB0%lI4aZFhI}yUvlm zPO0p{t6KxCcI${F{o)<-uCxX!PRq4;Tg7L#F{6^seOy-HxqzQ(tY`Q+26AyVd3l_b zpUmSa@>A1iGvues*Y)yK?)7x}Y4@k|E7rt?jq_WW21WTq^ zI`8oBe7SKm(>=#bOS*iO`pR}as0~d09QdJuX(*h7Lnj87Keb)V75&*9Ez-4zuM3}y zlBO({n7d1ep54Y?{?F0R=AezhnHvZSl`5-Bi|lM^Z)pgj!*N)v>%K++~>)rjJhoYT2g;KLpLgXa89G+ z^}~(z-@8=cR|i=pZFIMmC9#s_q?aAYV7Nn_O?#vDo1X3o3SU1@`p=CLHW+Q&oeaHG zMoDA+1|IE8epOG((6YGKpENN3>)NwfXBM8a4_lghn=||b2gDHkV7zY2>GrhIrUfj= z1|1n)zVNXao*{`xe z)v;}SHhR0|rg=?Ko1Awy^6t`ju8@m8_2%pjZh+OncMdM1x;63-OzIh7HB`^%3RT7L z1yHw%nws)Z?{{1NZPzRN&Ox7xFGezh=4^JRA)4;ml)mZu`!22MU+UljH~aMEbZwxg zjSZTaQFnvfQ$0o%mt5e{GMpijPLhx3=k!D#XwM%`hBm(+zn)&zJ_MXRn9MC^FaI_B zSw7O$1RR5&bQPE8M-*bvg7C@b*~`DA^bI>x*!;mvzJPnN?eYtIKl3aQ`935UH*CDR zGs>n76>!vc0~=pc8MGg)zuFENrh>s3OVqIz?Vx%7IjcGAkh^I?(&@}#>&!Ey^n(1%HnIFMC02M~ z0RsyQh&^J}kI`q*_vt9pn!|70l(C`x-twaM*5Lv4k1-B)tGo98*hJkn)uDEW*!rfu zS}vA!Z5A6bB(a}3u3aOG4=?V7+(YK>eaM&jw3LsL`yxt7XSfOk$m{6p(Sbk20SbF_Bhd#7ib3Aw#(`4 zT>A89JJBl!=@Yd0c=DOmE_qcy>Pc6V7|Wi`><&e6{~PLa)Jr6v=zcb)xi&sjAB!60 zK3%^>_LApWo!QtiY|hv-Ol3=6LZzFte;}5YSzaZU+_W$-;t|J+=sg|};+u*jm25F* zjf^yX89J$nwkK#v+B_8d)g1Ke-wno+c+wPL3CkKfjAV;O-}=ZiX%_(y~ENBz>|Sq{2-+#pUe-^ zd;a14pxCPak$+gR;J|#y=?-w_T^h)1<_4}^8jnTKJyq2{z}|&EZQg^pxOXH6aY*d> z=6!o|C?EBEO1sggOgkjns7RyF`nIZHAb%jn_NWIIVuWbfx7C9?+V+8zK;x?)G3Gma zR4jks$f2WsXLgCpTzn=eF@4oDel=Mp||x zOHnrZ(2QW?tA~#4I{KrZ?mE$Xrtj>pncnjUZYs@W5%g^BJbp%=Ir);Na_#}-dM5b* z^2wvvvc^~6W!i$~nLVllXMS|H@BE>g%IUsaxRYI*!ng5E5OXw)j2E}e>V~68I6H8X?l2bMvumv#Aw8M z0P@iOhv;~9Q-&BNlQHsjA42M3j`nwq3i}RmN{??GgS2D4$IW*!nwV;p_HMzDL`>hy zGiXS1d+rrJrd>6Y^lNreun zyOVlScPBHcjc3f79&O|KmoB{YYU68-zf`?!P6ajf#Z>ccL#exOyOe59Ye?OlhWiZQ z8AzWA`b^ws0nb7@X-H!OsuQZ8naPG!_s>=5%#cfEkI5}=dRkBCy|C$Evq!5Vaw`|p zsBG>Y)iwUC7p+I;py#8dMF_z8{SgmA939V1rl^ zd)5%W@^tnw1FLP#K4pmR>&_lqa%Rc-6h8Z`non}OMjL-7mB}xkEQ$K$yNptvR1%jW z+|E8&T|(uWNs?Qb!#=3x-z@1J-mPg6+fI0aEwJ+jHagbETh2;8HoE-4FSCbKFSC61 zaWy~3eK##niX6B9k@8f+Y&Jj04+dwlmyl!g{tGk5#D{r6aTjG0vRLK2sFI9j(R&>?^*v|G5%F^xAm;!M)^qdUcR}x~hG~ zVb$^&OJ~A|FcThL9)0?1yXBO9#pyQvokvwCW1u+P z1K874@Bfn7n#?1EKiFV5BJ;DYQAH12hx_^cEa1UDe0@e81=Im2a9j^V{ zz9O+aRf+y+P6^u<!&O2FiuQU$O*^4qtXtnBYkp2yO}o)4i{a?Y^A+k*$1d0g*x>p>yG?_4g8rLEiZm)SxNSJ8jJ3I zN%0(~oDoMQwojt>`s!92R}1+lKggcA_=T*jHSB>W$Y99+YFByuMoXjvT9> zf((`PYjTBR{SBTbo`xmUVdHNNnS^SspFsC^@lCH}QBdnkVIb`A3}>{AjJO z52VJ@zfAg993(y8Jn@^@_J!m*t+YomVx67Tzxe}hThOw-zv~crsiXhHePnT0!;!f` z{Wsa)wgvk;524)`j3x(D+U_0luqv)#NE(M*1G9)#h4l_Qi|Ev?BMnEIYT9Q-eE)j`NM3vw3BV9p>zTU4bG-TqR2uTPM|@ zteaeOnskN@4dW`Tg?*S`lZ$uqeJ%FGg0PQ#JLn!ZuzJQa;%<@WI?1!?yoBWNXM*Z| zfmwB*?c#sbX%EiZV*ZD-Gtu*AR=mgX2B!IX{OT{H*A8b5dVWVP7GTX&Fi8ERX)o{2 zZm~rBqVkxiN**)M$Ka3oTM0=X%;(s)x?gy(yO8F`ifQOMP8G;RP3Y>&oPEe*UbN4G z*~)#!uMDUNr+y_z6?-Lvc}Z*N!d_6LZ;QLj;Wwc9a`{Kc+wHYm8-lnJL-Qv~QUBnhc&f#Nc+5C%3%we5w|+WTdM1 zC0U!!=co)TGMFXW*c>f0)b!&meO-=2hlzc=&~!!Ra1D_3OAOz%cbL4h)4Z>b(3);q zZtfkmU}wbASnoKvk3>$hJn8`o=K1$+U7DFkYk-}qqp=by`F(d%y?Qsw`!avui>v## zI}RPeo>tG(0y{ux33qp^7qIs^T-UIxtu6CcZGm0_xc~>P6jd zR-;J{pjP$Q(}fKOXQt(E8=dsB&|CR|g8byMOILIS?BRj}W1*oloe1Omh~ZoY5jGu6 zwRbilx*4uz5cgop=#SenyBYq+-P;=S3$urli(|h_C(#DkA1-YhOItZRXzx7GEL`48 zdYki$ZTn{j``LY5ez7!qUTyCcX$P9qRtk-KwQcQ==pN3HQ9@!F3&^%Dcd_nHNm}>6 zH1b%>p9-8RSlNChrVVINJGBA*ocf$O8-DkiJF3)Vj+&6wM6HtYuKJ#2n^pJAHd?Ig zX=XPkW8Fx1vV^anOC6wVDs4J34mRye8a8ZSK(LF`d4;^QCHnd!Z*3|`9#rp58pep! z{atatg4DAttoXMFwTlO1EcOQmo6a2>c3r`42vK@P{psH37^X9lCfq+=FG(Au>#L@7 zqr*{Fcyq7nX#Evl_~-|!Mm~0Jbe6}>aDAQT=l7xnmQ5a<+}Sj+r{vhT>OQF+oJFqR z-&tCeS))|*%aO$BfXJDwIxKv!mtX`ZY+n0*!?}Y*|Bm6>HTBL0%y^P9zfYsI-P#_K zlGnE1@CwUNs-cT#K>w(s5xw=J*!fX1?>Dh4+L;pX5W(0+X5`K2j(zZf`e*}oRH&p` z67@8wLu5BN^m#Ng(HK=p?&KG@EFKYr?~*%%_CYZ|4NEmKz0`>4bTuwIJ;%S)nU#0v zvr2T@@c!qclhT@&X1dy4z)SJ|$UVY_PRvL-?Ls+1t=g_oV(AMA^|BdT7Iu|uLye2O zns_Zy2Q!G7fd!Z$5)HP!;p=V>E{rye#OkiiYB*8Oh8h-ks(Fm-88KE*4t1LA_oC*| zpQJzA+ZE-rqRL8bRoP^_>Gj&eUPK*zzy99-yLln9N3@fo%|-0F zW-JIkbJ-gDJ3AXK+_DGh*n}Fzir^=Hpg*7Jkpd z3A?>0+btb-dr`I<#ye$&`{3)`vcPa)1I7u-$Lk5xEj?Wvm+q^^C-;Z@TC$sxd5kD? zE4Bzp+C?*2A;_xyyzue9R3;#_YEACV2f8dRYF0~FtziNCsR24?cU+!({} zFB74j+~H_BM`CI+(~v}F9cb1@8-l{-k=xbH8Y1u-VrVv!ZTk(izndkL)oKLc4UN00 z>4=f2kIaW{on)it-ul>wAF2(Hf2i(Dx;^Qyo~3=^-?j{G@g(Vqu;opYp*cXFZ=up{ z-G=7*WFG!HNfbLkySjEY7k%8YE4JB1V(%RpcSjF&^|vhNVy`SDLR-@8>=DBUZo=?F zdmlN_tlq34QH|ltNuL_>=8(mmmv}W-V42u|1kk4bX0JM^wA% z1IzFL%P=18ICe+Ub(q`EvK-FtxcYKeeT*e(OQOOX1Mo^b9nU(MeJqvD9*aHB{(0xm zm;e*(Z+w@uG{@2dq@{C+UAS3oP9plGSnm7dhI2I}wn89()n3=0-F4YG@Y#@t)dVq^0d0+o%0Bb{HcS+I!z;KWb5zyiA1JJzDq3fo7&X zhoyV8x1-PA-Yj|6U{ZF=fL2df-hNV;ab9B?E8vnj%ki^(_OX4@tYBl;otK!vQ24qr zhmW*pk2&f($S{vLFsd?Mx0KZ^|T~+5;-nL)8FqfzosL7*2cT3zGgUt*dhw7hi z**Sn6_3PSGStl1Bzk|0l$GYZ_j~m&%14)Pz{41p4znFl2e@dRv`6e-(O(&V%f2-Zp zvf-JAz&|!EIhN+P9M1P=k7Pyt;Z4Tu!4TEzS~JxO@2s+*Ra9f=&xl%i5T|V2v-w88 zpFPMe;~NXB1FvIu-;dq>11)sTVhG}$)D@|p^S-;g<)?exQF&Auo9uZQH7}B{Alj?4 z$1KO(*a;~a%l}H`L@XVCjL53Vu@CEl_K{8LcWc_W%w%UYux$3=HjVr6R*pG#CYXac z*2wr-*tF`By5x;<2J3s_&V#Vod@_^W#UsL$@R%tzm1u~*yMb4igvV2Y!VLQMNVsDZ ze0#=cvRRm2sFzbdCOEHKy}Wr`y&z1Q7Vd$Jti0^)$+;_Y-Poaek(j?UMADK~+xvp| zzSX1Y$p~qF{K>ZN{Ge}$YD$7L*%%ugZ*PdQmcgvf?8DnMiF(d>Cz&hcAftfYrKxvw zGIm@{h^4&BKT$>djgb)he;z zM~DSopIJJZ&K@Dc`i|tgSRsc^wGZDul5x1 zYQg&!j>{g~6`dX26}0C`{ejaz@>bU2;y@1nI$w>N_^(LbucF?6l6wDp)X&oFqW5D( zA^KUgyFPa$H!Hd?`g}{qW=T?a+TB-P3uHy9oH#pd-jRDew4qtU>^Xq=4t_}XR3D?!sw#kw87eB0=!mgXG3 zm|rH>VZ+r1=s|l6`pQY#SHwD?&1hd)#`mEPk6gbp&=9Ej2{`%Z__WbRzF>62fHE&X z&^&;sp4}|W_<(#XKMU)h#l!Bw#_f&4*?F|BGxu*hM(cVgy0}G|k2a#;PdrbIY*L+8 zJtw{UFfNVNwGhJx3rXzFg~WJt-tf*VjrF<*j5*1}#YeCIo3xs2tXJj}2O2SpH({^k zz7f3r`|@`HR;`KX+Hgtt$S0+^^$qrOQO4?ljAqjEkzB%#E9n;79JZ43Ki#5`?cL%>^(Q> zPb$HR;u^`=+}n(qTTnwb%`w-X#rVc*s(#EkmyBc4Lnzalud}tY6vj7t4QYlrjZ=d zbYvN3(Pr&7Vu)?VyM!JR`_MpkUS{&(AF7P)>bxXkCT|dRUJu^U43V59Re*oiu{;BI z(Q%H|m&v7XI_7*x9@(;WXm(zIw22rtda&X>OoUI~#LR=mNN=ux6A>WFR((RxY@88K zZS*z?wFfa@cmy%Y&)>DVf5mr+aCv|%+3Yw(_a7^^unm1(Ro5Kb&yoJ7%N)x)($5j$ z?S73ANFu$B_ja>Q*E85)|7^Ug-+Y-f>`Ou}N6m5~{5*-gu$it`UuwdvK^5El-uTXA zhoUWN*c+=~Ay-TKny#l``MCeu``EYZ|M}0!3(>}UdF=Q=y6^rE>E1+`V9HUP{b1OJ z>n-nKwco;4eQXCmA?wzbR;;a!R9DxnuU&L^s`z^y_?Zr}BDmC)`_O~IrFkXG_<$+@ zp+Y`bQc&1u;inSHBrK{IhtShak-T=ZANS}#xVO_;q6OuBKwiRhKPgz}U z?K(Mi57N?*<`3kWgivKgS#_wk0>q7>%DTv6Q|b>vp9A{OaHqfJAYRoHeHlu|0nLWT!nvqUCkPk!Y=`lUx$+oRF~UvkLNFotgcuK0*7?1QeJswXtgOM z)K+Ya&^(2p>2cTp|8_C)71G56`V1Wh$Wgd}h=Mo2K~i$pDSXcirqh%ud^&kR%~ z3>b6}j5?qkWQSlpag>dvb)y%!bfN`l!=g5NWTz#`7kD8Me4z*I###z<{LrJ`a+2K7isWl=o+gE~+_3?x%|^g;pnPxLR4s0vV9R1Wr*Qgq;_ z)x{koT0we=Ng^xFB34sOm;NBDqB@mI86X!QhSzs!SR=zjOGd{dAGcIwr4_+{j3|rN z8y{Lz+5nVB>*Pu!S*S{~CX#|;-6m9m6kL){v|`lWv@B|6`X4O<)kCRh za%xT}19A8(n@Y^8NT$WcT{j}BoIb6eI~6{152naw*$1`jwK4>I=D)k`5!UFD%wDetv0*F~D}{8s=f_E-E4`b^4+} zk-r4p=39DFSnTKF#Co=v%w=^f2W6?s8nzgVCw-B=$moY0I5}BqWQ&Y?=mN{a{fmrD zF}#m@5Rz$CSYjkRtsix0^k{~5b!s>2fT)2~X=I^1$c~;R*+jGj9%)v{$Hf&5m( z6crJJYN0xmjxyj#R0mv=WHAR-30kad)t%UOI)%A#VaL`EV>Eg$+Rm@k7w*^^ApR3a zXK=-xcf59iWB3y(59o^vUt76#t}c4Cy{(;W-EnsAN{%~mq9}!Q?6~991uU&c+m|v$ z#jmX_)cDz>em%!sAdhww=Fe+ONzv=i&Yf%293`iDUAl3rt~jtGnm)=t0DmwR>(I8m zpV7}S8Vh&$b;dTlx`3|6d7LiF^DJXLHMejjqbmxqOcbrT)wpuoN~V4(ALWY*i+6M+ z>zKBNwt2j!2vt=P!=bvy(Z&(39yI>wIxUlJ%n0~L`#n_fSUL*F=? z%%eFOy z93*;?erceiB)Pbg%TM>in~HQAV+n(%=FxqM^+lXc@7I_3jTkU!lfmKqq=*=4;6@X1 zMKr{S?g3Tcl16bj82Gf;{;EftDOawJL-kyJE= zG*VI@XP9WoN>aFz@VqfSiuTkMK1zoljEsc={k)ZE4Lv5$MMh(ZhKhw?mP1vkv+^iy zWic&IEDym0su;n7kpg;1&loh&YsCNI=+yhER;(X2hb3sdrhS9xU~yQRrlOLN2r4a( zr%V)6N5lV;0mRxB84ERfT{In$2+>0a$Dy@Eag4s0f%{>T(@5AIJ2X_RUJs9im_mw* zslX_FLG0gD197^7O5+J7P^*fi(-2!^k1L4X@!s< z9HQwdmm~@$&?>;LCOWN3e>LM3IV&Nr^Ots`9x@De7|sF+q_g3KDH7>q1qOwVPnPxrqOE z>L1qdzb*d%ngDsdbHgg4!-h8p7#PEmvUPQ9g-BRj%wYYszOGhT{CpogcYsG(%S`f0 z5^v-tI@9ag8+Wp!IURg7?-clWt{yrQc$t>u}Jk|Q$az%WVcyt7Kdrk zeZrcE@YuS#+WTs%h5ITiH&)%dzIxr?U;{)1CTF?FS>|zuPER(?;y8&==M=i_dPc-n+J9^_tqsCryi7j*!P`D;Jz5 zY)`oEwOQ}Ax$h?aFGJDliW*@fVeZ5uzEEDh(P59+*IP>k`sZsx$hO}4x!Ta((}_z% zu8wB|(ck8f5>@2h2`YVH2o*v9Ee`2Yllv3!p96msNB=5ils0R`hZXpn9ESy9T^z>m zjFNle@YjKzadO=Nz4w3 zp8yxc(dWY!7o^QIQT~^K(LN@q0R34~0(=sCXa&GjpY6b@IGEv;_5TF;6y)JmgIM1M z$XJQWz994CfGhb3bfx}%z>5}4;$HwSPRD+wEbq^uOFrm|{r(0x9EUrBD}fi`a0j*T zAn<rF5RPmcpre^vOu22A^#f;R!v z_EK;?Fzx>eZUUz5r{EUgM}beGfvNrnWvtMD2%HW7YeoX4|3=0N{l9>ze=7JYaEAkL zf001-{}cE%jSmX_E?`<81!n@EF+pb}Q2A?Qtk53=rtwO_Yk}tj!wp3Kf0nUA|5xC9 zK)1;B-^o~^e+W$5U%{UMtFk8Xb>OEF0~9*`;irl7U9>*gGM4BXaxXBAKMJ-2D`&kZ z|6j;hq5l;y?QaTR1+1I_qx|2Lu|nShOyjSDw*hYjhFgjD{Z|<)^q&CJc&gxGVCC!> zmH&>675clt-;d*eA6Pj{M)~i;p)ZOR{!C!yeFmi$$XKC21bixve;KfH=8W=hlCi>H z53HO;qx8R*u|hupOyjMBUj)`6UMlr}O~wlSKY*39YE)h-4(3s;&}RVCc&%U^uyTft z^8YUxEA%D6G=3}C4_pYWM*`)ql(9lz2TbF;f~$dPd{=NCu<}le=I@rVLf;Kc)A@jcF935y!7B3az;u2iT^jOB;ITM-37E!bh5iOG zjdu$E6|g2w-hTprD-OQ{ylSF+EqPbQ36Yy99{%Ha}nSf6x;Ij$% zl?4210{%q;zLwe<=ZHCEx`Kcu@lW(*$fuz_tYJOu(K5yf^{>Spwz~aBcz) zCg7zBxF7*P1Uz|N9bXoQ>H72=z?!)E$5X)Z{kt)apMd`(%pEDeIR8=MS6$-hRNg-U zr^U(ZOOSUkj-Sfgj=3b|$C`?$An%89bSm%lg#1_H=rsSY<1po)jk$Wf{^`Jz*VXa4 zahUSwB=GZbbSghT4paW>1pc}>I^};N4paVJ3H*EG=#+nd9H#td68O)>(JB9hI86Cf zSUbem7e}t*uB+q8ahUSk6ZlYREU}!0#2QnAF0Vd z1IO$0ufUVn)$tQ(Z_2Ou&rjm$w7j=~XUEBVJ3-!WN$fNv9c{~j-#7^pTPWv&WCCK#lWA&(er^DCqN}!)e!2gzj-$=l3C*W%d z_%_5#Wq*LS-yI3~z687^0p};+l?hmovC`gO2QHK6|9bcnkN6T_KC#emkP*uo?4Jwk z!VzMrjXVKdy)GQ84H1jtmL(P}o!E!7jhx5+dF*_r1dEI*X0We}evR zDOfL5M=DF#R9J6>wm6bK?%5Id?2LPM#XY;@o;`8T-neI9+_TL}O8??Po9A=7+Z+Mq z^k`n;(uX{#)W!-SV)LxA3HETgqb%$!4~MPZawkuJXW!GwM%>g ze|fnr6b=hsoNx394qq8R!R!Gb*pug&JP*k}WYtPzhp;;@!E(2Sml!|5&ac|tZJY_mq}ZzVdRH6=y!}4x4wf&Tbr>t*flHIqKFvUL9IrS{|yX6f5fvM?4-Y z&UQv@eZ2OUgf$n z94J-l|Hn<`kz99bhIPuG;-%hJx+YZVs1^iwZoxyjUwxQ1x5w)AxE*0@7hlh< z(aP4XlUyiN^F^LC={aF<1ijngF2{*-A#6t^oNAe_saM)H^-8}E(VQ>u;U0t5z@gnSW)t=w)!=nGkekh8qZ;SH60B5te8 zVW;C!d>EwqxE?O{7xNG-l!sgx%*x%)GAq7O5E4~)OW{q4vd}G3R=3(>c{s4Nuo~aO z7d`d!4!eR5UQ}XpQdJhJsd>`orr!lA z4OipK>=>aCUVY1Ig@6cs_`V1R`A~HggoaQ-ALc}5zA}%`YL~|6_)31UM0a4>(&hXz z2ti*GFn(LTb}NPvuQbTsl1?_;T1*5>*GH-=)`=4iufq`*Ph^CxAv=vq(8=zUYgZ~( zZbMz9E>c=oTTxkKb4!5!u)%8lwm>B8Dxy@IOB5Sh_g_!vr@Hu!FAPDm~%&} zZI~Q}#i z^38L2kRyy~EOJ>r=$WBN#N)KWTgwHdOu2I95qxPU>=m3I2c4yQg|d*l%onDU%COBH z@>(3Sf;TTHp^A3NuIsBK#7~qp~roXhaQWR)`n^xLsk#QEV`1x zM8oN|*?o#GlKrO|%3+Q${eP0dw`wSt_ZAK1zSWj? zi!CjA#~=MmW5!MP4QxvvEG#K54Ho$G=%UUmxCMK}=JeQIE+39M@=~n-KgxOIJm~Yw zL1$rU2`v+Yx7Ugpflx+Q;2{?+^N&{N|3L=qLaoQ{Ky_(ZT}5RrmRc4}^swR#d))Yr zsSC$O#SJB^G{A^`da7Aq5R99*ltpo-sTQI1hpF-_zBg5VrTa{kUx@%yUVy7Hj4s0Ys$RXJ8?za zcEK0%MqCq9&|7i?EJkkCb8PU0TXZ&?G9*vcIJf9LHaL%>QfX=KL(BNmr4Ql<1(%f+ z7C%f%OZk-m5Bdw@Q<1jRQ;L~t=?2UTF)P9@uaZ{0^Z_hT;|O9x-~qp_G&kS>;DdYt zVpkmbMoK)oQOu2$c;xs(c@7fK=lnuG=M8+WFXVIGz=y4%FQ|w620m<(d;uRQ*k!M- zC0HnjD`7_ZO9J3tE$%&*uE&lMX8!XBE!ultXP*qn+aUfD2!d@@0h{{(a!EbW;!s4Y5J@_#B9CgKt zKprVv9v~P(AI>Msa&w7)MG5^8JF?WCYb)iK%lBoZdgkQTcH&j_kO0eIrJm+9z z{xPvx$iq+86k~gzJiM;F_VG}4gkYBszo>v;uY=r*8pNN~Ya`+K)F8D0Sz1{cSskj3 z<0b{`LgBb96iU1gW9bozqlj1{$p(ey(hmtv7?gh?$N%udkCX=Z!BKVT z!WlW`oi0tF@5<7tBZHL6PJ<)N2Vs^(Ra!3X5qYX<^Y`lsU`RI|B z`{&~mAY>KJQ!1Atj~-v9`q5*;^j-10Wzz4DD17wn0=?+5Px?&x=sV;z0q-vHtXzt` zY$Pc1=(}vn7k^P2y=?f Date: Thu, 6 Nov 2025 19:19:58 -0700 Subject: [PATCH 122/148] update .gitmodules to use https --- .gitmodules | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index e953eec..84792a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ [submodule "ch32v-insert-coin/ext/ch32-hal"] path = ch32v-insert-coin/ext/ch32-hal - url = git@github.com:sigil-03/ch32-hal.git + url = https://github.com/sigil-03/ch32-hal.git [submodule "ch32v-insert-coin/ext/qingke"] path = ch32v-insert-coin/ext/qingke - url = git@github.com:ch32-rs/qingke.git + 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 = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git + 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 From 5288cba869471122f3fa46183afa75904cbfdf02 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 12 Nov 2025 20:06:17 -0700 Subject: [PATCH 123/148] add AdcCore for ADC measurement --- ch32v-insert-coin/src/app.rs | 1 + ch32v-insert-coin/src/main.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index c48a073..fbd2ddc 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -301,6 +301,7 @@ pub struct Sequences { // things that touch hardware pub struct Interfaces { pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, + pub adc_core: crate::AdcCore, } pub struct App { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 1e9e74c..d8dc81d 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -44,6 +44,30 @@ use crate::app::sequencer::{DynamicSequence, SequenceEntry}; static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; +use hal::adc::Adc; +use hal::peripherals::{ADC1, PD4}; + +pub struct AdcCore { + adc: Adc<'static, ADC1>, + battery_pin: PD4, +} + +impl AdcCore { + pub fn new(mut adc: Adc<'static, ADC1>, pin: PD4) -> Self { + adc.calibrate(); + Self { + adc, + battery_pin: pin, + } + } + + // TODO make this a float or something + pub fn get_battery_voltage(&mut self) -> u16 { + self.adc + .convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241) + } +} + #[derive(Debug)] struct Flag { value: bool, @@ -205,13 +229,13 @@ fn app_main(mut p: hal::Peripherals) -> ! { // 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()); let mut usb_detect_pin = p.PD5; // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); - let adc_cal = adc.calibrate(); // #[cfg(feature = "enable_print")] // println!("ADC calibration value: {}", adc_cal); @@ -289,7 +313,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { audio: &SEQUENCE_LIST, }; - let app_interfaces = Interfaces { pwm_core }; + let app_interfaces = Interfaces { pwm_core, adc_core }; let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); From 43790abbc5fdc64da74f30f6701283d66ab0369a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Wed, 12 Nov 2025 20:13:57 -0700 Subject: [PATCH 124/148] initial commit off adc into app --- ch32v-insert-coin/src/app.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index fbd2ddc..4cc0dd7 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -389,8 +389,12 @@ impl App { } if self.timers.batt_adc_timer.need_service() { self.timers.batt_adc_timer.service(); + let bv = self.interfaces.adc_core.get_battery_voltage(); #[cfg(feature = "enable_print")] - println!("batt adc service"); + println!("batt adc service: {bv}"); + + // TODO: + // do stuff if the battery voltage is below some threshold } if self.timers.usb_adc_timer.need_service() { self.timers.usb_adc_timer.service(); From b0b77a15382d46c7a7eda66421f67c7e83be833a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 13 Nov 2025 20:12:38 -0700 Subject: [PATCH 125/148] add initial ADC shutdown code --- ch32v-insert-coin/src/app.rs | 13 ++++++++++--- ch32v-insert-coin/src/main.rs | 30 +++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 4cc0dd7..3904630 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -183,7 +183,7 @@ use crate::synthesizer::SynthesizerService; pub use settings::Settings; -#[cfg(feature = "enable_print")] +// #[cfg(feature = "enable_print")] use ch32_hal::println; pub struct TimerConfig { @@ -390,8 +390,15 @@ impl App { if self.timers.batt_adc_timer.need_service() { self.timers.batt_adc_timer.service(); let bv = self.interfaces.adc_core.get_battery_voltage(); - #[cfg(feature = "enable_print")] - println!("batt adc service: {bv}"); + let avg = self.interfaces.adc_core.get_average(); + // #[cfg(feature = "enable_print")] + // println!("batt adc service: {bv}, {avg}"); + if avg < 421 { + // self.services + // .sequencer + // .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.set_state(State::DeepSleep); + } // TODO: // do stuff if the battery voltage is below some threshold diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index d8dc81d..facacbf 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -50,21 +50,41 @@ 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 { - self.adc - .convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241) + 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 } } @@ -272,7 +292,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let timer_config = TimerConfig { sp_timer_ms: 1000, lp_timer_ms: 3000, - batt_adc_timer_ms: 10000, + batt_adc_timer_ms: 1000, usb_adc_timer_ms: 10000, led0_timer_ms: 100, led1_timer_ms: 100, @@ -289,11 +309,11 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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/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); + // sample_player.load_data(coin_sound); let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz); From 17d6f156db5cf49f6cdcb41e2bd8dd3906f57d79 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 12:56:11 -0700 Subject: [PATCH 126/148] remove led2 (tiny led) --- ch32v-insert-coin/src/app.rs | 74 +++++++++++++++++------------------ ch32v-insert-coin/src/main.rs | 17 ++++---- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 3904630..5775a60 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -193,7 +193,7 @@ pub struct TimerConfig { pub usb_adc_timer_ms: usize, pub led0_timer_ms: usize, pub led1_timer_ms: usize, - pub led2_timer_ms: usize, + // pub led2_timer_ms: usize, } pub struct Timers { @@ -203,7 +203,7 @@ pub struct Timers { usb_adc_timer: TickTimerService, led0_timer: TickTimerService, led1_timer: TickTimerService, - led2_timer: TickTimerService, + // led2_timer: TickTimerService, } impl Timers { @@ -233,10 +233,10 @@ impl Timers { TickServiceData::new(config.led1_timer_ms * system_tick_rate_hz / 1000), true, ), - led2_timer: TickTimerService::new( - TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), - true, - ), + // led2_timer: TickTimerService::new( + // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), + // true, + // ), } } pub fn tick(&mut self) { @@ -246,7 +246,7 @@ impl Timers { self.usb_adc_timer.tick(); self.led0_timer.tick(); self.led1_timer.tick(); - self.led2_timer.tick(); + // self.led2_timer.tick(); } pub fn need_service(&self) -> bool { self.sp_timer.need_service() @@ -255,7 +255,7 @@ impl Timers { | self.usb_adc_timer.need_service() | self.led0_timer.need_service() | self.led1_timer.need_service() - | self.led2_timer.need_service() + // | self.led2_timer.need_service() } pub fn init(&mut self) { self.led0_timer.reset(); @@ -272,7 +272,7 @@ impl Timers { pub struct Services { pub led0: LedService, pub led1: LedService, - pub led2: LedService, + // pub led2: LedService, pub synth0: SynthesizerService, pub sample_player: DacService<'static>, pub sequencer: sequencer::DynamicSequence<'static>, @@ -294,7 +294,7 @@ pub struct Config { pub struct Sequences { pub led0: sequencer::BasicSequence<'static>, pub led1: sequencer::BasicSequence<'static>, - pub led2: sequencer::BasicSequence<'static>, + // pub led2: sequencer::BasicSequence<'static>, pub audio: &'static [(&'static [sequencer::SequenceEntry], usize)], } @@ -345,8 +345,8 @@ impl App { self.timers.led1_timer.reset(); self.timers.led1_timer.enable(true); - self.timers.led2_timer.reset(); - self.timers.led2_timer.enable(true); + // self.timers.led2_timer.reset(); + // self.timers.led2_timer.enable(true); self.services.synth0.set_freq(1); self.services.synth0.disable(); @@ -442,23 +442,23 @@ impl App { // #[cfg(feature = "enable_print")] // println!("led1 service"); } - 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); + // 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"); - } + // // #[cfg(feature = "enable_print")] + // // println!("led2 service"); + // } // services if self.services.led0.need_service() { @@ -473,12 +473,12 @@ impl App { .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(); - } + // 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 @@ -525,9 +525,9 @@ impl App { self.interfaces .pwm_core .write_amplitude(self.services.led1.channel, 0); - self.interfaces - .pwm_core - .write_amplitude(self.services.led2.channel, 0); + // self.interfaces + // .pwm_core + // .write_amplitude(self.services.led2.channel, 0); self.interfaces .pwm_core .disable(ch32_hal::timer::Channel::Ch4); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index facacbf..5d75d4f 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -215,9 +215,9 @@ fn app_main(mut p: hal::Peripherals) -> ! { let led1_pin = PwmPin::new_ch1::<0>(p.PD2); let led1_ch = hal::timer::Channel::Ch1; - // LED2 output setup - let led2_pin = PwmPin::new_ch2::<0>(p.PA1); - let led2_ch = hal::timer::Channel::Ch2; + // // LED2 output setup + // let led2_pin = PwmPin::new_ch2::<0>(p.PA1); + // let led2_ch = hal::timer::Channel::Ch2; // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); @@ -227,7 +227,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { let mut pwm = SimplePwm::new( p.TIM1, Some(led1_pin), - Some(led2_pin), + // Some(led2_pin), + None, Some(led0_pin), Some(dac_pin), Hertz::khz(200), @@ -236,7 +237,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { pwm.set_polarity(led0_ch, OutputPolarity::ActiveHigh); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); - pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow); + // pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow); let tick_rate_hz = 50000; @@ -296,7 +297,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { usb_adc_timer_ms: 10000, led0_timer_ms: 100, led1_timer_ms: 100, - led2_timer_ms: 100, + // led2_timer_ms: 100, }; let app_config = Config { @@ -320,7 +321,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let app_services = Services { led0: LedService::new(led0_ch), led1: LedService::new(led1_ch), - led2: LedService::new(led2_ch), + // led2: LedService::new(led2_ch), synth0: SynthesizerService::new(tick_rate_hz), sample_player, sequencer, @@ -329,7 +330,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let app_sequences = Sequences { led0: BasicSequence::new(&LED0_SEQ), led1: BasicSequence::new(&LED0_SEQ), - led2: BasicSequence::new(&LED0_SEQ), + // led2: BasicSequence::new(&LED0_SEQ), audio: &SEQUENCE_LIST, }; From 1c2823eb1b1b3b706501e9789c58927ca27c6af6 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 13:14:02 -0700 Subject: [PATCH 127/148] add 4 hour shutdown timer --- ch32v-insert-coin/src/app.rs | 37 +++++++++++++++++++++++++---------- ch32v-insert-coin/src/main.rs | 1 + 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 5775a60..f2e46c0 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -193,6 +193,7 @@ pub struct TimerConfig { pub usb_adc_timer_ms: usize, pub led0_timer_ms: usize, pub led1_timer_ms: usize, + pub shutdown_timer_ms: usize, // pub led2_timer_ms: usize, } @@ -203,6 +204,7 @@ pub struct Timers { usb_adc_timer: TickTimerService, led0_timer: TickTimerService, led1_timer: TickTimerService, + shutdown_timer: TickTimerService, // led2_timer: TickTimerService, } @@ -233,10 +235,13 @@ impl Timers { TickServiceData::new(config.led1_timer_ms * system_tick_rate_hz / 1000), true, ), - // led2_timer: TickTimerService::new( - // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), - // true, - // ), + shutdown_timer: TickTimerService::new( + TickServiceData::new(config.shutdown_timer_ms * system_tick_rate_hz / 1000), + false, + ), // led2_timer: TickTimerService::new( + // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), + // true, + // ), } } pub fn tick(&mut self) { @@ -246,6 +251,7 @@ impl Timers { self.usb_adc_timer.tick(); self.led0_timer.tick(); self.led1_timer.tick(); + self.shutdown_timer.tick(); // self.led2_timer.tick(); } pub fn need_service(&self) -> bool { @@ -255,6 +261,7 @@ impl Timers { | self.usb_adc_timer.need_service() | self.led0_timer.need_service() | self.led1_timer.need_service() + | self.shutdown_timer.need_service() // | self.led2_timer.need_service() } pub fn init(&mut self) { @@ -345,6 +352,9 @@ impl App { self.timers.led1_timer.reset(); self.timers.led1_timer.enable(true); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + // self.timers.led2_timer.reset(); // self.timers.led2_timer.enable(true); @@ -394,14 +404,8 @@ impl App { // #[cfg(feature = "enable_print")] // println!("batt adc service: {bv}, {avg}"); if avg < 421 { - // self.services - // .sequencer - // .play_sequence(&crate::sequences::COIN_CHIRP, 0); self.set_state(State::DeepSleep); } - - // TODO: - // do stuff if the battery voltage is below some threshold } if self.timers.usb_adc_timer.need_service() { self.timers.usb_adc_timer.service(); @@ -442,6 +446,11 @@ impl App { // #[cfg(feature = "enable_print")] // println!("led1 service"); } + if self.timers.shutdown_timer.need_service() { + self.timers.shutdown_timer.service(); + self.timers.shutdown_timer.reset(); + self.set_state(State::DeepSleep); + } // if self.timers.led2_timer.need_service() { // let out = match self.settings.brightness { // Level::Off => 0, @@ -536,11 +545,15 @@ impl App { self.settings.volume.next(); #[cfg(feature = "enable_print")] println!("new volume: {:?}", self.settings.volume); + 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 @@ -548,8 +561,10 @@ impl App { 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) { @@ -581,6 +596,8 @@ impl App { self.services .sequencer .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 5d75d4f..b46f114 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -297,6 +297,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { usb_adc_timer_ms: 10000, led0_timer_ms: 100, led1_timer_ms: 100, + shutdown_timer_ms: 4 * 60 * 60 * 1000, // led2_timer_ms: 100, }; From 8ea4b4401ec0fa99ac88b15e83cdefedead9d8b7 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 13:30:01 -0700 Subject: [PATCH 128/148] add amp_en control --- ch32v-insert-coin/src/app.rs | 6 ++++++ ch32v-insert-coin/src/main.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index f2e46c0..2eb5de9 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -309,6 +309,7 @@ pub struct Sequences { pub struct Interfaces { pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, pub adc_core: crate::AdcCore, + pub amp: crate::Amplifier, } pub struct App { @@ -502,10 +503,14 @@ impl App { } else { self.services.sequencer.disable(); self.services.synth0.disable(); + self.interfaces.amp.disable(); } } if self.services.synth0.need_service() { + if !self.interfaces.amp.enabled() { + self.interfaces.amp.enable(); + } let out = match self.services.synth0.service() { Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), None => 0, @@ -540,6 +545,7 @@ impl App { self.interfaces .pwm_core .disable(ch32_hal::timer::Channel::Ch4); + self.interfaces.amp.disable(); } pub fn volume_button(&mut self) { self.settings.volume.next(); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index b46f114..6a29a7a 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -44,6 +44,27 @@ use crate::app::sequencer::{DynamicSequence, SequenceEntry}; static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; +pub struct Amplifier { + amp_en: Output<'static>, +} + +impl Amplifier { + pub fn new(amp_en: Output<'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}; @@ -271,7 +292,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let extra_io_2 = p.PD3; let mut amp_en_output = Output::new(amp_en, Level::Low, Default::default()); - amp_en_output.set_low(); + 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) }; @@ -335,7 +356,11 @@ fn app_main(mut p: hal::Peripherals) -> ! { audio: &SEQUENCE_LIST, }; - let app_interfaces = Interfaces { pwm_core, adc_core }; + let app_interfaces = Interfaces { + pwm_core, + adc_core, + amp, + }; let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); From c43cc5599e016afe37c1267565c836cf57b0b181 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 14:34:29 -0700 Subject: [PATCH 129/148] add usb power detection + check to ADC shutdown logic --- ch32v-insert-coin/src/app.rs | 15 +++++++++------ ch32v-insert-coin/src/main.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 2eb5de9..482c354 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -310,6 +310,7 @@ 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 { @@ -400,12 +401,14 @@ impl App { } if self.timers.batt_adc_timer.need_service() { self.timers.batt_adc_timer.service(); - 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}"); - if avg < 421 { - self.set_state(State::DeepSleep); + 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}"); + if avg < 421 { + self.set_state(State::DeepSleep); + } } } if self.timers.usb_adc_timer.need_service() { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 6a29a7a..0bb8271 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -44,6 +44,19 @@ 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 struct Amplifier { amp_en: Output<'static>, } @@ -276,6 +289,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { // adc2 // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); let mut usb_detect_pin = p.PD5; + let usb_detect_input = Input::new(usb_detect_pin, Pull::Down); + let usb = Usb::new(usb_detect_input); // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); @@ -360,6 +375,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { pwm_core, adc_core, amp, + usb, }; let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); From 2f92805c1a0424b3707bc92c9b9f026ada234a47 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 15:08:40 -0700 Subject: [PATCH 130/148] add USB check for powerup --- ch32v-insert-coin/src/app.rs | 10 ++++++++++ ch32v-insert-coin/src/main.rs | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 482c354..56162b1 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -648,6 +648,16 @@ impl App { pub fn get_state(&self) -> State { self.state } + 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 diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 0bb8271..8e8a6dc 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -526,9 +526,10 @@ fn app_main(mut p: hal::Peripherals) -> ! { } unsafe { #[allow(static_mut_refs)] - if INPUT_FLAGS.sense_coin_flag.active() + if (INPUT_FLAGS.sense_coin_flag.active() || (INPUT_FLAGS.main_btn_flag.active() - && main_btn_input.is_high_immediate()) + && main_btn_input.is_high_immediate())) + && app.should_wake() { unsafe { use hal::pac::Interrupt; From 64aa1808d21efc36ebb638030466e78355f0019d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 15:15:03 -0700 Subject: [PATCH 131/148] change direction of the USB detect input --- ch32v-insert-coin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 8e8a6dc..3414329 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -289,7 +289,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { // adc2 // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); let mut usb_detect_pin = p.PD5; - let usb_detect_input = Input::new(usb_detect_pin, Pull::Down); + let usb_detect_input = Input::new(usb_detect_pin, Pull::Up); let usb = Usb::new(usb_detect_input); // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); From 7e187680f5b75172fa8445f528c9e2951d5ecccf Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 15:46:28 -0700 Subject: [PATCH 132/148] don't use pullups --- ch32v-insert-coin/src/debounced_gpio.rs | 2 +- ch32v-insert-coin/src/main.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/debounced_gpio.rs b/ch32v-insert-coin/src/debounced_gpio.rs index baec633..784c3fd 100644 --- a/ch32v-insert-coin/src/debounced_gpio.rs +++ b/ch32v-insert-coin/src/debounced_gpio.rs @@ -17,7 +17,7 @@ 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::Up), + input: Input::new(pin, Pull::None), value: false, ready: false, active: false, diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3414329..c4e404f 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -55,6 +55,12 @@ impl Usb { 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 { From c71ace5063111ba4187d9e800e7fd976ac0231ba Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 16:09:27 -0700 Subject: [PATCH 133/148] fix sleep timer overflow --- ch32v-insert-coin/src/app.rs | 29 +++++++++++++++++++++-------- ch32v-insert-coin/src/main.rs | 4 +++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 56162b1..c2596e1 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -193,7 +193,7 @@ pub struct TimerConfig { pub usb_adc_timer_ms: usize, pub led0_timer_ms: usize, pub led1_timer_ms: usize, - pub shutdown_timer_ms: usize, + pub shutdown_timer_s: usize, // pub led2_timer_ms: usize, } @@ -205,6 +205,7 @@ pub struct Timers { led0_timer: TickTimerService, led1_timer: TickTimerService, shutdown_timer: TickTimerService, + pps_timer: TickTimerService, // led2_timer: TickTimerService, } @@ -236,12 +237,14 @@ impl Timers { true, ), shutdown_timer: TickTimerService::new( - TickServiceData::new(config.shutdown_timer_ms * system_tick_rate_hz / 1000), + TickServiceData::new(config.shutdown_timer_s), false, - ), // led2_timer: TickTimerService::new( - // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), - // true, - // ), + ), + 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) { @@ -251,7 +254,7 @@ impl Timers { self.usb_adc_timer.tick(); self.led0_timer.tick(); self.led1_timer.tick(); - self.shutdown_timer.tick(); + self.pps_timer.tick(); // self.led2_timer.tick(); } pub fn need_service(&self) -> bool { @@ -262,6 +265,7 @@ impl Timers { | 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) { @@ -357,6 +361,9 @@ impl App { 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); @@ -406,6 +413,7 @@ impl App { 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); } @@ -450,9 +458,14 @@ impl App { // #[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() { @@ -652,7 +665,7 @@ impl App { if self.interfaces.usb.powered() { return true; } else { - if self.interfaces.adc_core.get_average() > 421 { + if self.interfaces.adc_core.get_average() >= 421 { return true; } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c4e404f..9693e27 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -339,7 +339,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { usb_adc_timer_ms: 10000, led0_timer_ms: 100, led1_timer_ms: 100, - shutdown_timer_ms: 4 * 60 * 60 * 1000, + // 4 hours: + shutdown_timer_s: 4 * 60 * 60, // led2_timer_ms: 100, }; @@ -517,6 +518,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.shut_down(); loop { + riscv::asm::delay(20_000); unsafe { system::enter_standby() }; unsafe { #[allow(static_mut_refs)] From 6ba94f1cbb9f414f07a3db69bcc6654eecebde5d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 16:14:09 -0700 Subject: [PATCH 134/148] make amp_en OutputOpenDrain --- ch32v-insert-coin/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9693e27..159a55e 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -31,7 +31,7 @@ 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, Pin, Pull}; +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}; @@ -64,11 +64,11 @@ impl Usb { } pub struct Amplifier { - amp_en: Output<'static>, + amp_en: OutputOpenDrain<'static>, } impl Amplifier { - pub fn new(amp_en: Output<'static>) -> Self { + pub fn new(amp_en: OutputOpenDrain<'static>) -> Self { let mut amp = Self { amp_en }; amp.disable(); amp @@ -312,7 +312,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let extra_io_1 = p.PD0; let extra_io_2 = p.PD3; - let mut amp_en_output = Output::new(amp_en, Level::Low, Default::default()); + let mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default()); let amp = Amplifier::new(amp_en_output); // set up interrupts From 45db5e8af861dad93c34e218b240cd4f79a09977 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 14 Nov 2025 18:57:36 -0700 Subject: [PATCH 135/148] fix deep sleep --- ch32v-insert-coin/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 159a55e..8b17c75 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -518,14 +518,13 @@ fn app_main(mut p: hal::Peripherals) -> ! { app.shut_down(); loop { - riscv::asm::delay(20_000); - unsafe { system::enter_standby() }; unsafe { #[allow(static_mut_refs)] INPUT_FLAGS.sense_coin_flag.clear(); #[allow(static_mut_refs)] INPUT_FLAGS.main_btn_flag.clear(); } + unsafe { system::enter_standby() }; riscv::asm::wfi(); let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; From cd91b3f540b91cff9c0605565a5c08d5a966db47 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 01:01:10 -0700 Subject: [PATCH 136/148] clean up sleep handling a tiny amount --- ch32v-insert-coin/src/main.rs | 42 ++++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 8b17c75..ba69e0b 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -526,11 +526,6 @@ fn app_main(mut p: hal::Peripherals) -> ! { } unsafe { system::enter_standby() }; riscv::asm::wfi(); - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { - hal::rcc::init(config.rcc); - } unsafe { #[allow(static_mut_refs)] if (INPUT_FLAGS.sense_coin_flag.active() @@ -538,28 +533,29 @@ fn app_main(mut p: hal::Peripherals) -> ! { && main_btn_input.is_high_immediate())) && app.should_wake() { - 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); - } - - app.set_state(State::Active); - break; } } } + let mut config = hal::Config::default(); + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + unsafe { + hal::rcc::init(config.rcc); + } + 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); + } + + app.set_state(State::Active); } // for everything else, don't do anything _ => {} From b6fa0e3b2d528299484de0d48e97ca715bb78be5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 14:56:22 -0700 Subject: [PATCH 137/148] first working deep sleep entry with all peripherals enabled prior --- ch32v-insert-coin/ext/ch32-hal | 2 +- ch32v-insert-coin/src/app.rs | 14 +- .../src/insert_coin/insert_coin.rs | 2 +- ch32v-insert-coin/src/main.rs | 198 ++++++++++++++---- 4 files changed, 164 insertions(+), 52 deletions(-) diff --git a/ch32v-insert-coin/ext/ch32-hal b/ch32v-insert-coin/ext/ch32-hal index f413367..4f11d68 160000 --- a/ch32v-insert-coin/ext/ch32-hal +++ b/ch32v-insert-coin/ext/ch32-hal @@ -1 +1 @@ -Subproject commit f41336744c4e2548c8f6ba9b323ae4aa39959f1d +Subproject commit 4f11d68e62dcb0e7098eecf357168724a8322d80 diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index c2596e1..e2af3ef 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -551,16 +551,20 @@ impl App { pub fn shut_down(&mut self) { self.interfaces .pwm_core - .write_amplitude(self.services.led0.channel, 0); + .disable(ch32_hal::timer::Channel::Ch1); self.interfaces .pwm_core - .write_amplitude(self.services.led1.channel, 0); - // self.interfaces - // .pwm_core - // .write_amplitude(self.services.led2.channel, 0); + .disable(ch32_hal::timer::Channel::Ch2); + self.interfaces + .pwm_core + .disable(ch32_hal::timer::Channel::Ch3); self.interfaces .pwm_core .disable(ch32_hal::timer::Channel::Ch4); + + self.interfaces.pwm_core.pwm.borrow().shutdown(); + self.interfaces.adc_core.shutdown(); + self.interfaces.amp.disable(); } pub fn volume_button(&mut self) { diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index d453bea..8c0a606 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -11,7 +11,7 @@ use crate::insert_coin::services::{DacService, LedService, Service, TickService, // static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8]; pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { - pwm: core::cell::RefCell>, + pub pwm: core::cell::RefCell>, } impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ba69e0b..989a3ff 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -126,6 +126,10 @@ impl AdcCore { } sum / self.batt_values.len() as u16 } + + pub fn shutdown(&mut self) { + self.adc.shutdown() + } } #[derive(Debug)] @@ -244,7 +248,7 @@ bind_interrupts!(struct Irqs { use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -fn app_main(mut p: hal::Peripherals) -> ! { +fn app_main(mut p: hal::Peripherals) { // === output setup === // LED0 output setup @@ -309,8 +313,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { 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 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); @@ -340,6 +344,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { led0_timer_ms: 100, led1_timer_ms: 100, // 4 hours: + // shutdown_timer_s: 1, shutdown_timer_s: 4 * 60 * 60, // led2_timer_ms: 100, }; @@ -516,48 +521,8 @@ fn app_main(mut p: hal::Peripherals) -> ! { // enter standby app::State::DeepSleep => { app.shut_down(); - - loop { - unsafe { - #[allow(static_mut_refs)] - INPUT_FLAGS.sense_coin_flag.clear(); - #[allow(static_mut_refs)] - INPUT_FLAGS.main_btn_flag.clear(); - } - 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() - && main_btn_input.is_high_immediate())) - && app.should_wake() - { - break; - } - } - } - let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - unsafe { - hal::rcc::init(config.rcc); - } - 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); - } - - app.set_state(State::Active); + return; } - // for everything else, don't do anything _ => {} } } @@ -573,6 +538,137 @@ fn app_main(mut p: hal::Peripherals) -> ! { // // } // } +use ch32_hal::timer::low_level::{OutputCompareMode, Timer}; +use ch32_hal::timer::Channel; + +// fn shutdown_main(p: Peripherals) { +fn shutdown_main(p: hal::Peripherals) { + // drop(p.TIM1); + // 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, true) }; + + let sense_coin_pin = Input::new(sense_coin_pin, Pull::None); + let main_btn_pin = Input::new(main_btn_pin, Pull::None); + + let mut t = Timer::new(p.TIM1); + // // t.stop(); + // // t.enable_channel(ch32_hal::timer::Channel::Ch1, false); + // // t.enable_channel(ch32_hal::timer::Channel::Ch2, false); + // // t.enable_channel(ch32_hal::timer::Channel::Ch3, false); + // // t.enable_channel(ch32_hal::timer::Channel::Ch4, false); + // // t.enable_update_interrupt(false); + // // t.set_moe(false); + + t.set_counting_mode(CountingMode::default()); + t.set_frequency(Hertz::khz(300)); + t.enable_outputs(); + t.start(); + + [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + .iter() + .for_each(|&channel| { + t.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + t.set_output_compare_preload(channel, true); + t.set_compare_value(channel, 1000); + t.enable_channel(channel, true); + }); + + // [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + // .iter() + // .for_each(|&channel| { + // t.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + // t.set_output_compare_preload(channel, false); + // t.enable_channel(channel, false); + // }); + + // t.stop(); + // t.disable_outputs() + + // PWM timer setup + // let mut pwm = SimplePwm::new( + // p.TIM1, + // None, + // // Some(led2_pin), + // None, + // None, + // None, + // Hertz::hz(1), + // CountingMode::default(), + // ); + // drop(pwm); + + // pwm.disable(); + + riscv::asm::delay(1_000_000); + // 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; + + unsafe { system::enter_standby() }; + riscv::asm::wfi(); + + // t.set_moe(false); + // // pwm.shutdown(); + + // riscv::asm::delay(1_000_000); + + // unsafe { system::enter_standby() }; + // riscv::asm::wfi(); + // let mut b = t.get_compare_value(Channel::Ch1); + // b += 1; + // loop { + // unsafe { + // #[allow(static_mut_refs)] + // INPUT_FLAGS.sense_coin_flag.clear(); + // #[allow(static_mut_refs)] + // INPUT_FLAGS.main_btn_flag.clear(); + // } + // 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() && main_btn_input.is_high_immediate())) + // && app.should_wake() + // { + // break; + // } + // } + // } + // let mut config = hal::Config::default(); + // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; + // unsafe { + // hal::rcc::init(config.rcc); + // } + // 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); + // } +} + #[qingke_rt::entry] fn main() -> ! { #[cfg(feature = "enable_print")] @@ -588,7 +684,19 @@ fn main() -> ! { // println!("post"); // debug_main(p); - app_main(p); + loop { + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + app_main(p); + + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + shutdown_main(p); + } } #[panic_handler] From f3ac43614cf70eb8f0f82fa542d82b2b7cb772ef Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 15:22:24 -0700 Subject: [PATCH 138/148] fix amp_en --- ch32v-insert-coin/src/app.rs | 11 +++-- ch32v-insert-coin/src/main.rs | 75 +---------------------------------- 2 files changed, 6 insertions(+), 80 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index e2af3ef..d5ab28a 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -346,6 +346,8 @@ impl App { 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); @@ -367,10 +369,11 @@ impl App { // self.timers.led2_timer.reset(); // self.timers.led2_timer.enable(true); - self.services.synth0.set_freq(1); + // 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) { @@ -519,14 +522,10 @@ impl App { } else { self.services.sequencer.disable(); self.services.synth0.disable(); - self.interfaces.amp.disable(); } } if self.services.synth0.need_service() { - if !self.interfaces.amp.enabled() { - self.interfaces.amp.enable(); - } let out = match self.services.synth0.service() { Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), None => 0, diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 989a3ff..7517136 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -527,23 +527,12 @@ fn app_main(mut p: hal::Peripherals) { } } } -// // if adc1_timer.need_service() { -// // let val = adc.convert(&mut batt_monitor_pin, hal::adc::SampleTime::CYCLES241); -// // let val = adc.convert(&mut usb_detect_pin, hal::adc::SampleTime::CYCLES241); -// // #[cfg(feature = "enable_print")] -// // println!("ADC value: {}", val); - -// // adc1_timer.reset(); -// // adc1_timer.enable(true); -// // } -// } use ch32_hal::timer::low_level::{OutputCompareMode, Timer}; use ch32_hal::timer::Channel; // fn shutdown_main(p: Peripherals) { fn shutdown_main(p: hal::Peripherals) { - // drop(p.TIM1); // 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()); @@ -559,78 +548,16 @@ fn shutdown_main(p: hal::Peripherals) { 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, true) }; + 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); - let mut t = Timer::new(p.TIM1); - // // t.stop(); - // // t.enable_channel(ch32_hal::timer::Channel::Ch1, false); - // // t.enable_channel(ch32_hal::timer::Channel::Ch2, false); - // // t.enable_channel(ch32_hal::timer::Channel::Ch3, false); - // // t.enable_channel(ch32_hal::timer::Channel::Ch4, false); - // // t.enable_update_interrupt(false); - // // t.set_moe(false); - - t.set_counting_mode(CountingMode::default()); - t.set_frequency(Hertz::khz(300)); - t.enable_outputs(); - t.start(); - - [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] - .iter() - .for_each(|&channel| { - t.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); - t.set_output_compare_preload(channel, true); - t.set_compare_value(channel, 1000); - t.enable_channel(channel, true); - }); - - // [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] - // .iter() - // .for_each(|&channel| { - // t.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); - // t.set_output_compare_preload(channel, false); - // t.enable_channel(channel, false); - // }); - - // t.stop(); - // t.disable_outputs() - - // PWM timer setup - // let mut pwm = SimplePwm::new( - // p.TIM1, - // None, - // // Some(led2_pin), - // None, - // None, - // None, - // Hertz::hz(1), - // CountingMode::default(), - // ); - // drop(pwm); - - // pwm.disable(); - riscv::asm::delay(1_000_000); - // 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; unsafe { system::enter_standby() }; riscv::asm::wfi(); - // t.set_moe(false); - // // pwm.shutdown(); - - // riscv::asm::delay(1_000_000); - - // unsafe { system::enter_standby() }; - // riscv::asm::wfi(); - // let mut b = t.get_compare_value(Channel::Ch1); - // b += 1; // loop { // unsafe { // #[allow(static_mut_refs)] From 5934336984edd7122bddc3cc163159dbb650473c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 15:33:38 -0700 Subject: [PATCH 139/148] add battery level checking --- ch32v-insert-coin/src/main.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 7517136..4f9aaae 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -249,6 +249,15 @@ use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; fn app_main(mut p: hal::Peripherals) { + // 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 bv = adc_core.get_battery_voltage(); + if bv < 421 { + adc_core.shutdown(); + return; + } // === output setup === // LED0 output setup @@ -292,9 +301,9 @@ fn app_main(mut p: hal::Peripherals) { // === 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); + // 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()); @@ -555,8 +564,18 @@ fn shutdown_main(p: hal::Peripherals) { riscv::asm::delay(1_000_000); - unsafe { system::enter_standby() }; - riscv::asm::wfi(); + 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; + } + } + } // loop { // unsafe { From 10a38f9bbc10936f71bb7b67440b9ddff563de1c Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 15:40:08 -0700 Subject: [PATCH 140/148] add shutdown delay --- ch32v-insert-coin/src/app.rs | 1 + ch32v-insert-coin/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index d5ab28a..1599599 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -565,6 +565,7 @@ impl App { self.interfaces.adc_core.shutdown(); self.interfaces.amp.disable(); + crate::riscv::asm::delay(20_000_000); } pub fn volume_button(&mut self) { self.settings.volume.next(); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 4f9aaae..6370d78 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -354,7 +354,7 @@ fn app_main(mut p: hal::Peripherals) { led1_timer_ms: 100, // 4 hours: // shutdown_timer_s: 1, - shutdown_timer_s: 4 * 60 * 60, + shutdown_timer_s: 4 * 60 * 60 * 30 / 100, // led2_timer_ms: 100, }; From 60d6aaecd6a93c6d0566bf4319899c7029c148fc Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 15:55:23 -0700 Subject: [PATCH 141/148] add ability for main button sound --- ch32v-insert-coin/src/app.rs | 14 ++++---------- ch32v-insert-coin/src/main.rs | 24 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 1599599..1d5d0f3 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -550,22 +550,16 @@ impl App { pub fn shut_down(&mut self) { self.interfaces .pwm_core - .disable(ch32_hal::timer::Channel::Ch1); + .write_amplitude(self.services.led0.channel, 0); self.interfaces .pwm_core - .disable(ch32_hal::timer::Channel::Ch2); - self.interfaces - .pwm_core - .disable(ch32_hal::timer::Channel::Ch3); - self.interfaces - .pwm_core - .disable(ch32_hal::timer::Channel::Ch4); + .write_amplitude(self.services.led1.channel, 0); + crate::riscv::asm::delay(20_000_000); self.interfaces.pwm_core.pwm.borrow().shutdown(); self.interfaces.adc_core.shutdown(); self.interfaces.amp.disable(); - crate::riscv::asm::delay(20_000_000); } pub fn volume_button(&mut self) { self.settings.volume.next(); @@ -629,7 +623,7 @@ impl App { // Events impl App { - fn main_button_click(&mut self) { + pub fn main_button_click(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("click"); diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 6370d78..ca5a9dd 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -268,10 +268,6 @@ fn app_main(mut p: hal::Peripherals) { let led1_pin = PwmPin::new_ch1::<0>(p.PD2); let led1_ch = hal::timer::Channel::Ch1; - // // LED2 output setup - // let led2_pin = PwmPin::new_ch2::<0>(p.PA1); - // let led2_ch = hal::timer::Channel::Ch2; - // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); // let dac_ch = hal::timer::Channel::Ch4; @@ -290,14 +286,16 @@ fn app_main(mut p: hal::Peripherals) { 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); - let pwm_core = SimplePwmCore::new(pwm); - // === input setup === // adc @@ -401,6 +399,17 @@ fn app_main(mut p: hal::Peripherals) { let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); + 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); @@ -432,6 +441,9 @@ fn app_main(mut p: hal::Peripherals) { 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 From f26920a0994c811d18f4fb47d663e9af19b2d735 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:03:32 -0700 Subject: [PATCH 142/148] add persistent settings --- ch32v-insert-coin/src/app.rs | 8 ++++++-- ch32v-insert-coin/src/main.rs | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 1d5d0f3..775a908 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -9,7 +9,7 @@ pub enum State { Active, } -mod settings { +pub mod settings { #[derive(Debug, Default, Clone, Copy)] pub enum Level { @@ -333,10 +333,11 @@ impl App { services: Services, sequences: Sequences, interfaces: Interfaces, + settings: Settings, ) -> Self { Self { state: State::default(), - settings: Settings::default(), + settings, timers: Timers::new(config.timers, config.system_tick_rate_hz), services, sequences, @@ -659,6 +660,9 @@ 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; diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index ca5a9dd..c92f562 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -245,10 +245,11 @@ bind_interrupts!(struct Irqs { }); // TODO: remove +use app::settings::Settings; use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -fn app_main(mut p: hal::Peripherals) { +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; @@ -256,7 +257,7 @@ fn app_main(mut p: hal::Peripherals) { let bv = adc_core.get_battery_voltage(); if bv < 421 { adc_core.shutdown(); - return; + return app_settings; } // === output setup === @@ -397,7 +398,13 @@ fn app_main(mut p: hal::Peripherals) { usb, }; - let mut app = App::new(app_config, app_services, app_sequences, app_interfaces); + let mut app = App::new( + app_config, + app_services, + app_sequences, + app_interfaces, + app_settings, + ); let need_sound = unsafe { #[allow(static_mut_refs)] @@ -542,7 +549,7 @@ fn app_main(mut p: hal::Peripherals) { // enter standby app::State::DeepSleep => { app.shut_down(); - return; + return app.get_settings(); } _ => {} } @@ -642,12 +649,14 @@ fn main() -> ! { // 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_main(p); + app_settings = app_main(p, app_settings); unsafe { hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); From d053f8d080448f353b4ca56492e336976e4b04f7 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:06:54 -0700 Subject: [PATCH 143/148] decrease wait time for app shutdown --- ch32v-insert-coin/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 775a908..14138f8 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -555,7 +555,7 @@ impl App { self.interfaces .pwm_core .write_amplitude(self.services.led1.channel, 0); - crate::riscv::asm::delay(20_000_000); + crate::riscv::asm::delay(10_000_000); self.interfaces.pwm_core.pwm.borrow().shutdown(); self.interfaces.adc_core.shutdown(); From 1d2e4a5482c46e26979400505010ac44a8426433 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:09:06 -0700 Subject: [PATCH 144/148] update shutdown timer addtl time to 10% from 30% --- ch32v-insert-coin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c92f562..c628f65 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -353,7 +353,7 @@ fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { led1_timer_ms: 100, // 4 hours: // shutdown_timer_s: 1, - shutdown_timer_s: 4 * 60 * 60 * 30 / 100, + shutdown_timer_s: 4 * 60 * 60 * 10 / 100, // led2_timer_ms: 100, }; From 4c80907c0f417562648e2371608f4abdaad51794 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:10:35 -0700 Subject: [PATCH 145/148] change fudge factor to 110% bc i'm stupid --- ch32v-insert-coin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c628f65..9e6ea47 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -353,7 +353,7 @@ fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { led1_timer_ms: 100, // 4 hours: // shutdown_timer_s: 1, - shutdown_timer_s: 4 * 60 * 60 * 10 / 100, + shutdown_timer_s: 4 * 60 * 60 * 110 / 100, // led2_timer_ms: 100, }; From 472e43056dc03b4978611117520cb03867b16576 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:23:18 -0700 Subject: [PATCH 146/148] add systick disable --- ch32v-insert-coin/src/main.rs | 45 +++++++---------------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 9e6ea47..4556f25 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -239,6 +239,13 @@ fn systick_init(tick_freq_hz: usize) { 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; @@ -561,6 +568,7 @@ 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()); @@ -595,43 +603,6 @@ fn shutdown_main(p: hal::Peripherals) { } } } - - // loop { - // unsafe { - // #[allow(static_mut_refs)] - // INPUT_FLAGS.sense_coin_flag.clear(); - // #[allow(static_mut_refs)] - // INPUT_FLAGS.main_btn_flag.clear(); - // } - // 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() && main_btn_input.is_high_immediate())) - // && app.should_wake() - // { - // break; - // } - // } - // } - // let mut config = hal::Config::default(); - // config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; - // unsafe { - // hal::rcc::init(config.rcc); - // } - // 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); - // } } #[qingke_rt::entry] From d8477e3d2d6b74ce14b8a260b5ad6d4a06734fa8 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 15 Nov 2025 16:38:07 -0700 Subject: [PATCH 147/148] add usb power gating --- ch32v-insert-coin/src/main.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 4556f25..694ce75 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -261,8 +261,15 @@ fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { 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 bv < 421 { + + // 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; } @@ -313,9 +320,6 @@ fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { // adc2 // let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); - 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); // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); From 3265325d283fc4c0ac3f6d212ec42d367bad0cd1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 17 Nov 2025 18:42:16 -0700 Subject: [PATCH 148/148] add coin chirp when you click the volume button --- ch32v-insert-coin/src/app.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 14138f8..a8fdbb8 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -566,6 +566,9 @@ impl App { 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); }