Compare commits

..

63 commits

Author SHA1 Message Date
3265325d28 add coin chirp when you click the volume button 2025-11-17 18:42:16 -07:00
d8477e3d2d add usb power gating 2025-11-15 16:38:07 -07:00
472e43056d add systick disable 2025-11-15 16:23:18 -07:00
4c80907c0f change fudge factor to 110% bc i'm stupid 2025-11-15 16:10:35 -07:00
1d2e4a5482 update shutdown timer addtl time to 10% from 30% 2025-11-15 16:09:06 -07:00
d053f8d080 decrease wait time for app shutdown 2025-11-15 16:06:54 -07:00
f26920a099 add persistent settings 2025-11-15 16:03:32 -07:00
60d6aaecd6 add ability for main button sound 2025-11-15 15:55:23 -07:00
10a38f9bbc add shutdown delay 2025-11-15 15:40:08 -07:00
5934336984 add battery level checking 2025-11-15 15:37:29 -07:00
f3ac43614c fix amp_en 2025-11-15 15:22:24 -07:00
b6fa0e3b2d first working deep sleep entry with all peripherals enabled prior 2025-11-15 14:56:22 -07:00
cd91b3f540 clean up sleep handling a tiny amount 2025-11-15 01:01:10 -07:00
45db5e8af8 fix deep sleep 2025-11-14 18:57:36 -07:00
6ba94f1cbb make amp_en OutputOpenDrain 2025-11-14 16:14:09 -07:00
c71ace5063 fix sleep timer overflow 2025-11-14 16:09:27 -07:00
7e187680f5 don't use pullups 2025-11-14 15:46:28 -07:00
64aa1808d2 change direction of the USB detect input 2025-11-14 15:15:03 -07:00
2f92805c1a add USB check for powerup 2025-11-14 15:08:40 -07:00
c43cc5599e add usb power detection + check to ADC shutdown logic 2025-11-14 14:34:29 -07:00
8ea4b4401e add amp_en control 2025-11-14 13:30:01 -07:00
1c2823eb1b add 4 hour shutdown timer 2025-11-14 13:14:02 -07:00
17d6f156db remove led2 (tiny led) 2025-11-14 12:56:11 -07:00
b0b77a1538 add initial ADC shutdown code 2025-11-13 20:12:38 -07:00
43790abbc5 initial commit off adc into app 2025-11-12 20:13:57 -07:00
5288cba869 add AdcCore for ADC measurement 2025-11-12 20:06:17 -07:00
f4759a0c71 update .gitmodules to use https 2025-11-06 19:19:58 -07:00
3d8ddd1317 repo cleanup 2025-11-06 19:12:06 -07:00
713f3882a2 add breathing 2025-11-06 18:42:20 -07:00
a7cd209989 do interrupt handling for sleep 2025-11-06 17:46:41 -07:00
935129baed insert coin noise works but crashes at end 2025-11-06 16:54:46 -07:00
4e9428cb5f fix debouncer + other misc fixes for prod board 2025-11-06 15:10:08 -07:00
b52d911c12 allow no-print 2025-11-06 13:47:21 -07:00
a50895ec96 some more sequence tuning 2025-11-06 12:22:13 -07:00
88d07fa69d fix sequence frequency stuff to allow for rest notes 2025-11-06 11:54:17 -07:00
a1767420f2 update sequences 2025-11-06 11:31:26 -07:00
1bdb68c0d4 add some sequences 2025-11-06 10:53:57 -07:00
48b836904b add support for multi-sequence 2025-11-06 09:02:08 -07:00
9328087e23 minor fixes to sequence support 2025-11-05 20:25:29 -07:00
9e67026345 dynamic sequencer support 2025-11-05 19:42:44 -07:00
7d93cd8977 add initial long press deep sleep handling 2025-11-05 15:08:37 -07:00
5aa56a244d add click, short, and long press handling / detection 2025-11-05 14:26:50 -07:00
2d8e2ce6ee add short and long press timer integration 2025-11-05 12:20:54 -07:00
d815507bcc add main button press handling 2025-11-05 12:14:21 -07:00
c8c42c0194 add coin button handling and move coin dac inside app 2025-11-05 11:42:27 -07:00
930d10218f add basic brightness control 2025-11-05 10:24:19 -07:00
95b55f88a8 add basic volume control 2025-11-05 10:14:22 -07:00
d3ccc70782 move synthesizer into app struct 2025-11-05 08:54:37 -07:00
b786bf174a move debounced gpio to module file 2025-11-04 12:14:13 -07:00
f17811cdce more cleanup of dead code in main.rs 2025-11-04 12:05:16 -07:00
9e34e77e95 remove SystemState from main 2025-11-04 11:25:39 -07:00
e1943accd2 move led services inside app 2025-11-02 13:50:18 -07:00
791d5db4c8 convert app to tick servicing 2025-11-02 12:33:55 -07:00
008bf334a4 move appdata out to new module 2025-11-02 09:14:59 -07:00
0836fc58df use systick event system to drive synthesis 2025-11-01 09:36:27 -06:00
58579ae6c2 first wavetable synth demo 2025-10-27 21:10:36 -06:00
3ea7aac1f4 add other audio 2025-10-26 18:56:52 -06:00
17c82e5a6c add some button noises 2025-10-26 18:08:26 -06:00
9f12502a3d add edge detecting inputs for volume and brightness settings 2025-10-26 17:26:34 -06:00
144d24cf59 initial volume and light control button support w/ debouncer 2025-10-26 16:13:59 -06:00
485f617515 cursed input flags stuff 2025-10-26 15:44:35 -06:00
b1d7574a80 add additional InputFlags and minor refactor 2025-10-26 14:00:29 -06:00
73e4b482a6 add settings struct 2025-10-26 13:31:18 -06:00
30 changed files with 1765 additions and 398 deletions

