From 50c872460737e66db48a075721c55c5d5504d4de Mon Sep 17 00:00:00 2001 From: nils Date: Fri, 21 Aug 2026 09:34:59 +0200 Subject: [PATCH] Strip non-essential comments Keep license/SPDX headers, public-API doc comments, spec/hardware references (UUIDs, APA102 frame, MIDI byte semantics), and genuine 'why' notes (BOOTSEL edge-trigger, adv/scan split, borrow-checker free functions). Remove code-restating narration, section dividers, milestone/porting asides, and completed TODOs. No code logic changed. --- src/codec.rs | 7 +++--- src/keypad.rs | 17 ++++++------- src/main.rs | 62 +++++++++------------------------------------- src/midi_keypad.rs | 11 ++++---- src/octave.rs | 13 +++++----- 5 files changed, 33 insertions(+), 77 deletions(-) diff --git a/src/codec.rs b/src/codec.rs index a93909d..0214877 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -135,7 +135,7 @@ impl Ring { } } -/// Encoder state for one connection (mirrors `to_ble_midi_stream_t`). +/// Encoder state for one connection. #[derive(Clone, Copy, Default)] struct ToBleStream { mes: BleMidiMessage, @@ -174,8 +174,7 @@ impl Codec { self.ble_mtu = ble_mtu; } - /// Update MTU, capped at the packet buffer size (matches the C `update_mtu` - /// which does `MIN(ble_mtu, sizeof(pending_ble_pkt.pkt))`). + /// Update MTU, capped at the packet buffer size. pub fn update_mtu(&mut self, ble_mtu: usize) { self.ble_mtu = core::cmp::min(ble_mtu, MAX_BLE_MIDI_PACKET); } @@ -440,7 +439,7 @@ impl Codec { return ndecoded; } } else { - // not enough data; leave for next? C returns. + // not enough data for the full message. return ndecoded; } } else { diff --git a/src/keypad.rs b/src/keypad.rs index c86d40e..86cd5da 100644 --- a/src/keypad.rs +++ b/src/keypad.rs @@ -1,4 +1,4 @@ -//! Pimoroni Pico RGB Keypad driver — Rust port of `pico_rgb_keypad.cpp`. +//! Pimoroni Pico RGB Keypad driver. //! //! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later //! This file is part of pico-pi-midi. See LICENSE for the full notice. @@ -6,11 +6,10 @@ //! Hardware: 16 APA102-style RGB LEDs driven over SPI (CS=17, SCK=18, MOSI=19) //! and a 16-bit button state read over I2C from a GPIO expander at address //! 0x20 (SDA=4, SCL=5). Reading register 0 returns two bytes (little-endian -//! button bitmap); the C driver inverts them so a pressed pad reads as 1. +//! button bitmap); the value is inverted so a pressed pad reads as 1. //! //! APA102 frame: 4 start bytes (0x00), then per-LED 4 bytes -//! (0xE0|brightness, B, G, R), then a 4-byte end frame (0xFF). Total 72 bytes, -//! matching the C buffer layout `[start(4)][led0(4)]..[led15(4)][end(4)]`. +//! (0xE0|brightness, B, G, R), then a 4-byte end frame (0xFF). Total 72 bytes. use embassy_rp::gpio::Output; use embassy_rp::i2c::{self, Blocking as I2cBlocking}; @@ -20,7 +19,6 @@ pub const NUM_PADS: usize = 16; const KEYPAD_ADDRESS: u8 = 0x20; /// 5-bit global brightness (0..=31), stored in each LED's frame header. -/// `set_brightness(1.0)` in the C driver -> 0b11111. const BRIGHTNESS: u8 = 0b11111; pub struct RgbKeypad { @@ -68,8 +66,8 @@ impl RgbKeypad { self.cs.set_high(); } - /// Set an LED by linear index `i` (0..16) to `(r, g, b)`. Mirrors the C - /// `illuminate(i, r, g, b)`: frame byte order is brightness,B,G,R. + /// Set an LED by linear index `i` (0..16) to `(r, g, b)`. Frame byte order + /// is brightness, B, G, R. pub fn illuminate(&mut self, i: usize, r: u8, g: u8, b: u8) { if i >= NUM_PADS { return; @@ -82,14 +80,13 @@ impl RgbKeypad { } /// Read the 16-bit button state bitmap. Bit `i` is 1 when pad `i` is - /// pressed (the raw expander value is inverted, matching the C driver). + /// pressed (the raw expander value is inverted). pub fn get_button_states(&mut self) -> u16 { let mut buf = [0u8; 2]; - // Write register 0, then read 2 bytes (repeated-start, like the C code). + // Write register 0, then read 2 bytes (repeated-start). let _ = self .i2c .blocking_write_read(KEYPAD_ADDRESS, &[0x00], &mut buf); - // C returns ~((buf[0]) | (buf[1] << 8)) !(u16::from(buf[0]) | (u16::from(buf[1]) << 8)) } } diff --git a/src/main.rs b/src/main.rs index 1508209..6e3711d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! RGB MIDI Keypad — Rust port for Raspberry Pi Pico W (RP2040 + CYW43439). +//! RGB MIDI Keypad for Raspberry Pi Pico W (RP2040 + CYW43439). //! //! Copyright (C) 2026 Nils //! @@ -17,17 +17,8 @@ //! //! SPDX-License-Identifier: GPL-3.0-or-later //! -//! Wires the Pimoroni RGB Keypad (16 pads + APA102 LEDs) to a BLE-MIDI 1.0 -//! GATT service so pad presses send Note On/Off over Bluetooth MIDI and -//! incoming Note messages light the pads. -//! -//! Concurrency: -//! - `cyw43_task` : drives the CYW43 controller (WiFi+BT firmware). -//! - `led_task` : blinks the onboard LED as a heartbeat. -//! - `keypad_task` : polls pads, sends Note On/Off on `OUTBOUND`, lights pads -//! from `INBOUND`. -//! - main (BLE) : runs the trouble host; advertises, accepts a connection, -//! and pumps MIDI in both directions through the codec. +//! Pimoroni RGB Keypad (16 pads) wired to a BLE-MIDI 1.0 GATT service: pad +//! presses send Note On/Off over Bluetooth MIDI, incoming Notes light pads. #![no_std] #![no_main] @@ -80,11 +71,8 @@ static OUTBOUND: Channel = Channel::new(); /// BLE -> keypad: each entry is a 3-byte channel message to light a pad. static INBOUND: Channel = Channel::new(); -// ---- BLE-MIDI 1.0 GATT service definition -------------------------------- -// -// Service: 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 (MIDI Service) -// Char: 7772E5DB-3868-4112-A1A9-F2669D106BF3 (MIDI I/O) -// Props: read | write-without-response | notify +// BLE-MIDI 1.0: service 03B80E5A-…, characteristic 7772E5DB-… +// (read | write-without-response | notify). #[gatt_server] struct Server { midi_service: MidiService, @@ -96,8 +84,6 @@ struct MidiService { midi_io: (), } -// ---- CYW43 runner + heartbeat tasks -------------------------------------- - #[embassy_executor::task] async fn cyw43_task( runner: cyw43::Runner< @@ -119,19 +105,15 @@ async fn led_task(mut control: cyw43::Control<'static>) { } } -/// Poll the keypad: send Note On/Off for changed pads, light pads from inbound -/// MIDI, repeat every 10ms (matching the C main loop cadence). #[embassy_executor::task] async fn keypad_task(mut keypad: RgbKeypad, mut bootsel: embassy_rp::Peri<'static, embassy_rp::peripherals::BOOTSEL>) { - // Startup blink: all LEDs white, then off — a visible "main is alive and - // the keypad/SPI driver works" signal that does not depend on the radio. for i in 0..keypad::NUM_PADS { keypad.illuminate(i, 64, 64, 64); } keypad.update(); Timer::after_millis(400).await; - let mut midi = MidiKeypad::new(&mut keypad, 48); // note offset C3; paints underlay + let mut midi = MidiKeypad::new(&mut keypad, 48); let mut out_buf: Vec = Vec::new(); let outbound = OUTBOUND.sender(); let inbound = INBOUND.receiver(); @@ -139,11 +121,6 @@ async fn keypad_task(mut keypad: RgbKeypad, mut bootsel: embassy_rp::Peri<'stati let mut bootsel_held = false; loop { - // 1. read pads -> Note On/Off. Each message is routed two ways, matching - // the C firmware's connect_ports("MidiKeypad","MidiKeypad") local - // feedback + connect_ports("MidiKeypad","BluetoothMidi"): - // (a) local: light the pressed pad immediately, - // (b) outbound: send to the BLE side to notify the host. out_buf.clear(); midi.update(&mut keypad, &mut out_buf); for m in out_buf.iter() { @@ -152,14 +129,12 @@ async fn keypad_task(mut keypad: RgbKeypad, mut bootsel: embassy_rp::Peri<'stati } out_buf.clear(); - // 2. light pads from inbound MIDI (host -> keypad) while let Ok(msg) = inbound.try_receive() { midi.receive_midi_message(&mut keypad, &msg); } - // 3. BOOTSEL: cycle octave +12 (wrap at 120) and flash the octave digit. - // Edge-triggered so one press = one step (C firmware re-checked every - // loop while held, which multi-triggered; we debounce to a press edge). + // Edge-triggered: one BOOTSEL press = one octave step (the C firmware + // re-checked every loop while held and multi-triggered). let pressed = embassy_rp::bootsel::is_bootsel_pressed(bootsel.reborrow()); if pressed && !bootsel_held { note_offset = (note_offset + 12) % 120; @@ -172,8 +147,6 @@ async fn keypad_task(mut keypad: RgbKeypad, mut bootsel: embassy_rp::Peri<'stati } } -// ---- TrouBLE host runner -------------------------------------------------- - async fn ble_task(mut runner: Runner<'_, C, P>) { loop { if let Err(e) = runner.run().await { @@ -182,17 +155,14 @@ async fn ble_task(mut runner: Runner<'_, C, P>) { } } -// ---- Advertising ---------------------------------------------------------- - async fn advertise<'a, 'b, C: Controller>( peripheral: &mut Peripheral<'a, C, DefaultPacketPool>, server: &'b Server<'a>, ) -> Result, BleHostError> { - // adv_data: Flags + MIDI service UUID (so MIDI hosts auto-discover). - // scan_data: device name (won't fit in adv_data alongside the 128-bit UUID). let mut advertiser_data = [0u8; 31]; let mut scan_response = [0u8; 31]; - // MIDI Service UUID 03B80E5A-EDE8-4B33-A751-6CE34EC4C700, little-endian. + // MIDI Service UUID 03B80E5A-EDE8-4B33-A751-6CE34EC4C700, little-endian. Put + // the UUID in adv_data and the name in scan_data — they don't both fit in 31B. let midi_service_uuid: [u8; 16] = [ 0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7, 0x33, 0x4b, 0xe8, 0xed, 0x5a, 0x0e, 0xb8, 0x03, @@ -224,8 +194,6 @@ async fn advertise<'a, 'b, C: Controller>( Ok(conn) } -// ---- Connection: pump MIDI both ways ------------------------------------- - async fn run_connection<'a, 'b, P: PacketPool>( conn: &GattConnection<'a, 'b, P>, server: &'b Server<'a>, @@ -247,7 +215,6 @@ async fn run_connection<'a, 'b, P: PacketPool>( let reply = match event { GattEvent::Write(e) => { if e.handle() == midi_handle { - // Copy the written bytes out, then decode. let mut buf = [0u8; codec::MAX_BLE_MIDI_PACKET]; let mut len = 0usize; e.with_data(|_offset, data| { @@ -273,7 +240,6 @@ async fn run_connection<'a, 'b, P: PacketPool>( _ => {} }, Either::Second(msg) => { - // Outbound Note On/Off: stamp + encode + notify. let now = Instant::now().as_millis() as u16; let (_, ready) = codec.push_midi(&msg, now); if ready { @@ -293,12 +259,12 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); info!("[boot] embassy_rp initialised"); - // ---- USB serial logger (routes log::info!/error! to the serial port) - let driver = Driver::new(p.USB, Irqs); let _ = spawner.spawn(unwrap!(logger_task(driver))); info!("[boot] usb logger task spawned"); - // ---- Keypad task FIRST (I2C0 + SPI0; independent of the radio) ------ + // Spawned before cyw43 init so the keypad stays responsive even if the + // radio bring-up stalls. Pins: SPI0 (SCK=18, MOSI=19, CS=17), I2C0 (SCL=5, SDA=4). let mut kp_spi_cfg = embassy_rp::spi::Config::default(); kp_spi_cfg.frequency = 4_000_000; let kp_spi = embassy_rp::spi::Spi::new_blocking_txonly(p.SPI0, p.PIN_18, p.PIN_19, kp_spi_cfg); @@ -312,13 +278,11 @@ async fn main(spawner: Spawner) { let _ = spawner.spawn(unwrap!(keypad_task(keypad, p.BOOTSEL))); info!("[boot] keypad task spawned"); - // ---- CYW43 firmware blobs (committed under cyw43-firmware/) ----------- let fw = cyw43::aligned_bytes!("../cyw43-firmware/43439A0.bin"); let clm = cyw43::aligned_bytes!("../cyw43-firmware/43439A0_clm.bin"); let btfw = cyw43::aligned_bytes!("../cyw43-firmware/43439A0_btfw.bin"); let nvram = cyw43::aligned_bytes!("../cyw43-firmware/nvram_rp2040.bin"); - // ---- CYW43 PIO-SPI bus (Pico W wiring) ------------------------------- let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); @@ -345,7 +309,6 @@ async fn main(spawner: Spawner) { let _ = spawner.spawn(unwrap!(led_task(control))); - // ---- TrouBLE BLE host ------------------------------------------------- let controller: ExternalController<_, 10> = ExternalController::new(bt_device); let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); log::info!("[ble] controller wrapped, address set"); @@ -379,7 +342,6 @@ async fn main(spawner: Spawner) { match advertise(&mut peripheral, &server).await { Ok(conn) => { log::info!("[ble] connected"); - // Fresh codec state per connection. codec = codec::Codec::new(); run_connection(&conn, &server, &mut codec, &outbound, &inbound).await; } diff --git a/src/midi_keypad.rs b/src/midi_keypad.rs index 17614b2..f3e969f 100644 --- a/src/midi_keypad.rs +++ b/src/midi_keypad.rs @@ -1,4 +1,4 @@ -//! MIDI keypad state machine — Rust port of `MidiKeypad.cpp`. +//! MIDI keypad state machine. //! //! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later //! This file is part of pico-pi-midi. See LICENSE for the full notice. @@ -21,7 +21,7 @@ pub struct MidiOut { } /// Flip a 4x4 grid index so that physical pad 0 (top-left) maps to the -/// bottom-left note, matching the C `transform_index`. +/// bottom-left note. fn transform_index(i: usize) -> usize { let row = i / 4; let column = i % 4; @@ -106,8 +106,7 @@ impl MidiKeypad { } /// Handle an incoming 3-byte MIDI channel message, lighting the matching - /// pad if it falls in the keypad's note range. Mirrors the C - /// `receive_midi_message` (with the duplicated Note-On block de-duplicated). + /// pad if it falls in the keypad's note range. pub fn receive_midi_message(&mut self, keypad: &mut RgbKeypad, message: &[u8]) { if message.len() < 3 || self.overlay_active { return; @@ -180,9 +179,9 @@ impl MidiKeypad { for i in 0..NUM_PADS { let note = (self.note_offset.wrapping_add(i as u8)) % 12; self.underlay_colors[i] = match note { - 0 => [5, 1, 5], // root (purple) + 0 => [5, 1, 5], // root (purple) 1 | 3 | 6 | 8 | 10 => [0, 0, 0], // black keys (dim) - _ => [1, 1, 10], // white keys (blue) + _ => [1, 1, 10], // white keys (blue) }; } self.update_underlay(keypad); diff --git a/src/octave.rs b/src/octave.rs index fdde1da..5839782 100644 --- a/src/octave.rs +++ b/src/octave.rs @@ -1,16 +1,16 @@ -//! Octave display — Rust port of the octave bitmaps + `display_active_octave` -//! from `main.cpp`. On a BOOTSEL press, the active octave is shown as a digit -//! on the 4x4 keypad grid for 500ms via the keypad overlay, then cleared. +//! Octave display. //! //! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later //! This file is part of pico-pi-midi. See LICENSE for the full notice. +//! +//! On a BOOTSEL press, the active octave is shown as a digit on the 4x4 keypad +//! grid for 500ms via the keypad overlay, then cleared. use crate::keypad::{RgbKeypad, NUM_PADS}; use crate::midi_keypad::MidiKeypad; /// 4x4 bitmaps for octaves -1..=9. Each row is 4 bits (MSB = leftmost column), /// rows ordered top-to-bottom. Index 0 = octave -1, index k = octave k-1. -/// (Direct copy of the C `digit_bitmaps[11][4]`.) const DIGIT_BITMAPS: [[u8; 4]; 11] = [ [0b0001, 0b0001, 0b1101, 0b0001], // -1 [0b0010, 0b0101, 0b0101, 0b0010], // 0 @@ -26,15 +26,14 @@ const DIGIT_BITMAPS: [[u8; 4]; 11] = [ ]; /// Paint a 4x4 bitmap onto the keypad overlay (white where the bit is set). -/// Mirrors the C `display_bitmap`: row 0 is top, and the grid is written so -/// that physical row (3-row) is at the top — matching `transform_index`. +/// Row 0 is top; the grid is written so physical row (3-row) is at the top, +/// matching `transform_index`. fn display_bitmap(keypad: &mut RgbKeypad, bitmap: &[u8; 4]) { for row in 0..4 { let row_data = bitmap[row]; for col in 0..4 { let on = row_data & (1 << (3 - col)) != 0; let c = if on { 255 } else { 0 }; - // C: set_overlay_led((3 - row) * 4 + col, ...) let pad_index = (3 - row) * 4 + col; keypad.illuminate(pad_index, c, c, c); }