feat: debug probe support
This commit is contained in:
parent
6ba735bd2c
commit
29cf1459d8
@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
runner = "elf2uf2-rs --deploy --serial"
|
||||
runner = "probe-rs run --chip RP2040"
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -772,7 +772,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "geode_piano"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"byte-slice-cast 1.2.2",
|
||||
"cortex-m",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "geode_piano"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
|
38
README.md
38
README.md
@ -21,11 +21,20 @@ https://github.com/dogeystamp/geode-piano/assets/61116261/2a5f732a-5d3e-4b5f-946
|
||||
|
||||
## installation
|
||||
|
||||
- Follow the materials and wiring sections below.
|
||||
- Clone project.
|
||||
- Go into project directory.
|
||||
- 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`.
|
||||
- Follow the materials and wiring sections below.
|
||||
- Set `runner = "elf2uf2-rs --deploy --serial"` in `.cargo/config`.
|
||||
- Set the Pico into BOOTSEL mode:
|
||||
- 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.
|
||||
@ -33,8 +42,28 @@ https://github.com/dogeystamp/geode-piano/assets/61116261/2a5f732a-5d3e-4b5f-946
|
||||
- `cargo run --release --bin [binary]`
|
||||
- `[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.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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 all of LMMS's features, `qsampler` can work too.
|
||||
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.
|
||||
You should be able to play now.
|
||||
|
||||
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)
|
||||
- Two alligator clips
|
||||
- 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.
|
||||
Unplug them from the PCB, and count the amount of pins on them.
|
||||
|
@ -460,7 +460,7 @@ async fn main(_spawner: Spawner) {
|
||||
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).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 scl = p.PIN_17;
|
||||
|
||||
@ -469,7 +469,7 @@ async fn main(_spawner: Spawner) {
|
||||
i2c_config.frequency = freq;
|
||||
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(
|
||||
i2c,
|
||||
[0x20, 0x27],
|
||||
@ -481,15 +481,15 @@ async fn main(_spawner: Spawner) {
|
||||
))
|
||||
.await;
|
||||
|
||||
log::info!("main: starting piano task");
|
||||
defmt::info!("main: starting piano task");
|
||||
_spawner.spawn(piano_task(pin_driver)).unwrap();
|
||||
|
||||
log::info!("main: starting sustain pedal task");
|
||||
defmt::info!("main: starting sustain pedal task");
|
||||
_spawner
|
||||
.spawn(matrix::pedal(
|
||||
midi::Controller::SustainPedal,
|
||||
p.PIN_8.into(),
|
||||
false,
|
||||
true,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -13,11 +13,13 @@ pub mod midi;
|
||||
pub mod pins;
|
||||
pub mod usb;
|
||||
|
||||
/// Unwrap, but log before panic
|
||||
/// Wrapper over unwrap.
|
||||
///
|
||||
/// Waits a bit to give time for the logger to flush before halting.
|
||||
/// This exists because I do not own a debug probe 😎
|
||||
/// Logs over usb instead of instantly panicking.
|
||||
/// 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 {
|
||||
return res.unwrap();
|
||||
#[allow(unreachable_code)]
|
||||
match res {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
|
@ -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 };
|
||||
inp.wait_for_low().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;
|
||||
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 counter = 0;
|
||||
let mut prof_col_idx = 0;
|
||||
|
||||
defmt::debug!("using {} columns", N_COLS);
|
||||
|
||||
loop {
|
||||
let profile: bool = counter == 0;
|
||||
counter += 1;
|
||||
counter %= 50;
|
||||
let profile = counter == 0;
|
||||
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() {
|
||||
unwrap(pin_driver.set_output(*col)).await;
|
||||
let input = unwrap(pin_driver.read_all()).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
|
||||
let mask = input ^ (((1 << pin_driver.n_usable_pins()) - 1) ^ (1 << col));
|
||||
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 {
|
||||
(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;
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
use embassy_rp::usb::{Driver, Instance};
|
||||
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
|
||||
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 {
|
||||
SustainPedal = 64,
|
||||
}
|
||||
@ -86,7 +87,7 @@ impl MidiMsg {
|
||||
/// Note identifiers
|
||||
///
|
||||
/// See src/midi/note_def.py for how this is generated
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Format)]
|
||||
pub enum Note {
|
||||
A0 = 21,
|
||||
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;
|
||||
// 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];
|
||||
log::trace!("midi_session: note {:?}", packet);
|
||||
defmt::trace!("midi_session: note {:?}", packet);
|
||||
midi.write_packet(&packet).await?
|
||||
}
|
||||
MsgType::Controller(ctrl) => {
|
||||
let status: u8 = (0b1011_0000) | msg.channel;
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ impl TransparentPins {
|
||||
ret.pins.n_usable = ret.usable_extended_pins + N_REGULAR_PINS;
|
||||
}
|
||||
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)
|
||||
}
|
||||
@ -221,7 +221,7 @@ impl TransparentPins {
|
||||
// ports are flipped from what it should be
|
||||
let port_a = (val & (0xff00)) >> 8;
|
||||
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)
|
||||
} else {
|
||||
val
|
||||
@ -239,7 +239,7 @@ impl TransparentPins {
|
||||
|
||||
/// Write all pins from a single 64-bit value.
|
||||
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 {
|
||||
// value for this 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.
|
||||
pub fn read_all(&mut self) -> Result<u64, Error> {
|
||||
log::trace!("read_all: called");
|
||||
defmt::trace!("read_all: called");
|
||||
let mut ret: u64 = 0;
|
||||
for i in 0..N_PIN_EXTENDERS {
|
||||
let mut ext = extender!(self, i)?;
|
||||
|
@ -47,10 +47,10 @@ pub async fn usb_task(
|
||||
log_level: log::LevelFilter,
|
||||
) {
|
||||
// Create embassy-usb Config
|
||||
let mut config = Config::new(0xc0de, 0xcafe);
|
||||
let mut config = Config::new(0xdead, 0xbeef);
|
||||
config.manufacturer = Some("dogeystamp");
|
||||
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_packet_size_0 = 64;
|
||||
|
||||
@ -97,9 +97,9 @@ pub async fn usb_task(
|
||||
let midi_fut = async {
|
||||
loop {
|
||||
midi_class.wait_connection().await;
|
||||
log::info!("Connected");
|
||||
defmt::info!("Connected");
|
||||
let _ = midi_session(&mut midi_class).await;
|
||||
log::info!("Disconnected");
|
||||
defmt::info!("Disconnected");
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user