#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] mod insert_coin; use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig}; use {ch32_hal as hal}; use hal::{bind_interrupts}; use hal::delay::Delay; use hal::gpio::{AnyPin, Input, Pin, Pull}; use hal::time::Hertz; use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; use hal::println; use qingke::riscv; struct DebouncedGPIO<'a> { input: Input<'a>, // value of the GPIO value: bool, // GPIO is ready (debounced) ready: 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, timer: TickTimerService::new(TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), false), } } pub fn ready(&self) -> bool { self.ready } pub fn value(&self) -> bool { self.value } pub fn begin(&mut self) { self.reset(); self.timer.enable(true); } pub fn reset(&mut self) { self.timer.reset(); self.ready = false; } pub fn service(&mut self) { self.timer.tick(); if self.timer.need_service() { self.timer.reset(); self.value = self.input.is_high(); self.ready = true; } } } // 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, } /// enter standby (SLEEPDEEP) mode, with WFE enabled. /// from CH32V003 reference manual 2.3.1: /// entry: /// 1. configure core register control bit: SLEEPDEEP=1 | PDDS = 1 /// 2. configure external interrupt register to mask all but EXTI7_0 /// 3. execute WFI or WFE (optionally SEVONPEND) and SLEEPONEXIT /// * (probably WFI?) /// exit: /// 1. any interrupt/event (set in external interrupt register) unsafe fn enter_standby(pin: usize) { critical_section::with(|_| { use hal::pac::Interrupt; use qingke_rt::CoreInterrupt; // configure core register control bit (SLEEPDEEP=1 | PDDS=1) let pfic = &hal::pac::PFIC; pfic.sctlr().modify(|w| { // we only want to wake on enabled interrupts w.set_sevonpend(true); // we want to enable deep sleep w.set_sleepdeep(true); w.set_wfitowfe(true); w.set_sleeponexit(false); }); // // disable all exti interrupts // let exti = &hal::pac::EXTI; // exti.intenr().write(| w| { // w.0 = 0x00000000; // // w.set_mr(pin, true); // // w.set_mr(pin, true); // }); // // clear all pending exti interrupts // let bits = 0xFFFFFFFF; // exti.intfr().write(|w| w.0 = bits); // // enable all exti interrupts // let exti = &hal::pac::EXTI; // exti.intenr().write(| w| { // w.0 = 0x00FFFFFF; // // w.set_mr(pin, true); // // w.set_mr(pin, true); // }); unsafe { // qingke::pfic::disable_interrupt(Interrupt::EXTI7_0 as u8); // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); // qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); } // execute WFI println!("WFI CONFIGURED HOPEFULLY"); // core::arch::asm!("wfi"); // pfic. // qingke::pfic:: }); } unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { critical_section::with(|_| { println!("init_gpio_irq"); 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 { println!("coin irq!"); unsafe { INPUT_FLAGS.coin_flag = true; } } // button_flag if (bits & (0x1 << button_pin)) != 0x0 { println!("button irq!"); unsafe { INPUT_FLAGS.button_flag = true; } } // Clear pending - Clears the EXTI's line pending bits. exti.intfr().write(|w| w.0 = bits); 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, button_flag: bool, } static mut INPUT_FLAGS: InputFlags = InputFlags{coin_flag: false, button_flag: false}; struct Test { } impl Handler for Test { unsafe fn on_interrupt() { println!("on_interrupt()"); critical_section::with(|_| { clear_interrupt(2, 6); }); } } bind_interrupts!(struct Irqs { EXTI7_0 => Test; }); // TODO: remove use insert_coin::{TickService, TickServiceData}; use insert_coin::TickTimerService; #[qingke_rt::entry] fn main() -> ! { hal::debug::SDIPrint::enable(); let mut config = hal::Config::default(); config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let p = hal::init(config); // delay to let the debugger attach let mut delay = Delay; delay.delay_ms(1000); // === output setup === // 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); let led1_ch = hal::timer::Channel::Ch1; // LED2 output setup // DAC output setup let dac_pin = PwmPin::new_ch4::<0>(p.PC4); // let dac_ch = hal::timer::Channel::Ch4; // PWM timer setup let mut pwm = SimplePwm::new( p.TIM1, Some(led1_pin), None, Some(led0_pin), Some(dac_pin), Hertz::khz(100), CountingMode::default(), ); pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow); let sample_rate_hz = 16000; let dac_tick_per_service = 5; let tick_rate_hz = sample_rate_hz * dac_tick_per_service; let core_config = CoreConfig::new(tick_rate_hz); // === input setup === // adc let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); let mut adc_pin = p.PD4; // println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel()); delay.delay_ms(1000); let adc_cal = adc.calibrate(); println!("ADC calibration value: {}", adc_cal); // definitions let coin_pin = p.PC2; let button_pin = p.PD6; println!("coin pin: {} | coin port: {}", coin_pin.pin(), coin_pin.port()); println!("push pin: {} | push port: {}", button_pin.pin(), button_pin.port()); //2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 // 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 // set up interrupts unsafe {init_gpio_irq(coin_pin.pin(), coin_pin.port(), false, true)}; unsafe {init_gpio_irq(button_pin.pin(), button_pin.port(), true, true)}; // coin debouncer (100ms) let mut coin_input = DebouncedGPIO::new(coin_pin.degrade(), core_config.tick_rate_hz, 100); // button debouncer (100ms) let mut button_input = DebouncedGPIO::new(button_pin.degrade(), core_config.tick_rate_hz, 100); let pwm_core = SimplePwmCore::new(pwm); let mut interfaces = InsertCoin::new(core_config, pwm_core); interfaces.set_active(true); // insert_coin.init(); let mut led0_index = 0; let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; let mut led1_index = 0; let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; // tick timer 0 let tt0_fire_rate_hz = 9; let tt0_tick_per_service = interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2); let tt0_service_data = TickServiceData::new(tt0_tick_per_service); let 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); let tick_interval_us = 1000000/interfaces.config.tick_rate_hz - 10; // dac data let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); let button_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); let mut system_state = SystemState::Active; interfaces.led0.set_amplitude(0); interfaces.led1.set_amplitude(0); interfaces.service(); unsafe { use hal::pac::Interrupt; // use qingke_rt::CoreInterrupt; // qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); clear_interrupt(4, 6); qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); } // MAIN APPLICATION // process // -depress big button (insert coin button) and it wakes up and turns on led one led at a fixed brightness // -we will want one sound, the coin insert sound, to play when coin button is pressed. (This is when they insert a coin) // -We will want a different sound or potentially multiple different sounds played in a rotating fashion when someone presses the button. Probably do some led blinking as well.(This is when they depress the big insert to play button) // -depress the big button for approx 2s and it puts the led into low light mode. Just making it dimmer than usual. // -depress the big button for 5s and it goes into deep sleep, everything off with the low sleep current draw println!("begin"); loop { { // system input servicing unsafe { if INPUT_FLAGS.coin_flag { println!("coin flag active"); INPUT_FLAGS.coin_flag = false; coin_input.begin(); // enter the active state system_state = SystemState::Active; // todo: enter active tt0.enable(true); tt1.enable(true); interfaces.dac.load_data(coin_sound); } if INPUT_FLAGS.button_flag { println!("button flag active"); INPUT_FLAGS.button_flag = false; button_input.begin(); } } // debouncer coin_input.service(); button_input.service(); if coin_input.ready() { println!("debounced coin_input value: {}", coin_input.value()); coin_input.reset(); } if button_input.ready() { let value = button_input.value(); button_input.reset(); println!("debounced button_input value: {}", value); if !value { interfaces.dac.load_data(button_sound); println!("reset hold timers + enable"); sp_timer.reset(); sp_timer.enable(true); lp_timer.reset(); lp_timer.enable(true); } else { sp_timer.reset(); lp_timer.reset(); } } // timers sp_timer.tick(); lp_timer.tick(); adc1_timer.tick(); if sp_timer.need_service() { println!("sp detect!"); sp_timer.reset(); // todo enter idle system_state = SystemState::Idle; // TODO: fix polarity interfaces.led0.set_amplitude(10); interfaces.led1.set_amplitude(10); interfaces.service(); } if lp_timer.need_service() { println!("lp detect!"); lp_timer.reset(); // todo enter deepsleep system_state = SystemState::DeepSleep; // TODO: fix polarity interfaces.led0.set_amplitude(0); interfaces.led1.set_amplitude(0); interfaces.service(); } if adc1_timer.need_service() { let val = adc.convert(&mut adc_pin, hal::adc::SampleTime::CYCLES241); println!("ADC value: {}", val); adc1_timer.reset(); adc1_timer.enable(true); } } match system_state { SystemState::DeepSleep => { // TODO: make this REALLY deep sleep unsafe{enter_standby(4)}; loop { riscv::asm::wfi(); unsafe{ if INPUT_FLAGS.coin_flag { break; } }; } }, SystemState::Idle => { }, SystemState::Active => { tt0.tick(); tt1.tick(); if tt0.need_service() { interfaces.led0.set_amplitude(led0_dcs[led0_index]); led0_index += 1; if led0_index > led0_dcs.len() - 1 { led0_index = 0; } tt0.service(); } if tt1.need_service() { interfaces.led1.set_amplitude(led1_dcs[led1_index]); led1_index += 1; if led1_index > led1_dcs.len() - 1 { led1_index = 0; } tt1.service() } interfaces.service(); }, } delay.delay_us(tick_interval_us as u32); } } #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }