From 1a420018d1b326452d2d11740205be58c4558cb3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Fri, 12 Sep 2025 15:47:45 -0600 Subject: [PATCH 01/68] 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 02/68] 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 03/68] 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 04/68] 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 05/68] 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 06/68] 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 07/68] 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 08/68] 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 09/68] 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 10/68] 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 11/68] 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 12/68] 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 13/68] 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 14/68] 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 15/68] 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 16/68] 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 17/68] 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 18/68] 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 19/68] 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 20/68] 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 21/68] 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 22/68] 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 23/68] 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 24/68] 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 25/68] 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 26/68] 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 27/68] 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 28/68] 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 29/68] 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 30/68] 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 31/68] 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 32/68] 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 33/68] 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 34/68] 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 35/68] 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 36/68] 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 37/68] 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 38/68] insert coin noise works but crashes at end --- ch32v-insert-coin/audio/coin3.raw | Bin 0 -> 7999 bytes ch32v-insert-coin/audio/coin4.raw | Bin 0 -> 7164 bytes ch32v-insert-coin/audio/coin5.raw | Bin 0 -> 6224 bytes ch32v-insert-coin/src/app.rs | 2 +- .../src/insert_coin/services/dac.rs | 6 +++++- ch32v-insert-coin/src/main.rs | 2 +- 6 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 ch32v-insert-coin/audio/coin3.raw create mode 100644 ch32v-insert-coin/audio/coin4.raw create mode 100644 ch32v-insert-coin/audio/coin5.raw diff --git a/ch32v-insert-coin/audio/coin3.raw b/ch32v-insert-coin/audio/coin3.raw new file mode 100644 index 0000000000000000000000000000000000000000..8c590d9611af6e92db2156177783344b25d97ae2 GIT binary patch literal 7999 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 literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/audio/coin5.raw b/ch32v-insert-coin/audio/coin5.raw new file mode 100644 index 0000000000000000000000000000000000000000..922bb2cc6a704c38f152b3f470fbf90d286690a3 GIT binary patch literal 6224 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
  • g+8qH~Evu@8&aoXMuE zJ-GPAt|(e?C5UZJR0)^?7ewjkX3^t)UL>&#dNue!jl_TKjY7Xz-sTaegDZJwHGHi;w>w_rJK=f64zHJn%m@0sR{Q literal 0 HcmV?d00001 diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs index 00b764e..9703585 100644 --- a/ch32v-insert-coin/src/app.rs +++ b/ch32v-insert-coin/src/app.rs @@ -470,7 +470,7 @@ impl App { if self.services.sample_player.need_service() { self.services.sample_player.service(); - let out = self.services.sample_player.get_amplitude() / 3; + let out = self.services.sample_player.get_amplitude() / 2; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); diff --git a/ch32v-insert-coin/src/insert_coin/services/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index d262a14..0a3b364 100644 --- a/ch32v-insert-coin/src/insert_coin/services/dac.rs +++ b/ch32v-insert-coin/src/insert_coin/services/dac.rs @@ -59,6 +59,10 @@ impl<'a> TickService for DacService<'a> { fn service(&self) { let mut tc = self.service_data.borrow_mut(); tc.ticks_remaining = tc.ticks_per_service; - self.set_amplitude(self.dpcm_decoder.borrow_mut().output_next()); + if (self.dpcm_decoder.borrow().is_done()) { + self.set_amplitude(0); + } else { + self.set_amplitude(self.dpcm_decoder.borrow_mut().output_next()); + } } } diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index c14a77a..d29f7d5 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -265,7 +265,7 @@ fn app_main(mut p: hal::Peripherals) -> ! { let dac_tick_per_service = tick_rate_hz / dac_sample_rate_hz; let dac_service_data = TickServiceData::new(dac_tick_per_service); - let coin_sound = include_bytes!("../audio/coin.raw"); + let coin_sound = include_bytes!("../audio/coin5.raw"); // let coin_sound = include_bytes!("../audio/coin2.raw"); let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); From a7cd209989d0bc55f27fe9446a1e563c067a3c32 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 17:46:41 -0700 Subject: [PATCH 39/68] 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 40/68] 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 41/68] 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 42/68] 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 43/68] 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 44/68] 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 45/68] 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 46/68] 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 47/68] 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 48/68] 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 49/68] 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 50/68] 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 51/68] 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 52/68] 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 53/68] 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 54/68] 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 55/68] 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 56/68] 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 57/68] 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 58/68] 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 59/68] 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 60/68] 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 61/68] 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 62/68] 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 63/68] 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 64/68] 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 65/68] 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 66/68] 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 67/68] 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 68/68] 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); }