9
.gitmodules vendored
View file

@ -1,9 +1,12 @@
[submodule "ch32v-insert-coin/ext/ch32-hal"] [submodule "ch32v-insert-coin/ext/ch32-hal"]
path = 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"] [submodule "ch32v-insert-coin/ext/qingke"]
path = 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"] [submodule "ch32v-insert-coin/ext/adpcm-pwm-dac"]
path = 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

View file

@ -78,6 +78,7 @@ dependencies = [
"panic-halt", "panic-halt",
"qingke 0.5.0", "qingke 0.5.0",
"qingke-rt", "qingke-rt",
"wavetable-synth",
] ]
[[package]] [[package]]
@ -626,3 +627,7 @@ name = "void"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "wavetable-synth"
version = "0.1.0"

View file

@ -31,6 +31,8 @@ qingke = {path = "ext/qingke"}
adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" } adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" }
wavetable-synth = { path = "ext/wavetable-synth" }
critical-section = { version = "1.2.0" } critical-section = { version = "1.2.0" }
[profile.release] [profile.release]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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,
]

Binary file not shown.

View file

@ -1,4 +0,0 @@
TO FLASH / RUN:
`wlink -v flash --enable-sdi-print --watch-serial bins/coin_sound_8ksps.bin`

@ -1 +1 @@
Subproject commit ba25b7c89f4deb52426d97fd35eb13496f183775 Subproject commit 99ce71e8d03e382b51732db7d0771349c51c7f48

@ -1 +1 @@
Subproject commit f41336744c4e2548c8f6ba9b323ae4aa39959f1d Subproject commit 4f11d68e62dcb0e7098eecf357168724a8322d80

@ -0,0 +1 @@
Subproject commit 30033e1438c25aed4ea506cdbd69cc4ceffa0b4e

View file

@ -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<u16> {
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

View file

@ -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()
}
}

View file

@ -1,9 +1,8 @@
use ch32_hal::timer::GeneralInstance16bit;
use ch32_hal::timer::simple_pwm::SimplePwm; use ch32_hal::timer::simple_pwm::SimplePwm;
use ch32_hal::timer::Channel; 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 mut led0_index: usize = 0;
// static LED0: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; // 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 mut LED1_INDEX: usize = 0;
// static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8]; // static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8];
pub struct SimplePwmCore<'d, T: GeneralInstance16bit> { pub struct SimplePwmCore<'d, T: GeneralInstance16bit> {
pwm: core::cell::RefCell<SimplePwm<'d, T>>, pub pwm: core::cell::RefCell<SimplePwm<'d, T>>,
} }
impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> { 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); self.pwm.borrow_mut().set_duty(ch, dc);
} }
// pub fn disable(&self, ch: Channel) { pub fn disable(&self, ch: Channel) {
// self.pwm.borrow_mut().disable(ch); self.pwm.borrow_mut().disable(ch);
// } }
} }
// pub struct SimplePwmHandle<T: GeneralInstance16Bit> {
// core: &'static SimplePwmCore<'_, T: GeneralInstance16Bit>,
// channel: Channel,
// }
// impl<T: GeneralInstance16Bit> SimplePwmHandle<T> {
// pub fn set_amplitude(&self, amplitude: u8) {}
// }
pub struct CoreConfig { pub struct CoreConfig {
pub tick_rate_hz: usize, pub tick_rate_hz: usize,
} }
impl CoreConfig { impl CoreConfig {
pub fn new(tick_rate_hz: usize) -> Self { pub fn new(tick_rate_hz: usize) -> Self {
Self { Self { tick_rate_hz }
tick_rate_hz,
}
} }
} }
@ -70,25 +73,20 @@ pub struct InsertCoin<'a, T: GeneralInstance16bit> {
pub led0: LedService, pub led0: LedService,
pub led1: LedService, pub led1: LedService,
// led2: LedService, // led2: LedService,
pub dac: DacService<'a>, pub dac: DacService<'a>,
} }
impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> { impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
pub fn new(config: CoreConfig, pwm_core: SimplePwmCore<'a, T>) -> Self { pub fn new(config: CoreConfig, pwm_core: SimplePwmCore<'a, T>) -> Self {
// LED0 servicer setup // LED0 servicer setup
let led0 = LedService::new(ch32_hal::timer::Channel::Ch3); let led0 = LedService::new(ch32_hal::timer::Channel::Ch3);
// LED1 servicer setup // LED1 servicer setup
let led1 = LedService::new(ch32_hal::timer::Channel::Ch1); let led1 = LedService::new(ch32_hal::timer::Channel::Ch1);
// DAC servicer setup // DAC servicer setup
let dac_sample_rate_hz = 16000; let dac_sample_rate_hz = 4000;
let dac_tick_per_service = config.tick_rate_hz/(dac_sample_rate_hz); let dac_tick_per_service = config.tick_rate_hz / (dac_sample_rate_hz);
let dac_service_data = TickServiceData::new(dac_tick_per_service); let dac_service_data = TickServiceData::new(dac_tick_per_service);
let dac = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data); 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 /// takes self reference and runs
pub fn service(&mut self) { pub fn service(&mut self) {
if self.is_active() {
if self.is_active() {
self.dac.tick(); self.dac.tick();
if self.led0.need_service() { 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(); self.led0.service();
} }
if self.led1.need_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(); self.led1.service();
} }
if self.dac.need_service() { if self.dac.need_service() {
self.dac.service(); self.dac.service();
// TODO: adpcm-pwm-dac:e4c811653781e69e40b63fd27a8c1e20 // 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 // /// consumes self and runs
@ -134,7 +132,6 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
// let mut delay = Delay; // let mut delay = Delay;
// let tick_interval_us = 1000000/self.config.tick_rate_hz; // let tick_interval_us = 1000000/self.config.tick_rate_hz;
// let mut led0_index = 0; // let mut led0_index = 0;
// let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; // let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
@ -143,12 +140,11 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
// loop { // loop {
// self.dac.tick(); // self.dac.tick();
// if(self.led0.need_service()) { // if(self.led0.need_service()) {
// self.led0.set_amplitude(led0_dcs[led0_index]); // self.led0.set_amplitude(led0_dcs[led0_index]);
// self.pwm_core.write_amplitude(self.led0.channel, self.led0.amplitude); // self.pwm_core.write_amplitude(self.led0.channel, self.led0.amplitude);
// led0_index += 1; // led0_index += 1;
// if led0_index > led0_dcs.len() - 1 { // if led0_index > led0_dcs.len() - 1 {
// led0_index = 0; // led0_index = 0;
@ -159,7 +155,7 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
// if(self.led1.need_service()) { // if(self.led1.need_service()) {
// self.led1.set_amplitude(led1_dcs[led1_index]); // self.led1.set_amplitude(led1_dcs[led1_index]);
// self.pwm_core.write_amplitude(self.led1.channel, self.led1.amplitude); // self.pwm_core.write_amplitude(self.led1.channel, self.led1.amplitude);
// led1_index += 1; // led1_index += 1;
// if led1_index > led1_dcs.len() - 1 { // if led1_index > led1_dcs.len() - 1 {
// led1_index = 0; // led1_index = 0;
@ -178,7 +174,7 @@ impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
// } // }
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
self.core.active self.core.active
} }
pub fn set_active(&mut self, active: bool) { pub fn set_active(&mut self, active: bool) {

View file

@ -1,7 +1,7 @@
mod insert_coin; mod insert_coin;
mod services; 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 services::{TickService, TickServiceData};
pub use insert_coin::{InsertCoin, CoreConfig, SimplePwmCore};

View file

@ -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 adpcm_pwm_dac::dac::DpcmDecoder;
use ch32_hal::timer::Channel; use ch32_hal::timer::Channel;
@ -9,6 +8,7 @@ pub struct DacService<'a> {
dpcm_decoder: core::cell::RefCell<DpcmDecoder<'a>>, dpcm_decoder: core::cell::RefCell<DpcmDecoder<'a>>,
amplitude: core::cell::RefCell<usize>, amplitude: core::cell::RefCell<usize>,
pub channel: Channel, pub channel: Channel,
enabled: bool,
} }
impl<'a> DacService<'a> { impl<'a> DacService<'a> {
@ -18,9 +18,19 @@ impl<'a> DacService<'a> {
dpcm_decoder: core::cell::RefCell::new(DpcmDecoder::new()), dpcm_decoder: core::cell::RefCell::new(DpcmDecoder::new()),
amplitude: core::cell::RefCell::new(0), amplitude: core::cell::RefCell::new(0),
channel, 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]) { pub fn load_data(&self, data: &'a [u8]) {
self.dpcm_decoder.borrow_mut().load_data(data); self.dpcm_decoder.borrow_mut().load_data(data);
self.dpcm_decoder.borrow_mut().seek_to_sample(0); self.dpcm_decoder.borrow_mut().seek_to_sample(0);
@ -36,17 +46,23 @@ impl<'a> DacService<'a> {
impl<'a> TickService for DacService<'a> { impl<'a> TickService for DacService<'a> {
fn tick(&self) { fn tick(&self) {
let mut tc = self.service_data.borrow_mut(); if self.enabled {
tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1); let mut tc = self.service_data.borrow_mut();
tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1);
}
} }
fn need_service(&self) -> bool { fn need_service(&self) -> bool {
self.service_data.borrow().ticks_remaining == 0 self.enabled && self.service_data.borrow().ticks_remaining == 0
} }
fn service(&self) { fn service(&self) {
let mut tc = self.service_data.borrow_mut(); let mut tc = self.service_data.borrow_mut();
tc.ticks_remaining = tc.ticks_per_service; 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());
}
} }
} }

View file

@ -1,6 +1,6 @@
use ch32_hal::timer::Channel; use ch32_hal::timer::Channel;
use crate::insert_coin::services::{Service}; use crate::insert_coin::services::Service;
pub struct LedService { pub struct LedService {
// need_service: core::cell::RefCell<bool>, // need_service: core::cell::RefCell<bool>,
@ -25,9 +25,7 @@ impl LedService {
} }
} }
impl Service for LedService { impl Service for LedService {
fn need_service(&self) -> bool { fn need_service(&self) -> bool {
self.need_service self.need_service
} }
@ -36,5 +34,3 @@ impl Service for LedService {
self.need_service = false; self.need_service = false;
} }
} }

