feat: debug probe support

This commit is contained in:
dogeystamp 2024-10-13 21:41:10 -04:00
parent 6ba735bd2c
commit 29cf1459d8
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
10 changed files with 86 additions and 33 deletions

View File

@ -26,7 +26,7 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))'] [target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "elf2uf2-rs --deploy --serial" runner = "probe-rs run --chip RP2040"
[build] [build]
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+

2
Cargo.lock generated
View File

@ -772,7 +772,7 @@ dependencies = [
[[package]] [[package]]
name = "geode_piano" name = "geode_piano"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"byte-slice-cast 1.2.2", "byte-slice-cast 1.2.2",
"cortex-m", "cortex-m",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "geode_piano" name = "geode_piano"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-only" license = "GPL-3.0-only"

View File

@ -21,11 +21,20 @@ https://github.com/dogeystamp/geode-piano/assets/61116261/2a5f732a-5d3e-4b5f-946
## installation ## installation
- Follow the materials and wiring sections below.
- Clone project. - Clone project.
- Go into project directory. - Go into project directory.
- Install the `thumbv6m-none-eabi` target using rustup. - Install the `thumbv6m-none-eabi` target using rustup.
You now have two choices for installation.
You can either use a debug probe such as the [Raspberry Pi Debug Probe](https://www.raspberrypi.com/products/debug-probe/), or install over USB.
The debug probe is more convenient
for iterating quickly,
but is harder to set up.
### no debug probe
- Install `elf2uf2-rs`. - Install `elf2uf2-rs`.
- Follow the materials and wiring sections below. - Set `runner = "elf2uf2-rs --deploy --serial"` in `.cargo/config`.
- Set the Pico into BOOTSEL mode: - Set the Pico into BOOTSEL mode:
- Hold down the BOOTSEL button on the Pico. Keep holding it during the following step. - Hold down the BOOTSEL button on the Pico. Keep holding it during the following step.
- Reset the Pico: either replug the power, or short Pin 30 (RUN) to GND through a button or wire. - Reset the Pico: either replug the power, or short Pin 30 (RUN) to GND through a button or wire.
@ -33,8 +42,28 @@ https://github.com/dogeystamp/geode-piano/assets/61116261/2a5f732a-5d3e-4b5f-946
- `cargo run --release --bin [binary]` - `cargo run --release --bin [binary]`
- `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them. - `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them.
### with debug probe
- Install `probe-rs`.
- Follow the wiring instructions in the [Pico Getting Started Guide](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf), at _Appendix A: Using Picoprobe_ in the Picoprobe Wiring section.
You only need to wire GND, SWCLK and SWDIO.
- If you are using a second Pico as a debug probe,
you must use a second USB data wire to communicate with both the debug probe and the geode-piano board.
- `cargo run --release --bin [binary]`
- `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them.
If you are missing dependencies, consult [Alex Wilson's guide](https://www.alexdwilson.dev/learning-in-public/how-to-program-a-raspberry-pi-pico) on Rust Pico development. If you are missing dependencies, consult [Alex Wilson's guide](https://www.alexdwilson.dev/learning-in-public/how-to-program-a-raspberry-pi-pico) on Rust Pico development.
Note that essential program output (e.g. pin scanner output)
goes through Embassy's USB serial logger rather than the defmt log that the debug probe has access to.
To see this output, install picocom and run
```
sudo picocom -b 115200 /dev/ttyACM0
```
changing `ttyACM0` to whichever serial device your Pico may be using.
## usage ## usage
The intended usage is to first plug the device into the piano keyboard, then use the `pin_scanner` binary to The intended usage is to first plug the device into the piano keyboard, then use the `pin_scanner` binary to
@ -60,8 +89,7 @@ Copy the keymap, as well as the `col_pins` and `row_pins` generated into this.
Once the keymap is done, run the `piano_firmware` binary and plug the USB cable to your computer. Once the keymap is done, run the `piano_firmware` binary and plug the USB cable to your computer.
Open up a DAW and select Geode-Piano as a MIDI input device. Open up a DAW and select Geode-Piano as a MIDI input device.
I use LMMS with the [Maestro Concert Grand v2](https://www.linuxsampler.org/instruments.html) samples. If you don't need a full DAW, you can use `qsampler` with, for example, the [Maestro Concert Grand v2](https://www.linuxsampler.org/instruments.html) samples.
If you don't need all of LMMS's features, `qsampler` can work too.
You should be able to play now. You should be able to play now.
Optionally, you can also hook up a speaker to the computer for better sound quality. Optionally, you can also hook up a speaker to the computer for better sound quality.
@ -78,6 +106,10 @@ Optionally, you can also hook up a speaker to the computer for better sound qual
- Many jumper cables (40 male-to-female, ? male-to-male) - Many jumper cables (40 male-to-female, ? male-to-male)
- Two alligator clips - Two alligator clips
- Breadboard - Breadboard
- (optional) 1 debug probe, could be a second Raspberry Pi Pico
- and necessary wires to use it, for example
- 3-pin JST-SH cable
- data micro-USB to USB cable
For the ribbon cable sockets, open up your piano and find the ribbon cables. For the ribbon cable sockets, open up your piano and find the ribbon cables.
Unplug them from the PCB, and count the amount of pins on them. Unplug them from the PCB, and count the amount of pins on them.

View File

@ -460,7 +460,7 @@ async fn main(_spawner: Spawner) {
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).await; unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).await;
unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await; unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await;
log::debug!("main: init i2c"); defmt::debug!("main: init i2c");
let sda = p.PIN_16; let sda = p.PIN_16;
let scl = p.PIN_17; let scl = p.PIN_17;
@ -469,7 +469,7 @@ async fn main(_spawner: Spawner) {
i2c_config.frequency = freq; i2c_config.frequency = freq;
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config); let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
log::debug!("main: starting transparent pin driver"); defmt::debug!("main: starting transparent pin driver");
let pin_driver = unwrap(pins::TransparentPins::new( let pin_driver = unwrap(pins::TransparentPins::new(
i2c, i2c,
[0x20, 0x27], [0x20, 0x27],
@ -481,15 +481,15 @@ async fn main(_spawner: Spawner) {
)) ))
.await; .await;
log::info!("main: starting piano task"); defmt::info!("main: starting piano task");
_spawner.spawn(piano_task(pin_driver)).unwrap(); _spawner.spawn(piano_task(pin_driver)).unwrap();
log::info!("main: starting sustain pedal task"); defmt::info!("main: starting sustain pedal task");
_spawner _spawner
.spawn(matrix::pedal( .spawn(matrix::pedal(
midi::Controller::SustainPedal, midi::Controller::SustainPedal,
p.PIN_8.into(), p.PIN_8.into(),
false, true,
)) ))
.unwrap(); .unwrap();
} }

View File

@ -13,11 +13,13 @@ pub mod midi;
pub mod pins; pub mod pins;
pub mod usb; pub mod usb;
/// Unwrap, but log before panic /// Wrapper over unwrap.
/// ///
/// Waits a bit to give time for the logger to flush before halting. /// Logs over usb instead of instantly panicking.
/// This exists because I do not own a debug probe 😎 /// If you don't have a debug probe, comment out the first line.
pub async fn unwrap<T, E: core::fmt::Debug>(res: Result<T, E>) -> T { pub async fn unwrap<T, E: core::fmt::Debug>(res: Result<T, E>) -> T {
return res.unwrap();
#[allow(unreachable_code)]
match res { match res {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {

View File

@ -19,10 +19,10 @@ pub async fn pedal(pedal: midi::Controller, pin: gpio::AnyPin, norm_open: bool)
let off_val = if norm_open { 0 } else { 64 }; let off_val = if norm_open { 0 } else { 64 };
inp.wait_for_low().await; inp.wait_for_low().await;
chan.controller(pedal, on_val).await; chan.controller(pedal, on_val).await;
log::debug!("{pedal:?} set to {on_val}"); defmt::debug!("{} set to {}", pedal, on_val);
inp.wait_for_high().await; inp.wait_for_high().await;
chan.controller(pedal, off_val).await; chan.controller(pedal, off_val).await;
log::debug!("{pedal:?} set to {off_val}"); defmt::debug!("{} set to {}", pedal, off_val);
} }
} }
@ -73,18 +73,27 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
let mut note_first: [Option<Instant>; MAX_NOTES] = [None; MAX_NOTES]; let mut note_first: [Option<Instant>; MAX_NOTES] = [None; MAX_NOTES];
let mut counter = 0; let mut counter = 0;
let mut prof_col_idx = 0;
defmt::debug!("using {} columns", N_COLS);
loop { loop {
let profile: bool = counter == 0;
counter += 1; counter += 1;
counter %= 50; counter %= 50;
let profile = counter == 0;
let prof_start = Instant::now(); let prof_start = Instant::now();
let mut prof_time_last_col = prof_start;
let mut prof_dur_col = Duration::from_ticks(0);
for (i, col) in self.col_pins.iter().enumerate() { for (i, col) in self.col_pins.iter().enumerate() {
unwrap(pin_driver.set_output(*col)).await; unwrap(pin_driver.set_output(*col)).await;
let input = unwrap(pin_driver.read_all()).await; let input = unwrap(pin_driver.read_all()).await;
unwrap(pin_driver.set_input(*col)).await; unwrap(pin_driver.set_input(*col)).await;
if profile && i == prof_col_idx {
prof_dur_col = prof_time_last_col.elapsed();
}
// values that are logical ON // values that are logical ON
let mask = input ^ (((1 << pin_driver.n_usable_pins()) - 1) ^ (1 << col)); let mask = input ^ (((1 << pin_driver.n_usable_pins()) - 1) ^ (1 << col));
for (j, row) in self.row_pins.iter().enumerate() { for (j, row) in self.row_pins.iter().enumerate() {
@ -111,7 +120,7 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
} else { } else {
(127 - min(dur, 250) / 5 - 70) as u8 (127 - min(dur, 250) / 5 - 70) as u8
}; };
log::debug!("{note:?} velocity {velocity} from dur {dur}ms"); defmt::debug!("{} velocity {} from dur {}ms", note, velocity, dur);
note_on[note as usize] = true; note_on[note as usize] = true;
chan.note_on(note, velocity).await; chan.note_on(note, velocity).await;
} }
@ -134,11 +143,20 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
midi::KeyAction::NOP => {} midi::KeyAction::NOP => {}
} }
} }
prof_time_last_col = Instant::now();
}
if profile {
let time_total = prof_start.elapsed();
prof_col_idx += 1;
prof_col_idx %= N_COLS;
defmt::debug!(
"profile: total scan took {}us, {}-th column {}us",
time_total.as_micros(),
prof_col_idx,
prof_dur_col.as_micros()
);
} }
if profile {
log::trace!("profile: scan took {}ms", prof_start.elapsed().as_millis())
}
ticker.next().await; ticker.next().await;
} }
} }

View File

@ -23,6 +23,7 @@
use embassy_rp::usb::{Driver, Instance}; use embassy_rp::usb::{Driver, Instance};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel}; use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_usb::{class::midi::MidiClass, driver::EndpointError}; use embassy_usb::{class::midi::MidiClass, driver::EndpointError};
use defmt::Format;
//////////////////////////////// ////////////////////////////////
//////////////////////////////// ////////////////////////////////
@ -42,7 +43,7 @@ impl NoteMsg {
} }
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Format)]
pub enum Controller { pub enum Controller {
SustainPedal = 64, SustainPedal = 64,
} }
@ -86,7 +87,7 @@ impl MidiMsg {
/// Note identifiers /// Note identifiers
/// ///
/// See src/midi/note_def.py for how this is generated /// See src/midi/note_def.py for how this is generated
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Format)]
pub enum Note { pub enum Note {
A0 = 21, A0 = 21,
AS0 = 22, AS0 = 22,
@ -225,13 +226,13 @@ pub async fn midi_session<'d, T: Instance + 'd>(
let status: u8 = (if note.on { 0b1001_0000 } else { 0b1000_0000 }) | msg.channel; let status: u8 = (if note.on { 0b1001_0000 } else { 0b1000_0000 }) | msg.channel;
// i'll be honest i have no idea where the first number here comes from // i'll be honest i have no idea where the first number here comes from
let packet = [8, status, note.note as u8, note.velocity]; let packet = [8, status, note.note as u8, note.velocity];
log::trace!("midi_session: note {:?}", packet); defmt::trace!("midi_session: note {:?}", packet);
midi.write_packet(&packet).await? midi.write_packet(&packet).await?
} }
MsgType::Controller(ctrl) => { MsgType::Controller(ctrl) => {
let status: u8 = (0b1011_0000) | msg.channel; let status: u8 = (0b1011_0000) | msg.channel;
let packet = [8, status, ctrl.controller as u8, ctrl.value]; let packet = [8, status, ctrl.controller as u8, ctrl.value];
log::trace!("midi_session: control {:?}", packet); defmt::trace!("midi_session: control {:?}", packet);
midi.write_packet(&packet).await? midi.write_packet(&packet).await?
} }
} }

View File

@ -209,7 +209,7 @@ impl TransparentPins {
ret.pins.n_usable = ret.usable_extended_pins + N_REGULAR_PINS; ret.pins.n_usable = ret.usable_extended_pins + N_REGULAR_PINS;
} }
ret.disable_unsafe_pins = true; ret.disable_unsafe_pins = true;
log::debug!("TransparentPins: {} usable pins", ret.pins.n_usable) defmt::debug!("TransparentPins: {} usable pins", ret.pins.n_usable)
} }
Ok(ret) Ok(ret)
} }
@ -221,7 +221,7 @@ impl TransparentPins {
// ports are flipped from what it should be // ports are flipped from what it should be
let port_a = (val & (0xff00)) >> 8; let port_a = (val & (0xff00)) >> 8;
let port_b = val & (0x00ff); let port_b = val & (0x00ff);
log::trace!("raw_to_usable: raw {val:016b} a {port_a:08b} b {port_b:08b}"); defmt::trace!("raw_to_usable: raw {:016b} a {:08b} b {:08b}", val, port_a, port_b);
(port_a & 0x7f) | ((port_b & 0x7f) << 7) (port_a & 0x7f) | ((port_b & 0x7f) << 7)
} else { } else {
val val
@ -239,7 +239,7 @@ impl TransparentPins {
/// Write all pins from a single 64-bit value. /// Write all pins from a single 64-bit value.
pub fn write_all(&mut self, val: u64) -> Result<(), Error> { pub fn write_all(&mut self, val: u64) -> Result<(), Error> {
log::trace!("write_all: called with val {}", val); defmt::trace!("write_all: called with val {}", val);
for i in 0..N_PIN_EXTENDERS { for i in 0..N_PIN_EXTENDERS {
// value for this extender // value for this extender
let ext_val = (val >> (i * self.usable_pins_per_extender)) let ext_val = (val >> (i * self.usable_pins_per_extender))
@ -259,7 +259,7 @@ impl TransparentPins {
/// Read all pins into a single 64-bit value. /// Read all pins into a single 64-bit value.
pub fn read_all(&mut self) -> Result<u64, Error> { pub fn read_all(&mut self) -> Result<u64, Error> {
log::trace!("read_all: called"); defmt::trace!("read_all: called");
let mut ret: u64 = 0; let mut ret: u64 = 0;
for i in 0..N_PIN_EXTENDERS { for i in 0..N_PIN_EXTENDERS {
let mut ext = extender!(self, i)?; let mut ext = extender!(self, i)?;

View File

@ -47,10 +47,10 @@ pub async fn usb_task(
log_level: log::LevelFilter, log_level: log::LevelFilter,
) { ) {
// Create embassy-usb Config // Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe); let mut config = Config::new(0xdead, 0xbeef);
config.manufacturer = Some("dogeystamp"); config.manufacturer = Some("dogeystamp");
config.product = Some("Geode-Piano MIDI keyboard"); config.product = Some("Geode-Piano MIDI keyboard");
config.serial_number = Some("alpha-12345"); config.serial_number = Some("0.2.0");
config.max_power = 100; config.max_power = 100;
config.max_packet_size_0 = 64; config.max_packet_size_0 = 64;
@ -97,9 +97,9 @@ pub async fn usb_task(
let midi_fut = async { let midi_fut = async {
loop { loop {
midi_class.wait_connection().await; midi_class.wait_connection().await;
log::info!("Connected"); defmt::info!("Connected");
let _ = midi_session(&mut midi_class).await; let _ = midi_session(&mut midi_class).await;
log::info!("Disconnected"); defmt::info!("Disconnected");
} }
}; };