Compare commits

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

77 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
3eb410c796 move most of the system functions out of main.rs 2025-10-26 08:25:11 -06:00
08d7289c93 formatting 2025-10-26 07:55:11 -06:00
8c88456fcb add second ADC pin 2025-10-25 14:09:54 -06:00
885c7746b3 deep sleep with PDDS is working 2025-10-19 11:12:15 -06:00
1a420018d1 deep sleep and wakeup working! 2025-10-19 11:11:12 -06:00
fba0fbec61 minor formatting cleanups 2025-10-19 10:53:58 -06:00
8d612d1559 update init and launch scripts 2025-10-19 10:52:44 -06:00
94848d0d43 update notes 2025-10-19 10:51:49 -06:00
bdfef566bf Merge branch 'adc' 2025-09-12 11:45:18 -06:00
3f4b8112fd support ADC readings 2025-09-12 11:44:04 -06:00
980fe9522c update notes with pinout 2025-09-08 10:37:18 -06:00
b334b18233 update notes with deep sleep references 2025-08-30 13:59:51 -06:00
83c2a64f12 holy shit i think it's deep sleeping 2025-08-30 13:54:13 -06:00
cb73b71ffe update build system to be a little more flexible 2025-08-30 11:10:35 -06:00
33 changed files with 2005 additions and 375 deletions

13
.gitmodules vendored
View file

@ -1,9 +1,12 @@
[submodule "adpcm-pwm-dac"]
path = ch32v-insert-coin/ext/adpcm-pwm-dac
url = ssh://git@git.glyphs.tech:222/sigil-03/adpcm-pwm-dac.git
[submodule "ch32v-insert-coin/ext/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"]
path = ch32v-insert-coin/ext/adpcm-pwm-dac
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

@ -3,6 +3,9 @@ name = "ch32v-insert-coin"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[features]
enable_print = []
[dependencies] [dependencies]
ch32-hal = { path = "ext/ch32-hal/", features = [ ch32-hal = { path = "ext/ch32-hal/", features = [
"ch32v003f4u6", "ch32v003f4u6",
@ -28,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.

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

View file

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
docker run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-env:latest cargo +nightly run --release cargo +nightly run --release

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

@ -1 +1 @@
Subproject commit 412b9f5ee3a3708de8602d6103ec83c6dd436b63 Subproject commit 4f11d68e62dcb0e7098eecf357168724a8322d80

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

View file

@ -2,8 +2,9 @@
DIR=${PWD} DIR=${PWD}
mkdir ${PWD}/tmp mkdir ${PWD}/tmp
cd tmp cd tmp
git clone ssh://git@git.glyphs.tech:222/sigil-03/docker-devtools.git git clone ssh://git@git.glyphs.tech:222/taproot-tech/docker-devtools.git
cd docker-devtools/ch32 cd docker-devtools/ch32
docker build --tag ch32-env . ls
./build.sh
cd $DIR cd $DIR
rm -rf tmp rm -rf tmp

2
ch32v-insert-coin/launch.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
podman run --privileged -it --rm -v "$PWD":/usr/src/app -w /usr/src/app ch32-rust:latest /bin/bash

View file

@ -14,5 +14,5 @@
"max-atomic-width": 32, "max-atomic-width": 32,
"panic-strategy": "abort", "panic-strategy": "abort",
"relocation-model": "static", "relocation-model": "static",
"target-pointer-width": "32" "target-pointer-width": 32
} }

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,10 +174,10 @@ 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) {
self.core.active = active; self.core.active = active;
} }
} }

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

