Compare commits
86 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3265325d28 | |||
| d8477e3d2d | |||
| 472e43056d | |||
| 4c80907c0f | |||
| 1d2e4a5482 | |||
| d053f8d080 | |||
| f26920a099 | |||
| 60d6aaecd6 | |||
| 10a38f9bbc | |||
| 5934336984 | |||
| f3ac43614c | |||
| b6fa0e3b2d | |||
| cd91b3f540 | |||
| 45db5e8af8 | |||
| 6ba94f1cbb | |||
| c71ace5063 | |||
| 7e187680f5 | |||
| 64aa1808d2 | |||
| 2f92805c1a | |||
| c43cc5599e | |||
| 8ea4b4401e | |||
| 1c2823eb1b | |||
| 17d6f156db | |||
| b0b77a1538 | |||
| 43790abbc5 | |||
| 5288cba869 | |||
| f4759a0c71 | |||
| 3d8ddd1317 | |||
| 713f3882a2 | |||
| a7cd209989 | |||
| 935129baed | |||
| 4e9428cb5f | |||
| b52d911c12 | |||
| a50895ec96 | |||
| 88d07fa69d | |||
| a1767420f2 | |||
| 1bdb68c0d4 | |||
| 48b836904b | |||
| 9328087e23 | |||
| 9e67026345 | |||
| 7d93cd8977 | |||
| 5aa56a244d | |||
| 2d8e2ce6ee | |||
| d815507bcc | |||
| c8c42c0194 | |||
| 930d10218f | |||
| 95b55f88a8 | |||
| d3ccc70782 | |||
| b786bf174a | |||
| f17811cdce | |||
| 9e34e77e95 | |||
| e1943accd2 | |||
| 791d5db4c8 | |||
| 008bf334a4 | |||
| 0836fc58df | |||
| 58579ae6c2 | |||
| 3ea7aac1f4 | |||
| 17c82e5a6c | |||
| 9f12502a3d | |||
| 144d24cf59 | |||
| 485f617515 | |||
| b1d7574a80 | |||
| 73e4b482a6 | |||
| 3eb410c796 | |||
| 08d7289c93 | |||
| 8c88456fcb | |||
| 885c7746b3 | |||
| 1a420018d1 | |||
| fba0fbec61 | |||
| 8d612d1559 | |||
| 94848d0d43 | |||
| bdfef566bf | |||
| 3f4b8112fd | |||
| 980fe9522c | |||
| b334b18233 | |||
| 83c2a64f12 | |||
| cb73b71ffe | |||
| 09ba2415d0 | |||
| a71c7986e6 | |||
| e3235b34a4 | |||
| 1a714bd907 | |||
| 51af5c3343 | |||
| 77188a3eaa | |||
| 0496b6e103 | |||
| 67d5817940 | |||
| c212831760 |
39 changed files with 3114 additions and 161 deletions
14
.gitmodules
vendored
14
.gitmodules
vendored
|
|
@ -1,6 +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"]
|
||||
path = ch32v-insert-coin/ext/ch32-hal
|
||||
url = git@github.com:ch32-rs/ch32-hal.git
|
||||
url = https://github.com/sigil-03/ch32-hal.git
|
||||
[submodule "ch32v-insert-coin/ext/qingke"]
|
||||
path = ch32v-insert-coin/ext/qingke
|
||||
url = 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
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ target = "riscv32ec-unknown-none-elf.json"
|
|||
# runner = "gdb -q -x openocd.gdb"
|
||||
# runner = "wlink -v flash"
|
||||
|
||||
runner = "wlink -v flash --enable-sdi-print --watch-serial"
|
||||
runner = "wlink -v flash --enable-sdi-print --watch-serial"
|
||||
|
||||
# Flash and debug chip with probe-rs. https://probe.rs/
|
||||
#runner = "probe-rs run --chip ch32v003"
|
||||
# runner = "probe-rs run --chip ch32v003"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
|
|
|
|||
20
ch32v-insert-coin/Cargo.lock
generated
20
ch32v-insert-coin/Cargo.lock
generated
|
|
@ -50,7 +50,7 @@ dependencies = [
|
|||
"futures",
|
||||
"nb 1.1.0",
|
||||
"proc-macro2",
|
||||
"qingke",
|
||||
"qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"qingke-rt",
|
||||
"quote",
|
||||
"rand_core",
|
||||
|
|
@ -74,11 +74,11 @@ dependencies = [
|
|||
"ch32-hal",
|
||||
"critical-section",
|
||||
"embassy-executor",
|
||||
"embassy-time",
|
||||
"embedded-hal 1.0.0",
|
||||
"panic-halt",
|
||||
"qingke",
|
||||
"qingke 0.5.0",
|
||||
"qingke-rt",
|
||||
"wavetable-synth",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -468,6 +468,14 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qingke"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"riscv 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qingke"
|
||||
version = "0.5.0"
|
||||
|
|
@ -485,7 +493,7 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b955c60adac70c6d40205b1dbe9f57e1151d06aa842069cdbaef7bc07ad283fd"
|
||||
dependencies = [
|
||||
"qingke",
|
||||
"qingke 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"qingke-rt-macros",
|
||||
]
|
||||
|
||||
|
|
@ -619,3 +627,7 @@ name = "void"
|
|||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "wavetable-synth"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -3,39 +3,42 @@ name = "ch32v-insert-coin"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
enable_print = []
|
||||
|
||||
[dependencies]
|
||||
ch32-hal = { path = "ext/ch32-hal/", features = [
|
||||
"ch32v003f4p6",
|
||||
"ch32v003f4u6",
|
||||
"memory-x",
|
||||
"embassy",
|
||||
"time-driver-tim2",
|
||||
"rt",
|
||||
] }
|
||||
|
||||
critical-section = "1.2.0"
|
||||
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"arch-riscv32",
|
||||
"arch-spin",
|
||||
"executor-thread",
|
||||
#"nightly",
|
||||
"task-arena-size-512", # or better use nightly, but fails on recent Rust versions
|
||||
"task-arena-size-128", # or better use nightly, but fails on recent Rust versions
|
||||
] }
|
||||
|
||||
embassy-time = "0.4.0"
|
||||
|
||||
panic-halt = "1.0"
|
||||
|
||||
embedded-hal = "1.0.0"
|
||||
|
||||
qingke = "*"
|
||||
qingke-rt = { version = "*", features = ["highcode"] }
|
||||
|
||||
qingke = {path = "ext/qingke"}
|
||||
|
||||
adpcm-pwm-dac = { path = "ext/adpcm-pwm-dac/" }
|
||||
|
||||
wavetable-synth = { path = "ext/wavetable-synth" }
|
||||
|
||||
critical-section = { version = "1.2.0" }
|
||||
|
||||
[profile.release]
|
||||
strip = false # symbols are not flashed to the microcontroller, so don't strip them.
|
||||
lto = true
|
||||
opt-level = "z" # Optimize for size.
|
||||
opt-level = "s" # Optimize for size.
|
||||
|
||||
[profile.dev]
|
||||
overflow-checks = false
|
||||
|
|
|
|||
BIN
ch32v-insert-coin/audio/button_1.raw
Normal file
BIN
ch32v-insert-coin/audio/button_1.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/button_2.raw
Normal file
BIN
ch32v-insert-coin/audio/button_2.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/button_3.raw
Normal file
BIN
ch32v-insert-coin/audio/button_3.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin.raw
Normal file
BIN
ch32v-insert-coin/audio/coin.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin2.raw
Normal file
BIN
ch32v-insert-coin/audio/coin2.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin3.raw
Normal file
BIN
ch32v-insert-coin/audio/coin3.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin4.raw
Normal file
BIN
ch32v-insert-coin/audio/coin4.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin5.raw
Normal file
BIN
ch32v-insert-coin/audio/coin5.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coin8ksps.raw
Normal file
BIN
ch32v-insert-coin/audio/coin8ksps.raw
Normal file
Binary file not shown.
BIN
ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw
Normal file
BIN
ch32v-insert-coin/audio/coinMixTest1_dpcm_u4.raw
Normal file
Binary file not shown.
18
ch32v-insert-coin/audio/sequences
Normal file
18
ch32v-insert-coin/audio/sequences
Normal 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,
|
||||
]
|
||||
BIN
ch32v-insert-coin/audio/sweep_dpcm_u4.raw
Normal file
BIN
ch32v-insert-coin/audio/sweep_dpcm_u4.raw
Normal file
Binary file not shown.
2
ch32v-insert-coin/build-run.sh
Executable file
2
ch32v-insert-coin/build-run.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
cargo +nightly run --release
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 714715b4aae512d1384b7387e3d2f695ea9b348e
|
||||
Subproject commit 99ce71e8d03e382b51732db7d0771349c51c7f48
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b8e1c864ba5545ee65b1c77dcb17c86a471b70c
|
||||
Subproject commit 4f11d68e62dcb0e7098eecf357168724a8322d80
|
||||
652
ch32v-insert-coin/ext/patches/optional-exti.patch
Normal file
652
ch32v-insert-coin/ext/patches/optional-exti.patch
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
From 7b086336e3820714c564aac13dc44fbaf7f5bc17 Mon Sep 17 00:00:00 2001
|
||||
From: mindshub <info@mindshub.com>
|
||||
Date: Sat, 9 Aug 2025 10:16:33 +0200
|
||||
Subject: [PATCH] optional exti
|
||||
|
||||
---
|
||||
Cargo.toml | 3 +-
|
||||
src/exti.rs | 548 ++++++++++++++++++++++++++--------------------------
|
||||
src/lib.rs | 1 +
|
||||
3 files changed, 281 insertions(+), 271 deletions(-)
|
||||
|
||||
diff --git a/Cargo.toml b/Cargo.toml
|
||||
index ec451bf..55fa288 100644
|
||||
--- a/Cargo.toml
|
||||
+++ b/Cargo.toml
|
||||
@@ -56,7 +56,7 @@ proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
[features]
|
||||
-default = ["embassy", "rt"]
|
||||
+default = ["embassy", "rt", "exti"]
|
||||
rt = ["dep:qingke-rt"]
|
||||
highcode = ["qingke-rt/highcode"]
|
||||
embassy = [
|
||||
@@ -66,6 +66,7 @@ embassy = [
|
||||
]
|
||||
defmt = ["dep:defmt"]
|
||||
memory-x = ["ch32-metapac/memory-x"]
|
||||
+exti = []
|
||||
|
||||
|
||||
# Features starting with `_` are for internal use only. They're not intended
|
||||
diff --git a/src/exti.rs b/src/exti.rs
|
||||
index a2458d1..3d613ea 100644
|
||||
--- a/src/exti.rs
|
||||
+++ b/src/exti.rs
|
||||
@@ -1,37 +1,11 @@
|
||||
-use core::future::Future;
|
||||
-use core::marker::PhantomData;
|
||||
-use core::pin::Pin;
|
||||
-use core::task::{Context, Poll};
|
||||
-
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
-use qingke_rt::interrupt;
|
||||
|
||||
-use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull};
|
||||
-use crate::{impl_peripheral, into_ref, peripherals, Peripheral};
|
||||
+use crate::{impl_peripheral, peripherals};
|
||||
|
||||
const EXTI_COUNT: usize = 24;
|
||||
const NEW_AW: AtomicWaker = AtomicWaker::new();
|
||||
static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [NEW_AW; EXTI_COUNT];
|
||||
|
||||
-pub unsafe fn on_irq() {
|
||||
- let exti = &crate::pac::EXTI;
|
||||
-
|
||||
- let bits = exti.intfr().read();
|
||||
-
|
||||
- // We don't handle or change any EXTI lines above 24.
|
||||
- let bits = bits.0 & 0x00FFFFFF;
|
||||
-
|
||||
- // 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);
|
||||
-
|
||||
- // Wake the tasks
|
||||
- for pin in BitIter(bits) {
|
||||
- EXTI_WAKERS[pin as usize].wake();
|
||||
- }
|
||||
-}
|
||||
-
|
||||
struct BitIter(u32);
|
||||
|
||||
impl Iterator for BitIter {
|
||||
@@ -48,150 +22,6 @@ impl Iterator for BitIter {
|
||||
}
|
||||
}
|
||||
|
||||
-/// EXTI input driver
|
||||
-pub struct ExtiInput<'d> {
|
||||
- pin: Input<'d>,
|
||||
-}
|
||||
-
|
||||
-impl<'d> Unpin for ExtiInput<'d> {}
|
||||
-
|
||||
-impl<'d> ExtiInput<'d> {
|
||||
- pub fn new<T: GpioPin>(
|
||||
- pin: impl Peripheral<P = T> + 'd,
|
||||
- ch: impl Peripheral<P = T::ExtiChannel> + 'd,
|
||||
- pull: Pull,
|
||||
- ) -> Self {
|
||||
- into_ref!(pin, ch);
|
||||
- // Needed if using AnyPin+AnyChannel.
|
||||
- assert_eq!(pin.pin(), ch.number());
|
||||
-
|
||||
- Self {
|
||||
- pin: Input::new(pin, pull),
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- pub fn is_high(&self) -> bool {
|
||||
- self.pin.is_high()
|
||||
- }
|
||||
-
|
||||
- pub fn is_low(&self) -> bool {
|
||||
- self.pin.is_low()
|
||||
- }
|
||||
-
|
||||
- pub fn get_level(&self) -> Level {
|
||||
- self.pin.get_level()
|
||||
- }
|
||||
-
|
||||
- pub async fn wait_for_high<'a>(&'a mut self) {
|
||||
- let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false);
|
||||
- if self.is_high() {
|
||||
- return;
|
||||
- }
|
||||
- fut.await
|
||||
- }
|
||||
-
|
||||
- pub async fn wait_for_low<'a>(&'a mut self) {
|
||||
- let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true);
|
||||
- if self.is_low() {
|
||||
- return;
|
||||
- }
|
||||
- fut.await
|
||||
- }
|
||||
-
|
||||
- pub async fn wait_for_rising_edge<'a>(&'a mut self) {
|
||||
- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await
|
||||
- }
|
||||
-
|
||||
- pub async fn wait_for_falling_edge<'a>(&'a mut self) {
|
||||
- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await
|
||||
- }
|
||||
-
|
||||
- pub async fn wait_for_any_edge<'a>(&'a mut self) {
|
||||
- ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await
|
||||
- }
|
||||
-}
|
||||
-
|
||||
-#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
-struct ExtiInputFuture<'a> {
|
||||
- pin: u8,
|
||||
- phantom: PhantomData<&'a mut AnyPin>,
|
||||
-}
|
||||
-
|
||||
-// EXTI0-EXTI23 Px0-Px23(x=A/B/C)
|
||||
-impl<'a> ExtiInputFuture<'a> {
|
||||
- fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self {
|
||||
- critical_section::with(|_| {
|
||||
- let exti = &crate::pac::EXTI;
|
||||
- let afio = &crate::pac::AFIO;
|
||||
-
|
||||
- let port = port as u8;
|
||||
- let pin = pin as usize;
|
||||
-
|
||||
- #[cfg(afio_v0)]
|
||||
- {
|
||||
- // AFIO_EXTICR
|
||||
- // stride: 2, len: 15, 8 lines
|
||||
- afio.exticr().modify(|w| w.set_exti(pin, port));
|
||||
- }
|
||||
- // V1, V2, V3, L1
|
||||
- #[cfg(any(afio_v3, afio_l1))]
|
||||
- {
|
||||
- // AFIO_EXTICRx
|
||||
- // stride: 4, len: 4, 16 lines
|
||||
- afio.exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port));
|
||||
- }
|
||||
- #[cfg(afio_x0)]
|
||||
- {
|
||||
- // stride: 2, len: 15, 24 lines
|
||||
- afio.exticr(pin / 16).modify(|w| w.set_exti(pin % 16, port));
|
||||
- }
|
||||
- #[cfg(afio_ch641)]
|
||||
- {
|
||||
- // single register
|
||||
- afio.exticr().modify(|w| w.set_exti(pin, port != 0));
|
||||
- }
|
||||
-
|
||||
- // See-also: 7.4.3
|
||||
- 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));
|
||||
- });
|
||||
-
|
||||
- Self {
|
||||
- pin,
|
||||
- phantom: PhantomData,
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-
|
||||
-impl<'a> Drop for ExtiInputFuture<'a> {
|
||||
- fn drop(&mut self) {
|
||||
- critical_section::with(|_| {
|
||||
- let exti = &crate::pac::EXTI;
|
||||
- let pin = self.pin;
|
||||
- exti.intenr().modify(|w| w.0 = w.0 & !(1 << pin));
|
||||
- });
|
||||
- }
|
||||
-}
|
||||
-
|
||||
-impl<'a> Future for ExtiInputFuture<'a> {
|
||||
- type Output = ();
|
||||
-
|
||||
- fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
- let exti = &crate::pac::EXTI;
|
||||
-
|
||||
- EXTI_WAKERS[self.pin as usize].register(cx.waker());
|
||||
-
|
||||
- if exti.intenr().read().mr(self.pin as _) == false {
|
||||
- // intenr cleared by on_irq, then we can assume it is triggered
|
||||
- Poll::Ready(())
|
||||
- } else {
|
||||
- Poll::Pending
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-
|
||||
trait SealedChannel {}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
@@ -207,6 +37,7 @@ pub trait Channel: SealedChannel + Sized {
|
||||
pub struct AnyChannel {
|
||||
number: u8,
|
||||
}
|
||||
+
|
||||
impl_peripheral!(AnyChannel);
|
||||
impl SealedChannel for AnyChannel {}
|
||||
impl Channel for AnyChannel {
|
||||
@@ -267,128 +98,305 @@ mod _exti_24lines {
|
||||
impl_exti!(EXTI23, 23);
|
||||
}
|
||||
|
||||
-/*
|
||||
-EXTI0
|
||||
-EXTI1
|
||||
-EXTI2
|
||||
-EXTI3
|
||||
-EXTI4
|
||||
-EXTI9_5
|
||||
-EXTI15_10
|
||||
-EXTI7_0
|
||||
-EXTI15_8
|
||||
-EXTI25_16
|
||||
-*/
|
||||
-
|
||||
-/// safety: must be called only once
|
||||
-#[cfg(gpio_x0)]
|
||||
-mod irq_impl {
|
||||
- use super::*;
|
||||
+#[cfg(feature = "exti")]
|
||||
+pub use exti_inner::*;
|
||||
+
|
||||
+#[cfg(feature = "exti")]
|
||||
+mod exti_inner {
|
||||
+ use super::{BitIter, Channel, EXTI_WAKERS};
|
||||
+ use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull};
|
||||
+ use crate::{into_ref, Peripheral};
|
||||
+ use core::future::Future;
|
||||
+ use core::marker::PhantomData;
|
||||
+ use core::pin::Pin;
|
||||
+ use core::task::{Context, Poll};
|
||||
+ use qingke_rt::interrupt;
|
||||
+
|
||||
+ /// EXTI input driver
|
||||
+ pub struct ExtiInput<'d> {
|
||||
+ pin: Input<'d>,
|
||||
+ }
|
||||
+
|
||||
+ impl<'d> Unpin for ExtiInput<'d> {}
|
||||
+
|
||||
+ impl<'d> ExtiInput<'d> {
|
||||
+ pub fn new<T: GpioPin>(
|
||||
+ pin: impl Peripheral<P = T> + 'd,
|
||||
+ ch: impl Peripheral<P = T::ExtiChannel> + 'd,
|
||||
+ pull: Pull,
|
||||
+ ) -> Self {
|
||||
+ into_ref!(pin, ch);
|
||||
+ // Needed if using AnyPin+AnyChannel.
|
||||
+ assert_eq!(pin.pin(), ch.number());
|
||||
+
|
||||
+ Self {
|
||||
+ pin: Input::new(pin, pull),
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI7_0() {
|
||||
- on_irq();
|
||||
- }
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI15_8() {
|
||||
- on_irq();
|
||||
- }
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI25_16() {
|
||||
- on_irq();
|
||||
- }
|
||||
+ pub fn is_high(&self) -> bool {
|
||||
+ self.pin.is_high()
|
||||
+ }
|
||||
|
||||
- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
- use crate::pac::Interrupt;
|
||||
+ pub fn is_low(&self) -> bool {
|
||||
+ self.pin.is_low()
|
||||
+ }
|
||||
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI25_16 as u8);
|
||||
- }
|
||||
-}
|
||||
+ pub fn get_level(&self) -> Level {
|
||||
+ self.pin.get_level()
|
||||
+ }
|
||||
|
||||
-#[cfg(all(gpio_v3, not(ch641)))]
|
||||
-mod irq_impl {
|
||||
- use super::*;
|
||||
+ pub async fn wait_for_high<'a>(&'a mut self) {
|
||||
+ let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false);
|
||||
+ if self.is_high() {
|
||||
+ return;
|
||||
+ }
|
||||
+ fut.await
|
||||
+ }
|
||||
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI0() {
|
||||
- on_irq();
|
||||
- }
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI1() {
|
||||
- on_irq();
|
||||
- }
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI2() {
|
||||
- on_irq();
|
||||
- }
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI3() {
|
||||
- on_irq();
|
||||
+ pub async fn wait_for_low<'a>(&'a mut self) {
|
||||
+ let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true);
|
||||
+ if self.is_low() {
|
||||
+ return;
|
||||
+ }
|
||||
+ fut.await
|
||||
+ }
|
||||
+
|
||||
+ pub async fn wait_for_rising_edge<'a>(&'a mut self) {
|
||||
+ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await
|
||||
+ }
|
||||
+
|
||||
+ pub async fn wait_for_falling_edge<'a>(&'a mut self) {
|
||||
+ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await
|
||||
+ }
|
||||
+
|
||||
+ pub async fn wait_for_any_edge<'a>(&'a mut self) {
|
||||
+ ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await
|
||||
+ }
|
||||
}
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI4() {
|
||||
- on_irq();
|
||||
+
|
||||
+ pub unsafe fn on_irq() {
|
||||
+ let exti = &crate::pac::EXTI;
|
||||
+
|
||||
+ let bits = exti.intfr().read();
|
||||
+
|
||||
+ // We don't handle or change any EXTI lines above 24.
|
||||
+ let bits = bits.0 & 0x00FFFFFF;
|
||||
+
|
||||
+ // 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);
|
||||
+
|
||||
+ // Wake the tasks
|
||||
+ for pin in BitIter(bits) {
|
||||
+ EXTI_WAKERS[pin as usize].wake();
|
||||
+ }
|
||||
}
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI9_5() {
|
||||
- on_irq();
|
||||
+
|
||||
+ #[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
+ struct ExtiInputFuture<'a> {
|
||||
+ pin: u8,
|
||||
+ phantom: PhantomData<&'a mut AnyPin>,
|
||||
+ }
|
||||
+
|
||||
+ // EXTI0-EXTI23 Px0-Px23(x=A/B/C)
|
||||
+ impl<'a> ExtiInputFuture<'a> {
|
||||
+ fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self {
|
||||
+ critical_section::with(|_| {
|
||||
+ let exti = &crate::pac::EXTI;
|
||||
+ let afio = &crate::pac::AFIO;
|
||||
+
|
||||
+ let port = port as u8;
|
||||
+ let pin = pin as usize;
|
||||
+
|
||||
+ #[cfg(afio_v0)]
|
||||
+ {
|
||||
+ // AFIO_EXTICR
|
||||
+ // stride: 2, len: 15, 8 lines
|
||||
+ afio.exticr().modify(|w| w.set_exti(pin, port));
|
||||
+ }
|
||||
+ // V1, V2, V3, L1
|
||||
+ #[cfg(any(afio_v3, afio_l1))]
|
||||
+ {
|
||||
+ // AFIO_EXTICRx
|
||||
+ // stride: 4, len: 4, 16 lines
|
||||
+ afio.exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port));
|
||||
+ }
|
||||
+ #[cfg(afio_x0)]
|
||||
+ {
|
||||
+ // stride: 2, len: 15, 24 lines
|
||||
+ afio.exticr(pin / 16).modify(|w| w.set_exti(pin % 16, port));
|
||||
+ }
|
||||
+ #[cfg(afio_ch641)]
|
||||
+ {
|
||||
+ // single register
|
||||
+ afio.exticr().modify(|w| w.set_exti(pin, port != 0));
|
||||
+ }
|
||||
+
|
||||
+ // See-also: 7.4.3
|
||||
+ 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));
|
||||
+ });
|
||||
+
|
||||
+ Self {
|
||||
+ pin,
|
||||
+ phantom: PhantomData,
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI15_10() {
|
||||
- on_irq();
|
||||
+
|
||||
+ impl<'a> Drop for ExtiInputFuture<'a> {
|
||||
+ fn drop(&mut self) {
|
||||
+ critical_section::with(|_| {
|
||||
+ let exti = &crate::pac::EXTI;
|
||||
+ let pin = self.pin;
|
||||
+ exti.intenr().modify(|w| w.0 = w.0 & !(1 << pin));
|
||||
+ });
|
||||
+ }
|
||||
}
|
||||
|
||||
- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
- use crate::pac::Interrupt;
|
||||
+ impl<'a> Future for ExtiInputFuture<'a> {
|
||||
+ type Output = ();
|
||||
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI0 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI1 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI2 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI3 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI4 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI9_5 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI15_10 as u8);
|
||||
- }
|
||||
-}
|
||||
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
+ let exti = &crate::pac::EXTI;
|
||||
|
||||
-#[cfg(gpio_v0)]
|
||||
-mod irq_impl {
|
||||
- use super::*;
|
||||
+ EXTI_WAKERS[self.pin as usize].register(cx.waker());
|
||||
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI7_0() {
|
||||
- on_irq();
|
||||
+ if exti.intenr().read().mr(self.pin as _) == false {
|
||||
+ // intenr cleared by on_irq, then we can assume it is triggered
|
||||
+ Poll::Ready(())
|
||||
+ } else {
|
||||
+ Poll::Pending
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
- use crate::pac::Interrupt;
|
||||
+ /*
|
||||
+ EXTI0
|
||||
+ EXTI1
|
||||
+ EXTI2
|
||||
+ EXTI3
|
||||
+ EXTI4
|
||||
+ EXTI9_5
|
||||
+ EXTI15_10
|
||||
+ EXTI7_0
|
||||
+ EXTI15_8
|
||||
+ EXTI25_16
|
||||
+ */
|
||||
+
|
||||
+ /// safety: must be called only once
|
||||
+ #[cfg(gpio_x0)]
|
||||
+ mod irq_impl {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI7_0() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI15_8() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI25_16() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
+ use crate::pac::Interrupt;
|
||||
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI25_16 as u8);
|
||||
+ }
|
||||
}
|
||||
-}
|
||||
|
||||
-#[cfg(all(gpio_v3, ch641))]
|
||||
-mod irq_impl {
|
||||
- use super::*;
|
||||
+ #[cfg(all(gpio_v3, not(ch641)))]
|
||||
+ mod irq_impl {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI0() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI1() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI2() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI3() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI4() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI9_5() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI15_10() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI7_0() {
|
||||
- on_irq();
|
||||
+ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
+ use crate::pac::Interrupt;
|
||||
+
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI0 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI1 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI2 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI3 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI4 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI9_5 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI15_10 as u8);
|
||||
+ }
|
||||
}
|
||||
|
||||
- #[interrupt]
|
||||
- unsafe fn EXTI15_8() {
|
||||
- on_irq();
|
||||
+ #[cfg(gpio_v0)]
|
||||
+ mod irq_impl {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI7_0() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
+ use crate::pac::Interrupt;
|
||||
+
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
+ }
|
||||
}
|
||||
|
||||
- pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
- use crate::pac::Interrupt;
|
||||
+ #[cfg(all(gpio_v3, ch641))]
|
||||
+ mod irq_impl {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI7_0() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+
|
||||
+ #[interrupt]
|
||||
+ unsafe fn EXTI15_8() {
|
||||
+ on_irq();
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) unsafe fn init(_cs: critical_section::CriticalSection) {
|
||||
+ use crate::pac::Interrupt;
|
||||
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
- qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI7_0 as u8);
|
||||
+ qingke::pfic::enable_interrupt(Interrupt::EXTI15_8 as u8);
|
||||
+ }
|
||||
}
|
||||
+ pub(crate) use irq_impl::*;
|
||||
}
|
||||
-
|
||||
-pub(crate) use irq_impl::*;
|
||||
diff --git a/src/lib.rs b/src/lib.rs
|
||||
index 997da34..b451a55 100644
|
||||
--- a/src/lib.rs
|
||||
+++ b/src/lib.rs
|
||||
@@ -140,6 +140,7 @@ pub fn init(config: Config) -> Peripherals {
|
||||
::critical_section::with(|cs| unsafe {
|
||||
gpio::init(cs);
|
||||
dma::init(cs, config.dma_interrupt_priority);
|
||||
+ #[cfg(feature = "exti")]
|
||||
exti::init(cs);
|
||||
});
|
||||
|
||||
|
||||
1
ch32v-insert-coin/ext/qingke
Submodule
1
ch32v-insert-coin/ext/qingke
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2443891811d7351d62e78085bcf60fa78c063c08
|
||||
1
ch32v-insert-coin/ext/wavetable-synth
Submodule
1
ch32v-insert-coin/ext/wavetable-synth
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 30033e1438c25aed4ea506cdbd69cc4ceffa0b4e
|
||||
10
ch32v-insert-coin/init.sh
Executable file
10
ch32v-insert-coin/init.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
DIR=${PWD}
|
||||
mkdir ${PWD}/tmp
|
||||
cd tmp
|
||||
git clone ssh://git@git.glyphs.tech:222/taproot-tech/docker-devtools.git
|
||||
cd docker-devtools/ch32
|
||||
ls
|
||||
./build.sh
|
||||
cd $DIR
|
||||
rm -rf tmp
|
||||
2
ch32v-insert-coin/launch.sh
Executable file
2
ch32v-insert-coin/launch.sh
Executable 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
|
||||
|
|
@ -14,5 +14,5 @@
|
|||
"max-atomic-width": 32,
|
||||
"panic-strategy": "abort",
|
||||
"relocation-model": "static",
|
||||
"target-pointer-width": "32"
|
||||
}
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
|
|
|
|||
705
ch32v-insert-coin/src/app.rs
Normal file
705
ch32v-insert-coin/src/app.rs
Normal 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
|
||||
66
ch32v-insert-coin/src/debounced_gpio.rs
Normal file
66
ch32v-insert-coin/src/debounced_gpio.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
183
ch32v-insert-coin/src/insert_coin/insert_coin.rs
Normal file
183
ch32v-insert-coin/src/insert_coin/insert_coin.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use ch32_hal::timer::simple_pwm::SimplePwm;
|
||||
use ch32_hal::timer::Channel;
|
||||
use ch32_hal::timer::GeneralInstance16bit;
|
||||
|
||||
use crate::insert_coin::services::{DacService, LedService, Service, TickService, TickServiceData};
|
||||
|
||||
// static mut led0_index: usize = 0;
|
||||
// static LED0: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
|
||||
|
||||
// static mut LED1_INDEX: usize = 0;
|
||||
// static LED1_DCS: [u8; 5] = [0u8, 25u8, 50u8, 75u8, 100u8];
|
||||
|
||||
pub struct SimplePwmCore<'d, T: GeneralInstance16bit> {
|
||||
pub pwm: core::cell::RefCell<SimplePwm<'d, T>>,
|
||||
}
|
||||
|
||||
impl<'d, T: GeneralInstance16bit> SimplePwmCore<'d, T> {
|
||||
pub fn new(pwm: SimplePwm<'d, T>) -> Self {
|
||||
Self {
|
||||
pwm: core::cell::RefCell::new(pwm),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn get_handle(&'d self, ch: Channel) -> SimplePwmHandle<'d, '_, T> {
|
||||
// SimplePwmHandle {
|
||||
// core: &self,
|
||||
// channel: ch,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn write_amplitude(&self, ch: Channel, amplitude: u8) {
|
||||
if !self.pwm.borrow().is_enabled(ch) {
|
||||
self.pwm.borrow_mut().enable(ch);
|
||||
}
|
||||
let max_duty = self.pwm.borrow().get_max_duty();
|
||||
let dc = amplitude as u32 * max_duty / 100;
|
||||
self.pwm.borrow_mut().set_duty(ch, dc);
|
||||
}
|
||||
|
||||
pub fn disable(&self, ch: Channel) {
|
||||
self.pwm.borrow_mut().disable(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct SimplePwmHandle<T: GeneralInstance16Bit> {
|
||||
// core: &'static SimplePwmCore<'_, T: GeneralInstance16Bit>,
|
||||
// channel: Channel,
|
||||
// }
|
||||
|
||||
// impl<T: GeneralInstance16Bit> SimplePwmHandle<T> {
|
||||
// pub fn set_amplitude(&self, amplitude: u8) {}
|
||||
// }
|
||||
|
||||
pub struct CoreConfig {
|
||||
pub tick_rate_hz: usize,
|
||||
}
|
||||
impl CoreConfig {
|
||||
pub fn new(tick_rate_hz: usize) -> Self {
|
||||
Self { tick_rate_hz }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Core {
|
||||
_tick: usize,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
pub struct InsertCoin<'a, T: GeneralInstance16bit> {
|
||||
core: Core,
|
||||
pub config: CoreConfig,
|
||||
pwm_core: SimplePwmCore<'a, T>,
|
||||
pub led0: LedService,
|
||||
pub led1: LedService,
|
||||
// led2: LedService,
|
||||
pub dac: DacService<'a>,
|
||||
}
|
||||
|
||||
impl<'a, T: GeneralInstance16bit> InsertCoin<'a, T> {
|
||||
pub fn new(config: CoreConfig, pwm_core: SimplePwmCore<'a, T>) -> Self {
|
||||
// LED0 servicer setup
|
||||
let led0 = LedService::new(ch32_hal::timer::Channel::Ch3);
|
||||
|
||||
// LED1 servicer setup
|
||||
let led1 = LedService::new(ch32_hal::timer::Channel::Ch1);
|
||||
|
||||
// DAC servicer setup
|
||||
let dac_sample_rate_hz = 4000;
|
||||
let dac_tick_per_service = config.tick_rate_hz / (dac_sample_rate_hz);
|
||||
let dac_service_data = TickServiceData::new(dac_tick_per_service);
|
||||
let dac = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data);
|
||||
|
||||
Self {
|
||||
config,
|
||||
core: Default::default(),
|
||||
pwm_core,
|
||||
led0,
|
||||
led1,
|
||||
// led2,
|
||||
dac,
|
||||
}
|
||||
}
|
||||
|
||||
/// takes self reference and runs
|
||||
pub fn service(&mut self) {
|
||||
if self.is_active() {
|
||||
self.dac.tick();
|
||||
|
||||
if self.led0.need_service() {
|
||||
self.pwm_core
|
||||
.write_amplitude(self.led0.channel, self.led0.amplitude);
|
||||
self.led0.service();
|
||||
}
|
||||
|
||||
if self.led1.need_service() {
|
||||
self.pwm_core
|
||||
.write_amplitude(self.led1.channel, self.led1.amplitude);
|
||||
self.led1.service();
|
||||
}
|
||||
|
||||
if self.dac.need_service() {
|
||||
self.dac.service();
|
||||
// TODO: adpcm-pwm-dac:e4c811653781e69e40b63fd27a8c1e20
|
||||
self.pwm_core
|
||||
.write_amplitude(self.dac.channel, self.dac.get_amplitude() as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /// consumes self and runs
|
||||
// pub fn run(mut self) -> ! {
|
||||
// let mut delay = Delay;
|
||||
// let tick_interval_us = 1000000/self.config.tick_rate_hz;
|
||||
|
||||
// let mut led0_index = 0;
|
||||
// let led0_dcs = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
|
||||
|
||||
// let mut led1_index = 0;
|
||||
// let led1_dcs = [0u8, 25u8, 50u8, 75u8, 100u8];
|
||||
|
||||
// loop {
|
||||
// self.dac.tick();
|
||||
|
||||
// if(self.led0.need_service()) {
|
||||
// self.led0.set_amplitude(led0_dcs[led0_index]);
|
||||
// self.pwm_core.write_amplitude(self.led0.channel, self.led0.amplitude);
|
||||
|
||||
// led0_index += 1;
|
||||
// if led0_index > led0_dcs.len() - 1 {
|
||||
// led0_index = 0;
|
||||
// }
|
||||
// self.led0.service();
|
||||
// }
|
||||
|
||||
// if(self.led1.need_service()) {
|
||||
// self.led1.set_amplitude(led1_dcs[led1_index]);
|
||||
// self.pwm_core.write_amplitude(self.led1.channel, self.led1.amplitude);
|
||||
|
||||
// led1_index += 1;
|
||||
// if led1_index > led1_dcs.len() - 1 {
|
||||
// led1_index = 0;
|
||||
// }
|
||||
// self.led1.service();
|
||||
// }
|
||||
|
||||
// if(self.dac.need_service()) {
|
||||
// self.dac.service();
|
||||
// // TODO: adpcm-pwm-dac:e4c811653781e69e40b63fd27a8c1e20
|
||||
// self.pwm_core.write_amplitude(self.dac.channel, self.dac.get_amplitude() as u8);
|
||||
// }
|
||||
|
||||
// delay.delay_us(tick_interval_us as u32);
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.core.active
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, active: bool) {
|
||||
self.core.active = active;
|
||||
}
|
||||
}
|
||||
7
ch32v-insert-coin/src/insert_coin/mod.rs
Normal file
7
ch32v-insert-coin/src/insert_coin/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mod insert_coin;
|
||||
mod services;
|
||||
|
||||
pub use services::{DacService, LedService, Service, TickTimerService};
|
||||
|
||||
pub use insert_coin::{CoreConfig, InsertCoin, SimplePwmCore};
|
||||
pub use services::{TickService, TickServiceData};
|
||||
68
ch32v-insert-coin/src/insert_coin/services/dac.rs
Normal file
68
ch32v-insert-coin/src/insert_coin/services/dac.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::insert_coin::services::{TickService, TickServiceData};
|
||||
|
||||
use adpcm_pwm_dac::dac::DpcmDecoder;
|
||||
use ch32_hal::timer::Channel;
|
||||
|
||||
pub struct DacService<'a> {
|
||||
service_data: core::cell::RefCell<TickServiceData>,
|
||||
dpcm_decoder: core::cell::RefCell<DpcmDecoder<'a>>,
|
||||
amplitude: core::cell::RefCell<usize>,
|
||||
pub channel: Channel,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl<'a> DacService<'a> {
|
||||
pub fn new(channel: Channel, service_data: TickServiceData) -> Self {
|
||||
Self {
|
||||
service_data: core::cell::RefCell::new(service_data),
|
||||
dpcm_decoder: core::cell::RefCell::new(DpcmDecoder::new()),
|
||||
amplitude: core::cell::RefCell::new(0),
|
||||
channel,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_sample(&mut self) {
|
||||
self.dpcm_decoder.borrow_mut().seek_to_sample(0);
|
||||
self.enabled = true;
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) {
|
||||
self.enabled = false;
|
||||
}
|
||||
|
||||
pub fn load_data(&self, data: &'a [u8]) {
|
||||
self.dpcm_decoder.borrow_mut().load_data(data);
|
||||
self.dpcm_decoder.borrow_mut().seek_to_sample(0);
|
||||
}
|
||||
|
||||
pub fn set_amplitude(&self, amplitude: usize) {
|
||||
self.amplitude.replace(amplitude);
|
||||
}
|
||||
pub fn get_amplitude(&self) -> usize {
|
||||
*self.amplitude.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TickService for DacService<'a> {
|
||||
fn tick(&self) {
|
||||
if self.enabled {
|
||||
let mut tc = self.service_data.borrow_mut();
|
||||
tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn need_service(&self) -> bool {
|
||||
self.enabled && self.service_data.borrow().ticks_remaining == 0
|
||||
}
|
||||
|
||||
fn service(&self) {
|
||||
let mut tc = self.service_data.borrow_mut();
|
||||
tc.ticks_remaining = tc.ticks_per_service;
|
||||
if (self.dpcm_decoder.borrow().is_done()) {
|
||||
self.set_amplitude(0);
|
||||
} else {
|
||||
self.set_amplitude(self.dpcm_decoder.borrow_mut().output_next());
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ch32v-insert-coin/src/insert_coin/services/led.rs
Normal file
36
ch32v-insert-coin/src/insert_coin/services/led.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use ch32_hal::timer::Channel;
|
||||
|
||||
use crate::insert_coin::services::Service;
|
||||
|
||||
pub struct LedService {
|
||||
// need_service: core::cell::RefCell<bool>,
|
||||
need_service: bool,
|
||||
pub channel: Channel,
|
||||
pub amplitude: u8,
|
||||
}
|
||||
|
||||
impl LedService {
|
||||
pub fn new(channel: Channel) -> Self {
|
||||
Self {
|
||||
// service_data: core::cell::RefCell::new(service_data),
|
||||
need_service: false,
|
||||
channel,
|
||||
amplitude: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_amplitude(&mut self, amplitude: u8) {
|
||||
self.amplitude = amplitude;
|
||||
self.need_service = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for LedService {
|
||||
fn need_service(&self) -> bool {
|
||||
self.need_service
|
||||
}
|
||||
|
||||
fn service(&mut self) {
|
||||
self.need_service = false;
|
||||
}
|
||||
}
|
||||
11
ch32v-insert-coin/src/insert_coin/services/mod.rs
Normal file
11
ch32v-insert-coin/src/insert_coin/services/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod services;
|
||||
pub use services::{TickService, TickServiceData, Service};
|
||||
|
||||
mod led;
|
||||
pub use led::LedService;
|
||||
|
||||
mod dac;
|
||||
pub use dac::DacService;
|
||||
|
||||
mod tick_timer;
|
||||
pub use tick_timer::TickTimerService;
|
||||
31
ch32v-insert-coin/src/insert_coin/services/services.rs
Normal file
31
ch32v-insert-coin/src/insert_coin/services/services.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
pub struct TickServiceData {
|
||||
pub ticks_per_service: usize,
|
||||
pub ticks_remaining: usize,
|
||||
}
|
||||
|
||||
impl TickServiceData {
|
||||
pub fn new(ticks_per_service: usize) -> Self {
|
||||
Self {
|
||||
ticks_per_service,
|
||||
ticks_remaining: ticks_per_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TickService {
|
||||
/// indicate to the service that a tick has occurred
|
||||
fn tick(&self);
|
||||
|
||||
/// return true if service needs the service() routine run
|
||||
fn need_service(&self) -> bool;
|
||||
|
||||
/// service routine - handle what needs to be done (non blocking) when
|
||||
/// the service needs to be serviced here
|
||||
fn service(&self);
|
||||
}
|
||||
|
||||
|
||||
pub trait Service {
|
||||
fn need_service(&self) -> bool;
|
||||
fn service(&mut self);
|
||||
}
|
||||
49
ch32v-insert-coin/src/insert_coin/services/tick_timer.rs
Normal file
49
ch32v-insert-coin/src/insert_coin/services/tick_timer.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use crate::insert_coin::services::{TickService, TickServiceData};
|
||||
|
||||
pub struct TickTimerService {
|
||||
service_data: core::cell::RefCell<TickServiceData>,
|
||||
_auto_reset: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl TickTimerService {
|
||||
pub fn new(service_data: TickServiceData, auto_reset: bool) -> Self {
|
||||
Self {
|
||||
service_data: core::cell::RefCell::new(service_data),
|
||||
_auto_reset: auto_reset,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
let mut sd = self.service_data.borrow_mut();
|
||||
sd.ticks_remaining = sd.ticks_per_service;
|
||||
self.enabled = false;
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, enable: bool) {
|
||||
self.enabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
impl TickService for TickTimerService {
|
||||
fn tick(&self) {
|
||||
if self.enabled {
|
||||
let mut tc = self.service_data.borrow_mut();
|
||||
tc.ticks_remaining = tc.ticks_remaining.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn need_service(&self) -> bool {
|
||||
self.enabled && self.service_data.borrow().ticks_remaining == 0
|
||||
}
|
||||
|
||||
fn service(&self) {
|
||||
let mut tc = self.service_data.borrow_mut();
|
||||
tc.ticks_remaining = tc.ticks_per_service;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,199 +3,646 @@
|
|||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
use core::any::Any;
|
||||
// app stuff
|
||||
mod insert_coin;
|
||||
|
||||
use adpcm_pwm_dac::dac::DpcmDac;
|
||||
use ch32_hal::{exti::ExtiInput, pac::systick::Systick};
|
||||
use {ch32_hal as hal};
|
||||
use hal::peripherals::EXTI4;
|
||||
use hal::{bind_interrupts, interrupt};
|
||||
// system stuff
|
||||
mod system;
|
||||
|
||||
mod debounced_gpio;
|
||||
use debounced_gpio::DebouncedGPIO;
|
||||
|
||||
// synthesizer :3
|
||||
mod synthesizer;
|
||||
use synthesizer::SynthesizerService;
|
||||
|
||||
// sequences
|
||||
mod sequences;
|
||||
use sequences::SEQUENCE_LIST;
|
||||
|
||||
mod app;
|
||||
use app::{
|
||||
sequencer::BasicSequence, App, Config, Interfaces, Sequences, Services, State, TimerConfig,
|
||||
};
|
||||
|
||||
use ch32_hal::{adc::AdcChannel, interrupt::typelevel::Handler, timer::low_level::OutputPolarity};
|
||||
use insert_coin::{CoreConfig, DacService, InsertCoin, LedService, SimplePwmCore};
|
||||
|
||||
use ch32_hal as hal;
|
||||
use hal::bind_interrupts;
|
||||
use hal::delay::Delay;
|
||||
use hal::gpio::{AnyPin, Level, Input, Output, Pin, Pull};
|
||||
use hal::gpio::{AnyPin, Input, Level, Output, OutputOpenDrain, Pin, Pull};
|
||||
use hal::time::Hertz;
|
||||
use hal::timer::low_level::CountingMode;
|
||||
use hal::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||
use hal::timer::{Channel, GeneralInstance16bit};
|
||||
|
||||
use hal::println;
|
||||
|
||||
use qingke::riscv;
|
||||
|
||||
use crate::app::sequencer::{DynamicSequence, SequenceEntry};
|
||||
|
||||
// embassy shit
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
static LED0_SEQ: [u8; 8] = [0u8, 25u8, 50u8, 75u8, 100u8, 75u8, 50u8, 25u8];
|
||||
|
||||
// const DAC_DATA: [u8; 4] = [0x0, 0x80, 0xFF, 0x80];
|
||||
const DAC_DATA: [u8; 8] = [0, 25, 50, 75, 100, 75, 50, 25];
|
||||
|
||||
// DPCS DATA
|
||||
// step size: 5
|
||||
// 0
|
||||
// + 25 -> [1, 0, 1, 1] -> 0xB
|
||||
// + 25 -> 0xB
|
||||
// + 25 -> 0xB
|
||||
// + 25 -> 0xB
|
||||
// - 25 -> 0xA
|
||||
// - 25 -> 0xA
|
||||
// - 25 -> 0xA
|
||||
// const DPCM_DAC_DATA: [u8; 4] = [0xBB, 0xBB, 0xAA, 0xAA];
|
||||
|
||||
|
||||
// const DATA2: [u8; 1] = [0x00u8];
|
||||
// const DAC_DATA_1: &'static [u8] = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw");
|
||||
|
||||
|
||||
static mut IRQ1_FLAG: bool = false;
|
||||
|
||||
struct SimplePwmDacPin<'d, T: GeneralInstance16bit>{
|
||||
pin: SimplePwm<'d, T>,
|
||||
ch: Channel,
|
||||
pub struct Usb {
|
||||
usb_pin: Input<'static>,
|
||||
}
|
||||
|
||||
impl Usb {
|
||||
pub fn new(usb_pin: Input<'static>) -> Self {
|
||||
Self { usb_pin }
|
||||
}
|
||||
pub fn powered(&self) -> bool {
|
||||
self.usb_pin.is_high()
|
||||
}
|
||||
// pub fn enable(&mut self) {
|
||||
// self.usb_pin.set_as_input(Pull::Up);
|
||||
// }
|
||||
// pub fn disable(&mut self) {
|
||||
// self.usb_pin.set_as_input(Pull::None);
|
||||
// }
|
||||
}
|
||||
|
||||
use adpcm_pwm_dac::{interface::DacInterface, dac::Dac};
|
||||
pub struct Amplifier {
|
||||
amp_en: OutputOpenDrain<'static>,
|
||||
}
|
||||
|
||||
impl<T> DacInterface for SimplePwmDacPin<'_, T>
|
||||
where T: GeneralInstance16bit {
|
||||
fn write_amplitude(&mut self, amplitude: u8) {
|
||||
if !self.pin.is_enabled(self.ch) {
|
||||
self.pin.enable(self.ch);
|
||||
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 {
|
||||
adc,
|
||||
battery_pin: pin,
|
||||
batt_values: [1024; 10],
|
||||
index: 0,
|
||||
}
|
||||
let max_duty = self.pin.get_max_duty();
|
||||
let dc = amplitude as u32 * max_duty / 100;
|
||||
self.pin.set_duty(self.ch, dc);
|
||||
}
|
||||
fn disable(&mut self) {
|
||||
self.pin.disable(self.ch);
|
||||
|
||||
// TODO make this a float or something
|
||||
pub fn get_battery_voltage(&mut self) -> u16 {
|
||||
let val = self
|
||||
.adc
|
||||
.convert(&mut self.battery_pin, hal::adc::SampleTime::CYCLES241);
|
||||
self.batt_values[self.index] = val;
|
||||
self.index += 1;
|
||||
if self.index > &self.batt_values.len() - 1 {
|
||||
self.index = 0;
|
||||
}
|
||||
val
|
||||
}
|
||||
|
||||
pub fn get_average(&self) -> u16 {
|
||||
let mut sum = 0;
|
||||
for value in &self.batt_values {
|
||||
sum += value;
|
||||
}
|
||||
sum / self.batt_values.len() as u16
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self) {
|
||||
self.adc.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = 2)]
|
||||
async fn led_task(mut pin: AnyPin, period: u64) {
|
||||
let mut led = Output::new(pin, Level::High, Default::default());
|
||||
|
||||
loop {
|
||||
led.toggle();
|
||||
Timer::after(Duration::from_millis(period)).await;
|
||||
#[derive(Debug)]
|
||||
struct Flag {
|
||||
value: bool,
|
||||
}
|
||||
impl Flag {
|
||||
pub fn active(&self) -> bool {
|
||||
unsafe { core::ptr::read_volatile(&raw const self.value as *const bool) }
|
||||
}
|
||||
pub fn set(&mut self) {
|
||||
unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, true) }
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
unsafe { core::ptr::write_volatile(&raw mut self.value as *mut bool, false) }
|
||||
}
|
||||
}
|
||||
|
||||
// #[embassy_executor::task(pool_size = 1)]
|
||||
// async fn audio_task(mut dac: DpcmDac<'static, SimplePwmDacPin<'static, hal::peripherals::TIM1>>) {
|
||||
// // // DAC servicer computations
|
||||
// // let data = include_bytes!("../../../dpcm-encoder-decoder/sweep_dpcm_u4.raw");
|
||||
#[derive(Debug)]
|
||||
struct InputFlags {
|
||||
sense_coin_flag: Flag,
|
||||
main_btn_flag: Flag,
|
||||
volume_btn_flag: bool,
|
||||
light_ctrl_btn_flag: bool,
|
||||
systick_flag: Flag,
|
||||
}
|
||||
|
||||
// let mut dac_active = true;
|
||||
// // let sample_rate_hz = 16000;
|
||||
// let sample_rate_hz = 440;
|
||||
// let tick_interval_us = 1000000/(sample_rate_hz);
|
||||
impl Default for InputFlags {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sense_coin_flag: Flag { value: false },
|
||||
main_btn_flag: Flag { value: false },
|
||||
volume_btn_flag: false,
|
||||
light_ctrl_btn_flag: false,
|
||||
systick_flag: Flag { value: false },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dac.load_data(&DAC_DATA);
|
||||
|
||||
// loop {
|
||||
// dac.output_next();
|
||||
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 },
|
||||
};
|
||||
|
||||
// // if(dac_need_service && dac_active) {
|
||||
// // dac_active = dac.output_next();
|
||||
// // if !dac_active {
|
||||
// // dac.disable_output();
|
||||
// // }
|
||||
// // }
|
||||
// Timer::after(Duration::from_micros(tick_interval_us)).await;
|
||||
// }
|
||||
// }
|
||||
struct Test {}
|
||||
impl Handler<hal::interrupt::typelevel::EXTI7_0> for Test {
|
||||
unsafe fn on_interrupt() {
|
||||
// #[cfg(feature = "enable_print")]
|
||||
// println!("on_interrupt()");
|
||||
critical_section::with(|_| unsafe {
|
||||
let flags = system::clear_interrupt(2, 6);
|
||||
if flags[0] {
|
||||
// safe because single-threaded
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.sense_coin_flag.set();
|
||||
}
|
||||
if flags[1] {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.main_btn_flag.set();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::main(entry = "ch32_hal::entry")]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let mut config = hal::Config::default();
|
||||
config.rcc = hal::rcc::Config::SYSCLK_FREQ_48MHZ_HSI;
|
||||
let p = hal::init(config);
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
||||
// p.EXTI4
|
||||
let mut ei = hal::exti::ExtiInput::new(p.PD4, p.EXTI4, Pull::Up);
|
||||
fn systick_init(tick_freq_hz: usize) {
|
||||
let r = &ch32_hal::pac::SYSTICK;
|
||||
|
||||
// LED output setup
|
||||
// let mut led = Output::new(p.PD0, Level::Low, Default::default());
|
||||
// Calculate counts per millisecond using HCLK/8 as clock source
|
||||
// HCLK/8 = 48MHz/8 = 6MHz
|
||||
// For tick_freq_hz interrupt: 6MHz / tick_freq_hz
|
||||
let systick_per_tick = (48_000_000 / 8 / tick_freq_hz) as u32;
|
||||
|
||||
// Reset SysTick
|
||||
r.ctlr().write(|w| {
|
||||
// Start with everything disabled
|
||||
});
|
||||
|
||||
// Set compare register and reset counter
|
||||
r.cmp().write_value(systick_per_tick - 1);
|
||||
r.cnt().write_value(0);
|
||||
|
||||
// Clear interrupt flag
|
||||
r.sr().write(|w| w.set_cntif(false));
|
||||
|
||||
// Configure and start SysTick
|
||||
r.ctlr().write(|w| {
|
||||
w.set_ste(true); // Enable counter
|
||||
w.set_stie(true); // Enable interrupt
|
||||
w.set_stre(true); // Auto reload enable
|
||||
w.set_stclk(ch32_hal::pac::systick::vals::Stclk::HCLK_DIV8); // HCLK/8 clock source
|
||||
});
|
||||
}
|
||||
fn systick_stop() {
|
||||
let r = &ch32_hal::pac::SYSTICK;
|
||||
// Reset SysTick
|
||||
r.ctlr().write(|w| {
|
||||
// Start with everything disabled
|
||||
});
|
||||
}
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
EXTI7_0 => Test;
|
||||
});
|
||||
|
||||
// TODO: remove
|
||||
use app::settings::Settings;
|
||||
use insert_coin::TickTimerService;
|
||||
use insert_coin::{TickService, TickServiceData};
|
||||
|
||||
fn app_main(mut p: hal::Peripherals, app_settings: Settings) -> Settings {
|
||||
// initialize ADC core first, and exit if battery is too low
|
||||
let mut adc = hal::adc::Adc::new(p.ADC1, Default::default());
|
||||
let mut batt_monitor_pin = p.PD4;
|
||||
let mut adc_core = AdcCore::new(adc, batt_monitor_pin);
|
||||
|
||||
let mut usb_detect_pin = p.PD5;
|
||||
let usb_detect_input = Input::new(usb_detect_pin, Pull::Up);
|
||||
let usb = Usb::new(usb_detect_input);
|
||||
|
||||
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 ===
|
||||
|
||||
// LED0 output setup
|
||||
let led0_pin = PwmPin::new_ch3::<0>(p.PC3);
|
||||
let led0_ch = hal::timer::Channel::Ch3;
|
||||
|
||||
// LED1 output setup
|
||||
// let mut led1 = Output::new(p.PD6, Level::High, Default::default());
|
||||
|
||||
let led1_pin = PwmPin::new_ch1::<0>(p.PD2);
|
||||
let led1_ch = hal::timer::Channel::Ch1;
|
||||
|
||||
// PWM DAC output pin setup
|
||||
let pin = PwmPin::new_ch4::<0>(p.PC4);
|
||||
let ch = hal::timer::Channel::Ch4;
|
||||
// DAC output setup
|
||||
let dac_pin = PwmPin::new_ch4::<0>(p.PC4);
|
||||
// let dac_ch = hal::timer::Channel::Ch4;
|
||||
|
||||
// PWM timer setup
|
||||
let mut pwm = SimplePwm::new(
|
||||
p.TIM1,
|
||||
Some(led1_pin),
|
||||
// Some(led2_pin),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(pin),
|
||||
Hertz::khz(100),
|
||||
Some(led0_pin),
|
||||
Some(dac_pin),
|
||||
Hertz::khz(200),
|
||||
CountingMode::default(),
|
||||
);
|
||||
let mut pwm_dac_pin = SimplePwmDacPin{pin: pwm, ch};
|
||||
|
||||
pwm.set_polarity(led0_ch, OutputPolarity::ActiveHigh);
|
||||
pwm.set_polarity(led1_ch, OutputPolarity::ActiveLow);
|
||||
let mut pwm_core = SimplePwmCore::new(pwm);
|
||||
pwm_core.write_amplitude(led0_ch, 0);
|
||||
pwm_core.write_amplitude(led1_ch, 0);
|
||||
|
||||
// DAC setup
|
||||
let mut dac = DpcmDac::new(pwm_dac_pin);
|
||||
// dac.load_data(data);
|
||||
// pwm.set_polarity(led2_ch, OutputPolarity::ActiveLow);
|
||||
|
||||
let tick_rate_hz = 50000;
|
||||
|
||||
// DAC servicer computations
|
||||
let mut dac_active = true;
|
||||
let sample_rate_hz = 16000;
|
||||
let dac_tick_per_service = 1;
|
||||
let tick_rate_hz = sample_rate_hz * dac_tick_per_service;
|
||||
let tick_interval_us = 1000000/(tick_rate_hz);
|
||||
let core_config = CoreConfig::new(tick_rate_hz);
|
||||
|
||||
// // LED servicer computations
|
||||
// let mut led_active = true;
|
||||
// let led_blink_rate_hz = 3;
|
||||
// let led_tick_per_service = (tick_rate_hz/(led_blink_rate_hz * 2));
|
||||
// === input setup ===
|
||||
|
||||
// // LED1 servicer vars
|
||||
// let mut led1_need_service = false;
|
||||
// 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());
|
||||
|
||||
// let mut tick: usize = 0;
|
||||
// println!("ADC_PIN CHANNEL: {}", adc_pin.channel().channel());
|
||||
|
||||
ei.wait_for_falling_edge().await;
|
||||
// #[cfg(feature = "enable_print")]
|
||||
// println!("ADC calibration value: {}", adc_cal);
|
||||
|
||||
spawner.spawn(led_task(p.PD6.degrade(), 250)).unwrap();
|
||||
spawner.spawn(led_task(p.PD0.degrade(), 100)).unwrap();
|
||||
// spawner.spawn(audio_task(dac)).unwrap();
|
||||
|
||||
loop{
|
||||
// let dac_need_service = ((tick % dac_tick_per_service) == 0);
|
||||
// if(dac_need_service && dac_active) {
|
||||
// dac_active = dac.output_next();
|
||||
// if !dac_active {
|
||||
// dac.disable_output();
|
||||
// }
|
||||
// }
|
||||
// definitions
|
||||
let sense_coin_pin = p.PC2;
|
||||
let main_btn_pin = p.PD6;
|
||||
let volume_btn_pin = p.PC6;
|
||||
let light_ctrl_btn_pin = p.PC7;
|
||||
let amp_en = p.PC5;
|
||||
// let extra_io_1 = p.PD0;
|
||||
// let extra_io_2 = p.PD3;
|
||||
|
||||
dac.output_next();
|
||||
let mut amp_en_output = OutputOpenDrain::new(amp_en, Level::Low, Default::default());
|
||||
let amp = Amplifier::new(amp_en_output);
|
||||
|
||||
// let led_need_service = ((tick % led_tick_per_service) == 0);
|
||||
// if(led_need_service && led_active) {
|
||||
// led.toggle();
|
||||
// }
|
||||
|
||||
// tick = tick.wrapping_add(1);
|
||||
// delay.delay_us(tick_interval as u32);
|
||||
// Timer::after_micros(tick_interval as u64).await;
|
||||
// Timer::after_micros(1000000u64).await;
|
||||
// Timer::after(Duration::from_millis(2000)).await;
|
||||
Timer::after(Duration::from_micros(tick_interval_us)).await;
|
||||
// set up interrupts
|
||||
unsafe { system::init_gpio_irq(sense_coin_pin.pin(), sense_coin_pin.port(), true, false) };
|
||||
unsafe { system::init_gpio_irq(main_btn_pin.pin(), main_btn_pin.port(), true, true) };
|
||||
|
||||
// coin debouncer (100ms)
|
||||
let mut sense_coin_input =
|
||||
DebouncedGPIO::new(sense_coin_pin.degrade(), core_config.tick_rate_hz, 20);
|
||||
// main button debouncer (100ms)
|
||||
let mut main_btn_input =
|
||||
DebouncedGPIO::new(main_btn_pin.degrade(), core_config.tick_rate_hz, 20);
|
||||
// button debouncer (100ms)
|
||||
let mut 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,
|
||||
};
|
||||
|
||||
// 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 coin_sound = include_bytes!("../audio/coin5.raw");
|
||||
// let coin_sound = include_bytes!("../audio/coin2.raw");
|
||||
|
||||
let sample_player = DacService::new(ch32_hal::timer::Channel::Ch4, dac_service_data);
|
||||
// sample_player.load_data(coin_sound);
|
||||
|
||||
let sequencer = app::sequencer::DynamicSequence::new(&SEQUENCE_LIST[0].0, tick_rate_hz);
|
||||
|
||||
let app_services = Services {
|
||||
led0: LedService::new(led0_ch),
|
||||
led1: LedService::new(led1_ch),
|
||||
// led2: LedService::new(led2_ch),
|
||||
synth0: SynthesizerService::new(tick_rate_hz),
|
||||
sample_player,
|
||||
sequencer,
|
||||
};
|
||||
|
||||
let app_sequences = Sequences {
|
||||
led0: BasicSequence::new(&LED0_SEQ),
|
||||
led1: BasicSequence::new(&LED0_SEQ),
|
||||
// led2: BasicSequence::new(&LED0_SEQ),
|
||||
audio: &SEQUENCE_LIST,
|
||||
};
|
||||
|
||||
let app_interfaces = Interfaces {
|
||||
pwm_core,
|
||||
adc_core,
|
||||
amp,
|
||||
usb,
|
||||
};
|
||||
|
||||
let mut app = App::new(
|
||||
app_config,
|
||||
app_services,
|
||||
app_sequences,
|
||||
app_interfaces,
|
||||
app_settings,
|
||||
);
|
||||
|
||||
let need_sound = unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if INPUT_FLAGS.main_btn_flag.active() {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.main_btn_flag.clear();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// init systick
|
||||
systick_init(tick_rate_hz);
|
||||
|
||||
// set up interrupts
|
||||
unsafe {
|
||||
use hal::pac::Interrupt;
|
||||
use qingke::interrupt::Priority;
|
||||
use qingke_rt::CoreInterrupt;
|
||||
|
||||
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
|
||||
// process
|
||||
// -depress big button (insert coin button) and it wakes up and turns on led one led at a fixed brightness
|
||||
// -we will want one sound, the coin insert sound, to play when coin button is pressed. (This is when they insert a coin)
|
||||
// -We will want a different sound or potentially multiple different sounds played in a rotating fashion when someone presses the button. Probably do some led blinking as well.(This is when they depress the big insert to play button)
|
||||
// -depress the big button for approx 2s and it puts the led into low light mode. Just making it dimmer than usual.
|
||||
// -depress the big button for 5s and it goes into deep sleep, everything off with the low sleep current draw
|
||||
|
||||
// #[cfg(feature = "enable_print")]
|
||||
// println!("begin");
|
||||
let mut volume_btn_prev = volume_btn_input.is_high_immediate();
|
||||
let mut light_ctrl_btn_prev = light_ctrl_btn_input.is_high_immediate();
|
||||
|
||||
app.init();
|
||||
if need_sound {
|
||||
app.main_button_click();
|
||||
}
|
||||
loop {
|
||||
// system servicing
|
||||
|
||||
// volume edge detector
|
||||
if !volume_btn_input.active() {
|
||||
let volume_btn_curr = volume_btn_input.is_high_immediate();
|
||||
if volume_btn_prev != volume_btn_curr {
|
||||
volume_btn_input.begin();
|
||||
volume_btn_prev = volume_btn_curr;
|
||||
}
|
||||
}
|
||||
|
||||
volume_btn_input.service();
|
||||
if volume_btn_input.ready() {
|
||||
// #[cfg(feature = "enable_print")]
|
||||
// println!("volume btn value: {}", volume_btn_input.value());
|
||||
if !volume_btn_input.value() {
|
||||
app.volume_button();
|
||||
}
|
||||
volume_btn_input.reset();
|
||||
}
|
||||
|
||||
// brightness edge detector
|
||||
if !light_ctrl_btn_input.active() {
|
||||
let light_ctrl_btn_curr = light_ctrl_btn_input.is_high_immediate();
|
||||
if light_ctrl_btn_prev != light_ctrl_btn_curr {
|
||||
light_ctrl_btn_input.begin();
|
||||
light_ctrl_btn_prev = light_ctrl_btn_curr;
|
||||
}
|
||||
}
|
||||
|
||||
light_ctrl_btn_input.service();
|
||||
if light_ctrl_btn_input.ready() {
|
||||
#[cfg(feature = "enable_print")]
|
||||
println!("brightness btn value: {}", light_ctrl_btn_input.value());
|
||||
if !light_ctrl_btn_input.value() {
|
||||
app.brightness_button();
|
||||
}
|
||||
light_ctrl_btn_input.reset();
|
||||
}
|
||||
|
||||
// coin_detect interrupt
|
||||
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if INPUT_FLAGS.sense_coin_flag.active() {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.sense_coin_flag.clear();
|
||||
sense_coin_input.begin();
|
||||
}
|
||||
sense_coin_input.service();
|
||||
if sense_coin_input.ready() {
|
||||
sense_coin_input.reset();
|
||||
app.coin_detect();
|
||||
}
|
||||
}
|
||||
|
||||
// main button handling
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if INPUT_FLAGS.main_btn_flag.active() {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.main_btn_flag.clear();
|
||||
main_btn_input.begin();
|
||||
}
|
||||
}
|
||||
main_btn_input.service();
|
||||
if main_btn_input.ready() {
|
||||
let value = main_btn_input.value();
|
||||
main_btn_input.reset();
|
||||
// #[cfg(feature = "enable_print")]
|
||||
// println!("main button", value);
|
||||
|
||||
match value {
|
||||
true => app.main_button_press(),
|
||||
false => app.main_button_release(),
|
||||
}
|
||||
}
|
||||
|
||||
// systick tick
|
||||
if unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.systick_flag.active()
|
||||
} {
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
INPUT_FLAGS.systick_flag.clear();
|
||||
}
|
||||
// app tick
|
||||
app.tick();
|
||||
}
|
||||
|
||||
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]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
// println!("panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
|
||||
|
|
|
|||
346
ch32v-insert-coin/src/sequences.rs
Normal file
346
ch32v-insert-coin/src/sequences.rs
Normal 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,
|
||||
];
|
||||
66
ch32v-insert-coin/src/synthesizer.rs
Normal file
66
ch32v-insert-coin/src/synthesizer.rs
Normal 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
|
||||
}
|
||||
}
|
||||
134
ch32v-insert-coin/src/system.rs
Normal file
134
ch32v-insert-coin/src/system.rs
Normal 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");
|
||||
});
|
||||
}
|
||||
97
notes.md
Normal file
97
notes.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# ENVIRONMENT
|
||||
there is a docker image that contains the entire toolchain + flashing utility. first, you need to build the base docker image with the following script located in the ch32v-insert-coin directory:
|
||||
```shell
|
||||
$ ./init.sh
|
||||
```
|
||||
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
|
||||
$ ./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.
|
||||
|
||||
to exit the serial debugger simply use `ctrl-c`. to exit the environment shell use:
|
||||
|
||||
```shell
|
||||
$ exit
|
||||
```
|
||||
|
||||
# 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.
|
||||
|
||||
the `wlink` utility will automatically detect the correct chip, but if it doesn't you can specify it with an additional argument.
|
||||
|
||||
`wlink --help` lists all the options to the `wlink` utility.
|
||||
|
||||
## DEVELOPMENT
|
||||
when building using the rust toolchain, you can simply add the following to your `.cargo/config.toml`:
|
||||
`runner = "wlink -v flash`
|
||||
|
||||
if you want to monitor prints via SDI, you can use the following instead:
|
||||
`runner = "wlink -v flash --enable-sdi-print --watch-serial"`
|
||||
|
||||
## STANDALONE
|
||||
when flashing standalone with the `wlink` utility, you can simply run the following:
|
||||
`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
|
||||
Loading…
Add table
Add a link
Reference in a new issue