View file

@ -1,4 +1,4 @@
use crate::insert_coin::services::{TickServiceData, TickService}; use crate::insert_coin::services::{TickService, TickServiceData};
pub struct TickTimerService { pub struct TickTimerService {
service_data: core::cell::RefCell<TickServiceData>, service_data: core::cell::RefCell<TickServiceData>,
@ -15,22 +15,21 @@ impl TickTimerService {
} }
} }
// pub fn is_enabled(&self) -> bool { pub fn is_enabled(&self) -> bool {
// self.enabled self.enabled
// } }
pub fn reset(&mut self) { pub fn reset(&mut self) {
let mut sd = self.service_data.borrow_mut(); let mut sd = self.service_data.borrow_mut();
sd.ticks_remaining = sd.ticks_per_service; sd.ticks_remaining = sd.ticks_per_service;
self.enabled = false; self.enabled = false;
} }
pub fn enable(&mut self, enable: bool) { pub fn enable(&mut self, enable: bool) {
self.enabled = enable; self.enabled = enable;
} }
} }
impl TickService for TickTimerService { impl TickService for TickTimerService {
fn tick(&self) { fn tick(&self) {
if self.enabled { if self.enabled {
@ -48,5 +47,3 @@ impl TickService for TickTimerService {
tc.ticks_remaining = tc.ticks_per_service; tc.ticks_remaining = tc.ticks_per_service;
} }
} }

View file

@ -9,13 +9,29 @@ mod insert_coin;
// system stuff // system stuff
mod system; 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 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 ch32_hal as hal;
use hal::bind_interrupts; use hal::bind_interrupts;
use hal::delay::Delay; 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::time::Hertz;
use hal::timer::low_level::CountingMode; use hal::timer::low_level::CountingMode;
use hal::timer::simple_pwm::{PwmPin, SimplePwm}; use hal::timer::simple_pwm::{PwmPin, SimplePwm};
@ -24,152 +40,249 @@ use hal::println;
use qingke::riscv; use qingke::riscv;
struct DebouncedGPIO<'a> { use crate::app::sequencer::{DynamicSequence, SequenceEntry};
input: Input<'a>,
// value of the GPIO static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
value: bool,
// GPIO is ready (debounced) pub struct Usb {
ready: bool, usb_pin: Input<'static>,
// debounce timer
timer: TickTimerService,
} }
impl<'a> DebouncedGPIO<'a> { impl Usb {
pub fn new(pin: AnyPin, system_tick_rate_hz: usize, debounce_time_ms: usize) -> Self { pub fn new(usb_pin: Input<'static>) -> Self {
// coin debounce timer (100ms) 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 { Self {
input: Input::new(pin, Pull::Up), adc,
value: false, battery_pin: pin,
ready: false, batt_values: [1024; 10],
timer: TickTimerService::new( index: 0,
TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000),
false,
),
} }
} }
pub fn ready(&self) -> bool { // TODO make this a float or something
self.ready pub fn get_battery_voltage(&mut self) -> u16 {
} let val = self
.adc
pub fn value(&self) -> bool { .convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241);
self.value self.batt_values[self.index] = val;
} self.index += 1;
if self.index > &self.batt_values.len() - 1 {
pub fn begin(&mut self) { self.index = 0;
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;
} }
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 #[derive(Debug)]
// Active --2s button--> Idle struct Flag {
// Idle/Active --5s button--> DeepSleep value: bool,
}
pub enum SystemState { impl Flag {
// system is asleep, waiting for wake from coin insertion pub fn active(&self) -> bool {
DeepSleep, unsafe { core::ptr::read_volatile(&raw const self.value as *const bool) }
// system is in low-power mode, dimmed lights, waiting for interaction }
Idle, pub fn set(&mut self) {
// system is active. on entry: play coin sound. on button press: play different sound unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, true) }
Active, }
pub fn clear(&mut self) {
unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, false) }
}
} }
#[derive(Debug)] #[derive(Debug)]
struct InputFlags { struct InputFlags {
coin_flag: bool, sense_coin_flag: Flag,
button_flag: bool, 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 { static mut INPUT_FLAGS: InputFlags = InputFlags {
coin_flag: false, sense_coin_flag: Flag { value: false },
button_flag: false, main_btn_flag: Flag { value: false },
volume_btn_flag: false,
light_ctrl_btn_flag: false,
systick_flag: Flag { value: false },
}; };
struct Test {} struct Test {}
impl Handler<hal::interrupt::typelevel::EXTI7_0> for Test { impl Handler<hal::interrupt::typelevel::EXTI7_0> for Test {
unsafe fn on_interrupt() { unsafe fn on_interrupt() {
#[cfg(feature = "enable_print")] // #[cfg(feature = "enable_print")]
println!("on_interrupt()"); // println!("on_interrupt()");
critical_section::with(|_| unsafe { critical_section::with(|_| unsafe {
INPUT_FLAGS = system::clear_interrupt(2, 6); 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 { bind_interrupts!(struct Irqs {
EXTI7_0 => Test; EXTI7_0 => Test;
}); });
// TODO: remove // TODO: remove
use app::settings::Settings;
use insert_coin::TickTimerService; use insert_coin::TickTimerService;
use insert_coin::{TickService, TickServiceData}; use insert_coin::{TickService, TickServiceData};
fn debug_main(mut p: hal::Peripherals, mut delay: Delay) -> ! { fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings {
// LED0 output setup // initialize ADC core first, and exit if battery is too low
use hal::gpio::{Level, Output}; let mut adc = hal::adc::Adc::new(p.ADC1, Default::default());
let mut led0_pin = Output::new(p.PC3, Level::High, Default::default()); let mut batt_monitor_pin = p.PD4;
let mut adc_core = AdcCore::new(adc, batt_monitor_pin);
// button pin setup let mut usb_detect_pin = p.PD5;
let button_pin = p.PD6; let usb_detect_input = Input::new(usb_detect_pin, Pull::Up);
unsafe { system::init_gpio_irq(button_pin.pin(), button_pin.port(), false, true) }; let usb = Usb::new(usb_detect_input);
let mut button_input = Input::new(button_pin, Pull::Up);
delay.delay_ms(1000); let bv = adc_core.get_battery_voltage();
unsafe { system::enter_standby(4) }; // if we don't have USB power, and the batt ADC reads under 421, don't wake
delay.delay_ms(1000); if !usb.powered() && bv < 421 {
riscv::asm::wfi(); adc_core.shutdown();
return app_settings;
// 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 === // === output setup ===
// LED0 output setup // LED0 output setup
let led0_pin = PwmPin::new_ch3::<0>(p.PC3); let led0_pin = PwmPin::new_ch3::<0>(p.PC3);
let led0_ch = hal::timer::Channel::Ch3; let 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 // LED1 output setup
let led1_pin = PwmPin::new_ch1::<0>(p.PD2); let led1_pin = PwmPin::new_ch1::<0>(p.PD2);
let led1_ch = hal::timer::Channel::Ch1; 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 // DAC output setup
let dac_pin = PwmPin::new_ch4::<0>(p.PC4); let dac_pin = PwmPin::new_ch4::<0>(p.PC4);
// let dac_ch = hal::timer::Channel::Ch4; // let dac_ch = hal::timer::Channel::Ch4;
@ -178,129 +291,158 @@ fn app_main(mut p: hal::Peripherals, mut delay: Delay) -> ! {
let mut pwm = SimplePwm::new( let mut pwm = SimplePwm::new(
p.TIM1, p.TIM1,
Some(led1_pin), Some(led1_pin),
Some(led2_pin), // Some(led2_pin),
None,
Some(led0_pin), Some(led0_pin),
Some(dac_pin), Some(dac_pin),
Hertz::khz(200), Hertz::khz(200),
CountingMode::default(), 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(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; // pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow);
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 core_config = CoreConfig::new(tick_rate_hz);
// === input setup === // === input setup ===
// adc // adc
let mut adc = hal::adc::Adc::new(p.ADC1, Default::default()); // let mut adc = hal::adc::Adc::new(p.ADC1, Default::default());
let mut adc_pin = p.PD4; // let mut batt_monitor_pin = p.PD4;
// let adc_core = AdcCore::new(adc, batt_monitor_pin);
// adc2 // adc2
// let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default()); // 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()); // 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); // println!("ADC calibration value: {}", adc_cal);
// definitions // definitions
let coin_pin = p.PC2; let sense_coin_pin = p.PC2;
let button_pin = p.PD6; let main_btn_pin = p.PD6;
// println!( let volume_btn_pin = p.PC6;
// "coin pin: {} | coin port: {}", let light_ctrl_btn_pin = p.PC7;
// coin_pin.pin(), let amp_en = p.PC5;
// coin_pin.port() // let extra_io_1 = p.PD0;
// ); // let extra_io_2 = p.PD3;
// println!(
// "push pin: {} | push port: {}",
// button_pin.pin(),
// button_pin.port()
// );
//2025-09-10 00:32:23.514: coin pin: 4 | coin port: 3 let mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default());
// 2025-09-10 00:32:23.515: push pin: 6 | push port: 3 let amp = Amplifier::new(amp_en_output);
// set up interrupts // set up interrupts
unsafe { system::init_gpio_irq(coin_pin.pin(), 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(button_pin.pin(), button_pin.port(), true, true) }; unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) };
// coin debouncer (100ms) // 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) // 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); let app_config = Config {
interfaces.set_active(true); system_tick_rate_hz: tick_rate_hz,
// insert_coin.init(); timers: timer_config,
};
let mut led0_index = 0; // DAC servicer setup
let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; 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 coin_sound = include_bytes!("../audio/coin5.raw");
let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8]; // let coin_sound = include_bytes!("../audio/coin2.raw");
// tick timer 0 let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data);
let tt0_fire_rate_hz = 9; // sample_player.load_data(coin_sound);
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 sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz);
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 app_services = Services {
let sp_ticks = 2 * interfaces.config.tick_rate_hz; led0: LedService::new(led0_ch),
let sp_timer_data = TickServiceData::new(sp_ticks); led1: LedService::new(led1_ch),
let mut sp_timer = TickTimerService::new(sp_timer_data, false); // led2: LedService::new(led2_ch),
sp_timer.reset(); synth0: SynthesizerService::new(tick_rate_hz),
sample_player,
sequencer,
};
// long press timer let app_sequences = Sequences {
let lp_ticks = 5 * interfaces.config.tick_rate_hz; led0: BasicSequence::new(&LED0_SEQ),
let lp_timer_data = TickServiceData::new(lp_ticks); led1: BasicSequence::new(&LED0_SEQ),
let mut lp_timer = TickTimerService::new(lp_timer_data, false); // led2: BasicSequence::new(&LED0_SEQ),
lp_timer.reset(); audio: &SEQUENCE_LIST,
};
// battery read timer let app_interfaces = Interfaces {
let adc1_ticks = 5 * interfaces.config.tick_rate_hz; pwm_core,
let adc1_timer_data = TickServiceData::new(adc1_ticks); adc_core,
let mut adc1_timer = TickTimerService::new(adc1_timer_data, false); amp,
adc1_timer.reset(); usb,
adc1_timer.enable(true); };
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 need_sound = unsafe {
// let coin_sound = include_bytes!("../audio/coin.raw"); #[allow(static_mut_refs)]
let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); if INPUT_FLAGS.main_btn_flag.active() {
// let button_sound = include_bytes!("../audio/coinMixTest1_dpcm_u4.raw"); #[allow(static_mut_refs)]
INPUT_FLAGS.main_btn_flag.clear();
true
} else {
false
}
};
let mut system_state = SystemState::Active; // init systick
systick_init(tick_rate_hz);
interfaces.led0.set_amplitude(0);
interfaces.led1.set_amplitude(0);
interfaces.service();
// set up interrupts
unsafe { unsafe {
use hal::pac::Interrupt; 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); 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(Interrupt::EXTI7_0 as u8);
// qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8); qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8);
} }
// MAIN APPLICATION // MAIN APPLICATION
@ -311,152 +453,159 @@ 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 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 // -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"); // 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 { loop {
{ // system servicing
// system input 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 { unsafe {
if INPUT_FLAGS.coin_flag { #[allow(static_mut_refs)]
#[cfg(feature = "enable_print")] INPUT_FLAGS.systick_flag.clear();
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);
} }
// app tick
app.tick();
} }
match system_state { app.service();
SystemState::DeepSleep => {
// TODO: make this REALLY deep sleep match app.get_state() {
unsafe { system::enter_standby(4) }; // enter standby
loop { app::State::DeepSleep => {
riscv::asm::wfi(); app.shut_down();
let mut config = hal::Config::default(); return app.get_settings();
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(); }
if tt0.need_service() { use ch32_hal::timer::low_level::{OutputCompareMode, Timer};
interfaces.led0.set_amplitude(led0_dcs[led0_index]); use ch32_hal::timer::Channel;
led0_index += 1;
if led0_index > led0_dcs.len() - 1 {
led0_index = 0;
}
tt0.service();
}
if tt1.need_service() { // fn shutdown_main(p: Peripherals) {
interfaces.led1.set_amplitude(led1_dcs[led1_index]); fn shutdown_main(p: hal::Peripherals) {
led1_index += 1; systick_stop();
if led1_index > led1_dcs.len() - 1 { // LED0 output setup
led1_index = 0; let led0_pin = OutputOpenDrain::new(p.PC3, Level::Low, Default::default());
} let led1_pin = OutputOpenDrain::new(p.PD2, Level::High, Default::default());
tt1.service() 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);
} }
} }
@ -470,14 +619,30 @@ fn main() -> ! {
let mut p = hal::init(config); let mut p = hal::init(config);
// delay to let the debugger attach // delay to let the debugger attach
let mut delay = Delay; // println!("pre");
delay.delay_ms(1000); riscv::asm::delay(20_000_000);
// println!("post");
// debug_main(p);
// debug_main(p, delay); let mut app_settings = Settings::default();
app_main(p, delay);
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] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {
// println!("panic: {info:?}");
loop {} loop {}
} }

View file

@ -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,
];

View file

@ -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<SimpleWavetable<'static, u8>>,
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<u8> {
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
}
}

View file

@ -4,7 +4,7 @@ use hal::println;
pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) {
critical_section::with(|_| { critical_section::with(|_| {
#[cfg(feature = "enable_print")] #[cfg(feature = "enable_print")]
println!("init_gpio_irq"); println!("init_gpio_irq {pin}:{port}");
let exti = &hal::pac::EXTI; let exti = &hal::pac::EXTI;
let afio = &hal::pac::AFIO; let afio = &hal::pac::AFIO;
@ -20,12 +20,8 @@ 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) -> [bool; 2] {
pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags { let mut output = [false, false];
let mut input_flags = crate::InputFlags {
coin_flag: false,
button_flag: false,
};
let exti = &hal::pac::EXTI; let exti = &hal::pac::EXTI;
@ -39,25 +35,21 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags {
// We don't handle or change any EXTI lines above 24. // We don't handle or change any EXTI lines above 24.
let bits = bits.0 & 0x00FFFFFF; let bits = bits.0 & 0x00FFFFFF;
#[cfg(feature = "enable_print")]
println!("bits: {bits:08x}");
// coin_flag // coin_flag
if (bits & (0x1 << coin_pin)) != 0x0 { if (bits & (0x1 << coin_pin)) != 0x0 {
#[cfg(feature = "enable_print")] #[cfg(feature = "enable_print")]
println!("coin irq!"); println!("coin irq!");
input_flags.coin_flag = true; output[0] = true;
// unsafe {
// INPUT_FLAGS.coin_flag = true;
// }
} }
// button_flag // button_flag
if (bits & (0x1 << button_pin)) != 0x0 { if (bits & (0x1 << button_pin)) != 0x0 {
#[cfg(feature = "enable_print")] #[cfg(feature = "enable_print")]
println!("button irq!"); println!("main_btn irq!");
input_flags.button_flag = true; output[1] = true;
// unsafe {
// INPUT_FLAGS.button_flag = true;
// }
} }
// Clear pending - Clears the EXTI's line pending bits. // Clear pending - Clears the EXTI's line pending bits.
@ -72,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(coin_pin, true)); // enable interrupt
exti.intenr().modify(|w| w.set_mr(button_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. /// enter standby (SLEEPDEEP) mode, with WFE enabled.
@ -84,7 +76,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags {
/// * (probably WFI?) /// * (probably WFI?)
/// exit: /// exit:
/// 1. any interrupt/event (set in external interrupt register) /// 1. any interrupt/event (set in external interrupt register)
pub unsafe fn enter_standby(pin: usize) { pub unsafe fn enter_standby() {
critical_section::with(|_| { critical_section::with(|_| {
use hal::pac::Interrupt; use hal::pac::Interrupt;
use qingke_rt::CoreInterrupt; use qingke_rt::CoreInterrupt;
@ -103,10 +95,7 @@ pub unsafe fn enter_standby(pin: usize) {
// set PDDS=1: // set PDDS=1:
// get current value of PWR_CTLR // get current value of PWR_CTLR
let mut reg: u32 = 0x4000_7000; let mut reg: u32 = 0x4000_7000;
let mut val: u32 = 0; let mut val: u32 = unsafe { (reg as *mut u32).read_volatile() };
unsafe {
val = (reg as *mut u32).read_volatile();
}
// modify PDDS // modify PDDS
val |= 1 << 1; // PWR_CTLR[1] -> PDDS val |= 1 << 1; // PWR_CTLR[1] -> PDDS
unsafe { unsafe {