From 33a9fb175e28b26d391d6a5bba5c7bad19ed0fb1 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 17:53:49 -0600 Subject: [PATCH 01/86] broken embassy example. leds work, but break when you sample faster --- ch32v-insert-coin/.cargo/config.toml | 4 +- ch32v-insert-coin/Cargo.lock | 45 +------ ch32v-insert-coin/Cargo.toml | 15 ++- ch32v-insert-coin/ext/adpcm-pwm-dac | 2 +- ch32v-insert-coin/src/main.rs | 191 ++++++++++++--------------- 5 files changed, 100 insertions(+), 157 deletions(-) diff --git a/ch32v-insert-coin/.cargo/config.toml b/ch32v-insert-coin/.cargo/config.toml index 57920f5..6825878 100644 --- a/ch32v-insert-coin/.cargo/config.toml +++ b/ch32v-insert-coin/.cargo/config.toml @@ -8,10 +8,10 @@ target = "riscv32ec-unknown-none-elf.json" # runner = "gdb -q -x openocd.gdb" # 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" +#runner = "probe-rs run --chip ch32v003" [unstable] build-std = ["core"] diff --git a/ch32v-insert-coin/Cargo.lock b/ch32v-insert-coin/Cargo.lock index 348625b..1ce9c48 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" @@ -92,11 +71,13 @@ name = "ch32v-insert-coin" version = "0.1.0" dependencies = [ "adpcm-pwm-dac", - "bitfields", "ch32-hal", + "critical-section", "embassy-executor", + "embassy-time", "embedded-hal 1.0.0", "panic-halt", + "qingke", "qingke-rt", ] @@ -615,26 +596,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..d8cdffe 100644 --- a/ch32v-insert-coin/Cargo.toml +++ b/ch32v-insert-coin/Cargo.toml @@ -5,32 +5,37 @@ edition = "2024" [dependencies] ch32-hal = { path = "ext/ch32-hal/", features = [ - "ch32v003f4u6", + "ch32v003f4p6", "memory-x", "embassy", "time-driver-tim2", "rt", ] } +critical-section = "1.2.0" + embassy-executor = { version = "0.7.0", features = [ - "arch-spin", + "arch-riscv32", "executor-thread", - "task-arena-size-128", # or better use nightly, but fails on recent Rust versions + #"nightly", + "task-arena-size-512", # or better use nightly, but fails on recent Rust versions ] } +embassy-time = "0.4.0" + panic-halt = "1.0" embedded-hal = "1.0.0" +qingke = "*" 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. lto = true -opt-level = "s" # Optimize for size. +opt-level = "z" # Optimize for size. [profile.dev] overflow-checks = false 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 diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 3f9e9ef..13e5c27 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -3,7 +3,10 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +use core::any::Any; + use adpcm_pwm_dac::dac::DpcmDac; +use ch32_hal::{exti::ExtiInput, pac::systick::Systick}; use {ch32_hal as hal}; use hal::peripherals::EXTI4; use hal::{bind_interrupts, interrupt}; @@ -14,6 +17,13 @@ use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; use hal::timer::{Channel, GeneralInstance16bit}; + + + +// embassy shit +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; + // const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80]; const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; @@ -27,9 +37,11 @@ const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25]; // - 25 -> 0xA // - 25 -> 0xA // - 25 -> 0xA -const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; +// const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA]; + // const DATA2: [u8; 1] = [0x00u8]; +// const DAC_DATA_1: &'static [u8] = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); static mut IRQ1_FLAG: bool = false; @@ -45,101 +57,69 @@ 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 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 disable(&mut self) { + self.pin.disable(self.ch); } } -fn pwm_blink(mut pwm_dac_pin: SimplePwmDacPin<'_, T>) { - let mut delay = Delay; +#[embassy_executor::task(pool_size = 2)] +async fn led_task(mut pin: AnyPin, period: u64) { + let mut led = Output::new(pin, Level::High, Default::default()); - - - 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); + led.toggle(); + Timer::after(Duration::from_millis(period)).await; } } -// // fn dac_run(mut dac: Dac<'_, T>, sample_rate: usize) { -// fn dac_run(mut dac: DpcmDac<'_, T>, sample_rate: usize) { +// #[embassy_executor::task(pool_size = 1)] +// async fn audio_task(mut dac: DpcmDac<'static, SimplePwmDacPin<'static, hal::peripherals::TIM1>>) { +// // // DAC servicer computations +// // let data = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw"); + +// let mut dac_active = true; +// // let sample_rate_hz = 16000; +// let sample_rate_hz = 440; +// let tick_interval_us = 1000000/(sample_rate_hz); + +// dac.load_data(&DAC_DATA); - -// dac.load_data(data); -// // dac.load_data(&DAC_DATA); - -// let interval_us = 1000000/sample_rate as u32; // loop { +// dac.output_next(); +// // if(dac_need_service && dac_active) { +// // dac_active = dac.output_next(); +// // if !dac_active { +// // dac.disable_output(); +// // } +// // } +// Timer::after(Duration::from_micros(tick_interval_us)).await; // } // } - -#[qingke_rt::interrupt] -fn EXTI4(){ - unsafe{IRQ1_FLAG = true;}; -} -// bind_interrupts!(struct Irqs { -// EXTI4 => button_press(); -// }); - -#[qingke_rt::entry] -fn main() -> ! { +#[embassy_executor::main(entry = "ch32_hal::entry")] +async fn main(spawner: Spawner) -> ! { let mut config = hal::Config::default(); - config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; + config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI; 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 mut 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()); + // 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()); + // let mut led1 = Output::new(p.PD6, Level::High, Default::default()); // PWM DAC output pin setup @@ -154,65 +134,62 @@ fn main() -> ! { 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.load_data(data); // DAC servicer computations let mut dac_active = true; let sample_rate_hz = 16000; - let dac_tick_per_service = 5; + let dac_tick_per_service = 1; let tick_rate_hz = sample_rate_hz * dac_tick_per_service; - let tick_interval = 1000000/(tick_rate_hz); + let tick_interval_us = 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)); + // // 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)); - // LED1 servicer vars - let mut led1_need_service = false; + // // LED1 servicer vars + // let mut led1_need_service = false; - // pwm_blink(pwm_dac_pin); - let mut tick: usize = 0; + // let mut tick: usize = 0; + + ei.wait_for_falling_edge().await; + + spawner.spawn(led_task(p.PD6.degrade(), 250)).unwrap(); + spawner.spawn(led_task(p.PD0.degrade(), 100)).unwrap(); + // spawner.spawn(audio_task(dac)).unwrap(); + 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(); + // if !dac_active { + // dac.disable_output(); + // } + // } - 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(); - } + // 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); + // Timer::after_micros(tick_interval as u64).await; + // Timer::after_micros(1000000u64).await; + // Timer::after(Duration::from_millis(2000)).await; + Timer::after(Duration::from_micros(tick_interval_us)).await; - if led1_need_service { - led1.set_low(); - led1_need_service = false; - } - tick = tick.wrapping_add(1); - delay.delay_us(tick_interval as u32); }; } From c212831760c4f6f55f2e7818cdfe2e0b4d02124f Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 17:55:11 -0600 Subject: [PATCH 02/86] 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 03/86] 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 04/86] 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 05/86] 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 06/86] 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 07/86] 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 08/86] 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 09/86] 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 10/86] 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 11/86] 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 12/86] 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 13/86] 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 14/86] 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 15/86] 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 16/86] 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 17/86] 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 18/86] 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 19/86] 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 20/86] 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 21/86] 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 22/86] 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 23/86] 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 24/86] 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 25/86] 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 26/86] 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 27/86] 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 28/86] 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 29/86] 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 30/86] 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 31/86] 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 32/86] 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 33/86] 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 34/86] 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 35/86] 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 36/86] 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 37/86] 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 38/86] 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 39/86] 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 40/86] 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 41/86] 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 42/86] 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 43/86] 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 44/86] 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 45/86] 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 46/86] 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 47/86] 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 48/86] 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 49/86] 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 50/86] 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 51/86] 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 52/86] 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 53/86] 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 54/86] 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 55/86] 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 56/86] 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 zcmeHL;fmurvX;Pt7`j0M32aEeiv$+5;RGa*!GX-XI1s}aCJ=)I9>}~)H=KqEY(odq zkORH%az8npIeYH@xy${t3#*xK%W_qzs#Nt=sg?Ne_>c9#m;a;YCnYHLr173g?g^3B z62THjbx=eK0x?`rE(lQ?4=;(QgixY67lQgi>TjM`8#*3F&kP4!urwI|n2+yMs!ab#df(%23fO*| z{`Cn8Sd6bxz3Kn7@n5O`(Vpm+=_{zC8z$RtGOzkxnJ1CI4r9_0WH-U!j^lO$Q;baF zCO=~xry2)5K|L%%PGZUoMQ|eRNWvNH?>N)0l@*uVf{aVW65$)hs{)6qq)(Cxd<46J zUK4~ohqFH&0}`wwbR|ooS=b*DLMcZ|aU_$S2x4p_?qYyUhZWV1l-LRU-~(qA87)I* z;N|9%>C?mpNuMKb7cH4o_&lCyPaH5aPIUBKbj(C{WBjM*FOfiio3wxME)F%WM$_Zr zQ;q%fZj`h=wNpd8{&rRaAJ>FsYb=;nxuYV=HE#990oA|c=>q(ty zJQfWbD zZ`br>XQLunDv6b8VMB!VFs!LBU4jsyKunk?nf73);K2mpBZ^8p*7z#(G~fUl>XDfN zBvH|T%%?zHL{TXP3sN|(m4Rf^Cjcszcw}z$t(7VsJ69S8MOFv6$2%2PA?slUHYCDD zJfMjM)W*Pz?#6)$ZxeXgf+GgLpYR6Z79_yPMsZ|hQUl7F7X1wG7Dfn>@SJ9fLRj=R z_97f0j+x0LP;-gM!p^;(GG{v3h)v{Is%h*W>0@HSDIN~NEBu78=G;y|Ml5V3#l02> z7`=IT%CUv|O}>!_5mrrJ?pI%zg>l@Z7foS4xiooU7w6e7bnbN|Lo?iGKkoQNfAgZq zR^=%5BHIQ-{6^dj+|PUdoa(bi4YIwI{^`MJ-^=$Q^fN`ieAM#wD29jmtm3@CZ2dXF z*6Qo`gFc?}%c|bw#kO6nmWTHJZ*SSha^5TR@FsP+=!4p=c(r*q5A)-WmdcKUVe0GB z6_?-DN;;9MmCeNK=|HsYvh7=vT)0t*CwAV8bj#eO7;plLBjUxc~|5V;-J5#*>z&3RKvW*p$b9VkM8i*8qI+qym@s#riOMWn8CheKmR zTW5fbVIbOLXDgfpf->Y;gf}7j!fKJDh~ooqml>j~vnH(_rwDLwoTStsYQ3vtk&LlG ziBZ@iMq&(8FDC zs)d0zNAyTUumYd~e<&o_2N?m)AQL?ida1Z>fjS}7=J>!rBv0?PLfaL9{vrTDQ9+P} zEgGMIY^D(jP*YsvsThDW0Bv|Ecx^26K|&Om;XaNOW)gmjwo+kKpl}EDq&hk};(-;A z799!=(alAjZ3Kr&dHjlH28ttFt)*s2E>51T*ZceV9;o?ub86D33;WK_)wuiH1I?S= zuoJuTnDdZ)e9b*c%lGj8@!WhVlWsg8@;lr5^@)Z0v3m42mr}cR*eo68%y` zyBz`*dFGOu@NLr@S@w|erfPbeMwSLu}`18A(b!S=cj(OU; zp${b7=Q_D1wbUq8ZYRj#FK zqkOd1C3YB_RLYLIraaGWH73>pE9+*vn_D*Mw5|;vTR&&wTv%7@t>Z7%D155p{B=bG-yQIJhXO@w9l+X zncO8BZ!IvIav&Jsm}gcE=rvGEoVI;pR2{EZR0WiN;6sodK2YEtfZFj=Ksye11!JUri z)&o$0S7>f2fJtdILb2mC6(I(d9$jjz{*2QE=xicwh${e8pfCEZh;FDOk;|ZEkpjx2 zDbj3yNLpr(>F}1Y%iXC2dcG`dKVPwAxpa4nTKaA;ovRaDp1&>EVxiM&w^B3o4CS!K zHkPpY*X3%zSv<)7~lHO(1vbF7T4TvSi4H;0McGite zW%}oxCS~GgxUzN;@-JBc#A~13f2l1>M&}6h6KyrzrBcD2lH!tS2M!QMmuGC5mAk`8 z%?-O)eR>9|la*J~v#-sz6ml8K`R5u=c zftizRG^h;-)wW#(;~mesEeho>%Qm*82$oBN-l;456@sUd>B{y6MOet#!h=tZTEt=` z#y*p#<-Itofk-2bZ%{0i|mld`<#~gxI^tl7z2z=$`Nnk z$V5H7qzK^>b$RV+dz!)K5rtZEm8C|w1{XX)fONV=mLfLIRA!MFdA2|kpQw8JV1+Gt z2t-l=(SMaB?k_a2yrISQy|!pBr#8rP##S{>?WXdOaYv2>_YJ4 zC3rzeVvB$h53xulP{!sNFNg|ZTR0aR#v8ztKDgf0Xg(kk-k1dYqP*Tsk^jQQKj

  • 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 57/86] 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 58/86] 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 59/86] 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 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 Date: Thu, 6 Nov 2025 19:19:58 -0700 Subject: [PATCH 60/86] 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 61/86] 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 62/86] 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 63/86] 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 64/86] 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 65/86] 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 66/86] 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 67/86] 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 68/86] 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 69/86] 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 70/86] 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 71/86] 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 72/86] 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 73/86] 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 74/86] 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 75/86] 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 76/86] 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 77/86] 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 78/86] 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 79/86] 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 80/86] 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 81/86] 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 82/86] 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 83/86] 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 84/86] 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 85/86] 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 86/86] 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); }