644 lines
18 KiB
Rust
644 lines
18 KiB
Rust
#[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,
|
|
}
|
|
|
|
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 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,
|
|
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,
|
|
),
|
|
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.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.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 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,
|
|
) -> Self {
|
|
Self {
|
|
state: State::default(),
|
|
settings: Settings::default(),
|
|
timers: Timers::new(config.timers, config.system_tick_rate_hz),
|
|
services,
|
|
sequences,
|
|
interfaces,
|
|
}
|
|
}
|
|
|
|
pub fn init(&mut self) {
|
|
// self.timers.init();
|
|
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.led2_timer.reset();
|
|
self.timers.led2_timer.enable(true);
|
|
|
|
self.services.synth0.set_freq(1);
|
|
self.services.synth0.disable();
|
|
|
|
self.services.sequencer.disable();
|
|
}
|
|
|
|
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();
|
|
let bv = self.interfaces.adc_core.get_battery_voltage();
|
|
#[cfg(feature = "enable_print")]
|
|
println!("batt adc service: {bv}");
|
|
|
|
// TODO:
|
|
// do stuff if the battery voltage is below some threshold
|
|
}
|
|
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.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);
|
|
self.interfaces
|
|
.pwm_core
|
|
.write_amplitude(self.services.led2.channel, 0);
|
|
self.interfaces
|
|
.pwm_core
|
|
.disable(ch32_hal::timer::Channel::Ch4);
|
|
}
|
|
pub fn volume_button(&mut self) {
|
|
self.settings.volume.next();
|
|
#[cfg(feature = "enable_print")]
|
|
println!("new volume: {:?}", self.settings.volume);
|
|
}
|
|
pub fn brightness_button(&mut self) {
|
|
self.settings.brightness.next();
|
|
#[cfg(feature = "enable_print")]
|
|
println!("new brightness: {:?}", self.settings.brightness);
|
|
}
|
|
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.sp_timer.enable(true);
|
|
self.timers.lp_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);
|
|
}
|
|
}
|
|
|
|
// Events
|
|
impl App {
|
|
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
|
|
}
|
|
}
|
|
|
|
// 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
|