@ -3,15 +3,35 @@
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
// app stuff
mod insert_coin; mod insert_coin;
use ch32_hal::{interrupt::typelevel::Handler, timer::low_level::OutputPolarity};
use insert_coin::{InsertCoin, SimplePwmCore, CoreConfig};
// system stuff
mod system;
use {ch32_hal as hal}; mod debounced_gpio;
use hal::{bind_interrupts}; use debounced_gpio::DebouncedGPIO;
// synthesizer :3
mod synthesizer;
use synthesizer::SynthesizerService;
// sequences
mod sequences;
use sequences::SEQUENCE_LIST;
mod app;
use app::{
sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig,
};
use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity};
use insert_coin::{CoreConfig, DacService, InsertCoin, LedService, SimplePwmCore};
use ch32_hal as hal;
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};
@ -20,288 +40,411 @@ use hal::println;
use qingke::riscv; use qingke::riscv;
use crate::app::sequencer::{DynamicSequence, SequenceEntry};
struct DebouncedGPIO<'a> { static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
input: Input<'a>,
// value of the GPIO pub struct Usb {
value: bool, usb_pin: Input<'static>,
// GPIO is ready (debounced)
ready: bool,
// 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(TickServiceData::new(system_tick_rate_hz * debounce_time_ms / 1000), false), index: 0,
} }
} }
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()
} }
} }
#[derive(Debug)]
// DeepSleep --coin button irq--> Active struct Flag {
// Active --2s button--> Idle value: bool,
// Idle/Active --5s button--> DeepSleep
pub enum SystemState {
// system is asleep, waiting for wake from coin insertion
DeepSleep,
// system is in low-power mode, dimmed lights, waiting for interaction
Idle,
// system is active. on entry: play coin sound. on button press: play different sound
Active,
} }
impl Flag {
unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) { pub fn active(&self) -> bool {
critical_section::with(|_| { unsafe { core::ptr::read_volatile(&raw const self.value as *const bool) }
println!("init_gpio_irq");
let exti = &hal::pac::EXTI;
let afio = &hal::pac::AFIO;
let port = port as u8;
let pin = pin as usize;
// let b = afio.exticr().read();
afio.exticr().modify(|w| w.set_exti(pin, port));
exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt
exti.rtenr().modify(|w| w.set_tr(pin, rising));
exti.ftenr().modify(|w| w.set_tr(pin, falling));
});
}
fn clear_interrupt (coin_pin: u8, button_pin: u8) {
let exti = &hal::pac::EXTI;
let coin_pin = coin_pin as usize;
let button_pin = button_pin as usize;
exti.intenr().modify(|w| w.set_mr(coin_pin, false)); // disable interrupt
exti.intenr().modify(|w| w.set_mr(button_pin, false)); // disable interrupt
let bits = exti.intfr().read();
// We don't handle or change any EXTI lines above 24.
let bits = bits.0 & 0x00FFFFFF;
// coin_flag
if (bits & (0x1 << coin_pin)) != 0x0 {
println!("coin irq!");
unsafe { INPUT_FLAGS.coin_flag = true; }
} }
pub fn set(&mut self) {
unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, true) }
// button_flag }
if (bits & (0x1 << button_pin)) != 0x0 { pub fn clear(&mut self) {
println!("button irq!"); unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, false) }
unsafe { INPUT_FLAGS.button_flag = true; }
} }
// Clear pending - Clears the EXTI's line pending bits.
exti.intfr().write(|w| w.0 = bits);
exti.intenr().modify(|w| w.0 = w.0 & !bits);
exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt
exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt
} }
#[derive(Debug)] #[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,
} }
static mut INPUT_FLAGS: InputFlags = InputFlags{coin_flag: false, button_flag: false}; impl Default for InputFlags {
fn default() -> Self {
struct Test { 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 {
sense_coin_flag: Flag { value: false },
main_btn_flag: Flag { value: false },
volume_btn_flag: false,
light_ctrl_btn_flag: false,
systick_flag: Flag { value: false },
};
struct Test {}
impl Handler<hal::interrupt::typelevel::EXTI7_0> for Test { impl Handler<hal::interrupt::typelevel::EXTI7_0> for Test {
unsafe fn on_interrupt() { unsafe fn on_interrupt() {
println!("on_interrupt()"); // #[cfg(feature = "enable_print")]
critical_section::with(|_| { // println!("on_interrupt()");
clear_interrupt(4, 6); critical_section::with(|_| unsafe {
let flags = system::clear_interrupt(2, 6);
if flags[0] {
// safe because single-threaded
#[allow(static_mut_refs)]
INPUT_FLAGS.sense_coin_flag.set();
}
if flags[1] {
#[allow(static_mut_refs)]
INPUT_FLAGS.main_btn_flag.set();
}
}); });
} }
} }
#[qingke_rt::interrupt(core)]
fn SysTick() {
let r = &ch32_hal::pac::SYSTICK;
// Clear interrupt flag
r.sr().write(|w| w.set_cntif(false));
unsafe {
// safe because single-threaded
#[allow(static_mut_refs)]
INPUT_FLAGS.systick_flag.set();
}
}
fn systick_init(tick_freq_hz: usize) {
let r = &ch32_hal::pac::SYSTICK;
// Calculate counts per millisecond using HCLK/8 as clock source
// HCLK/8 = 48MHz/8 = 6MHz
// For tick_freq_hz interrupt: 6MHz / tick_freq_hz
let systick_per_tick = (48_000_000 / 8 / tick_freq_hz) as u32;
// Reset SysTick
r.ctlr().write(|w| {
// Start with everything disabled
});
// Set compare register and reset counter
r.cmp().write_value(systick_per_tick - 1);
r.cnt().write_value(0);
// Clear interrupt flag
r.sr().write(|w| w.set_cntif(false));
// Configure and start SysTick
r.ctlr().write(|w| {
w.set_ste(true); // Enable counter
w.set_stie(true); // Enable interrupt
w.set_stre(true); // Auto reload enable
w.set_stclk(ch32_hal::pac::systick::vals::Stclk::HCLK_DIV8); // HCLK/8 clock source
});
}
fn systick_stop() {
let r = &ch32_hal::pac::SYSTICK;
// Reset SysTick
r.ctlr().write(|w| {
// Start with everything disabled
});
}
bind_interrupts!(struct Irqs { bind_interrupts!(struct Irqs {
EXTI7_0 => Test; EXTI7_0 => Test;
}); });
// TODO: remove // TODO: remove
use insert_coin::{TickService, TickServiceData}; use app::settings::Settings;
use insert_coin::TickTimerService; use insert_coin::TickTimerService;
use insert_coin::{TickService, TickServiceData};
#[qingke_rt::entry] fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings {
fn main() -> ! { // initialize ADC core first, and exit if battery is too low
hal::debug::SDIPrint::enable(); let mut adc = hal::adc::Adc::new(p.ADC1, Default::default());
let mut config = hal::Config::default(); let mut batt_monitor_pin = p.PD4;
config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSE; let mut adc_core = AdcCore::new(adc, batt_monitor_pin);
let p = hal::init(config);
// delay to let the debugger attach let mut usb_detect_pin = p.PD5;
let mut delay = Delay; let usb_detect_input = Input::new(usb_detect_pin, Pull::Up);
delay.delay_ms(1000); let usb = Usb::new(usb_detect_input);
let bv = adc_core.get_battery_voltage();
// if we don't have USB power, and the batt ADC reads under 421, don't wake
if !usb.powered() && bv < 421 {
adc_core.shutdown();
return app_settings;
}
// === 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
// 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;
// PWM timer setup // PWM timer setup
let mut pwm = SimplePwm::new( let mut pwm = SimplePwm::new(
p.TIM1, p.TIM1,
Some(led1_pin), Some(led1_pin),
// Some(led2_pin),
None, None,
Some(led0_pin), Some(led0_pin),
Some(dac_pin), Some(dac_pin),
Hertz::khz(100), 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
// let mut adc = hal::adc::Adc::new(p.ADC1, Default::default());
// let mut batt_monitor_pin = p.PD4;
// let adc_core = AdcCore::new(adc, batt_monitor_pin);
// adc2
// let mut usb_detect_dc = hal::adc::Adc::new(p.ADC1, Default::default());
// println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel());
// #[cfg(feature = "enable_print")]
// println!("ADC calibration value: {}", adc_cal);
// definitions // definitions
let coin_pin = p.PD4; let sense_coin_pin = p.PC2;
let button_pin = p.PD6; let main_btn_pin = p.PD6;
println!("coin pin: {} | coin port: {}", coin_pin.pin(), coin_pin.port()); let volume_btn_pin = p.PC6;
println!("push pin: {} | push port: {}", button_pin.pin(), button_pin.port()); let light_ctrl_btn_pin = p.PC7;
let amp_en = p.PC5;
// let extra_io_1 = p.PD0;
// let extra_io_2 = p.PD3;
let mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default());
let amp = Amplifier::new(amp_en_output);
// set up interrupts // set up interrupts
unsafe {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 {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 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 app_config = Config {
system_tick_rate_hz: tick_rate_hz,
timers: timer_config,
};
let pwm_core = SimplePwmCore::new(pwm); // DAC servicer setup
let dac_sample_rate_hz = 16000;
let dac_tick_per_service = tick_rate_hz / dac_sample_rate_hz;
let dac_service_data = TickServiceData::new(dac_tick_per_service);
let mut interfaces = InsertCoin::new(core_config, pwm_core); // let coin_sound = include_bytes!("../audio/coin5.raw");
interfaces.set_active(true); // let coin_sound = include_bytes!("../audio/coin2.raw");
// insert_coin.init();
let mut led0_index = 0; let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data);
let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8]; // sample_player.load_data(coin_sound);
let mut led1_index = 0; let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz);
let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8];
// tick timer 0
let tt0_fire_rate_hz = 9;
let tt0_tick_per_service = interfaces.config.tick_rate_hz/(tt0_fire_rate_hz * 2);
let tt0_service_data = TickServiceData::new(tt0_tick_per_service);
let mut tt0 = TickTimerService::new(tt0_service_data, true);
// tick timer 1 let app_services = Services {
let tt1_fire_rate_hz = 3; led0: LedService::new(led0_ch),
let tt1_tick_per_service = interfaces.config.tick_rate_hz/(tt1_fire_rate_hz * 2); led1: LedService::new(led1_ch),
let tt1_service_data = TickServiceData::new(tt1_tick_per_service); // led2: LedService::new(led2_ch),
let mut tt1 = TickTimerService::new(tt1_service_data, true); synth0: SynthesizerService::new(tick_rate_hz),
sample_player,
sequencer,
};
let app_sequences = Sequences {
led0: BasicSequence::new(&LED0_SEQ),
led1: BasicSequence::new(&LED0_SEQ),
// led2: BasicSequence::new(&LED0_SEQ),
audio: &SEQUENCE_LIST,
};
// short press timer let app_interfaces = Interfaces {
let sp_ticks = 2 * interfaces.config.tick_rate_hz; pwm_core,
let sp_timer_data = TickServiceData::new(sp_ticks); adc_core,
let mut sp_timer = TickTimerService::new(sp_timer_data, false); amp,
sp_timer.reset(); usb,
};
// long press timer let mut app = App::new(
let lp_ticks = 5 * interfaces.config.tick_rate_hz; app_config,
let lp_timer_data = TickServiceData::new(lp_ticks); app_services,
let mut lp_timer = TickTimerService::new(lp_timer_data, false); app_sequences,
lp_timer.reset(); app_interfaces,
app_settings,
);
let mut delay = Delay; let need_sound = unsafe {
let tick_interval_us = 1000000/interfaces.config.tick_rate_hz - 10; #[allow(static_mut_refs)]
if INPUT_FLAGS.main_btn_flag.active() {
#[allow(static_mut_refs)]
INPUT_FLAGS.main_btn_flag.clear();
true
} else {
false
}
};
// dac data // init systick
let coin_sound = include_bytes!("../audio/sweep_dpcm_u4.raw"); systick_init(tick_rate_hz);
let button_sound = include_bytes!("../audio/sweep_dpcm_u4.raw");
let mut system_state = SystemState::DeepSleep; // set up interrupts
interfaces.led0.set_amplitude(0);
interfaces.led1.set_amplitude(0);
interfaces.service();
unsafe { unsafe {
use hal::pac::Interrupt; use hal::pac::Interrupt;
use qingke::interrupt::Priority;
use qingke_rt::CoreInterrupt;
qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8); system::clear_interrupt(2, 6);
qingke::pfic::set_priority(CoreInterrupt::SysTick as u8, Priority::P15 as u8);
qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
qingke::pfic::enable_interrupt(CoreInterrupt::SysTick as u8);
} }
// MAIN APPLICATION // MAIN APPLICATION
// process // process
// -depress big button (insert coin button) and it wakes up and turns on led one led at a fixed brightness // -depress big button (insert coin button) and it wakes up and turns on led one led at a fixed brightness
@ -310,134 +453,196 @@ fn main() -> ! {
// -depress the big button for approx 2s and it puts the led into low light mode. Just making it dimmer than usual. // -depress the big button for 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
println!("begin"); // #[cfg(feature = "enable_print")]
// println!("begin");
let mut volume_btn_prev = volume_btn_input.is_high_immediate();
let mut light_ctrl_btn_prev = light_ctrl_btn_input.is_high_immediate();
app.init();
if need_sound {
app.main_button_click();
}
loop { loop {
match system_state { // system servicing
SystemState::DeepSleep => {
// TODO: make this REALLY deep sleep
riscv::asm::wfi();
},
SystemState::Idle => {
}, // volume edge detector
SystemState::Active => { if !volume_btn_input.active() {
tt0.tick(); let volume_btn_curr = volume_btn_input.is_high_immediate();
tt1.tick(); if volume_btn_prev != volume_btn_curr {
volume_btn_input.begin();
if tt0.need_service() { volume_btn_prev = volume_btn_curr;
interfaces.led0.set_amplitude(led0_dcs[led0_index]); }
led0_index += 1;
if led0_index > led0_dcs.len() - 1 {
led0_index = 0;
}
tt0.service();
}
if tt1.need_service() {
interfaces.led1.set_amplitude(led1_dcs[led1_index]);
led1_index += 1;
if led1_index > led1_dcs.len() - 1 {
led1_index = 0;
}
tt1.service()
}
interfaces.service();
},
} }
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
// system input servicing if !light_ctrl_btn_input.active() {
let light_ctrl_btn_curr = light_ctrl_btn_input.is_high_immediate();
if light_ctrl_btn_prev != light_ctrl_btn_curr {
light_ctrl_btn_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)]
println!("coin flag active"); INPUT_FLAGS.systick_flag.clear();
INPUT_FLAGS.coin_flag = false;
coin_input.begin();
// enter the active state
system_state = SystemState::Active;
// todo: enter active
tt0.enable(true);
tt1.enable(true);
interfaces.dac.load_data(coin_sound);
}
if INPUT_FLAGS.button_flag {
println!("button flag active");
INPUT_FLAGS.button_flag = false;
button_input.begin();
}
}
// debouncer
coin_input.service();
button_input.service();
if coin_input.ready() {
println!("debounced coin_input value: {}", coin_input.value());
coin_input.reset();
}
if button_input.ready() {
let value = button_input.value();
button_input.reset();
println!("debounced button_input value: {}", value);
if !value {
interfaces.dac.load_data(button_sound);
println!("reset hold timers + enable");
sp_timer.reset();
sp_timer.enable(true);
lp_timer.reset();
lp_timer.enable(true);
}
else {
sp_timer.reset();
lp_timer.reset();
}
}
// timers
sp_timer.tick();
lp_timer.tick();
if sp_timer.need_service() {
println!("sp detect!");
sp_timer.reset();
// todo enter idle
system_state = SystemState::Idle;
// TODO: fix polarity
interfaces.led0.set_amplitude(10);
interfaces.led1.set_amplitude(10);
interfaces.service();
}
if lp_timer.need_service() {
println!("lp detect!");
lp_timer.reset();
// todo enter deepsleep
system_state = SystemState::DeepSleep;
// TODO: fix polarity
interfaces.led0.set_amplitude(0);
interfaces.led1.set_amplitude(0);
interfaces.service();
} }
// app tick
app.tick();
} }
delay.delay_us(tick_interval_us as u32); app.service();
match app.get_state() {
// enter standby
app::State::DeepSleep => {
app.shut_down();
return app.get_settings();
}
_ => {}
}
} }
} }
use ch32_hal::timer::low_level::{OutputCompareMode, Timer};
use ch32_hal::timer::Channel;
// fn shutdown_main(p: Peripherals) {
fn shutdown_main(p: hal::Peripherals) {
systick_stop();
// LED0 output setup
let led0_pin = OutputOpenDrain::new(p.PC3, Level::Low, Default::default());
let led1_pin = OutputOpenDrain::new(p.PD2, Level::High, Default::default());
let led2_pin = OutputOpenDrain::new(p.PA1, Level::High, Default::default());
let dac_pin = OutputOpenDrain::new(p.PC4, Level::Low, Default::default());
let mut amp_pin = OutputOpenDrain::new(p.PC5, Level::Low, Default::default());
amp_pin.set_high();
let volume_btn_pin = OutputOpenDrain::new(p.PC6, Level::Low, Default::default());
let light_ctrl_btn_pin = OutputOpenDrain::new(p.PC7, Level::Low, Default::default());
let usb_detect_input = OutputOpenDrain::new(p.PD5, Level::Low, Default::default());
let sense_coin_pin = p.PC2;
let main_btn_pin = p.PD6;
unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) };
unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, 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;
}
}
}
}
#[qingke_rt::entry]
fn main() -> ! {
#[cfg(feature = "enable_print")]
hal::debug::SDIPrint::enable();
let mut config = hal::Config::default();
config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI;
let mut p = hal::init(config);
// delay to let the debugger attach
// println!("pre");
riscv::asm::delay(20_000_000);
// println!("post");
// debug_main(p);
let mut app_settings = Settings::default();
loop {
unsafe {
hal::rcc::init(hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI);
}
let mut p = unsafe { hal::Peripherals::steal() };
app_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

@ -0,0 +1,134 @@
use ch32_hal as hal;
use hal::println;
pub unsafe fn init_gpio_irq(pin: u8, port: u8, rising: bool, falling: bool) {
critical_section::with(|_| {
#[cfg(feature = "enable_print")]
println!("init_gpio_irq {pin}:{port}");
let exti = &hal::pac::EXTI;
let afio = &hal::pac::AFIO;
let port = port as u8;
let pin = pin as usize;
// let b = afio.exticr().read();
afio.exticr().modify(|w| w.set_exti(pin, port));
exti.intenr().modify(|w| w.set_mr(pin, true)); // enable interrupt
exti.rtenr().modify(|w| w.set_tr(pin, rising));
exti.ftenr().modify(|w| w.set_tr(pin, falling));
});
}
pub fn clear_interrupt(coin_pin: u8, button_pin: u8) -> [bool; 2] {
let mut output = [false, false];
let exti = &hal::pac::EXTI;
let coin_pin = coin_pin as usize;
let button_pin = button_pin as usize;
exti.intenr().modify(|w| w.set_mr(coin_pin, false)); // disable interrupt
exti.intenr().modify(|w| w.set_mr(button_pin, false)); // disable interrupt
let bits = exti.intfr().read();
// We don't handle or change any EXTI lines above 24.
let bits = bits.0 & 0x00FFFFFF;
#[cfg(feature = "enable_print")]
println!("bits: {bits:08x}");
// coin_flag
if (bits & (0x1 << coin_pin)) != 0x0 {
#[cfg(feature = "enable_print")]
println!("coin irq!");
output[0] = true;
}
// button_flag
if (bits & (0x1 << button_pin)) != 0x0 {
#[cfg(feature = "enable_print")]
println!("main_btn irq!");
output[1] = true;
}
// Clear pending - Clears the EXTI's line pending bits.
exti.intfr().write(|w| w.0 = bits);
use hal::pac::Interrupt;
unsafe {
qingke::pfic::unpend_interrupt(Interrupt::EXTI7_0 as u8);
};
// exti.intenr().modify(|w| w.0 = w.0 & !bits);
exti.intenr().modify(|w| w.set_mr(coin_pin, true)); // enable interrupt
exti.intenr().modify(|w| w.set_mr(button_pin, true)); // enable interrupt
output
}
/// enter standby (SLEEPDEEP) mode, with WFE enabled.
/// from CH32V003 reference manual 2.3.1:
/// entry:
/// 1. configure core register control bit: SLEEPDEEP=1 | PDDS = 1
/// 2. configure external interrupt register to mask all but EXTI7_0
/// 3. execute WFI or WFE (optionally SEVONPEND) and SLEEPONEXIT
/// * (probably WFI?)
/// exit:
/// 1. any interrupt/event (set in external interrupt register)
pub unsafe fn enter_standby() {
critical_section::with(|_| {
use hal::pac::Interrupt;
use qingke_rt::CoreInterrupt;
// configure core register control bit (SLEEPDEEP=1 | PDDS=1)
let pfic = &hal::pac::PFIC;
pfic.sctlr().modify(|w| {
// we only want to wake on enabled interrupts
w.set_sevonpend(false);
// we want to enable deep sleep
w.set_sleepdeep(true);
w.set_wfitowfe(false);
w.set_sleeponexit(false);
});
// set PDDS=1:
// get current value of PWR_CTLR
let mut reg: u32 = 0x4000_7000;
let mut val: u32 = unsafe { (reg as *mut u32).read_volatile() };
// modify PDDS
val |= 1 << 1; // PWR_CTLR[1] -> PDDS
unsafe {
(reg as *mut u32).write_volatile(val);
}
// // disable all exti interrupts
let exti = &hal::pac::EXTI;
// exti.intenr().write(| w| {
// w.0 = 0x00000000;
// // w.set_mr(pin, true);
// // w.set_mr(pin, true);
// });
// clear all pending exti interrupts
let bits = 0xFFFFFFFF;
exti.intfr().write(|w| w.0 = bits);
// enable all exti interrupts
let exti = &hal::pac::EXTI;
exti.intenr().write(|w| {
w.0 = 0x00FFFFFF;
// w.set_mr(pin, true);
// w.set_mr(pin, true);
});
unsafe {
qingke::pfic::disable_interrupt(CoreInterrupt::SysTick as u8);
qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
}
// execute WFI
#[cfg(feature = "enable_print")]
println!("WFI CONFIGURED HOPEFULLY");
});
}

View file

@ -3,12 +3,24 @@ there is a docker image that contains the entire toolchain + flashing utility. f
```shell ```shell
$ ./init.sh $ ./init.sh
``` ```
once built, you won't need to build this again. for the remainder of your development, you can use the following script: once built, you won't need to build this again. for the remainder of your development, you can use the following script to launch the environment shell:
```shell
$ ./launch.sh
```
this will launch the docker image and give you a shell which has all of the toolchains and flashing utilities installed. to build the firmware image and flash it to the ch32 (assuming you have a wch-linke attached) run the following from inside the env shell:
```shell ```shell
$ ./build-run.sh $ ./build-run.sh
``` ```
this will build the firmware image, and attempt to upload it to the board using `wlink`. once uploaded, it will attach a serial debugger. this will build the firmware image, and attempt to upload it to the board using `wlink`. once uploaded, it will attach a serial debugger.
to exit the serial debugger simply use `ctrl-c`. to exit the environment shell use:
```shell
$ exit
```
# FLASHING # FLASHING
flashing is done using the [`wlink`](https://github.com/ch32-rs/wlink?tab=readme-ov-file#install) utility. `probe-rs` also works, but can be flaky, and does not support SDI prints very well. flashing is done using the [`wlink`](https://github.com/ch32-rs/wlink?tab=readme-ov-file#install) utility. `probe-rs` also works, but can be flaky, and does not support SDI prints very well.
@ -27,3 +39,59 @@ if you want to monitor prints via SDI, you can use the following instead:
when flashing standalone with the `wlink` utility, you can simply run the following: when flashing standalone with the `wlink` utility, you can simply run the following:
`wlink -v flash <PATH_TO_BINARY>` `wlink -v flash <PATH_TO_BINARY>`
# CH32V MISC NOTES
## SLEEP / DEEP SLEEP
it turns out you can not put the chip into deep sleep and have it wake up correctly until you power cycle it. WHAT THE FUCK. see page 36 of the [qingkev3 processor manual](https://www.wch-ic.com/download/file?id=368) at the bottom of section 6.1 Enter Sleep
# EVAL BOARD NOTES
## PINOUT
WCHLinkE SWDIO -> PD1
# AUDIO NOTES
## LIMITS
we have ~9-10kB of space remaining on the chip.
i can likely squeeze out _maybe_ another kB by dropping every print, but
that appears to cause some lockup, i think because the SDI peripheral is
trying to attach, but unable to.
for how things sit currently, we have the following (conservative) limits
to fit within 9kB:
16ksps, 4b depth -> 64kbps -> 1.125s
8ksps, 4b depth -> 32kbps -> 2.25s
6ksps, 4b depth -> 24kbps -> 3.0s
4ksps, 4b depth -> 16kbps -> 4.5s
in reality, the numbers i have seen are a little smaller than this, likely due
to compiler optimizations when the dac is unloaded / there's no audio.
a 16ksps file has an _experimental_ max of 0.75s. applying that offset
everywhere, we get empirical limits of:
16ksps, 4b depth: 0.75s
8ksps, 4b depth: 1.875s
6ksps, 4b depth: 2.625s
4ksps, 4b depth: 4.225s
## FFMPEG
use the following command to convert an input .wav to a raw mono pcm_u8 with 8ksps:
```shell
ffmpeg -i input.wav -f u8 -c:a pcm_u8 -ar 8000 -ac 1 output.raw
```
# PINOUTS
LED0: PC3
LED1: PD2
DAC: PC4
COIN SWITCH: PC2
BUTTON SWITCH: PD6
ADC: PD4
GPIO: any remaining pin
## FORBIDDEN:
PD1