Compare commits
3 Commits
4b851614c0
...
38e36b7bd5
Author | SHA1 | Date | |
---|---|---|---|
38e36b7bd5 | |||
e50f5051fc | |||
0489e7c8f8 |
@ -8,7 +8,13 @@ This project only attempts to expose the keyboard as a MIDI device.
|
|||||||
- Clone project.
|
- Clone project.
|
||||||
- Go into project directory.
|
- Go into project directory.
|
||||||
- Install `elf2uf2-rs`.
|
- Install `elf2uf2-rs`.
|
||||||
- `cargo run --bin --release geode-piano`
|
- Follow the materials and wiring sections below.
|
||||||
|
- Set the Pico into BOOTSEL mode:
|
||||||
|
- Hold down the BOOTSEL button on the Pico. Keep holding it during the following steps.
|
||||||
|
- Reset the Pico: either replug the power, or short Pin 30 (RUN) to GND through a button or wire.
|
||||||
|
- Mount the Pico's storage on your device.
|
||||||
|
- `cargo run --release --bin [binary]`
|
||||||
|
- `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them.
|
||||||
|
|
||||||
## materials
|
## materials
|
||||||
|
|
||||||
|
141
src/bin/pin_scanner.rs
Normal file
141
src/bin/pin_scanner.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
geode-piano
|
||||||
|
Copyright (C) 2024 dogeystamp <dogeystamp@disroot.org>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Scanner utility to detect which pins are directly connected.
|
||||||
|
//! This can be useful to reverse-engineer a key-matrix.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![deny(rust_2018_idioms)]
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
|
use embassy_rp::gpio;
|
||||||
|
use embassy_rp::i2c;
|
||||||
|
use embassy_rp::peripherals::USB;
|
||||||
|
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use geode_piano::usb::usb_task;
|
||||||
|
use geode_piano::{blinky, pin_array, pins, unwrap};
|
||||||
|
|
||||||
|
/// Represents a connection between two pins as detected by the scanner.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Connection {
|
||||||
|
/// Active low pin number
|
||||||
|
gnd_pin: u8,
|
||||||
|
/// Pull-up input pin number
|
||||||
|
input_pin: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn scanner_task(mut pin_driver: pins::TransparentPins) {
|
||||||
|
log::info!("scanner_task: setting pins as input");
|
||||||
|
for i in 0..pin_driver.n_total_pins {
|
||||||
|
unwrap(pin_driver.set_input(i as u8)).await;
|
||||||
|
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
const MAX_CONNECTIONS: usize = 10;
|
||||||
|
let mut n_connections = 0;
|
||||||
|
let mut connections: [Option<Connection>; MAX_CONNECTIONS] = [None; MAX_CONNECTIONS];
|
||||||
|
|
||||||
|
// for all outputs, use active low
|
||||||
|
// (only one pin will be output at a time)
|
||||||
|
unwrap(pin_driver.write_all(0)).await;
|
||||||
|
log::info!("");
|
||||||
|
log::info!("---");
|
||||||
|
log::info!("STARTING SCAN...");
|
||||||
|
for gnd_pin in 0..pin_driver.n_total_pins {
|
||||||
|
let gnd_pin = gnd_pin as u8;
|
||||||
|
unwrap(pin_driver.set_output(gnd_pin)).await;
|
||||||
|
let input = unwrap(pin_driver.read_all()).await;
|
||||||
|
unwrap(pin_driver.set_input(gnd_pin)).await;
|
||||||
|
|
||||||
|
// this represents the pins that are different from expected
|
||||||
|
let mask = input ^ (((1 << pin_driver.n_total_pins) - 1) ^ (1 << gnd_pin));
|
||||||
|
for input_pin in 0..pin_driver.n_total_pins {
|
||||||
|
let input_pin = input_pin as u8;
|
||||||
|
if ((1 << input_pin) & mask) != 0 {
|
||||||
|
if n_connections < MAX_CONNECTIONS {
|
||||||
|
connections[n_connections] = Some(Connection { gnd_pin, input_pin });
|
||||||
|
n_connections += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this should avoid overexerting the components
|
||||||
|
// in total it will take 0.4 seconds per scan
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("SCAN RESULTS");
|
||||||
|
for i in 0..n_connections {
|
||||||
|
match connections[i] {
|
||||||
|
None => {}
|
||||||
|
Some(con) => {
|
||||||
|
log::warn!("GND {:0>2} -> INPUT {:0>2}", con.gnd_pin, con.input_pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n_connections < MAX_CONNECTIONS {
|
||||||
|
log::info!("{n_connections} connections found.");
|
||||||
|
} else {
|
||||||
|
log::warn!("more than maximum ({MAX_CONNECTIONS}) connections found. list has been truncated. this might mean you used pins GPA7 or GPB7 on the MCP23017, which are unsafe as inputs, and therefore set as outputs.");
|
||||||
|
}
|
||||||
|
Timer::after_millis(3000).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
|
let driver = Driver::new(p.USB, Irqs);
|
||||||
|
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).await;
|
||||||
|
unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await;
|
||||||
|
|
||||||
|
Timer::after_secs(2).await;
|
||||||
|
|
||||||
|
log::info!("main: init i2c");
|
||||||
|
let sda = p.PIN_16;
|
||||||
|
let scl = p.PIN_17;
|
||||||
|
|
||||||
|
let mut i2c_config = i2c::Config::default();
|
||||||
|
let freq = 100_000;
|
||||||
|
i2c_config.frequency = freq;
|
||||||
|
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
|
||||||
|
|
||||||
|
log::info!("main: starting transparent pin driver");
|
||||||
|
let pin_driver = unwrap(pins::TransparentPins::new(
|
||||||
|
i2c,
|
||||||
|
[0x20, 0x27],
|
||||||
|
pin_array!(
|
||||||
|
p.PIN_15, p.PIN_14, p.PIN_13, p.PIN_12, p.PIN_11, p.PIN_10, p.PIN_9, p.PIN_18,
|
||||||
|
p.PIN_19, p.PIN_20, p.PIN_21, p.PIN_22
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
log::info!("main: starting scanner task");
|
||||||
|
_spawner.spawn(scanner_task(pin_driver)).unwrap();
|
||||||
|
}
|
73
src/bin/pin_test.rs
Normal file
73
src/bin/pin_test.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! Tester for `geode_piano::pins::TransparentPins`.
|
||||||
|
//!
|
||||||
|
//! This is quickly hacked together.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![deny(rust_2018_idioms)]
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
|
use embassy_rp::gpio;
|
||||||
|
use embassy_rp::i2c;
|
||||||
|
use embassy_rp::peripherals::USB;
|
||||||
|
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use geode_piano::usb::usb_task;
|
||||||
|
use geode_piano::{blinky, pin_array, pins, unwrap};
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn read_task(mut pin_driver: pins::TransparentPins) {
|
||||||
|
loop {
|
||||||
|
log::warn!("{:036b}", unwrap(pin_driver.read_all()).await);
|
||||||
|
Timer::after_millis(1000).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
|
let driver = Driver::new(p.USB, Irqs);
|
||||||
|
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Info))).await;
|
||||||
|
unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await;
|
||||||
|
|
||||||
|
Timer::after_secs(2).await;
|
||||||
|
|
||||||
|
log::info!("main: init i2c");
|
||||||
|
let sda = p.PIN_16;
|
||||||
|
let scl = p.PIN_17;
|
||||||
|
|
||||||
|
let mut i2c_config = i2c::Config::default();
|
||||||
|
let freq = 100_000;
|
||||||
|
i2c_config.frequency = freq;
|
||||||
|
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
|
||||||
|
|
||||||
|
log::info!("main: starting transparent pin driver");
|
||||||
|
let mut pin_driver = unwrap(pins::TransparentPins::new(
|
||||||
|
i2c,
|
||||||
|
[0x20, 0x27],
|
||||||
|
pin_array!(
|
||||||
|
p.PIN_15, p.PIN_14, p.PIN_13, p.PIN_12, p.PIN_11, p.PIN_10, p.PIN_9, p.PIN_18,
|
||||||
|
p.PIN_19, p.PIN_20, p.PIN_21, p.PIN_22
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
log::info!("main: setting pins as input");
|
||||||
|
for i in 0..pin_driver.n_total_pins {
|
||||||
|
unwrap(pin_driver.set_input(i as u8)).await;
|
||||||
|
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
||||||
|
}
|
||||||
|
log::debug!("main: setting pin 0 as output, active low");
|
||||||
|
unwrap(pin_driver.set_output(0)).await;
|
||||||
|
unwrap(pin_driver.write_all(0)).await;
|
||||||
|
|
||||||
|
log::debug!("main: starting read task");
|
||||||
|
_spawner.spawn(read_task(pin_driver)).unwrap();
|
||||||
|
}
|
17
src/blinky.rs
Normal file
17
src/blinky.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//! blinky task
|
||||||
|
|
||||||
|
use embassy_rp::gpio::{Level, Output};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn blink_task(pin: embassy_rp::gpio::AnyPin) {
|
||||||
|
let mut led = Output::new(pin, Level::Low);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
led.set_high();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
|
led.set_low();
|
||||||
|
Timer::after_millis(900).await;
|
||||||
|
}
|
||||||
|
}
|
27
src/lib.rs
Normal file
27
src/lib.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![deny(rust_2018_idioms)]
|
||||||
|
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
pub mod blinky;
|
||||||
|
pub mod midi;
|
||||||
|
pub mod pins;
|
||||||
|
pub mod usb;
|
||||||
|
|
||||||
|
/// Unwrap, but log before panic
|
||||||
|
///
|
||||||
|
/// Waits a bit to give time for the logger to flush before halting.
|
||||||
|
/// This exists because I do not own a debug probe 😎
|
||||||
|
pub async fn unwrap<T, E: core::fmt::Debug>(res: Result<T, E>) -> T {
|
||||||
|
match res {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("[FATAL] {:?}", e);
|
||||||
|
log::error!("HALTING DUE TO PANIC.");
|
||||||
|
Timer::after_secs(1).await;
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
src/main.rs
105
src/main.rs
@ -1,105 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
#![deny(rust_2018_idioms)]
|
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_rp::bind_interrupts;
|
|
||||||
use embassy_rp::gpio;
|
|
||||||
use embassy_rp::i2c;
|
|
||||||
use embassy_rp::peripherals::USB;
|
|
||||||
use embassy_rp::usb::{Driver, InterruptHandler};
|
|
||||||
use embassy_time::Timer;
|
|
||||||
use gpio::{Level, Output};
|
|
||||||
use usb::usb_task;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
|
||||||
|
|
||||||
mod midi;
|
|
||||||
mod pins;
|
|
||||||
mod usb;
|
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
|
||||||
USBCTRL_IRQ => InterruptHandler<USB>;
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Unwrap, but log before panic
|
|
||||||
///
|
|
||||||
/// Waits a bit to give time for the logger to flush before halting.
|
|
||||||
/// This exists because I do not own a debug probe 😎
|
|
||||||
async fn unwrap<T, E: core::fmt::Debug>(res: Result<T, E>) -> T {
|
|
||||||
match res {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("[FATAL] {:?}", e);
|
|
||||||
log::error!("HALTING DUE TO PANIC.");
|
|
||||||
Timer::after_millis(10).await;
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn blink_task(pin: embassy_rp::gpio::AnyPin) {
|
|
||||||
let mut led = Output::new(pin, Level::Low);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
led.set_high();
|
|
||||||
Timer::after_millis(100).await;
|
|
||||||
|
|
||||||
led.set_low();
|
|
||||||
Timer::after_millis(900).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn read_task(mut pin_driver: pins::TransparentPins) {
|
|
||||||
loop {
|
|
||||||
log::warn!("{:b}", unwrap(pin_driver.read_all()).await);
|
|
||||||
Timer::after_millis(1000).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
|
||||||
async fn main(_spawner: Spawner) {
|
|
||||||
let p = embassy_rp::init(Default::default());
|
|
||||||
|
|
||||||
let driver = Driver::new(p.USB, Irqs);
|
|
||||||
_spawner.spawn(usb_task(driver)).unwrap();
|
|
||||||
|
|
||||||
_spawner.spawn(blink_task(p.PIN_25.into())).unwrap();
|
|
||||||
|
|
||||||
Timer::after_secs(2).await;
|
|
||||||
|
|
||||||
log::info!("main: init i2c");
|
|
||||||
let sda = p.PIN_16;
|
|
||||||
let scl = p.PIN_17;
|
|
||||||
|
|
||||||
let mut i2c_config = i2c::Config::default();
|
|
||||||
let freq = 100_000;
|
|
||||||
i2c_config.frequency = freq;
|
|
||||||
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
|
|
||||||
|
|
||||||
log::info!("main: starting transparent pin driver");
|
|
||||||
let mut pin_driver = pins::TransparentPins::new(
|
|
||||||
i2c,
|
|
||||||
[0x20, 0x27],
|
|
||||||
pins::pin_array!(p.PIN_15, p.PIN_14, p.PIN_13, p.PIN_12, p.PIN_11, p.PIN_10, p.PIN_18, p.PIN_19),
|
|
||||||
);
|
|
||||||
|
|
||||||
log::info!("main: setting pins as input");
|
|
||||||
for i in 0..(pins::N_EXTENDED_PINS + pins::N_REGULAR_PINS) {
|
|
||||||
unwrap(pin_driver.set_input(i as u8)).await;
|
|
||||||
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
|
||||||
}
|
|
||||||
log::debug!("main: setting pin 0 as output, active low");
|
|
||||||
unwrap(pin_driver.set_output(0)).await;
|
|
||||||
unwrap(pin_driver.write_all(((1 << 40 - 1)) & 0)).await;
|
|
||||||
|
|
||||||
// these pins are faulty as inputs
|
|
||||||
// unwrap(pin_driver.set_output(7)).await;
|
|
||||||
// unwrap(pin_driver.set_output(8 + 7)).await;
|
|
||||||
// unwrap(pin_driver.set_output(16 + 7)).await;
|
|
||||||
// unwrap(pin_driver.set_output(16 + 8 + 7)).await;
|
|
||||||
|
|
||||||
log::debug!("main: starting read task");
|
|
||||||
_spawner.spawn(read_task(pin_driver)).unwrap();
|
|
||||||
}
|
|
137
src/pins.rs
137
src/pins.rs
@ -28,13 +28,19 @@ use mcp23017;
|
|||||||
use mcp23017::MCP23017;
|
use mcp23017::MCP23017;
|
||||||
|
|
||||||
/// Number of pins driven by each MCP23017 pin extender.
|
/// Number of pins driven by each MCP23017 pin extender.
|
||||||
pub const PINS_PER_EXTENDER: usize = 16;
|
const PINS_PER_EXTENDER: usize = 16;
|
||||||
/// Number of MCP23017 chips used.
|
/// Number of MCP23017 chips used.
|
||||||
pub const N_PIN_EXTENDERS: usize = 2;
|
const N_PIN_EXTENDERS: usize = 2;
|
||||||
/// Number of pins driven directly by the board.
|
/// Number of pins driven directly by the board.
|
||||||
pub const N_REGULAR_PINS: usize = 8;
|
const N_REGULAR_PINS: usize = 12;
|
||||||
/// Number of total extended pins
|
/// Number of total extended pins
|
||||||
pub const N_EXTENDED_PINS: usize = PINS_PER_EXTENDER * N_PIN_EXTENDERS;
|
const N_EXTENDED_PINS: usize = PINS_PER_EXTENDER * N_PIN_EXTENDERS;
|
||||||
|
/// Number of unsafe pins per extender (GPA7, GPB7)
|
||||||
|
const UNSAFE_PER_EXTENDER: usize = 2;
|
||||||
|
/// Single extender address offset of PORTA
|
||||||
|
const PORT_A: u8 = 0;
|
||||||
|
/// Single extender address offset of PORTB
|
||||||
|
const PORT_B: u8 = 8;
|
||||||
|
|
||||||
type I2cPeripheral = i2c::I2c<'static, I2C0, Blocking>;
|
type I2cPeripheral = i2c::I2c<'static, I2C0, Blocking>;
|
||||||
type I2cBus = shared_bus::BusManagerSimple<I2cPeripheral>;
|
type I2cBus = shared_bus::BusManagerSimple<I2cPeripheral>;
|
||||||
@ -75,22 +81,35 @@ impl<E> From<mcp23017::Error<E>> for Error {
|
|||||||
|
|
||||||
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
||||||
///
|
///
|
||||||
/// This interface uses a single addressing scheme for all the pins it manages.
|
/// This interface uses a single addressing scheme for all the pins it manages. Extender A is 0-15,
|
||||||
/// Extender A is 0-15, Extender B is 16-31, and so on, then all the onboard pins.
|
/// Extender B is 16-31, and so on, then all the onboard pins. Port A is in the lower byte and port
|
||||||
/// Port A is in the lower byte and port B is in the upper byte of each extender range.
|
/// B is in the upper byte of each extender range. This addressing scheme may be changed with some
|
||||||
|
/// options. The exact pins each address refers to are not supposed to be important.
|
||||||
|
///
|
||||||
|
/// The MCP23017 is known to have two defective pins, GPA7 and GPB7. These can not be set as inputs
|
||||||
|
/// without risks of weird behaviour. To disable these pins, you may set `disable_unsafe_pins` in
|
||||||
|
/// the constructor. This will set them to output pins, and then remove them from the transparent
|
||||||
|
/// pins addressing scheme.
|
||||||
pub struct TransparentPins {
|
pub struct TransparentPins {
|
||||||
addrs: [u8; N_PIN_EXTENDERS],
|
addrs: [u8; N_PIN_EXTENDERS],
|
||||||
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
||||||
i2c_bus: I2cBus,
|
i2c_bus: I2cBus,
|
||||||
|
disable_unsafe_pins: bool,
|
||||||
|
/// Number of total usable pins. Transparent pins all have an address from `0..n_total_pins`.
|
||||||
|
pub n_total_pins: usize,
|
||||||
|
/// Usable pins per extender. Depends on `disable_unsafe_pins`.
|
||||||
|
usable_pins_per_extender: usize,
|
||||||
|
/// Usable pin count on all extenders. Depends on `disable_unsafe_pins`.
|
||||||
|
usable_extended_pins: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to define the onboard pins in TransparentPins
|
/// Helper to define the onboard pins in TransparentPins
|
||||||
|
#[macro_export]
|
||||||
macro_rules! pin_array {
|
macro_rules! pin_array {
|
||||||
($($pin: expr),*) => {
|
($($pin: expr),*) => {
|
||||||
[$($pin.into(),)*]
|
[$($pin.into(),)*]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) use pin_array;
|
|
||||||
|
|
||||||
/// Create a new short-lived MCP23017 struct.
|
/// Create a new short-lived MCP23017 struct.
|
||||||
///
|
///
|
||||||
@ -103,6 +122,34 @@ macro_rules! extender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransparentPins {
|
impl TransparentPins {
|
||||||
|
/// Transform addresses into a transparent pin number, taking into account pins that aren't being used.
|
||||||
|
fn addr_to_pin(&self, addr: u8) -> u8 {
|
||||||
|
if self.disable_unsafe_pins {
|
||||||
|
if addr as usize >= (self.usable_pins_per_extender * N_PIN_EXTENDERS) {
|
||||||
|
return addr + (UNSAFE_PER_EXTENDER as u8) * (N_PIN_EXTENDERS as u8);
|
||||||
|
}
|
||||||
|
// extender index
|
||||||
|
let div = addr as usize / self.usable_pins_per_extender;
|
||||||
|
// offset within extender
|
||||||
|
let m = addr as usize % self.usable_pins_per_extender;
|
||||||
|
// difference between `m` and the MCP23017 pin number within this extender
|
||||||
|
let mut offset = 0;
|
||||||
|
if m >= PORT_A as usize + 7 {
|
||||||
|
// these pins are offset by one because GPA7 is missing
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
// GPB7 doesn't need an offset because it is the last pin anyways
|
||||||
|
// the div-mod above takes care of that
|
||||||
|
|
||||||
|
(div * PINS_PER_EXTENDER + m + offset) as u8
|
||||||
|
} else {
|
||||||
|
addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a pin by its pin number.
|
||||||
|
///
|
||||||
|
/// This is NOT by the transparent address.
|
||||||
fn get_pin(&mut self, pin: u8) -> Result<TransparentPin, Error> {
|
fn get_pin(&mut self, pin: u8) -> Result<TransparentPin, Error> {
|
||||||
if pin as usize >= N_EXTENDED_PINS + N_REGULAR_PINS {
|
if pin as usize >= N_EXTENDED_PINS + N_REGULAR_PINS {
|
||||||
return Err(Error::InvalidPin(pin));
|
return Err(Error::InvalidPin(pin));
|
||||||
@ -120,11 +167,51 @@ impl TransparentPins {
|
|||||||
i2c: i2c::I2c<'static, I2C0, Blocking>,
|
i2c: i2c::I2c<'static, I2C0, Blocking>,
|
||||||
addrs: [u8; N_PIN_EXTENDERS],
|
addrs: [u8; N_PIN_EXTENDERS],
|
||||||
pins: [AnyPin; N_REGULAR_PINS],
|
pins: [AnyPin; N_REGULAR_PINS],
|
||||||
) -> Self {
|
disable_unsafe_pins: bool,
|
||||||
TransparentPins {
|
) -> Result<Self, Error> {
|
||||||
|
let mut ret = TransparentPins {
|
||||||
addrs,
|
addrs,
|
||||||
pins: pins.map(|x| Flex::new(x)),
|
pins: pins.map(|x| Flex::new(x)),
|
||||||
i2c_bus: shared_bus::BusManagerSimple::new(i2c),
|
i2c_bus: shared_bus::BusManagerSimple::new(i2c),
|
||||||
|
disable_unsafe_pins: false,
|
||||||
|
usable_pins_per_extender: PINS_PER_EXTENDER,
|
||||||
|
usable_extended_pins: N_EXTENDED_PINS,
|
||||||
|
n_total_pins: N_EXTENDED_PINS + N_REGULAR_PINS,
|
||||||
|
};
|
||||||
|
if disable_unsafe_pins {
|
||||||
|
for i in 0..N_PIN_EXTENDERS {
|
||||||
|
ret.set_output((i as u8) * (PINS_PER_EXTENDER as u8) + PORT_A + 7)?;
|
||||||
|
ret.set_output((i as u8) * (PINS_PER_EXTENDER as u8) + PORT_B + 7)?;
|
||||||
|
ret.usable_pins_per_extender = PINS_PER_EXTENDER - UNSAFE_PER_EXTENDER;
|
||||||
|
ret.usable_extended_pins = N_PIN_EXTENDERS * ret.usable_pins_per_extender;
|
||||||
|
ret.n_total_pins = ret.usable_extended_pins + N_REGULAR_PINS;
|
||||||
|
}
|
||||||
|
ret.disable_unsafe_pins = true;
|
||||||
|
log::debug!("TransparentPins: {} usable pins", ret.n_total_pins)
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the raw pin input for an extender to just usable pins
|
||||||
|
fn raw_to_usable(&self, val: u16) -> u16 {
|
||||||
|
if self.disable_unsafe_pins {
|
||||||
|
// read api is wonky (https://github.com/lucazulian/mcp23017/issues/8)
|
||||||
|
// 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}");
|
||||||
|
(port_a & 0x7f) | ((port_b & 0x7f) << 7)
|
||||||
|
} else {
|
||||||
|
val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the usable pin mask to raw pin output
|
||||||
|
fn usable_to_raw(&self, val: u16) -> u16 {
|
||||||
|
if self.disable_unsafe_pins {
|
||||||
|
(val & 0x00ff) | ((val & 0xff00) << 1)
|
||||||
|
} else {
|
||||||
|
val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,11 +220,12 @@ impl TransparentPins {
|
|||||||
log::trace!("write_all: called with val {}", val);
|
log::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*PINS_PER_EXTENDER)) & ((1 << PINS_PER_EXTENDER) - 1);
|
let ext_val = (val >> (i * self.usable_pins_per_extender))
|
||||||
extender!(self, i)?.write_gpioab(ext_val as u16)?;
|
& ((1 << self.usable_pins_per_extender) - 1);
|
||||||
|
extender!(self, i)?.write_gpioab(self.usable_to_raw(ext_val as u16))?;
|
||||||
}
|
}
|
||||||
for pin in 0..N_REGULAR_PINS {
|
for pin in 0..N_REGULAR_PINS {
|
||||||
self.pins[pin].set_level(match (val >> N_EXTENDED_PINS >> pin) & 1 {
|
self.pins[pin].set_level(match (val >> self.usable_extended_pins >> pin) & 1 {
|
||||||
0 => embassy_rp::gpio::Level::Low,
|
0 => embassy_rp::gpio::Level::Low,
|
||||||
1 => embassy_rp::gpio::Level::High,
|
1 => embassy_rp::gpio::Level::High,
|
||||||
_ => panic!("Invalid level"),
|
_ => panic!("Invalid level"),
|
||||||
@ -153,13 +241,11 @@ impl TransparentPins {
|
|||||||
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)?;
|
||||||
let read_val = ext.read_gpioab()? as u64;
|
let read_val = ext.read_gpioab()?;
|
||||||
// api is wonky (https://github.com/lucazulian/mcp23017/issues/8)
|
ret |= (self.raw_to_usable(read_val) as u64) << (i * self.usable_pins_per_extender);
|
||||||
let flipped_val = ((read_val & 0x00ff) << 8) | ((read_val & 0xff00) >> 8);
|
|
||||||
ret |= flipped_val << (i * PINS_PER_EXTENDER);
|
|
||||||
}
|
}
|
||||||
for pin in 0..N_REGULAR_PINS {
|
for pin in 0..N_REGULAR_PINS {
|
||||||
ret |= (self.pins[pin].is_high() as u64) << (N_EXTENDED_PINS + pin);
|
ret |= (self.pins[pin].is_high() as u64) << (self.usable_extended_pins + pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
@ -168,8 +254,9 @@ impl TransparentPins {
|
|||||||
/// Set the pull on an individual pin (0-index).
|
/// Set the pull on an individual pin (0-index).
|
||||||
///
|
///
|
||||||
/// Note: MCP23017 pins do not support pull-down.
|
/// Note: MCP23017 pins do not support pull-down.
|
||||||
pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), Error> {
|
pub fn set_pull(&mut self, addr: u8, pull: Pull) -> Result<(), Error> {
|
||||||
let pin = self.get_pin(pin)?;
|
let pin_n = self.addr_to_pin(addr);
|
||||||
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => {
|
TransparentPin::Onboard(p) => {
|
||||||
self.pins[p].set_pull(pull);
|
self.pins[p].set_pull(pull);
|
||||||
@ -188,8 +275,9 @@ impl TransparentPins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a pin as an input.
|
/// Sets a pin as an input.
|
||||||
pub fn set_input(&mut self, pin: u8) -> Result<(), Error> {
|
pub fn set_input(&mut self, addr: u8) -> Result<(), Error> {
|
||||||
let pin = self.get_pin(pin)?;
|
let pin_n = self.addr_to_pin(addr);
|
||||||
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
|
TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
|
||||||
TransparentPin::Extended(p) => {
|
TransparentPin::Extended(p) => {
|
||||||
@ -200,8 +288,9 @@ impl TransparentPins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a pin as an output.
|
/// Sets a pin as an output.
|
||||||
pub fn set_output(&mut self, pin: u8) -> Result<(), Error> {
|
pub fn set_output(&mut self, addr: u8) -> Result<(), Error> {
|
||||||
let pin = self.get_pin(pin)?;
|
let pin_n = self.addr_to_pin(addr);
|
||||||
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
|
TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
|
||||||
TransparentPin::Extended(p) => {
|
TransparentPin::Extended(p) => {
|
||||||
|
@ -43,6 +43,7 @@ use embassy_usb::{Builder, Config};
|
|||||||
pub async fn usb_task(
|
pub async fn usb_task(
|
||||||
// remember this is the Driver struct not the trait
|
// remember this is the Driver struct not the trait
|
||||||
driver: Driver<'static, USB>,
|
driver: Driver<'static, USB>,
|
||||||
|
log_level: log::LevelFilter,
|
||||||
) {
|
) {
|
||||||
// Create embassy-usb Config
|
// Create embassy-usb Config
|
||||||
let mut config = Config::new(0xc0de, 0xcafe);
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
@ -81,7 +82,7 @@ pub async fn usb_task(
|
|||||||
// Create classes on the builder.
|
// Create classes on the builder.
|
||||||
let mut midi_class = MidiClass::new(&mut builder, 1, 1, 64);
|
let mut midi_class = MidiClass::new(&mut builder, 1, 1, 64);
|
||||||
let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
|
let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
|
||||||
let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Trace, logger_class);
|
let log_fut = embassy_usb_logger::with_class!(1024, log_level, logger_class);
|
||||||
|
|
||||||
// The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
|
// The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
|
||||||
// let (sender, receiver) = class.split();
|
// let (sender, receiver) = class.split();
|
||||||
|
Loading…
Reference in New Issue
Block a user