pm-kit: peripheral self-test — buttons + joystick (ADC) + speaker (PWM)
Honest answer to 'do the inputs/speaker work?': they had NO Rust code. Add the drivers and a live self-test: buttons GP15/GP14 (pull-up), joystick GP26/GP27 via ADC, speaker GP13 via PWM (~2 kHz click on button press). draw_peripheral_test (pm-ui) shows button states, joystick dot + X/Y values, and beep activity; layout verified in the simulator (uisim --bin periphsim) before flashing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0ea442d68d
commit
04350f9d09
4 changed files with 135 additions and 24 deletions
|
|
@ -9,6 +9,7 @@ rp235x-hal = { version = "0.3", features = ["binary-info", "critical-section-imp
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
panic-halt = "1"
|
panic-halt = "1"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
|
embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7" } # ADC OneShot trait (rp235x-hal ADC)
|
||||||
embedded-hal-bus = "0.2"
|
embedded-hal-bus = "0.2"
|
||||||
mipidsi = "0.9"
|
mipidsi = "0.9"
|
||||||
embedded-graphics = "0.8"
|
embedded-graphics = "0.8"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use embedded_hal::delay::DelayNs;
|
use embedded_hal::delay::DelayNs;
|
||||||
use embedded_hal::digital::OutputPin;
|
use embedded_hal::digital::{InputPin, OutputPin};
|
||||||
|
use embedded_hal::pwm::SetDutyCycle;
|
||||||
|
use embedded_hal_0_2::adc::OneShot;
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
use mipidsi::interface::{Interface, SpiInterface};
|
use mipidsi::interface::{Interface, SpiInterface};
|
||||||
use mipidsi::models::ST7796;
|
use mipidsi::models::ST7796;
|
||||||
|
|
@ -133,31 +135,36 @@ fn main() -> ! {
|
||||||
.init(&mut timer)
|
.init(&mut timer)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// The real metronome screen the host simulator renders (rust/uisim → PNG). Static sample data
|
// ---- Peripheral self-test: buttons (GP15/GP14), joystick (GP26/GP27 ADC), speaker (GP13 PWM).
|
||||||
// for now (no allocator needed — draw_metronome takes borrowed slices); the live track + a
|
// Verifies the inputs + speaker actually work on the device; reads live and draws state.
|
||||||
// moving playhead come once pm-core is linked in.
|
let mut btn_a = pins.gpio15.into_pull_up_input();
|
||||||
let kick = [2u8, 1, 1, 1];
|
let mut btn_b = pins.gpio14.into_pull_up_input();
|
||||||
let snare = [0u8, 1, 0, 1];
|
|
||||||
let hat = [2u8, 1, 1, 1, 1, 1, 1, 1];
|
|
||||||
let ride = [2u8, 0, 1, 0, 1, 0, 1, 0];
|
|
||||||
let cow = [2u8, 1, 1];
|
|
||||||
let lanes = [
|
|
||||||
pm_ui::LaneView { name: "kick", levels: &kick, beats: 4, poly: false, muted: false },
|
|
||||||
pm_ui::LaneView { name: "snare", levels: &snare, beats: 4, poly: false, muted: false },
|
|
||||||
pm_ui::LaneView { name: "hatClosed", levels: &hat, beats: 4, poly: false, muted: false },
|
|
||||||
pm_ui::LaneView { name: "ride", levels: &ride, beats: 4, poly: false, muted: false },
|
|
||||||
pm_ui::LaneView { name: "cowbell", levels: &cow, beats: 3, poly: true, muted: false },
|
|
||||||
];
|
|
||||||
let screen = pm_ui::Screen { name: "Four-on-the-floor", bpm: 128, playing: true, phase: 0.30, lanes: &lanes };
|
|
||||||
pm_ui::draw_metronome(&mut display, &screen).unwrap();
|
|
||||||
|
|
||||||
// Reached the loop → display init + draw completed. Slow 1 Hz blink (vs the solid-ON during
|
let mut adc = hal::adc::Adc::new(pac.ADC, &mut pac.RESETS);
|
||||||
// init above) so "hung in init" / "running" / "reset loop" are distinguishable on the LED.
|
let mut joy_x = hal::adc::AdcPin::new(pins.gpio26).unwrap();
|
||||||
|
let mut joy_y = hal::adc::AdcPin::new(pins.gpio27).unwrap();
|
||||||
|
|
||||||
|
let pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS);
|
||||||
|
let mut spk = pwm_slices.pwm6; // GP13 = PWM slice 6, channel B
|
||||||
|
spk.set_div_int(125);
|
||||||
|
spk.set_top(600); // ~2 kHz click tone
|
||||||
|
spk.enable();
|
||||||
|
spk.channel_b.output_to(pins.gpio13);
|
||||||
|
|
||||||
|
let mut hb = false;
|
||||||
loop {
|
loop {
|
||||||
led.set_high().unwrap();
|
let a = btn_a.is_low().unwrap();
|
||||||
timer.delay_ms(500);
|
let b = btn_b.is_low().unwrap();
|
||||||
led.set_low().unwrap();
|
let x: u16 = adc.read(&mut joy_x).unwrap();
|
||||||
timer.delay_ms(500);
|
let y: u16 = adc.read(&mut joy_y).unwrap();
|
||||||
|
let beep = a || b;
|
||||||
|
let _ = spk.channel_b.set_duty_cycle(if beep { 300 } else { 0 });
|
||||||
|
|
||||||
|
pm_ui::draw_peripheral_test(&mut display, &pm_ui::PeriphState { a, b, x, y, beep }).ok();
|
||||||
|
|
||||||
|
hb = !hb; // LED heartbeat
|
||||||
|
let _ = if hb { led.set_high() } else { led.set_low() };
|
||||||
|
timer.delay_ms(40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,68 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Live state for the peripheral self-test.
|
||||||
|
pub struct PeriphState {
|
||||||
|
pub a: bool,
|
||||||
|
pub b: bool,
|
||||||
|
pub x: u16, // joystick X, 0..4095
|
||||||
|
pub y: u16, // joystick Y, 0..4095
|
||||||
|
pub beep: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peripheral self-test screen: button states, joystick position + values, speaker activity.
|
||||||
|
pub fn draw_peripheral_test<D>(d: &mut D, s: &PeriphState) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = Rgb565>,
|
||||||
|
{
|
||||||
|
let bb = d.bounding_box();
|
||||||
|
let w = bb.size.width as i32;
|
||||||
|
d.clear(BG)?;
|
||||||
|
|
||||||
|
Text::new("PERIPHERAL TEST", Point::new(12, 22), MonoTextStyle::new(&FONT_10X20, TXT)).draw(d)?;
|
||||||
|
|
||||||
|
// buttons
|
||||||
|
let lab = MonoTextStyle::new(&FONT_10X20, Rgb565::BLACK);
|
||||||
|
for (i, (name, on)) in [("A", s.a), ("B", s.b)].iter().enumerate() {
|
||||||
|
let bx = 16 + i as i32 * 96;
|
||||||
|
Rectangle::new(Point::new(bx, 48), Size::new(84, 84))
|
||||||
|
.into_styled(PrimitiveStyle::with_fill(if *on { GREEN } else { PANEL }))
|
||||||
|
.draw(d)?;
|
||||||
|
Text::with_alignment(name, Point::new(bx + 42, 98), lab, Alignment::Center).draw(d)?;
|
||||||
|
}
|
||||||
|
// speaker / beep
|
||||||
|
let beep_sty = MonoTextStyle::new(&FONT_9X18_BOLD, if s.beep { AMBER } else { MUTE });
|
||||||
|
Text::new("SPK", Point::new(216, 78), beep_sty).draw(d)?;
|
||||||
|
if s.beep {
|
||||||
|
Text::new("BEEP", Point::new(216, 100), MonoTextStyle::new(&FONT_6X10, AMBER)).draw(d)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// joystick box + dot
|
||||||
|
let jb_x = 40;
|
||||||
|
let jb_y = 170;
|
||||||
|
let jb = 200;
|
||||||
|
Rectangle::new(Point::new(jb_x, jb_y), Size::new(jb as u32, jb as u32))
|
||||||
|
.into_styled(PrimitiveStyleBuilder::new().stroke_color(GRID).stroke_width(2).fill_color(PANEL).build())
|
||||||
|
.draw(d)?;
|
||||||
|
Line::new(Point::new(jb_x + jb / 2, jb_y), Point::new(jb_x + jb / 2, jb_y + jb)).into_styled(PrimitiveStyle::with_stroke(GRID, 1)).draw(d)?;
|
||||||
|
Line::new(Point::new(jb_x, jb_y + jb / 2), Point::new(jb_x + jb, jb_y + jb / 2)).into_styled(PrimitiveStyle::with_stroke(GRID, 1)).draw(d)?;
|
||||||
|
let dx = jb_x + (s.x as i32 * (jb - 12) / 4095) + 6;
|
||||||
|
let dy = jb_y + (s.y as i32 * (jb - 12) / 4095) + 6;
|
||||||
|
Rectangle::new(Point::new(dx - 6, dy - 6), Size::new(12, 12)).into_styled(PrimitiveStyle::with_fill(CYAN)).draw(d)?;
|
||||||
|
|
||||||
|
// numeric values
|
||||||
|
let vs = MonoTextStyle::new(&FONT_6X10, TXT);
|
||||||
|
let mut nb = [0u8; 12];
|
||||||
|
Text::new("X", Point::new(jb_x, jb_y + jb + 18), vs).draw(d)?;
|
||||||
|
Text::new(fmt_u32(s.x as u32, &mut nb), Point::new(jb_x + 16, jb_y + jb + 18), vs).draw(d)?;
|
||||||
|
let mut nb2 = [0u8; 12];
|
||||||
|
Text::new("Y", Point::new(jb_x + 90, jb_y + jb + 18), vs).draw(d)?;
|
||||||
|
Text::new(fmt_u32(s.y as u32, &mut nb2), Point::new(jb_x + 106, jb_y + jb + 18), vs).draw(d)?;
|
||||||
|
|
||||||
|
Text::with_alignment("press A/B = click, move joystick", Point::new(w / 2, jb_y + jb + 44), vs, Alignment::Center).draw(d)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Bring-up diagnostic pattern (kept for hardware bring-up / fallback).
|
/// Bring-up diagnostic pattern (kept for hardware bring-up / fallback).
|
||||||
pub fn draw_ui<D>(d: &mut D) -> Result<(), D::Error>
|
pub fn draw_ui<D>(d: &mut D) -> Result<(), D::Error>
|
||||||
where
|
where
|
||||||
|
|
|
||||||
41
rust/uisim/src/bin/periphsim.rs
Normal file
41
rust/uisim/src/bin/periphsim.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
//! Render the peripheral-test screen (pm-ui) with mock input state → PNG, to check the layout
|
||||||
|
//! before flashing. `cargo run --bin periphsim`.
|
||||||
|
|
||||||
|
use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
|
||||||
|
use pm_ui::PeriphState;
|
||||||
|
|
||||||
|
const W: u32 = 320;
|
||||||
|
const H: u32 = 480;
|
||||||
|
|
||||||
|
struct Fb {
|
||||||
|
px: Vec<Rgb565>,
|
||||||
|
}
|
||||||
|
impl DrawTarget for Fb {
|
||||||
|
type Color = Rgb565;
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
fn draw_iter<I: IntoIterator<Item = Pixel<Rgb565>>>(&mut self, pixels: I) -> Result<(), Self::Error> {
|
||||||
|
for Pixel(p, c) in pixels {
|
||||||
|
if p.x >= 0 && p.y >= 0 && (p.x as u32) < W && (p.y as u32) < H {
|
||||||
|
self.px[(p.y as u32 * W + p.x as u32) as usize] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl OriginDimensions for Fb {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
Size::new(W, H)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut fb = Fb { px: vec![Rgb565::BLACK; (W * H) as usize] };
|
||||||
|
let st = PeriphState { a: true, b: false, x: 2900, y: 1100, beep: true };
|
||||||
|
pm_ui::draw_peripheral_test(&mut fb, &st).unwrap();
|
||||||
|
let img = image::RgbImage::from_fn(W, H, |x, y| {
|
||||||
|
let c = fb.px[(y * W + x) as usize];
|
||||||
|
image::Rgb([(c.r() << 3) | (c.r() >> 2), (c.g() << 2) | (c.g() >> 4), (c.b() << 3) | (c.b() >> 2)])
|
||||||
|
});
|
||||||
|
img.save("periph.png").unwrap();
|
||||||
|
println!("wrote periph.png");
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue