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.
This commit is contained in:
+3
-4
@@ -135,7 +135,7 @@ impl<T: Copy + Default, const N: usize> Ring<T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
||||
+7
-10
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
+12
-50
@@ -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<CriticalSectionRawMutex, [u8; 3], 16> = Channel::new();
|
||||
/// BLE -> keypad: each entry is a 3-byte channel message to light a pad.
|
||||
static INBOUND: Channel<CriticalSectionRawMutex, [u8; 3], 16> = 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<MidiOut, 16> = 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<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
|
||||
loop {
|
||||
if let Err(e) = runner.run().await {
|
||||
@@ -182,17 +155,14 @@ async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Advertising ----------------------------------------------------------
|
||||
|
||||
async fn advertise<'a, 'b, C: Controller>(
|
||||
peripheral: &mut Peripheral<'a, C, DefaultPacketPool>,
|
||||
server: &'b Server<'a>,
|
||||
) -> Result<GattConnection<'a, 'b, DefaultPacketPool>, BleHostError<C::Error>> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
+3
-4
@@ -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;
|
||||
|
||||
+6
-7
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user