diff --git a/.gitmodules b/.gitmodules index fb915f4..84792a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +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 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/audio/button_1.raw b/ch32v-insert-coin/audio/button_1.raw new file mode 100644 index 0000000..ade05bd Binary files /dev/null and b/ch32v-insert-coin/audio/button_1.raw differ diff --git a/ch32v-insert-coin/audio/button_2.raw b/ch32v-insert-coin/audio/button_2.raw new file mode 100644 index 0000000..16b9b36 Binary files /dev/null and b/ch32v-insert-coin/audio/button_2.raw differ diff --git a/ch32v-insert-coin/audio/button_3.raw b/ch32v-insert-coin/audio/button_3.raw new file mode 100644 index 0000000..0477e75 Binary files /dev/null and b/ch32v-insert-coin/audio/button_3.raw differ diff --git a/ch32v-insert-coin/audio/coin2.raw b/ch32v-insert-coin/audio/coin2.raw new file mode 100644 index 0000000..1caabb9 Binary files /dev/null and b/ch32v-insert-coin/audio/coin2.raw differ diff --git a/ch32v-insert-coin/audio/coin3.raw b/ch32v-insert-coin/audio/coin3.raw new file mode 100644 index 0000000..8c590d9 Binary files /dev/null and b/ch32v-insert-coin/audio/coin3.raw differ diff --git a/ch32v-insert-coin/audio/coin4.raw b/ch32v-insert-coin/audio/coin4.raw new file mode 100644 index 0000000..f350cd0 Binary files /dev/null and b/ch32v-insert-coin/audio/coin4.raw differ diff --git a/ch32v-insert-coin/audio/coin5.raw b/ch32v-insert-coin/audio/coin5.raw new file mode 100644 index 0000000..922bb2c Binary files /dev/null and b/ch32v-insert-coin/audio/coin5.raw differ diff --git a/ch32v-insert-coin/audio/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/bins.zip b/ch32v-insert-coin/bins.zip deleted file mode 100644 index b77a194..0000000 Binary files a/ch32v-insert-coin/bins.zip and /dev/null differ 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 c003c2a..0000000 Binary files a/ch32v-insert-coin/bins/coin_sound_16ksps.bin and /dev/null differ 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 9796caf..0000000 Binary files a/ch32v-insert-coin/bins/coin_sound_6ksps.bin and /dev/null differ diff --git a/ch32v-insert-coin/bins/coin_sound_8ksps.bin b/ch32v-insert-coin/bins/coin_sound_8ksps.bin deleted file mode 100755 index 6cafcc9..0000000 Binary files a/ch32v-insert-coin/bins/coin_sound_8ksps.bin and /dev/null differ diff --git a/ch32v-insert-coin/ext/adpcm-pwm-dac b/ch32v-insert-coin/ext/adpcm-pwm-dac index ba25b7c..99ce71e 160000 --- a/ch32v-insert-coin/ext/adpcm-pwm-dac +++ b/ch32v-insert-coin/ext/adpcm-pwm-dac @@ -1 +1 @@ -Subproject commit ba25b7c89f4deb52426d97fd35eb13496f183775 +Subproject commit 99ce71e8d03e382b51732db7d0771349c51c7f48 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/ext/wavetable-synth b/ch32v-insert-coin/ext/wavetable-synth new file mode 160000 index 0000000..30033e1 --- /dev/null +++ b/ch32v-insert-coin/ext/wavetable-synth @@ -0,0 +1 @@ +Subproject commit 30033e1438c25aed4ea506cdbd69cc4ceffa0b4e diff --git a/ch32v-insert-coin/src/app.rs b/ch32v-insert-coin/src/app.rs new file mode 100644 index 0000000..a8fdbb8 --- /dev/null +++ b/ch32v-insert-coin/src/app.rs @@ -0,0 +1,705 @@ +#[derive(Default, Clone, Copy)] +pub enum State { + // system is asleep, waiting for wake from coin insertion + DeepSleep, + // system is in low-power mode, dimmed lights, waiting for interaction + Idle, + // system is active. on entry: play coin sound. on button press: play different sound + #[default] + Active, +} + +pub mod settings { + + #[derive(Debug, Default, Clone, Copy)] + pub enum Level { + Off, + Low, + #[default] + Medium, + High, + Maximum, + } + + impl Level { + pub fn next(&mut self) { + *self = match self { + Self::Off => Self::Low, + Self::Low => Self::Medium, + Self::Medium => Self::High, + Self::High => Self::Maximum, + Self::Maximum => Self::Off, + }; + } + } + + // volume control + impl Level { + pub fn as_volume_divisor(&self) -> u8 { + match self { + Self::Off => u8::MAX, + Self::Low => 4, + Self::Medium => 3, + Self::High => 2, + Self::Maximum => 1, + } + } + + pub fn as_brightness_divisor(&self) -> u8 { + match self { + Self::Off => u8::MAX, + Self::Low => 8, + Self::Medium => 6, + Self::High => 4, + Self::Maximum => 2, + } + } + } + + #[derive(Clone, Copy)] + pub struct Settings { + pub brightness: Level, + pub volume: Level, + pub button_sound_index: usize, + } + + impl Default for Settings { + fn default() -> Self { + Self { + brightness: Level::Medium, + volume: Level::Medium, + button_sound_index: 0, + } + } + } +} + +pub mod sequencer { + pub struct BasicSequence<'a> { + sequence: &'a [u8], + index: usize, + } + impl<'a> BasicSequence<'a> { + pub fn new(sequence: &'a [u8]) -> Self { + Self { sequence, index: 0 } + } + pub fn next(&mut self) { + self.index += 1; + if self.index > self.sequence.len() - 1 { + self.index = 0; + } + } + pub fn get_value(&self) -> u8 { + self.sequence[self.index] + } + } + + pub struct SequenceEntry { + pub frequency_hz: u16, + pub duration_ms: u16, + } + + pub struct DynamicSequence<'a> { + sequence: &'a [SequenceEntry], + index: usize, + + // timer stuff + system_tick_rate_hz: usize, + ticks_remaining: usize, + + // playback stuff + num_loops: usize, + loop_count: usize, + + // misc + enabled: bool, + } + + impl<'a> DynamicSequence<'a> { + pub fn new(sequence: &'a [SequenceEntry], system_tick_rate_hz: usize) -> Self { + Self { + sequence, + index: 0, + system_tick_rate_hz, + ticks_remaining: 0, + num_loops: 1, + loop_count: 0, + enabled: false, + } + } + + pub fn play_sequence(&mut self, sequence: &'a [SequenceEntry], num_loops: usize) { + self.sequence = sequence; + self.num_loops = num_loops; + self.loop_count = 0; + self.enabled = true; + self.index = 0; + self.ticks_remaining = 0; + } + + pub fn enable(&mut self) { + self.enabled = true; + } + + pub fn disable(&mut self) { + self.enabled = false; + } + + pub fn tick(&mut self) { + if self.enabled { + self.ticks_remaining = self.ticks_remaining.saturating_sub(1); + } + } + + pub fn need_service(&self) -> bool { + self.enabled && self.ticks_remaining == 0 + } + + pub fn service(&mut self) -> Option { + let entry = &self.sequence[self.index]; + self.ticks_remaining = self.system_tick_rate_hz * (entry.duration_ms as usize) / 1000; + self.index = self.index.saturating_add(1); + + let out = if self.loop_count > self.num_loops { + None + } else { + Some(entry.frequency_hz) + }; + + if self.index > self.sequence.len() - 1 { + self.index = 0; + self.loop_count = self.loop_count.saturating_add(1); + } + + out + } + } +} + +use crate::insert_coin::{ + DacService, LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, +}; +use crate::synthesizer::SynthesizerService; + +pub use settings::Settings; + +// #[cfg(feature = "enable_print")] +use ch32_hal::println; + +pub struct TimerConfig { + pub sp_timer_ms: usize, + pub lp_timer_ms: usize, + pub batt_adc_timer_ms: usize, + pub usb_adc_timer_ms: usize, + pub led0_timer_ms: usize, + pub led1_timer_ms: usize, + pub shutdown_timer_s: usize, + // pub led2_timer_ms: usize, +} + +pub struct Timers { + sp_timer: TickTimerService, + lp_timer: TickTimerService, + batt_adc_timer: TickTimerService, + usb_adc_timer: TickTimerService, + led0_timer: TickTimerService, + led1_timer: TickTimerService, + shutdown_timer: TickTimerService, + pps_timer: TickTimerService, + // led2_timer: TickTimerService, +} + +impl Timers { + pub fn new(config: TimerConfig, system_tick_rate_hz: usize) -> Self { + Self { + sp_timer: TickTimerService::new( + TickServiceData::new(config.sp_timer_ms * system_tick_rate_hz / 1000), + false, + ), + lp_timer: TickTimerService::new( + TickServiceData::new(config.lp_timer_ms * system_tick_rate_hz / 1000), + false, + ), + batt_adc_timer: TickTimerService::new( + TickServiceData::new(config.batt_adc_timer_ms * system_tick_rate_hz / 1000), + false, + ), + usb_adc_timer: TickTimerService::new( + TickServiceData::new(config.usb_adc_timer_ms * system_tick_rate_hz / 1000), + false, + ), + led0_timer: TickTimerService::new( + TickServiceData::new(config.led0_timer_ms * system_tick_rate_hz / 1000), + true, + ), + led1_timer: TickTimerService::new( + TickServiceData::new(config.led1_timer_ms * system_tick_rate_hz / 1000), + true, + ), + shutdown_timer: TickTimerService::new( + TickServiceData::new(config.shutdown_timer_s), + false, + ), + pps_timer: TickTimerService::new(TickServiceData::new(system_tick_rate_hz), true), + // led2_timer: TickTimerService::new( + // TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), + // true, + // ), + } + } + pub fn tick(&mut self) { + self.sp_timer.tick(); + self.lp_timer.tick(); + self.batt_adc_timer.tick(); + self.usb_adc_timer.tick(); + self.led0_timer.tick(); + self.led1_timer.tick(); + self.pps_timer.tick(); + // self.led2_timer.tick(); + } + pub fn need_service(&self) -> bool { + self.sp_timer.need_service() + | self.lp_timer.need_service() + | self.batt_adc_timer.need_service() + | self.usb_adc_timer.need_service() + | self.led0_timer.need_service() + | self.led1_timer.need_service() + | self.shutdown_timer.need_service() + | self.pps_timer.need_service() + // | self.led2_timer.need_service() + } + pub fn init(&mut self) { + self.led0_timer.reset(); + self.led0_timer.enable(true); + } +} + +// pub struct ServiceConfigs { + +// } +// things that sort of don't touch hardware but also do? +// TODO: make this merged with the interfaces maybe +// but also maybe not, since these are things that require servicing +pub struct Services { + pub led0: LedService, + pub led1: LedService, + // pub led2: LedService, + pub synth0: SynthesizerService, + pub sample_player: DacService<'static>, + pub sequencer: sequencer::DynamicSequence<'static>, +} + +impl Services { + pub fn tick(&mut self) { + self.synth0.tick(); + self.sample_player.tick(); + self.sequencer.tick(); + } +} + +pub struct Config { + pub system_tick_rate_hz: usize, + pub timers: TimerConfig, +} + +pub struct Sequences { + pub led0: sequencer::BasicSequence<'static>, + pub led1: sequencer::BasicSequence<'static>, + // pub led2: sequencer::BasicSequence<'static>, + pub audio: &'static [(&'static [sequencer::SequenceEntry], usize)], +} + +// things that touch hardware +pub struct Interfaces { + pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, + pub adc_core: crate::AdcCore, + pub amp: crate::Amplifier, + pub usb: crate::Usb, +} + +pub struct App { + state: State, + pub settings: Settings, + timers: Timers, + services: Services, + sequences: Sequences, + interfaces: Interfaces, +} + +use settings::Level; +impl App { + pub fn new( + config: Config, + services: Services, + sequences: Sequences, + interfaces: Interfaces, + settings: Settings, + ) -> Self { + Self { + state: State::default(), + settings, + timers: Timers::new(config.timers, config.system_tick_rate_hz), + services, + sequences, + interfaces, + } + } + + pub fn init(&mut self) { + // self.timers.init(); + self.interfaces.amp.enable(); + + self.timers.batt_adc_timer.reset(); + self.timers.batt_adc_timer.enable(true); + + self.timers.usb_adc_timer.reset(); + self.timers.usb_adc_timer.enable(true); + + self.timers.led0_timer.reset(); + self.timers.led0_timer.enable(true); + + self.timers.led1_timer.reset(); + self.timers.led1_timer.enable(true); + + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + + self.timers.pps_timer.reset(); + self.timers.pps_timer.enable(true); + + // self.timers.led2_timer.reset(); + // self.timers.led2_timer.enable(true); + + // self.services.synth0.set_freq(1); + self.services.synth0.disable(); + self.services.sequencer.disable(); + + crate::riscv::asm::delay(2_500_000); + } + + pub fn set_state(&mut self, state: State) { + self.state = state + } + + pub fn state(&self) -> State { + self.state + } + + pub fn settings(&self) -> Settings { + self.settings + } + + pub fn tick(&mut self) { + self.timers.tick(); + self.services.tick(); + } + + pub fn service(&mut self) { + // timers + if self.timers.sp_timer.need_service() { + self.timers.sp_timer.service(); + #[cfg(feature = "enable_print")] + println!("sp service"); + self.timers.sp_timer.reset(); + self.main_button_short_press(); + } + if self.timers.lp_timer.need_service() { + self.timers.lp_timer.service(); + #[cfg(feature = "enable_print")] + println!("lp service"); + self.timers.lp_timer.reset(); + self.main_button_long_press(); + } + if self.timers.batt_adc_timer.need_service() { + self.timers.batt_adc_timer.service(); + if !self.interfaces.usb.powered() { + let bv = self.interfaces.adc_core.get_battery_voltage(); + let avg = self.interfaces.adc_core.get_average(); + // #[cfg(feature = "enable_print")] + // println!("batt adc service: {bv}, {avg}"); + // println!("none USB"); + if avg < 421 { + self.set_state(State::DeepSleep); + } + } + } + if self.timers.usb_adc_timer.need_service() { + self.timers.usb_adc_timer.service(); + #[cfg(feature = "enable_print")] + println!("usb adc service"); + } + if self.timers.led0_timer.need_service() { + let out = match self.settings.brightness { + Level::Off => 0, + Level::Low => 5, + Level::Medium => 25, + Level::High => 75, + Level::Maximum => { + self.sequences.led0.next(); + self.sequences.led0.get_value() / 6 + } + }; + + self.timers.led0_timer.service(); + self.services.led0.set_amplitude(out); + // #[cfg(feature = "enable_print")] + // println!("led0 sevice {}", self.sequences.led0.get_value()); + } + if self.timers.led1_timer.need_service() { + let out = match self.settings.brightness { + Level::Off => 0, + Level::Low => 5, + Level::Medium => 25, + Level::High => 75, + Level::Maximum => { + self.sequences.led1.next(); + self.sequences.led1.get_value() / 6 + } + }; + self.timers.led1_timer.service(); + self.services.led1.set_amplitude(out); + + // #[cfg(feature = "enable_print")] + // println!("led1 service"); + } + if self.timers.pps_timer.need_service() { + self.timers.pps_timer.service(); + self.timers.shutdown_timer.tick(); + } + if self.timers.shutdown_timer.need_service() { + self.timers.shutdown_timer.service(); + self.timers.shutdown_timer.reset(); + // println!("eepy"); + self.set_state(State::DeepSleep); + } + // if self.timers.led2_timer.need_service() { + // let out = match self.settings.brightness { + // Level::Off => 0, + // Level::Low => 5, + // Level::Medium => 25, + // Level::High => 75, + // Level::Maximum => { + // self.sequences.led2.next(); + // self.sequences.led2.get_value() / 6 + // } + // }; + // self.timers.led2_timer.service(); + // self.services.led2.set_amplitude(out); + + // // #[cfg(feature = "enable_print")] + // // println!("led2 service"); + // } + + // services + if self.services.led0.need_service() { + self.interfaces + .pwm_core + .write_amplitude(self.services.led0.channel, self.services.led0.amplitude); + self.services.led0.service(); + } + if self.services.led1.need_service() { + self.interfaces + .pwm_core + .write_amplitude(self.services.led1.channel, self.services.led1.amplitude); + self.services.led1.service(); + } + // if self.services.led2.need_service() { + // self.interfaces + // .pwm_core + // .write_amplitude(self.services.led2.channel, self.services.led2.amplitude); + // self.services.led2.service(); + // } + + // TODO: disable when you get to the end automatically + // in the sequencer, not here + if self.services.sequencer.need_service() { + if let Some(out) = self.services.sequencer.service() { + if out == 0 { + self.services.synth0.disable(); + } else { + self.services.synth0.enable(); + self.services.synth0.set_freq(out.into()); + } + } else { + self.services.sequencer.disable(); + self.services.synth0.disable(); + } + } + + if self.services.synth0.need_service() { + let out = match self.services.synth0.service() { + Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), + None => 0, + }; + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out); + } + + if self.services.sample_player.need_service() { + self.services.sample_player.service(); + let out = self.services.sample_player.get_amplitude() / 2; + self.interfaces + .pwm_core + .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); + } + } +} + +// interfaces to the app (for buttons, etc.) +impl App { + pub fn shut_down(&mut self) { + self.interfaces + .pwm_core + .write_amplitude(self.services.led0.channel, 0); + self.interfaces + .pwm_core + .write_amplitude(self.services.led1.channel, 0); + crate::riscv::asm::delay(10_000_000); + + self.interfaces.pwm_core.pwm.borrow().shutdown(); + self.interfaces.adc_core.shutdown(); + + self.interfaces.amp.disable(); + } + pub fn volume_button(&mut self) { + self.settings.volume.next(); + #[cfg(feature = "enable_print")] + println!("new volume: {:?}", self.settings.volume); + self.services + .sequencer + .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } + pub fn brightness_button(&mut self) { + self.settings.brightness.next(); + #[cfg(feature = "enable_print")] + println!("new brightness: {:?}", self.settings.brightness); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } + pub fn main_button_press(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("main button press"); + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); + self.timers.shutdown_timer.reset(); + self.timers.sp_timer.enable(true); + self.timers.lp_timer.enable(true); + self.timers.shutdown_timer.enable(true); + self.main_button_click(); + } + pub fn main_button_release(&mut self) { + // TODO + // #[cfg(feature = "enable_print")] + // println!("main button release"); + // let timers = ( + // self.timers.sp_timer.is_enabled(), + // self.timers.lp_timer.is_enabled(), + // ); + + self.timers.sp_timer.reset(); + self.timers.lp_timer.reset(); + // match timers { + // // click + // (true, true) => self.main_button_click(), + // // short press + // (false, true) => self.main_button_short_press(), + // // long press + // (false, false) => self.main_button_long_press(), + // // anything else is not possible + // _ => {} + // } + } + pub fn coin_detect(&mut self) { + #[cfg(feature = "enable_print")] + println!("coin detect"); + // self.services.sample_player.play_sample(); + self.services + .sequencer + .play_sequence(&crate::sequences::COIN_CHIRP, 0); + self.timers.shutdown_timer.reset(); + self.timers.shutdown_timer.enable(true); + } +} + +// Events +impl App { + pub fn main_button_click(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("click"); + let data = self.sequences.audio[self.settings.button_sound_index]; + self.services.sequencer.play_sequence(data.0, data.1); + + self.settings.button_sound_index += 1; + if self.settings.button_sound_index > self.sequences.audio.len() - 1 { + self.settings.button_sound_index = 0; + } + + self.services.synth0.enable(); + + // TODO: + // this is a hack to stop the coin thing from playing. + self.services.sample_player.disable(); + } + fn main_button_short_press(&self) { + // TODO + #[cfg(feature = "enable_print")] + println!("short press"); + } + fn main_button_long_press(&mut self) { + // TODO + #[cfg(feature = "enable_print")] + println!("long press"); + self.set_state(State::DeepSleep); + } +} + +// Getters +impl App { + pub fn get_state(&self) -> State { + self.state + } + pub fn get_settings(&self) -> Settings { + self.settings + } + pub fn should_wake(&self) -> bool { + if self.interfaces.usb.powered() { + return true; + } else { + if self.interfaces.adc_core.get_average() >= 421 { + return true; + } + } + return false; + } +} + +// TODO LIST +// BROKEN: +// 1. audio scaling causes crash +// 2. popping on sample playback +// 3. actual app sequence (start at idle?) +// +// AUDIO: +// 3. amp_en control +// +// LED: +// +// INTERFACE: +// 1. short press handling +// 2. long press handling +// +// SYSTEM: +// 1. deep sleep +// 2. battery voltage monitoring +// 3. battery voltage cutoff +// +// STRETCH TODO LIST +// 1. clean up edge detector +// 2. better handling for pwm scaling (brightness / volume) +// 3. better interrupt handling structs +// 4. led DynamicSequence diff --git a/ch32v-insert-coin/src/debounced_gpio.rs b/ch32v-insert-coin/src/debounced_gpio.rs new file mode 100644 index 0000000..784c3fd --- /dev/null +++ b/ch32v-insert-coin/src/debounced_gpio.rs @@ -0,0 +1,66 @@ +use crate::insert_coin::{TickService, TickServiceData, TickTimerService}; +use ch32_hal::gpio::{AnyPin, Input, Pull}; + +pub struct DebouncedGPIO<'a> { + input: Input<'a>, + // value of the GPIO + value: bool, + // GPIO is ready (debounced) + ready: bool, + // debouncer is active + active: bool, + // debounce timer + timer: TickTimerService, +} + +impl<'a> DebouncedGPIO<'a> { + pub fn new(pin: AnyPin, system_tick_rate_hz: usize, debounce_time_ms: usize) -> Self { + // coin debounce timer (100ms) + Self { + input: Input::new(pin, Pull::None), + value: false, + ready: false, + active: false, + timer: TickTimerService::new( + TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), + false, + ), + } + } + + pub fn ready(&self) -> bool { + self.ready + } + + pub fn active(&self) -> bool { + self.active + } + + pub fn value(&self) -> bool { + self.value + } + + pub fn begin(&mut self) { + self.reset(); + self.timer.enable(true); + self.active = true; + } + + pub fn reset(&mut self) { + self.timer.reset(); + self.ready = false; + self.active = false; + } + + pub fn service(&mut self) { + self.timer.tick(); + if self.timer.need_service() { + self.timer.reset(); + self.value = self.input.is_high(); + self.ready = true; + } + } + pub fn is_high_immediate(&self) -> bool { + self.input.is_high() + } +} diff --git a/ch32v-insert-coin/src/insert_coin/insert_coin.rs b/ch32v-insert-coin/src/insert_coin/insert_coin.rs index 90d98c2..8c0a606 100644 --- a/ch32v-insert-coin/src/insert_coin/insert_coin.rs +++ b/ch32v-insert-coin/src/insert_coin/insert_coin.rs @@ -1,9 +1,8 @@ -use ch32_hal::timer::GeneralInstance16bit; use ch32_hal::timer::simple_pwm::SimplePwm; use ch32_hal::timer::Channel; +use ch32_hal::timer::GeneralInstance16bit; -use crate::insert_coin::services::{DacService, LedService, TickService, TickServiceData, Service}; - +use crate::insert_coin::services::{DacService, LedService, Service, TickService, TickServiceData}; // static mut led0_index: usize = 0; // static LED0: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; @@ -11,8 +10,8 @@ use crate::insert_coin::services::{DacService, LedService, TickService, TickServ // static mut LED1_INDEX: usize = 0; // static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8]; -pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { - pwm: core::cell::RefCell>, +pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { + pub pwm: core::cell::RefCell>, } impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { @@ -38,22 +37,26 @@ 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 { +// core: &'static SimplePwmCore<'_, T: GeneralInstance16Bit>, +// channel: Channel, +// } - +// impl SimplePwmHandle { +// pub fn set_amplitude(&self, amplitude: u8) {} +// } pub struct CoreConfig { pub tick_rate_hz: usize, } impl CoreConfig { pub fn new(tick_rate_hz: usize) -> Self { - Self { - tick_rate_hz, - } + Self { tick_rate_hz } } } @@ -70,25 +73,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 +103,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 +132,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 +140,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 +155,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 +174,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/insert_coin/mod.rs b/ch32v-insert-coin/src/insert_coin/mod.rs index e7c42d8..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::TickTimerService; +pub use services::{DacService, 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/dac.rs b/ch32v-insert-coin/src/insert_coin/services/dac.rs index 4cdaec0..0a3b364 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; @@ -9,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> { @@ -18,9 +18,19 @@ 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(&mut self) { + self.dpcm_decoder.borrow_mut().seek_to_sample(0); + self.enabled = true; + } + + pub fn disable(&mut self) { + self.enabled = false; + } + pub fn load_data(&self, data: &'a [u8]) { self.dpcm_decoder.borrow_mut().load_data(data); self.dpcm_decoder.borrow_mut().seek_to_sample(0); @@ -36,17 +46,23 @@ 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) { 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/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/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; } } - - diff --git a/ch32v-insert-coin/src/main.rs b/ch32v-insert-coin/src/main.rs index 40b71dc..694ce75 100644 --- a/ch32v-insert-coin/src/main.rs +++ b/ch32v-insert-coin/src/main.rs @@ -3,14 +3,35 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +// app stuff mod insert_coin; + +// system stuff +mod system; + +mod debounced_gpio; +use debounced_gpio::DebouncedGPIO; + +// synthesizer :3 +mod synthesizer; +use synthesizer::SynthesizerService; + +// sequences +mod sequences; +use sequences::SEQUENCE_LIST; + +mod app; +use app::{ + sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig, +}; + use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity}; -use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore}; +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, OutputOpenDrain, Pin, Pull}; use hal::time::Hertz; use hal::timer::low_level::CountingMode; use hal::timer::simple_pwm::{PwmPin, SimplePwm}; @@ -19,283 +40,249 @@ 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, +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<'a> DebouncedGPIO<'a> { - pub fn new(pin: AnyPin, system_tick_rate_hz: usize, debounce_time_ms: usize) -> Self { - // coin debounce timer (100ms) +impl Usb { + pub fn new(usb_pin: Input<'static>) -> Self { + Self { usb_pin } + } + pub fn powered(&self) -> bool { + self.usb_pin.is_high() + } + // pub fn enable(&mut self) { + // self.usb_pin.set_as_input(Pull::Up); + // } + // pub fn disable(&mut self) { + // self.usb_pin.set_as_input(Pull::None); + // } +} + +pub struct Amplifier { + amp_en: OutputOpenDrain<'static>, +} + +impl Amplifier { + pub fn new(amp_en: OutputOpenDrain<'static>) -> Self { + let mut amp = Self { amp_en }; + amp.disable(); + amp + } + pub fn enable(&mut self) { + self.amp_en.set_low(); + } + pub fn disable(&mut self) { + self.amp_en.set_high(); + } + pub fn enabled(&self) -> bool { + !self.amp_en.is_set_high() + } +} + +use hal::adc::Adc; +use hal::peripherals::{ADC1, PD4}; + +pub struct AdcCore { + adc: Adc<'static, ADC1>, + battery_pin: PD4, + batt_values: [u16; 10], + index: usize, +} + +impl AdcCore { + pub fn new(mut adc: Adc<'static, ADC1>, pin: PD4) -> Self { + riscv::asm::delay(20_000); + adc.calibrate(); Self { - input: Input::new(pin, Pull::Up), - value: false, - ready: false, - timer: TickTimerService::new( - TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), - false, - ), + adc, + battery_pin: pin, + batt_values: [1024; 10], + index: 0, } } - 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; + // TODO make this a float or something + pub fn get_battery_voltage(&mut self) -> u16 { + let val = self + .adc + .convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241); + self.batt_values[self.index] = val; + self.index += 1; + if self.index > &self.batt_values.len() - 1 { + self.index = 0; } + val + } + + pub fn get_average(&self) -> u16 { + let mut sum = 0; + for value in &self.batt_values { + sum += value; + } + sum / self.batt_values.len() as u16 + } + + pub fn shutdown(&mut self) { + self.adc.shutdown() } } -// 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, } - -/// 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; - } +impl Flag { + pub fn active(&self) -> bool { + unsafe { core::ptr::read_volatile(&raw const self.value as *const bool) } } - - // button_flag - if (bits & (0x1 << button_pin)) != 0x0 { - #[cfg(feature="enable_print")] - println!("button irq!"); - unsafe { - INPUT_FLAGS.button_flag = true; - } + 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) } } - - // 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, + sense_coin_flag: Flag, + main_btn_flag: Flag, + volume_btn_flag: bool, + light_ctrl_btn_flag: bool, + systick_flag: Flag, +} + +impl Default for InputFlags { + fn default() -> Self { + Self { + sense_coin_flag: Flag { value: false }, + main_btn_flag: Flag { value: false }, + volume_btn_flag: false, + light_ctrl_btn_flag: false, + systick_flag: Flag { value: false }, + } + } } static mut INPUT_FLAGS: InputFlags = InputFlags { - coin_flag: false, - button_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 }, }; struct Test {} impl Handler for Test { unsafe fn on_interrupt() { - #[cfg(feature="enable_print")] - println!("on_interrupt()"); - critical_section::with(|_| { - clear_interrupt(2, 6); + // #[cfg(feature = "enable_print")] + // println!("on_interrupt()"); + critical_section::with(|_| unsafe { + let flags = system::clear_interrupt(2, 6); + if flags[0] { + // safe because single-threaded + #[allow(static_mut_refs)] + INPUT_FLAGS.sense_coin_flag.set(); + } + if flags[1] { + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.set(); + } }); } } +#[qingke_rt::interrupt(core)] +fn SysTick() { + let r = &ch32_hal::pac::SYSTICK; + + // Clear interrupt flag + r.sr().write(|w| w.set_cntif(false)); + + unsafe { + // safe because single-threaded + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.set(); + } +} + +fn systick_init(tick_freq_hz: usize) { + let r = &ch32_hal::pac::SYSTICK; + + // Calculate counts per millisecond using HCLK/8 as clock source + // HCLK/8 = 48MHz/8 = 6MHz + // For tick_freq_hz interrupt: 6MHz / tick_freq_hz + let systick_per_tick = (48_000_000 / 8 / tick_freq_hz) as u32; + + // Reset SysTick + r.ctlr().write(|w| { + // Start with everything disabled + }); + + // Set compare register and reset counter + r.cmp().write_value(systick_per_tick - 1); + r.cnt().write_value(0); + + // Clear interrupt flag + r.sr().write(|w| w.set_cntif(false)); + + // Configure and start SysTick + r.ctlr().write(|w| { + w.set_ste(true); // Enable counter + w.set_stie(true); // Enable interrupt + w.set_stre(true); // Auto reload enable + w.set_stclk(ch32_hal::pac::systick::vals::Stclk::HCLK_DIV8); // HCLK/8 clock source + }); +} +fn systick_stop() { + let r = &ch32_hal::pac::SYSTICK; + // Reset SysTick + r.ctlr().write(|w| { + // Start with everything disabled + }); +} + bind_interrupts!(struct Irqs { EXTI7_0 => Test; }); // TODO: remove +use app::settings::Settings; use insert_coin::TickTimerService; use insert_coin::{TickService, TickServiceData}; -fn 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()); +fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings { + // initialize ADC core first, and exit if battery is too low + let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); + let mut batt_monitor_pin = p.PD4; + let mut adc_core = AdcCore::new(adc, batt_monitor_pin); - // 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); + 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); - delay.delay_ms(1000); - - unsafe{enter_standby(4)}; - delay.delay_ms(1000); - riscv::asm::wfi(); + let bv = adc_core.get_battery_voltage(); - // 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); + // 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; } -} - -fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { - // === 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 - 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; @@ -304,128 +291,158 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { 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), CountingMode::default(), ); - pwm.set_polarity(led0_ch, OutputPolarity::ActiveLow); + 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); - let sample_rate_hz = 16000; - let dac_tick_per_service = 5; - let tick_rate_hz = sample_rate_hz * dac_tick_per_service; + // pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow); + + let tick_rate_hz = 50000; let core_config = CoreConfig::new(tick_rate_hz); // === input setup === // adc - let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); - let mut adc_pin = p.PD4; + // 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_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")] - println!("ADC calibration value: {}", adc_cal); + + // #[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() - // ); - // println!( - // "push pin: {} | push port: {}", - // button_pin.pin(), - // button_pin.port() - // ); + let sense_coin_pin = p.PC2; + let main_btn_pin = p.PD6; + let volume_btn_pin = p.PC6; + let light_ctrl_btn_pin = p.PC7; + let amp_en = p.PC5; + // let extra_io_1 = p.PD0; + // let extra_io_2 = p.PD3; - //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 mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default()); + let amp = Amplifier::new(amp_en_output); // 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(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) }; + unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) }; // coin debouncer (100ms) - let mut 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, 20); + // main button debouncer (100ms) + let mut main_btn_input = + DebouncedGPIO::new(main_btn_pin.degrade(), core_config.tick_rate_hz, 20); // button debouncer (100ms) - let mut 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); + let timer_config = TimerConfig { + sp_timer_ms: 1000, + lp_timer_ms: 3000, + batt_adc_timer_ms: 1000, + usb_adc_timer_ms: 10000, + led0_timer_ms: 100, + led1_timer_ms: 100, + // 4 hours: + // shutdown_timer_s: 1, + shutdown_timer_s: 4 * 60 * 60 * 110 / 100, + // led2_timer_ms: 100, + }; - let mut interfaces = InsertCoin::new(core_config, pwm_core); - interfaces.set_active(true); - // insert_coin.init(); + let app_config = Config { + system_tick_rate_hz: tick_rate_hz, + timers: timer_config, + }; - let mut led0_index = 0; - let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; + // 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 mut led1_index = 0; - let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; + // let coin_sound = include_bytes!("../audio/coin5.raw"); + // let coin_sound = include_bytes!("../audio/coin2.raw"); - // 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); + let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); + // sample_player.load_data(coin_sound); - // 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); + let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz); - // 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(); + 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, + }; - // long press timer - let lp_ticks = 5 * interfaces.config.tick_rate_hz; - let lp_timer_data = TickServiceData::new(lp_ticks); - let mut lp_timer = TickTimerService::new(lp_timer_data, false); - lp_timer.reset(); + let app_sequences = Sequences { + led0: BasicSequence::new(&LED0_SEQ), + led1: BasicSequence::new(&LED0_SEQ), + // led2: BasicSequence::new(&LED0_SEQ), + audio: &SEQUENCE_LIST, + }; - // 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 app_interfaces = Interfaces { + pwm_core, + adc_core, + amp, + usb, + }; - let tick_interval_us = 1000000 / interfaces.config.tick_rate_hz - 10; + let mut app = App::new( + app_config, + app_services, + app_sequences, + app_interfaces, + app_settings, + ); - // dac data - let coin_sound = include_bytes!("../audio/coin.raw"); - // let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); + 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 + } + }; - 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); - clear_interrupt(2, 6); - qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); - // qingke::pfic::enable_interrupt(CoreInterrupt::SysTick 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); } // MAIN APPLICATION @@ -436,158 +453,165 @@ 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")] - 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(); + + app.init(); + if need_sound { + app.main_button_click(); + } loop { - { - // system input servicing + // system servicing + + // volume edge detector + if !volume_btn_input.active() { + let volume_btn_curr = volume_btn_input.is_high_immediate(); + if volume_btn_prev != volume_btn_curr { + volume_btn_input.begin(); + volume_btn_prev = volume_btn_curr; + } + } + + volume_btn_input.service(); + if volume_btn_input.ready() { + // #[cfg(feature = "enable_print")] + // println!("volume btn value: {}", volume_btn_input.value()); + if !volume_btn_input.value() { + app.volume_button(); + } + volume_btn_input.reset(); + } + + // brightness edge detector + if !light_ctrl_btn_input.active() { + let light_ctrl_btn_curr = light_ctrl_btn_input.is_high_immediate(); + if light_ctrl_btn_prev != light_ctrl_btn_curr { + light_ctrl_btn_input.begin(); + light_ctrl_btn_prev = light_ctrl_btn_curr; + } + } + + light_ctrl_btn_input.service(); + if light_ctrl_btn_input.ready() { + #[cfg(feature = "enable_print")] + println!("brightness btn value: {}", light_ctrl_btn_input.value()); + if !light_ctrl_btn_input.value() { + app.brightness_button(); + } + light_ctrl_btn_input.reset(); + } + + // coin_detect interrupt + + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.sense_coin_flag.active() { + #[allow(static_mut_refs)] + INPUT_FLAGS.sense_coin_flag.clear(); + sense_coin_input.begin(); + } + sense_coin_input.service(); + if sense_coin_input.ready() { + sense_coin_input.reset(); + app.coin_detect(); + } + } + + // main button handling + unsafe { + #[allow(static_mut_refs)] + if INPUT_FLAGS.main_btn_flag.active() { + #[allow(static_mut_refs)] + INPUT_FLAGS.main_btn_flag.clear(); + main_btn_input.begin(); + } + } + main_btn_input.service(); + if main_btn_input.ready() { + let value = main_btn_input.value(); + main_btn_input.reset(); + // #[cfg(feature = "enable_print")] + // println!("main button", value); + + match value { + true => app.main_button_press(), + false => app.main_button_release(), + } + } + + // systick tick + if unsafe { + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.active() + } { unsafe { - if INPUT_FLAGS.coin_flag { - #[cfg(feature="enable_print")] - 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 { - #[cfg(feature="enable_print")] - println!("button flag active"); - INPUT_FLAGS.button_flag = false; - button_input.begin(); - } - } - - // debouncer - coin_input.service(); - 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); - 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() { - #[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 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); - - adc1_timer.reset(); - adc1_timer.enable(true); + #[allow(static_mut_refs)] + INPUT_FLAGS.systick_flag.clear(); } + // app tick + app.tick(); } - 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; - } - }; - } - }, - SystemState::Idle => { + app.service(); - }, - SystemState::Active => { - tt0.tick(); - tt1.tick(); + match app.get_state() { + // enter standby + app::State::DeepSleep => { + app.shut_down(); + return app.get_settings(); + } + _ => {} + } + } +} - 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(); - } +use ch32_hal::timer::low_level::{OutputCompareMode, Timer}; +use ch32_hal::timer::Channel; - 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() - } +// fn shutdown_main(p: Peripherals) { +fn shutdown_main(p: hal::Peripherals) { + systick_stop(); + // LED0 output setup + let led0_pin = OutputOpenDrain::new(p.PC3, Level::Low, Default::default()); + let led1_pin = OutputOpenDrain::new(p.PD2, Level::High, Default::default()); + let led2_pin = OutputOpenDrain::new(p.PA1, Level::High, Default::default()); + let dac_pin = OutputOpenDrain::new(p.PC4, Level::Low, Default::default()); + let mut amp_pin = OutputOpenDrain::new(p.PC5, Level::Low, Default::default()); + amp_pin.set_high(); + let volume_btn_pin = OutputOpenDrain::new(p.PC6, Level::Low, Default::default()); + let light_ctrl_btn_pin = OutputOpenDrain::new(p.PC7, Level::Low, Default::default()); + let usb_detect_input = OutputOpenDrain::new(p.PD5, Level::Low, Default::default()); - interfaces.service(); + let sense_coin_pin = p.PC2; + let main_btn_pin = p.PD6; + + unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) }; + unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, false) }; + + let sense_coin_pin = Input::new(sense_coin_pin, Pull::None); + let main_btn_pin = Input::new(main_btn_pin, Pull::None); + + riscv::asm::delay(1_000_000); + + loop { + unsafe { system::enter_standby() }; + riscv::asm::wfi(); + unsafe { + #[allow(static_mut_refs)] + if (INPUT_FLAGS.sense_coin_flag.active() || INPUT_FLAGS.main_btn_flag.active()) + // && app.should_wake() + { + break; } } - - delay.delay_us(tick_interval_us as u32); } } #[qingke_rt::entry] fn main() -> ! { - #[cfg(feature="enable_print")] + #[cfg(feature = "enable_print")] hal::debug::SDIPrint::enable(); let mut config = hal::Config::default(); @@ -595,17 +619,30 @@ 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); + let mut app_settings = Settings::default(); + loop { + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + app_settings = app_main(p, app_settings); - + unsafe { + hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI); + } + let mut p = unsafe { hal::Peripherals::steal() }; + shutdown_main(p); + } } - #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { + // println!("panic: {info:?}"); loop {} } diff --git a/ch32v-insert-coin/src/sequences.rs b/ch32v-insert-coin/src/sequences.rs new file mode 100644 index 0000000..4cf6e4c --- /dev/null +++ b/ch32v-insert-coin/src/sequences.rs @@ -0,0 +1,346 @@ +use crate::app::sequencer::SequenceEntry; + +pub static TEST_SEQ: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 1000, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 1000, + }, +]; + +pub static TEST_SEQ1: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 100, + }, + SequenceEntry { + frequency_hz: 220, + duration_ms: 100, + }, +]; + +// play twice +pub static WAHWAH: [SequenceEntry; 9] = [ + SequenceEntry { + frequency_hz: 100, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 25, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 25, + }, +]; + +pub static DECENDING_TONES: [SequenceEntry; 21] = [ + SequenceEntry { + frequency_hz: 1100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1050, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 950, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 850, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 750, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 650, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 550, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 100, + duration_ms: 50, + }, +]; + +// PLAY ONCE +pub static COIN_CHIRP: [SequenceEntry; 2] = [ + SequenceEntry { + frequency_hz: 1000, + duration_ms: 100, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 500, + }, +]; + +// PLAY ONCE +pub static SOUND_8_BIT_GAME_4: [SequenceEntry; 4] = [ + SequenceEntry { + frequency_hz: 220, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 660, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 880, + duration_ms: 50, + }, +]; + +// PLAY TWICE +// TODO: this isn't working quite right with the 0hz "non note" for a rest... +pub static SOUND_8_BIT_GAME_1: [SequenceEntry; 3] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 0, + duration_ms: 50, + }, +]; + +// PLAY 3 TIMES +pub static SOUND_8_BIT_GAME_3: [SequenceEntry; 3] = [ + SequenceEntry { + frequency_hz: 440, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 660, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, +]; + +// PLAY 1 TIMES +pub static ASCENDING_TONES: [SequenceEntry; 29] = [ + SequenceEntry { + frequency_hz: 100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 500, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 550, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 600, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 650, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 700, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 750, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 800, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 850, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 900, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 950, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1000, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1050, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1100, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1150, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1200, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1250, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1300, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1350, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1400, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1450, + duration_ms: 50, + }, + SequenceEntry { + frequency_hz: 1500, + duration_ms: 1000, + }, +]; + +pub static SEQUENCE_LIST: [(&'static [SequenceEntry], usize); 7] = [ + (&SOUND_8_BIT_GAME_3, 2), + (&SOUND_8_BIT_GAME_4, 0), + (&ASCENDING_TONES, 0), + (&COIN_CHIRP, 0), + (&WAHWAH, 5), + (&DECENDING_TONES, 0), + (&SOUND_8_BIT_GAME_1, 1), + // &TEST_SEQ, +]; diff --git a/ch32v-insert-coin/src/synthesizer.rs b/ch32v-insert-coin/src/synthesizer.rs new file mode 100644 index 0000000..810336e --- /dev/null +++ b/ch32v-insert-coin/src/synthesizer.rs @@ -0,0 +1,66 @@ +use crate::insert_coin::SimplePwmCore; +use ch32_hal::println; +use ch32_hal::timer::GeneralInstance16bit; +use wavetable_synth::{synthesizer::SimpleWavetableSynthesizer, wavetable::SimpleWavetable}; + +const SQUARE_WAVETABLE: [u8; 2] = [0, 100]; +// const SQUARE_WAVETABLE: [u8; 32] = [ +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, +// 100, 100, 100, 100, 100, 100, 100, +// ]; + +pub struct SynthesizerService { + pub synth: SimpleWavetableSynthesizer>, + pub enabled: bool, + pub need_service: bool, +} + +impl SynthesizerService { + pub fn new(clock_freq_hz: usize) -> Self { + let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE); + let synth = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz); + + Self { + synth, + enabled: true, + need_service: false, + } + } + + pub fn tick(&mut self) { + if self.enabled { + self.synth.tick(); + } + } + + pub fn need_service(&self) -> bool { + self.need_service || (self.enabled && self.synth.has_new_output()) + } + + pub fn service(&mut self) -> Option { + self.need_service = false; + if self.enabled { + Some(self.synth.get_output()) + } else { + None + } + } +} + +impl SynthesizerService { + pub fn set_freq(&mut self, freq_hz: usize) { + self.synth.set_freq(freq_hz); + } + + pub fn enable(&mut self) { + self.enabled = true; + self.need_service = true; + // TODO: write the enable function + } + + pub fn disable(&mut self) { + self.enabled = false; + self.need_service = true; + // TODO: write the disable function + } +} diff --git a/ch32v-insert-coin/src/system.rs b/ch32v-insert-coin/src/system.rs new file mode 100644 index 0000000..484d513 --- /dev/null +++ b/ch32v-insert-coin/src/system.rs @@ -0,0 +1,134 @@ +use ch32_hal as hal; +use hal::println; + +pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { + critical_section::with(|_| { + #[cfg(feature = "enable_print")] + println!("init_gpio_irq {pin}:{port}"); + let exti = &hal::pac::EXTI; + let afio = &hal::pac::AFIO; + + let port = port as u8; + let pin = pin as usize; + + // let b = afio.exticr().read(); + afio.exticr().modify(|w| w.set_exti(pin, port)); + + exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt + exti.rtenr().modify(|w| w.set_tr(pin, rising)); + exti.ftenr().modify(|w| w.set_tr(pin, falling)); + }); +} + +pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> [bool; 2] { + let mut output = [false, false]; + + let exti = &hal::pac::EXTI; + + let coin_pin = coin_pin as usize; + let button_pin = button_pin as usize; + + exti.intenr().modify(|w| w.set_mr(coin_pin, false)); // disable interrupt + exti.intenr().modify(|w| w.set_mr(button_pin, false)); // disable interrupt + + let bits = exti.intfr().read(); + + // We don't handle or change any EXTI lines above 24. + let bits = bits.0 & 0x00FFFFFF; + #[cfg(feature = "enable_print")] + println!("bits: {bits:08x}"); + + // coin_flag + if (bits & (0x1 << coin_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("coin irq!"); + output[0] = true; + } + + // button_flag + if (bits & (0x1 << button_pin)) != 0x0 { + #[cfg(feature = "enable_print")] + println!("main_btn irq!"); + output[1] = true; + } + + // Clear pending - Clears the EXTI's line pending bits. + exti.intfr().write(|w| w.0 = bits); + + use hal::pac::Interrupt; + unsafe { + qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8); + }; + + // exti.intenr().modify(|w| w.0 = w.0 & !bits); + exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt + exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt + + output +} + +/// enter standby (SLEEPDEEP) mode, with WFE enabled. +/// from CH32V003 reference manual 2.3.1: +/// entry: +/// 1. configure core register control bit: SLEEPDEEP=1 | PDDS = 1 +/// 2. configure external interrupt register to mask all but EXTI7_0 +/// 3. execute WFI or WFE (optionally SEVONPEND) and SLEEPONEXIT +/// * (probably WFI?) +/// exit: +/// 1. any interrupt/event (set in external interrupt register) +pub unsafe fn enter_standby() { + critical_section::with(|_| { + use hal::pac::Interrupt; + use qingke_rt::CoreInterrupt; + + // configure core register control bit (SLEEPDEEP=1 | PDDS=1) + let pfic = &hal::pac::PFIC; + pfic.sctlr().modify(|w| { + // we only want to wake on enabled interrupts + w.set_sevonpend(false); + // we want to enable deep sleep + w.set_sleepdeep(true); + w.set_wfitowfe(false); + w.set_sleeponexit(false); + }); + + // set PDDS=1: + // get current value of PWR_CTLR + let mut reg: u32 = 0x4000_7000; + let mut val: u32 = unsafe { (reg as *mut u32).read_volatile() }; + // modify PDDS + val |= 1 << 1; // PWR_CTLR[1] -> PDDS + unsafe { + (reg as *mut u32).write_volatile(val); + } + + // // disable all exti interrupts + let exti = &hal::pac::EXTI; + // exti.intenr().write(| w| { + // w.0 = 0x00000000; + // // w.set_mr(pin, true); + // // w.set_mr(pin, true); + // }); + + // clear all pending exti interrupts + let bits = 0xFFFFFFFF; + exti.intfr().write(|w| w.0 = bits); + + // enable all exti interrupts + let exti = &hal::pac::EXTI; + exti.intenr().write(|w| { + w.0 = 0x00FFFFFF; + // w.set_mr(pin, true); + // w.set_mr(pin, true); + }); + + unsafe { + qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8); + qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); + } + + // execute WFI + #[cfg(feature = "enable_print")] + println!("WFI CONFIGURED HOPEFULLY"); + }); +}