Compare commits

...
Sign in to create a new pull request.

54 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
23 changed files with 1598 additions and 657 deletions

6
.gitmodules vendored
View file

@ -1,12 +1,12 @@
[submodule "ch32v-insert-coin/ext/ch32-hal"]
path = ch32v-insert-coin/ext/ch32-hal
url = git@github.com:sigil-03/ch32-hal.git
url = https://github.com/sigil-03/ch32-hal.git
[submodule "ch32v-insert-coin/ext/qingke"]
path = ch32v-insert-coin/ext/qingke
url = git@github.com:ch32-rs/qingke.git
url = https://github.com/ch32-rs/qingke.git
[submodule "ch32v-insert-coin/ext/adpcm-pwm-dac"]
path = ch32v-insert-coin/ext/adpcm-pwm-dac
url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git
url = https://git.glyphs.tech/sigil-03/adpcm-pwm-dac.git
[submodule "ch32v-insert-coin/ext/wavetable-synth"]
path = ch32v-insert-coin/ext/wavetable-synth
url = https://git.glyphs.tech/sigil-03/wavetable-synth.git

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

@ -1 +1 @@
Subproject commit e6ca9c7ed8b9af193333be793983df5e48cb961a
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

@ -11,7 +11,7 @@ use crate::insert_coin::services::{DacService, LedService, Service, TickService,
// static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8];
pub struct SimplePwmCore<'d, T: GeneralInstance16bit> {
pwm: core::cell::RefCell<SimplePwm<'d, T>>,
pub pwm: core::cell::RefCell<SimplePwm<'d, T>>,
}
impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> {
@ -37,10 +37,19 @@ 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<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 tick_rate_hz: usize,

View file

@ -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};

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 ch32_hal::timer::Channel;
@ -9,6 +8,7 @@ pub struct DacService<'a> {
dpcm_decoder: core::cell::RefCell<DpcmDecoder<'a>>,
amplitude: core::cell::RefCell<usize>,
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) {
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;
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 crate::insert_coin::services::{Service};
use crate::insert_coin::services::Service;
pub struct LedService {
// need_service: core::cell::RefCell<bool>,
@ -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;
}
}

View file

@ -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<TickServiceData>,
@ -15,9 +15,9 @@ 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();
@ -30,7 +30,6 @@ impl TickTimerService {
}
}
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;
}
}

File diff suppressed because it is too large Load diff

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

@ -9,48 +9,58 @@ const SQUARE_WAVETABLE: [u8; 2] = [0, 100];
// 100, 100, 100, 100, 100, 100, 100,
// ];
pub struct AppSynthesizers<'a, T: GeneralInstance16bit> {
pub square: SimpleWavetableSynthesizer<SimpleWavetable<'static, u8>>,
output: SimplePwmCore<'a, T>,
pub struct SynthesizerService {
pub synth: SimpleWavetableSynthesizer<SimpleWavetable<'static, u8>>,
pub enabled: bool,
pub need_service: bool,
}
impl<'a, T: GeneralInstance16bit> AppSynthesizers<'a, T> {
pub fn new(clock_freq_hz: usize, output: SimplePwmCore<'a, T>) -> Self {
impl SynthesizerService {
pub fn new(clock_freq_hz: usize) -> Self {
let square_wt = SimpleWavetable::new(&SQUARE_WAVETABLE);
let square = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz);
let synth = SimpleWavetableSynthesizer::new(square_wt, clock_freq_hz);
Self { square, output }
Self {
synth,
enabled: true,
need_service: false,
}
}
pub fn tick(&mut self) {
self.square.tick();
if self.square.has_new_output() {
let out = self.square.get_output();
// println!("OUTPUT: {out}");
// println!("new out");
self.output.write_amplitude(
ch32_hal::timer::Channel::Ch4,
out / 2,
// (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8,
);
if self.enabled {
self.synth.tick();
}
}
// println!("{}{}", self.square.counter, self.square.has_new_output());
pub fn need_service(&self) -> bool {
self.need_service || (self.enabled && self.synth.has_new_output())
}
pub fn service(&mut self) {
// println!("HERE");
if self.square.has_new_output() {
let out = self.square.get_output();
// println!("OUTPUT: {out}");
pub fn service(&mut self) -> Option<u8> {
self.need_service = false;
if self.enabled {
Some(self.synth.get_output())
} else {
None
}
}
}
// println!("new out");
self.output.write_amplitude(
ch32_hal::timer::Channel::Ch4,
out / 2,
// (out as f32 * (u8::MAX as f32 / u32::MAX as f32)) as u8,
);
}
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

@ -20,8 +20,8 @@ pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) {
});
}
pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags {
let mut input_flags = crate::InputFlags::default();
pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> [bool; 2] {
let mut output = [false, false];
let exti = &hal::pac::EXTI;
@ -35,53 +35,23 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags {
// We don't handle or change any EXTI lines above 24.
let bits = bits.0 & 0x00FFFFFF;
#[cfg(feature = "enable_print")]
println!("bits: {bits:08x}");
// coin_flag
if (bits & (0x1 << coin_pin)) != 0x0 {
#[cfg(feature = "enable_print")]
println!("coin irq!");
input_flags.sense_coin_flag = true;
output[0] = true;
}
// button_flag
if (bits & (0x1 << button_pin)) != 0x0 {
#[cfg(feature = "enable_print")]
println!("main_btn irq!");
input_flags.main_btn_flag = true;
// CHECK PC6
// unsafe {
// let mut val = 0;
// let reg: u32 = 0x40011008;
// val = (reg as *mut u32).read_volatile();
// if ((val >> 0x6) & 0x1) == 0x0 {
// input_flags.volume_btn_flag = true;
// #[cfg(feature = "enable_print")]
// println!("volume irq!");
// println!("CBANK: {val:08x}");
// }
// }
// // CHECK PD6
// unsafe {
// let mut val = 0;
// let reg: u32 = 0x40011408;
// val = (reg as *mut u32).read_volatile();
// // if ((val >> 0x6) & 0x1) == 0x0 {
// input_flags.main_btn_flag = true;
// #[cfg(feature = "enable_print")]
// println!("main_btn irq!");
// println!("DBANK: {val:08x}");
// // }
// }
output[1] = true;
}
// light_ctrl_btn_flag
// if (bits & (0x1 << light_ctrl_btn_pin)) != 0x0 {
// #[cfg(feature = "enable_print")]
// println!("light ctrl btn irq!");
// input_flags.light_ctrl_btn_flag = true;
// }
// Clear pending - Clears the EXTI's line pending bits.
exti.intfr().write(|w| w.0 = bits);
@ -94,7 +64,7 @@ pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> crate::InputFlags {
exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt
exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt
input_flags
output
}
/// enter standby (SLEEPDEEP) mode, with WFE enabled.