chord controller
This commit is contained in:
parent
8e96e70cfd
commit
4d4d39c7a1
1 changed files with 140 additions and 75 deletions
|
|
@ -7,7 +7,6 @@ mod led_matrix;
|
||||||
use core::panic::PanicInfo;
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::select::select_array;
|
|
||||||
use embassy_rp::bind_interrupts;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::gpio::{Input, Level, Output, Pull};
|
use embassy_rp::gpio::{Input, Level, Output, Pull};
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
|
|
@ -16,16 +15,12 @@ use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
|
||||||
use embassy_rp::pwm::{self, Pwm};
|
use embassy_rp::pwm::{self, Pwm};
|
||||||
use embassy_rp::rom_data::reset_to_usb_boot;
|
use embassy_rp::rom_data::reset_to_usb_boot;
|
||||||
use embassy_rp::spi::{self, Blocking, Spi};
|
use embassy_rp::spi::{self, Blocking, Spi};
|
||||||
use embassy_time::{Delay, Duration, Instant, Ticker};
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embedded_graphics::image::{Image, ImageRawLE};
|
use embassy_sync::signal::Signal;
|
||||||
use embedded_graphics::mono_font::MonoTextStyle;
|
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
|
||||||
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
use embedded_graphics::pixelcolor::Rgb888;
|
||||||
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
|
use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size};
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle};
|
|
||||||
use embedded_graphics::text::Text;
|
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
use heapless::String;
|
|
||||||
use mipidsi::interface::SpiInterface;
|
use mipidsi::interface::SpiInterface;
|
||||||
use mipidsi::models::ST7789;
|
use mipidsi::models::ST7789;
|
||||||
use mipidsi::options::Orientation;
|
use mipidsi::options::Orientation;
|
||||||
|
|
@ -45,24 +40,22 @@ const DISPLAY_FREQ: u32 = 64_000_000;
|
||||||
async fn main(spawner: Spawner) -> ! {
|
async fn main(spawner: Spawner) -> ! {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
// Setup buttons
|
let buttons = Buttons {
|
||||||
let _btn_a = Input::new(p.PIN_12, Pull::Up);
|
f1: Input::new(p.PIN_11, Pull::Up),
|
||||||
let _btn_b = Input::new(p.PIN_13, Pull::Up);
|
f2: Input::new(p.PIN_14, Pull::Up),
|
||||||
// let _btn_x = Input::new(p.PIN_14, Pull::Up);
|
f3: Input::new(p.PIN_9, Pull::Up),
|
||||||
let btn_y = Input::new(p.PIN_15, Pull::Up);
|
f4: Input::new(p.PIN_3, Pull::Up),
|
||||||
|
a: Input::new(p.PIN_12, Pull::Up),
|
||||||
let mut btn_f1 = Input::new(p.PIN_11, Pull::Up);
|
b: Input::new(p.PIN_13, Pull::Up),
|
||||||
let mut btn_f2 = Input::new(p.PIN_14, Pull::Up);
|
// x = f2
|
||||||
let mut btn_f3 = Input::new(p.PIN_9, Pull::Up);
|
y: Input::new(p.PIN_15, Pull::Up),
|
||||||
let mut btn_f4 = Input::new(p.PIN_3, Pull::Up);
|
};
|
||||||
|
|
||||||
spawner.spawn(dfu_button(btn_y)).ok();
|
|
||||||
|
|
||||||
// display SPI
|
// display SPI
|
||||||
|
|
||||||
let mut display_buffer = [0u8; 512];
|
let mut display_buffer = [0u8; 512];
|
||||||
|
|
||||||
let mut display = {
|
let mut _display = {
|
||||||
let mut config = spi::Config::default();
|
let mut config = spi::Config::default();
|
||||||
config.frequency = DISPLAY_FREQ;
|
config.frequency = DISPLAY_FREQ;
|
||||||
config.phase = spi::Phase::CaptureOnSecondTransition;
|
config.phase = spi::Phase::CaptureOnSecondTransition;
|
||||||
|
|
@ -85,17 +78,8 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
.init(&mut Delay)
|
.init(&mut Delay)
|
||||||
.expect("display init")
|
.expect("display init")
|
||||||
};
|
};
|
||||||
let _display_backlight = Output::new(p.PIN_20, Level::High);
|
// Blank display - unused at the moment
|
||||||
|
let _display_backlight = Output::new(p.PIN_20, Level::Low);
|
||||||
let raw_image_data = ImageRawLE::new(include_bytes!("assets/ferris.raw"), 86);
|
|
||||||
let ferris = Image::new(&raw_image_data, Point::new(0, 0));
|
|
||||||
|
|
||||||
let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN);
|
|
||||||
let hello_text = Text::new(
|
|
||||||
"Hello embedded_graphics \n + embassy + RP2040!",
|
|
||||||
Point::new(0, 68),
|
|
||||||
style,
|
|
||||||
);
|
|
||||||
|
|
||||||
// setup RGB LED
|
// setup RGB LED
|
||||||
let mut rg_config = pwm::Config::default();
|
let mut rg_config = pwm::Config::default();
|
||||||
|
|
@ -106,7 +90,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
let mut rg_pwm = Pwm::new_output_ab(p.PWM_SLICE3, p.PIN_6, p.PIN_7, rg_config.clone());
|
let mut rg_pwm = Pwm::new_output_ab(p.PWM_SLICE3, p.PIN_6, p.PIN_7, rg_config.clone());
|
||||||
let mut b_pwm = Pwm::new_output_a(p.PWM_SLICE4, p.PIN_8, b_config.clone());
|
let mut b_pwm = Pwm::new_output_a(p.PWM_SLICE4, p.PIN_8, b_config.clone());
|
||||||
|
|
||||||
let mut set_led = |r: u8, g: u8, b: u8| {
|
let mut _set_led = |r: u8, g: u8, b: u8| {
|
||||||
rg_config.compare_a = 0x101 * (255 - r as u16);
|
rg_config.compare_a = 0x101 * (255 - r as u16);
|
||||||
rg_config.compare_b = 0x101 * (255 - g as u16);
|
rg_config.compare_b = 0x101 * (255 - g as u16);
|
||||||
b_config.compare_a = 0x101 * (255 - b as u16);
|
b_config.compare_a = 0x101 * (255 - b as u16);
|
||||||
|
|
@ -129,43 +113,116 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
spawner.spawn(led_task(led_surface)).ok();
|
spawner.spawn(led_task(led_surface)).ok();
|
||||||
|
spawner.spawn(controller_task(buttons)).ok();
|
||||||
|
|
||||||
let mut render = |f_keys: [bool; 4]| {
|
// go to sleep
|
||||||
display.clear(Rgb565::BLACK).ok();
|
loop {
|
||||||
ferris.draw(&mut display).ok();
|
Timer::after_secs(1).await
|
||||||
hello_text.draw(&mut display).ok();
|
|
||||||
|
|
||||||
let mut f_key_string: String<16> = String::new();
|
|
||||||
for (f, s) in f_keys.into_iter().zip(["1", "2", "3", "4"]) {
|
|
||||||
f_key_string.push_str(f.then_some(s).unwrap_or(" ")).ok();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text::new(&f_key_string, Point::new(0, 108), style)
|
struct Buttons {
|
||||||
.draw(&mut display)
|
f1: Input<'static>,
|
||||||
.ok();
|
f2: Input<'static>,
|
||||||
};
|
f3: Input<'static>,
|
||||||
|
f4: Input<'static>,
|
||||||
|
a: Input<'static>,
|
||||||
|
b: Input<'static>,
|
||||||
|
// x = f2
|
||||||
|
y: Input<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_state = None;
|
impl Buttons {
|
||||||
|
fn read(&self) -> ButtonStates {
|
||||||
|
ButtonStates {
|
||||||
|
f1: self.f1.is_low(),
|
||||||
|
f2: self.f2.is_low(),
|
||||||
|
f3: self.f3.is_low(),
|
||||||
|
f4: self.f4.is_low(),
|
||||||
|
a: self.a.is_low(),
|
||||||
|
b: self.b.is_low(),
|
||||||
|
y: self.y.is_low(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
struct ButtonStates {
|
||||||
|
f1: bool,
|
||||||
|
f2: bool,
|
||||||
|
f3: bool,
|
||||||
|
f4: bool,
|
||||||
|
a: bool,
|
||||||
|
b: bool,
|
||||||
|
// x = f2
|
||||||
|
y: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonStates {
|
||||||
|
fn chord(&self) -> u8 {
|
||||||
|
(self.f1 as u8) + ((self.f2 as u8) << 1) + ((self.f3 as u8) << 2) + ((self.f4 as u8) << 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LedConfig {
|
||||||
|
Blank,
|
||||||
|
Hal,
|
||||||
|
Happy,
|
||||||
|
}
|
||||||
|
|
||||||
|
static LED_CONFIG_SIGNAL: Signal<CriticalSectionRawMutex, LedConfig> = Signal::new();
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn controller_task(buttons: Buttons) {
|
||||||
|
let mut scan_ticker = Ticker::every(Duration::from_millis(1));
|
||||||
|
let mut chord_debounce = 0;
|
||||||
|
let mut stable_chord = 0;
|
||||||
|
let mut pending_chord = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let state = [
|
let state = buttons.read();
|
||||||
btn_f1.is_low(),
|
let new_chord = state.chord();
|
||||||
btn_f2.is_low(),
|
|
||||||
btn_f3.is_low(),
|
if state.y {
|
||||||
btn_f4.is_low(),
|
reset_to_usb_boot(0, 0);
|
||||||
];
|
|
||||||
if Some(state) != last_state {
|
|
||||||
render(state);
|
|
||||||
last_state = Some(state);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
select_array([
|
|
||||||
btn_f1.wait_for_any_edge(),
|
if new_chord != pending_chord {
|
||||||
btn_f2.wait_for_any_edge(),
|
// Track the most recent chord key state.
|
||||||
btn_f3.wait_for_any_edge(),
|
pending_chord = new_chord;
|
||||||
btn_f4.wait_for_any_edge(),
|
chord_debounce = 10;
|
||||||
])
|
} else if chord_debounce > 0 {
|
||||||
.await;
|
// Wait several scans to make sure it's a stable reading.
|
||||||
|
chord_debounce -= 1;
|
||||||
|
} else if (!stable_chord & pending_chord) != 0 {
|
||||||
|
// Once it's stable, if new keys were pressed compared to
|
||||||
|
// stable_chord, replace stable_chord.
|
||||||
|
stable_chord = pending_chord;
|
||||||
|
} else if pending_chord == 0 && stable_chord != 0 {
|
||||||
|
// If a stable 0 (all keys released) is read, then process
|
||||||
|
// stable_chord as an input.
|
||||||
|
match stable_chord {
|
||||||
|
0x1 => {
|
||||||
|
// blank display
|
||||||
|
LED_CONFIG_SIGNAL.signal(LedConfig::Blank);
|
||||||
|
}
|
||||||
|
0x2 => {
|
||||||
|
// happy
|
||||||
|
LED_CONFIG_SIGNAL.signal(LedConfig::Happy);
|
||||||
|
}
|
||||||
|
0x4 => {
|
||||||
|
// hal
|
||||||
|
LED_CONFIG_SIGNAL.signal(LedConfig::Hal);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// unknown pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stable_chord = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_ticker.next().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,27 +241,35 @@ async fn led_task(mut led_surface: LedMatrix<'static>) {
|
||||||
let mut ticker = Ticker::every(Duration::from_hz(30));
|
let mut ticker = Ticker::every(Duration::from_hz(30));
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let mut led_config = LedConfig::Blank;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
rainbow.elapsed = elapsed;
|
rainbow.elapsed = elapsed;
|
||||||
|
|
||||||
|
if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() {
|
||||||
|
led_config = new_config;
|
||||||
|
}
|
||||||
|
|
||||||
led_surface.clear(Rgb888::BLACK).ok();
|
led_surface.clear(Rgb888::BLACK).ok();
|
||||||
|
match led_config {
|
||||||
|
LedConfig::Blank => {}
|
||||||
|
LedConfig::Hal => {
|
||||||
|
hal.draw(&mut led_surface).ok();
|
||||||
|
}
|
||||||
|
LedConfig::Happy => {
|
||||||
eyes.draw(&mut RainbowFilter {
|
eyes.draw(&mut RainbowFilter {
|
||||||
inner_target: &mut led_surface,
|
inner_target: &mut led_surface,
|
||||||
rainbow: &rainbow,
|
rainbow: &rainbow,
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
led_surface.sync().await;
|
led_surface.sync().await;
|
||||||
ticker.next().await;
|
ticker.next().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
|
||||||
async fn dfu_button(mut btn_y: Input<'static>) {
|
|
||||||
btn_y.wait_for_falling_edge().await;
|
|
||||||
reset_to_usb_boot(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &PanicInfo) -> ! {
|
fn panic(_info: &PanicInfo) -> ! {
|
||||||
reset_to_usb_boot(0, 0);
|
reset_to_usb_boot(0, 0);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue