src/pins.rs: make TransparentPins struct
this interfaces with an MCP23017
This commit is contained in:
parent
448b8e232c
commit
05668f56d6
@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
runner = "elf2uf2-rs -d"
|
||||
runner = "elf2uf2-rs --deploy --serial"
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||
|
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -788,6 +788,7 @@ dependencies = [
|
||||
"embassy-time",
|
||||
"embassy-usb",
|
||||
"embassy-usb-logger",
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-hal 1.0.0",
|
||||
"embedded-hal-async",
|
||||
"embedded-hal-bus",
|
||||
@ -798,6 +799,7 @@ dependencies = [
|
||||
"futures",
|
||||
"heapless 0.8.0",
|
||||
"log",
|
||||
"mcp23017",
|
||||
"panic-probe",
|
||||
"portable-atomic",
|
||||
"smart-leds",
|
||||
@ -984,6 +986,15 @@ version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "mcp23017"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c32fd6627e73f1cfa95c00ddcdcb5a6a6ddbd10b308d08588a502c018b6e12c"
|
||||
dependencies = [
|
||||
"embedded-hal 0.2.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
|
@ -33,6 +33,7 @@ heapless = "0.8"
|
||||
usbd-hid = "0.7.0"
|
||||
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.7" }
|
||||
embedded-hal-async = "1.0"
|
||||
embedded-hal-bus = { version = "0.1", features = ["async"] }
|
||||
embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
|
||||
@ -41,5 +42,7 @@ static_cell = "2"
|
||||
portable-atomic = { version = "1.5", features = ["critical-section"] }
|
||||
log = "0.4"
|
||||
|
||||
mcp23017 = { version = "1.0.0" }
|
||||
|
||||
[profile.release]
|
||||
debug = 2
|
||||
|
33
README.md
33
README.md
@ -10,4 +10,37 @@ This project only attempts to expose the keyboard as a MIDI device.
|
||||
- Install `elf2uf2-rs`.
|
||||
- `cargo run --bin --release geode-piano`
|
||||
|
||||
## materials
|
||||
|
||||
- 1 Raspberry Pi Pico (preferably with pre-soldered headers)
|
||||
- 2 MCP23017 I/O extender chips, DIP format
|
||||
- 2 pull-up resistors for I²C (1-10kΩ), these are optional but recommended
|
||||
- 1 USB to Micro-USB cable with data transfer
|
||||
- Many jumper cables
|
||||
- Breadboard
|
||||
|
||||
## wiring
|
||||
|
||||
### rails
|
||||
|
||||
- Pin 3 -> GND rail
|
||||
- Pin 36 (3V3OUT) -> power (positive) rail
|
||||
|
||||
### i2c
|
||||
|
||||
Let's call the closest MCP23017 chip to the Pico MCP A, and the further one MCP B.
|
||||
|
||||
- GP16 -> MCP A SDA
|
||||
- GP17 -> MCP A SCL
|
||||
- Pull-up resistor from GP16 to power rail
|
||||
- Pull-up resistor from GP17 to power rail
|
||||
|
||||
For both MCP23017s:
|
||||
|
||||
- MCP RESET -> power rail
|
||||
- MCP A0, A1, A2 -> GND rail for 0, power rail for 1
|
||||
- MCP A should be 0x20 (GND, GND, GND), MCP B 0x27 (3V3, 3V3, 3V3)
|
||||
- MCP VDD -> power rail
|
||||
- MCP VSS -> GND rail
|
||||
|
||||
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.
|
||||
|
@ -81,7 +81,7 @@ pub async fn usb_task(
|
||||
// Create classes on the builder.
|
||||
let mut midi_class = MidiClass::new(&mut builder, 1, 1, 64);
|
||||
let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
|
||||
let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class);
|
||||
let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Trace, logger_class);
|
||||
|
||||
// The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
|
||||
// let (sender, receiver) = class.split();
|
||||
|
114
src/main.rs
114
src/main.rs
@ -4,9 +4,7 @@
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_rp::gpio;
|
||||
use embassy_rp::gpio::AnyPin;
|
||||
use embassy_rp::gpio::Input;
|
||||
use embassy_rp::gpio::Pull;
|
||||
use embassy_rp::i2c;
|
||||
use embassy_rp::peripherals::USB;
|
||||
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||
use embassy_time::Timer;
|
||||
@ -16,11 +14,28 @@ use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
mod geode_midi;
|
||||
mod geode_usb;
|
||||
mod pins;
|
||||
|
||||
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);
|
||||
@ -30,62 +45,15 @@ async fn blink_task(pin: embassy_rp::gpio::AnyPin) {
|
||||
Timer::after_millis(100).await;
|
||||
|
||||
led.set_low();
|
||||
Timer::after_secs(5).await;
|
||||
Timer::after_millis(900).await;
|
||||
}
|
||||
}
|
||||
|
||||
enum Note {
|
||||
C,
|
||||
Pedal,
|
||||
}
|
||||
|
||||
#[embassy_executor::task(pool_size = 2)]
|
||||
async fn button(pin: AnyPin, note: Note) {
|
||||
let mut button = Input::new(pin, Pull::Up);
|
||||
let chan = geode_midi::MidiChannel::new(0);
|
||||
#[embassy_executor::task]
|
||||
async fn read_task(mut pin_driver: pins::TransparentPins) {
|
||||
loop {
|
||||
let mut counter = 10;
|
||||
button.wait_for_falling_edge().await;
|
||||
loop {
|
||||
Timer::after_millis(5).await;
|
||||
if button.is_low() {
|
||||
counter -= 1;
|
||||
} else {
|
||||
counter = 10;
|
||||
}
|
||||
if counter <= 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match note {
|
||||
Note::C => chan.note_on(72, 64).await,
|
||||
Note::Pedal => {
|
||||
chan.controller(geode_midi::Controller::SustainPedal, 64)
|
||||
.await
|
||||
}
|
||||
}
|
||||
log::info!("button press");
|
||||
counter = 10;
|
||||
button.wait_for_rising_edge().await;
|
||||
loop {
|
||||
Timer::after_millis(5).await;
|
||||
if button.is_high() {
|
||||
counter -= 1;
|
||||
} else {
|
||||
counter = 10;
|
||||
}
|
||||
if counter <= 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match note {
|
||||
Note::C => chan.note_off(72, 0).await,
|
||||
Note::Pedal => {
|
||||
chan.controller(geode_midi::Controller::SustainPedal, 0)
|
||||
.await
|
||||
}
|
||||
}
|
||||
log::info!("button release");
|
||||
log::warn!("{:b}", unwrap(pin_driver.read_all()).await);
|
||||
Timer::after_millis(1000).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,11 +62,37 @@ 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();
|
||||
_spawner.spawn(button(p.PIN_16.into(), Note::C)).unwrap();
|
||||
_spawner
|
||||
.spawn(button(p.PIN_17.into(), Note::Pedal))
|
||||
.unwrap();
|
||||
_spawner.spawn(usb_task(driver)).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], []);
|
||||
|
||||
log::info!("main: setting pins as input");
|
||||
for i in 0..16 {
|
||||
log::debug!("main: setting pin {} as input, pull up", i);
|
||||
unwrap(pin_driver.set_input(i)).await;
|
||||
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).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();
|
||||
}
|
||||
|
162
src/pins.rs
Normal file
162
src/pins.rs
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
//! Manage I²C and provide a transparent pin interface for both onboard and MCP23017 pins.
|
||||
|
||||
extern crate embedded_hal_02;
|
||||
use embassy_rp::{
|
||||
gpio::{AnyPin, Flex, Pull},
|
||||
i2c::{self, Blocking},
|
||||
peripherals::{I2C0, I2C1},
|
||||
};
|
||||
|
||||
extern crate mcp23017;
|
||||
use embassy_time::Timer;
|
||||
use mcp23017::MCP23017;
|
||||
|
||||
/// Number of pins driven by each MCP23017 pin extender.
|
||||
const PINS_PER_EXTENDER: usize = 16;
|
||||
/// Number of MCP23017 chips used. This can not be changed without changing code.
|
||||
const N_PIN_EXTENDERS: usize = 1;
|
||||
/// Number of pins driven directly by the board.
|
||||
const N_REGULAR_PINS: usize = 0;
|
||||
/// Number of total extended pins
|
||||
const N_EXTENDED_PINS: usize = PINS_PER_EXTENDER * N_PIN_EXTENDERS;
|
||||
|
||||
/// "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.
|
||||
/// ext0 is 0-15, ext1 is 16-31, regular pins are 32-63.
|
||||
pub struct TransparentPins {
|
||||
ext0: MCP23017<i2c::I2c<'static, I2C0, Blocking>>,
|
||||
//ext1: MCP23017<i2c::I2c<'static, I2C1, Blocking>>,
|
||||
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
||||
}
|
||||
|
||||
/// GPIO extender pin
|
||||
struct ExtendedPin {
|
||||
/// ID (not address) of the extender being used
|
||||
ext_id: u8,
|
||||
/// Pin number in the extender's addressing scheme
|
||||
loc_pin: u8,
|
||||
}
|
||||
|
||||
enum TransparentPin {
|
||||
/// On-board GPIO (this is an index into `TransparentPins::pins` not the Pico numbering)
|
||||
Onboard(usize),
|
||||
/// Extender pin
|
||||
Extended(ExtendedPin),
|
||||
}
|
||||
|
||||
impl TransparentPins {
|
||||
fn get_pin(pin: u8) -> TransparentPin {
|
||||
if pin < (N_EXTENDED_PINS as u8) {
|
||||
let ext_id = pin / (PINS_PER_EXTENDER as u8);
|
||||
let loc_pin = pin % (PINS_PER_EXTENDER as u8);
|
||||
if ext_id >= N_PIN_EXTENDERS as u8 {
|
||||
panic!("invalid pin")
|
||||
}
|
||||
TransparentPin::Extended(ExtendedPin { ext_id, loc_pin })
|
||||
} else {
|
||||
TransparentPin::Onboard(pin as usize - N_EXTENDED_PINS)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
i2c0: i2c::I2c<'static, I2C0, Blocking>,
|
||||
//i2c1: i2c::I2c<'static, I2C1, Blocking>,
|
||||
addrs: [u8; N_PIN_EXTENDERS],
|
||||
pins: [AnyPin; N_REGULAR_PINS],
|
||||
) -> Self {
|
||||
let pin_init = pins.map(|x| Flex::new(x));
|
||||
return TransparentPins {
|
||||
ext0: MCP23017::new(i2c0, addrs[0]).unwrap(),
|
||||
pins: pin_init,
|
||||
};
|
||||
}
|
||||
|
||||
/// Read all pins into a single 64-bit value.
|
||||
pub fn read_all(&mut self) -> Result<u64, i2c::Error> {
|
||||
log::trace!("read_all: called");
|
||||
let mut ret: u64 = 0;
|
||||
// remember here port b is in the lower byte and port a in the upper byte
|
||||
ret |= self.ext0.read_gpioab()? as u64;
|
||||
for pin in 0..N_REGULAR_PINS {
|
||||
log::trace!("pin read: {}", pin);
|
||||
ret |= (self.pins[pin].is_high() as u64) << (N_EXTENDED_PINS + pin);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Set the pull on an individual pin (0-index).
|
||||
///
|
||||
/// Note: MCP23017 pins do not support pull-down.
|
||||
pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => {
|
||||
self.pins[p].set_pull(pull);
|
||||
}
|
||||
TransparentPin::Extended(p) => {
|
||||
let pull_on: bool = match pull {
|
||||
Pull::None => false,
|
||||
Pull::Up => true,
|
||||
// Extended pins don't seem to support pull-down
|
||||
Pull::Down => unimplemented!("MCP23017 does not support pull-down."),
|
||||
};
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pull_up(p.loc_pin, pull_on)?,
|
||||
//1 => self.ext1.pull_up(p.loc_pin, pull_on)?,
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_input(&mut self, pin: u8) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
|
||||
TransparentPin::Extended(p) => {
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?,
|
||||
//1 => self.ext1.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT).unwrap(),
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_output(&mut self, pin: u8) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
|
||||
TransparentPin::Extended(p) => {
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?,
|
||||
//1 => self.ext1.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT).unwrap(),
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user