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 embassy_executor::Spawner;
|
||||
use embassy_futures::select::select_array;
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_rp::gpio::{Input, Level, Output, Pull};
|
||||
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::rom_data::reset_to_usb_boot;
|
||||
use embassy_rp::spi::{self, Blocking, Spi};
|
||||
use embassy_time::{Delay, Duration, Instant, Ticker};
|
||||
use embedded_graphics::image::{Image, ImageRawLE};
|
||||
use embedded_graphics::mono_font::MonoTextStyle;
|
||||
use embedded_graphics::mono_font::ascii::FONT_10X20;
|
||||
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle};
|
||||
use embedded_graphics::text::Text;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::signal::Signal;
|
||||
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
|
||||
use embedded_graphics::pixelcolor::Rgb888;
|
||||
use embedded_graphics::prelude::{DrawTarget, Drawable, RgbColor, Size};
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use heapless::String;
|
||||
use mipidsi::interface::SpiInterface;
|
||||
use mipidsi::models::ST7789;
|
||||
use mipidsi::options::Orientation;
|
||||
|
|
@ -45,24 +40,22 @@ const DISPLAY_FREQ: u32 = 64_000_000;
|
|||
async fn main(spawner: Spawner) -> ! {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
// Setup buttons
|
||||
let _btn_a = Input::new(p.PIN_12, Pull::Up);
|
||||
let _btn_b = Input::new(p.PIN_13, Pull::Up);
|
||||
// let _btn_x = Input::new(p.PIN_14, Pull::Up);
|
||||
let btn_y = Input::new(p.PIN_15, Pull::Up);
|
||||
|
||||
let mut btn_f1 = Input::new(p.PIN_11, Pull::Up);
|
||||
let mut btn_f2 = Input::new(p.PIN_14, Pull::Up);
|
||||
let mut btn_f3 = Input::new(p.PIN_9, Pull::Up);
|
||||
let mut btn_f4 = Input::new(p.PIN_3, Pull::Up);
|
||||
|
||||
spawner.spawn(dfu_button(btn_y)).ok();
|
||||
let buttons = Buttons {
|
||||
f1: Input::new(p.PIN_11, Pull::Up),
|
||||
f2: Input::new(p.PIN_14, Pull::Up),
|
||||
f3: Input::new(p.PIN_9, Pull::Up),
|
||||
f4: Input::new(p.PIN_3, Pull::Up),
|
||||
a: Input::new(p.PIN_12, Pull::Up),
|
||||
b: Input::new(p.PIN_13, Pull::Up),
|
||||
// x = f2
|
||||
y: Input::new(p.PIN_15, Pull::Up),
|
||||
};
|
||||
|
||||
// display SPI
|
||||
|
||||
let mut display_buffer = [0u8; 512];
|
||||
|
||||
let mut display = {
|
||||
let mut _display = {
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = DISPLAY_FREQ;
|
||||
config.phase = spi::Phase::CaptureOnSecondTransition;
|
||||
|
|
@ -85,17 +78,8 @@ async fn main(spawner: Spawner) -> ! {
|
|||
.init(&mut Delay)
|
||||
.expect("display init")
|
||||
};
|
||||
let _display_backlight = Output::new(p.PIN_20, Level::High);
|
||||
|
||||
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,
|
||||
);
|
||||
// Blank display - unused at the moment
|
||||
let _display_backlight = Output::new(p.PIN_20, Level::Low);
|
||||
|
||||
// setup RGB LED
|
||||
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 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_b = 0x101 * (255 - g 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(controller_task(buttons)).ok();
|
||||
|
||||
let mut render = |f_keys: [bool; 4]| {
|
||||
display.clear(Rgb565::BLACK).ok();
|
||||
ferris.draw(&mut display).ok();
|
||||
hello_text.draw(&mut display).ok();
|
||||
// go to sleep
|
||||
loop {
|
||||
Timer::after_secs(1).await
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
struct Buttons {
|
||||
f1: Input<'static>,
|
||||
f2: Input<'static>,
|
||||
f3: Input<'static>,
|
||||
f4: Input<'static>,
|
||||
a: Input<'static>,
|
||||
b: Input<'static>,
|
||||
// x = f2
|
||||
y: Input<'static>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text::new(&f_key_string, Point::new(0, 108), style)
|
||||
.draw(&mut display)
|
||||
.ok();
|
||||
};
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
struct ButtonStates {
|
||||
f1: bool,
|
||||
f2: bool,
|
||||
f3: bool,
|
||||
f4: bool,
|
||||
a: bool,
|
||||
b: bool,
|
||||
// x = f2
|
||||
y: bool,
|
||||
}
|
||||
|
||||
let mut last_state = None;
|
||||
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 {
|
||||
let state = [
|
||||
btn_f1.is_low(),
|
||||
btn_f2.is_low(),
|
||||
btn_f3.is_low(),
|
||||
btn_f4.is_low(),
|
||||
];
|
||||
if Some(state) != last_state {
|
||||
render(state);
|
||||
last_state = Some(state);
|
||||
continue;
|
||||
let state = buttons.read();
|
||||
let new_chord = state.chord();
|
||||
|
||||
if state.y {
|
||||
reset_to_usb_boot(0, 0);
|
||||
}
|
||||
select_array([
|
||||
btn_f1.wait_for_any_edge(),
|
||||
btn_f2.wait_for_any_edge(),
|
||||
btn_f3.wait_for_any_edge(),
|
||||
btn_f4.wait_for_any_edge(),
|
||||
])
|
||||
.await;
|
||||
|
||||
if new_chord != pending_chord {
|
||||
// Track the most recent chord key state.
|
||||
pending_chord = new_chord;
|
||||
chord_debounce = 10;
|
||||
} else if chord_debounce > 0 {
|
||||
// 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 start = Instant::now();
|
||||
|
||||
let mut led_config = LedConfig::Blank;
|
||||
|
||||
loop {
|
||||
let elapsed = start.elapsed();
|
||||
rainbow.elapsed = elapsed;
|
||||
|
||||
if let Some(new_config) = LED_CONFIG_SIGNAL.try_take() {
|
||||
led_config = new_config;
|
||||
}
|
||||
|
||||
led_surface.clear(Rgb888::BLACK).ok();
|
||||
eyes.draw(&mut RainbowFilter {
|
||||
inner_target: &mut led_surface,
|
||||
rainbow: &rainbow,
|
||||
})
|
||||
.ok();
|
||||
match led_config {
|
||||
LedConfig::Blank => {}
|
||||
LedConfig::Hal => {
|
||||
hal.draw(&mut led_surface).ok();
|
||||
}
|
||||
LedConfig::Happy => {
|
||||
eyes.draw(&mut RainbowFilter {
|
||||
inner_target: &mut led_surface,
|
||||
rainbow: &rainbow,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
led_surface.sync().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]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
reset_to_usb_boot(0, 0);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue