Get most basics working

- Control (at least) 1 neopixel
- Add basic blinky panic handler
- Support N-key rollover
- Test GPIO pins
This commit is contained in:
2023-03-14 00:13:16 +01:00
parent 06a47b7a2c
commit 5fa52f7f48
12 changed files with 665 additions and 125 deletions

View File

@ -1,6 +1,8 @@
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_rp::gpio::{AnyPin, Input, Pin, Pull};
use embassy_time::{Duration, Timer};
use log::info;
pub const ROWS: usize = 3;
pub const COLS: usize = 5;
@ -62,6 +64,18 @@ pub enum Button {
Key { keycode: u8 },
}
#[embassy_executor::task(pool_size = 20)]
pub async fn monitor_switch(pin: AnyPin) -> ! {
let pin_nr = pin.pin();
let mut pin = Input::new(pin, Pull::Up);
loop {
pin.wait_for_low().await;
info!("pin {pin_nr} low");
pin.wait_for_high().await;
info!("pin {pin_nr} high");
}
}
#[allow(dead_code)]
impl Button {
pub const fn key(keycode: u8) -> Self {
@ -120,68 +134,103 @@ impl Button {
pub const KEY_RALT: Button = Button::key(0xE6);
}
pub fn letter_to_key(c: char) -> Option<Button> {
if !c.is_ascii() {
return None;
}
/// Random functions for testing
#[allow(dead_code)]
pub mod test {
use super::*;
let c = c.to_ascii_uppercase();
let key = match c {
'A' => Button::KEY_A,
'B' => Button::KEY_B,
'C' => Button::KEY_C,
'D' => Button::KEY_D,
'E' => Button::KEY_E,
'F' => Button::KEY_F,
'G' => Button::KEY_G,
'H' => Button::KEY_H,
'I' => Button::KEY_I,
'J' => Button::KEY_J,
'K' => Button::KEY_K,
'L' => Button::KEY_L,
'M' => Button::KEY_M,
'N' => Button::KEY_N,
'O' => Button::KEY_O,
'P' => Button::KEY_P,
'Q' => Button::KEY_Q,
'R' => Button::KEY_R,
'S' => Button::KEY_S,
'T' => Button::KEY_T,
'U' => Button::KEY_U,
'V' => Button::KEY_V,
'W' => Button::KEY_W,
'X' => Button::KEY_X,
'Y' => Button::KEY_Y,
'Z' => Button::KEY_Z,
' ' => Button::KEY_SPACE,
'\n' => Button::KEY_RETURN,
_ => {
log::info!("char {c:?} -> None");
pub fn letter_to_key(c: char) -> Option<Button> {
if !c.is_ascii() {
return None;
}
};
log::info!("char {c:?} -> {key:?}");
let c = c.to_ascii_uppercase();
Some(key)
}
let key = match c {
'A' => Button::KEY_A,
'B' => Button::KEY_B,
'C' => Button::KEY_C,
'D' => Button::KEY_D,
'E' => Button::KEY_E,
'F' => Button::KEY_F,
'G' => Button::KEY_G,
'H' => Button::KEY_H,
'I' => Button::KEY_I,
'J' => Button::KEY_J,
'K' => Button::KEY_K,
'L' => Button::KEY_L,
'M' => Button::KEY_M,
'N' => Button::KEY_N,
'O' => Button::KEY_O,
'P' => Button::KEY_P,
'Q' => Button::KEY_Q,
'R' => Button::KEY_R,
'S' => Button::KEY_S,
'T' => Button::KEY_T,
'U' => Button::KEY_U,
'V' => Button::KEY_V,
'W' => Button::KEY_W,
'X' => Button::KEY_X,
'Y' => Button::KEY_Y,
'Z' => Button::KEY_Z,
' ' => Button::KEY_SPACE,
'\n' => Button::KEY_RETURN,
_ => {
log::info!("char {c:?} -> None");
return None;
}
};
pub async fn test_type(s: &str) {
log::info!("typing {s:?}");
log::info!("char {c:?} -> {key:?}");
for c in s.chars() {
let Some(key) = letter_to_key(c) else {
Some(key)
}
pub async fn type_string(s: &str) {
log::info!("typing {s:?}");
for c in s.chars() {
let Some(key) = letter_to_key(c) else {
continue;
};
for col in 0..COLS {
for row in 0..ROWS {
if TEST_KEYMAP[row][col] == key {
MATRIX[row][col].press();
Timer::after(Duration::from_millis(20)).await;
MATRIX[row][col].release();
Timer::after(Duration::from_millis(5)).await;
for col in 0..COLS {
for row in 0..ROWS {
if TEST_KEYMAP[row][col] == key {
MATRIX[row][col].press();
Timer::after(Duration::from_millis(20)).await;
MATRIX[row][col].release();
Timer::after(Duration::from_millis(5)).await;
}
}
}
}
}
/// Press all keys at once
pub async fn rollover<const N: usize>(letters: [char; N]) {
log::info!("pressing all letters in {letters:?}");
let keys = letters.map(letter_to_key);
for key in &keys {
for col in 0..COLS {
for row in 0..ROWS {
if Some(&TEST_KEYMAP[row][col]) == key.as_ref() {
MATRIX[row][col].press();
}
}
}
}
Timer::after(Duration::from_millis(200)).await;
for key in &keys {
for col in 0..COLS {
for row in 0..ROWS {
if Some(&TEST_KEYMAP[row][col]) == key.as_ref() {
MATRIX[row][col].release();
}
}
}
}

View File

@ -11,16 +11,23 @@
#![feature(type_alias_impl_trait)]
extern crate cortex_m_rt;
extern crate panic_halt;
//extern crate panic_halt;
mod board;
mod keyboard;
mod neopixel;
mod panic_handler;
mod usb;
mod ws2812;
use board::Board;
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::{
gpio::{Level, Output, Pin},
pio::PioPeripheral,
};
use embassy_time::{Duration, Timer};
use ws2812::Rgb;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
@ -54,12 +61,21 @@ async fn main(spawner: Spawner) {
neopixel_power: p.PIN_16,
};
let mut led = Output::new(board.d13, Level::High);
//let mut led = Output::new(board.d13, Level::Low);
let _neopixel_power = Output::new(board.neopixel_power, Level::High);
let (_, sm, ..) = p.PIO0.split();
let mut neopixel = ws2812::Ws2812::new(sm, p.DMA_CH0, board.neopixel.degrade());
//let mut neopixel = ws2812::Ws2812::new(sm, p.DMA_CH0, board.d5.degrade());
neopixel.write(&[Rgb::new(0xb7, 0x31, 0x2c)]).await;
let mut builder = usb::builder(p.USB);
usb::logger::setup(&mut builder).await;
neopixel.write(&[Rgb::new(0xf0, 0xd0, 0x20)]).await;
log::error!("log_level: error");
log::warn!("log_level: warn");
log::info!("log_level: info");
@ -72,12 +88,41 @@ async fn main(spawner: Spawner) {
spawner.must_spawn(usb::run(usb));
Timer::after(Duration::from_millis(1000)).await;
Timer::after(Duration::from_millis(3000)).await;
crate::keyboard::test_type("Hello there!\n").await;
spawner.must_spawn(keyboard::monitor_switch(board.a0.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.a1.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.a2.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.a3.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d2.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d3.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d4.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d7.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d9.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d10.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d11.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d12.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d24.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.d25.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.scl.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.sda.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.mosi.degrade()));
spawner.must_spawn(keyboard::monitor_switch(board.miso.degrade()));
//keyboard::test::type_string("Hello there!\n").await;
//keyboard::test::rollover(['h', 'e', 'l', 'o', 't', 'r', 'a', 'b', 'c', 'd', 'i']).await;
loop {
Timer::after(Duration::from_millis(500)).await;
led.toggle();
//neopixel
// .write(&[
// Rgb::new(0xAA, 0xFF, 0x00),
// Rgb::new(0xAA, 0xFF, 0x00),
// Rgb::new(0xAA, 0xFF, 0x00),
// Rgb::new(0xAA, 0xFF, 0x00),
// Rgb::new(0xAA, 0xFF, 0x00),
// Rgb::new(0xAA, 0xFF, 0x00),
// ])
// .await;
//Timer::after(Duration::from_millis(10)).await;
Timer::after(Duration::from_secs(10)).await;
}
}

11
src/neopixel.pio Normal file
View File

@ -0,0 +1,11 @@
.program ws2812
.side_set 1
.wrap_target
bitloop:
out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls.
jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high.
do_one:
jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse)
do_zero:
nop side 0 [4]; Or drive low, for a zero (short pulse)
.wrap

122
src/neopixel.rs Normal file
View File

@ -0,0 +1,122 @@
/*
use embassy_rp::{
gpio::{AnyPin, Drive, SlewRate},
peripherals::{DMA_CH0, PIO0},
pio::{FifoJoin, PioPeripheral, PioStateMachine, ShiftDirection},
pio_instr_util,
relocate::RelocatedProgram,
PeripheralRef,
};
use embassy_time::{Duration, Timer};
#[embassy_executor::task]
pub async fn test(pio: PIO0, pin: AnyPin, dma: DMA_CH0) {
let (_common, mut sm, ..) = pio.split();
let mut dma = PeripheralRef::new(dma);
let pio_program = pio_proc::pio_file!("src/neopixel.pio");
let relocated = RelocatedProgram::new(&pio_program.program);
sm.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
let pin = sm.make_pio_pin(pin);
sm.set_set_pins(&[&pin]);
sm.set_sideset_base_pin(&pin);
sm.set_sideset_count(1);
// Clock config
// TODO CLOCK_FREQ should come from embassy_rp
const CLOCK_FREQ: u32 = 125_000_000;
const WS2812_FREQ: u32 = 800_000;
const CYCLES_PER_BIT: u32 = 16;
let bit_freq = WS2812_FREQ * CYCLES_PER_BIT;
let mut int = CLOCK_FREQ / bit_freq;
let rem = CLOCK_FREQ - (int * bit_freq);
let frac = (rem * 256) / bit_freq;
// 65536.0 is represented as 0 in the pio's clock divider
if int == 65536 {
int = 0;
}
sm.set_clkdiv((int << 8) | frac);
let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target);
sm.set_autopull(true);
sm.set_fifo_join(FifoJoin::TxOnly);
sm.set_pull_threshold(8); // 24?
sm.set_out_shift_dir(ShiftDirection::Left);
sm.set_enable(true);
log::info!("wrap: {:?}", sm.get_wrap());
log::info!("addr: {:?}", sm.get_addr());
log::info!("sideset_base: {:?}", sm.get_sideset_base());
log::info!("sideset_count: {:?}", sm.get_sideset_count());
log::info!("in_base: {:?}", sm.get_in_base());
log::info!("jmp_pin: {:?}", sm.get_jmp_pin());
log::info!("set_range: {:?}", sm.get_set_range());
log::info!("out_range: {:?}", sm.get_out_range());
log::info!("pull_threshold: {:?}", sm.get_pull_threshold());
log::info!("push_threshold: {:?}", sm.get_push_threshold());
//sm = rp2pio.StateMachine(
// assembled,
// frequency=12_800_000, # to get appropriate sub-bit times in PIO program
// first_sideset_pin=NEOPIXEL,
// auto_pull=True,
// out_shift_right=False,
// pull_threshold=8,
//)
loop {
log::info!("sending dma");
sm.dma_push(dma.reborrow(), &[0x0a, 0x00, 0x00]).await;
Timer::after(Duration::from_millis(500)).await;
sm.dma_push(dma.reborrow(), &[0x00, 0x0a, 0x00]).await;
Timer::after(Duration::from_millis(500)).await;
sm.dma_push(dma.reborrow(), &[0x00, 0x00, 0x0a]).await;
Timer::after(Duration::from_millis(500)).await;
//sm0.set_enable(true);
}
}
#[embassy_executor::task]
pub async fn test_blink(pio: PIO0, pin: AnyPin) {
log::info!("test blink hehe");
let (_, mut sm, ..) = pio.split();
// Setup sm2
// blink
let prg = pio_proc::pio_file!("src/blink.pio");
let relocated = RelocatedProgram::new(&prg.program);
let out_pin = sm.make_pio_pin(pin);
let pio_pins = [&out_pin];
sm.set_set_pins(&pio_pins);
sm.set_set_range(25, 1);
sm.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
// sm.set_clkdiv((65535 << 8) + 255 as u32);
// sm.set_clkdiv(0);
let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target);
// sm.set_clkdiv((125e6 / 20.0 / 2e2 * 256.0) as u32);
sm.set_enable(true);
// sm.wait_push().await as i32;
// sm.push_tx(1);
sm.wait_push(125_000_000).await;
log::info!("started");
loop {
sm.wait_irq(3).await;
log::info!("did it!");
}
}
*/

8
src/neopixel2.pio Normal file
View File

@ -0,0 +1,8 @@
.program ws2812
.origin 0
.wrap_target
out x 1
set pins,1 [1]
mov pins,x [1]
set pins,0
.wrap

28
src/panic_handler.rs Normal file
View File

@ -0,0 +1,28 @@
use core::panic::PanicInfo;
use embassy_executor::Executor;
use embassy_rp::gpio::{AnyPin, Level, Output, Pin};
use embassy_time::{Duration, Timer};
use static_cell::StaticCell;
#[panic_handler]
fn panic_blink(_info: &PanicInfo) -> ! {
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
// SAFETY: we panicked, so no other code will be running.
let p = unsafe { embassy_rp::Peripherals::steal() };
EXECUTOR.init(Executor::new()).run(|spawner| {
spawner.spawn(blink(p.PIN_11.degrade())).ok();
});
}
#[embassy_executor::task]
async fn blink(led: AnyPin) {
let mut led = Output::new(led, Level::High);
loop {
Timer::after(Duration::from_secs(1)).await;
led.toggle();
}
}

View File

@ -25,7 +25,7 @@ pub fn builder(usb: USB) -> Builder<'static, Driver<'static, USB>> {
});
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
let mut config = Config::new(0xb00b, 0x1355);
config.manufacturer = Some("Tux");
config.product = Some("Tangentbord1");
config.serial_number = Some("42069");

View File

@ -1,3 +1,5 @@
pub mod report;
use embassy_executor::Spawner;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_time::{Duration, Timer};
@ -8,9 +10,12 @@ use embassy_usb::{
};
use embassy_usb_driver::EndpointError;
use static_cell::StaticCell;
use usbd_hid::descriptor::{KeyboardReport, MouseReport, SerializedDescriptor};
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
use crate::keyboard::{Button, COLS, MATRIX, ROWS, TEST_KEYMAP};
use crate::{
keyboard::{Button, COLS, MATRIX, ROWS, TEST_KEYMAP},
usb::keyboard::report::{KeyboardReport, EMPTY_KEYBOARD_REPORT},
};
use super::MAX_PACKET_SIZE;
@ -84,44 +89,50 @@ async fn task(stream: HidStream, handler: &'static Handler) {
//}
}
async fn keyboard_test(mut stream: HidStream, handler: &'static Handler) -> Result<(), Error> {
async fn keyboard_test(mut stream: HidStream, _handler: &'static Handler) -> Result<(), Error> {
stream.ready().await;
loop {
Timer::after(Duration::from_millis(2)).await;
let keymap = &TEST_KEYMAP;
let mut keycodes = [0u8; 6];
let mut report = EMPTY_KEYBOARD_REPORT;
#[cfg(not(feature = "n-key-rollover"))]
let mut i = 0;
#[allow(unused_labels)]
'keyscan: for col in 0..COLS {
for row in 0..ROWS {
if !MATRIX[row][col].is_pressed() {
continue;
}
let Button::Key { keycode } = &keymap[row][col];
let &Button::Key { keycode } = &keymap[row][col];
// else { continue; };
keycodes[i] = *keycode;
i += 1;
if i >= keycodes.len() {
break 'keyscan;
#[cfg(feature = "n-key-rollover")]
report.set_key(keycode);
#[cfg(not(feature = "n-key-rollover"))]
{
report.keycodes[i] = keycode;
i += 1;
if i >= report.keycodes.len() {
break 'keyscan;
}
}
}
}
if keycodes.iter().any(|&b| b != 0) {
log::info!("keycodes: {keycodes:?}");
if report.keycodes != EMPTY_KEYBOARD_REPORT.keycodes {
log::debug!("keys: {:x?}", report.keycodes);
}
stream
.write_serialize(&KeyboardReport {
modifier: 0,
reserved: 0,
leds: 0,
keycodes,
})
.await?;
#[cfg(feature = "n-key-rollover")]
stream.write(&report.serialized()).await?;
#[cfg(not(feature = "n-key-rollover"))]
stream.write_serialize(&report).await?;
}
}

104
src/usb/keyboard/report.rs Normal file
View File

@ -0,0 +1,104 @@
#![allow(dead_code)]
/// KeyboardReport describes a report and its companion descriptor that can be
/// used to send keyboard button presses to a host and receive the status of the
/// keyboard LEDs.
///
/// Unlike usbd_hids KeyboardReport, this one supports N-key rollover.
#[derive(PartialEq, Eq)]
#[cfg(feature = "n-key-rollover")]
pub struct KeyboardReport {
pub modifier: u8,
/// Bitmap representing all keycodes from 0 to 104
pub keycodes: [u8; 13],
}
#[cfg(not(feature = "n-key-rollover"))]
pub use usbd_hid::descriptor::KeyboardReport;
#[cfg(feature = "n-key-rollover")]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
modifier: 0,
keycodes: [0; 13],
};
#[cfg(not(feature = "n-key-rollover"))]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
modifier: 0,
leds: 0,
reserved: 0,
keycodes: [0; 6],
};
#[cfg(feature = "n-key-rollover")]
impl KeyboardReport {
pub fn set_key(&mut self, keycode: u8) {
log::info!("setting keycode: {keycode}");
let byte = keycode >> 3;
let bit = keycode & 0b111;
let mask = 1 << bit;
if let Some(k) = self.keycodes.get_mut(byte as usize) {
*k |= mask;
} else {
log::warn!("Tried to set out-of-range keycode: {keycode:x}");
}
}
pub fn serialized(&self) -> [u8; 14] {
let [a, b, c, d, e, f, g, h, i, j, k, l, m] = self.keycodes;
[self.modifier, a, b, c, d, e, f, g, h, i, j, k, l, m]
}
}
// bitmasks for the `modifier` field
pub const MOD_LCTRL: u8 = 0x01;
pub const MOD_LSHIFT: u8 = 0x02;
pub const MOD_LALT: u8 = 0x04;
pub const MOD_LSUPER: u8 = 0x08;
pub const MOD_RCTRL: u8 = 0x10;
pub const MOD_RSHIFT: u8 = 0x20;
pub const MOD_RALT: u8 = 0x40;
pub const MOD_RSUPER: u8 = 0x80;
#[cfg(feature = "n-key-rollover")]
impl usbd_hid::descriptor::SerializedDescriptor for KeyboardReport {
fn desc() -> &'static [u8] {
// Manually define the descriptor since I can't figure out how to get
// gen_hid_descriptor to generate the correct one.
&[
0x05, 0x01, // usage page 1 (generic desktop)
0x09, 0x06, // usage (keyboard)
0xa1, 0x01, // collection (application)
//
0x05, 0x07, // usage page 7 (keyboard/keypad)
0x19, 0xe0, // local usage minimum
0x29, 0xe7, // local usage maximum
0x15, 0x00, // local minimum
0x25, 0x01, // local maximum
0x75, 0x01, // report size (1 bit)
0x95, 0x08, // report count (8)
0x81, 0x02, // input (variable)
//
0x19, 0x00, // local usage minimum
0x29, 0x67, // local usage maximum
0x95, 0x68, // report count
0x81, 0x02, // input (variable)
//
0x05, 0x08, // usage page 8 (led page)
0x19, 0x01, // local usage minimum
0x29, 0x05, // local usage maximum
0x15, 0x00, // logical min
0x25, 0x01, // logical max
0x75, 0x01, // report size
0x95, 0x05, // report count
0x91, 0x02, // output (variable)
//
0x75, 0x03, // report size
0x95, 0x01, // report count
0x91, 0x01, // output (constant)
0xc0, // end collection
]
}
}

126
src/ws2812.rs Normal file
View File

@ -0,0 +1,126 @@
use core::fmt::{self, Debug};
use core::mem::transmute;
use embassy_rp::dma::{self, AnyChannel};
use embassy_rp::pio::{
FifoJoin, PioInstance, PioStateMachine, PioStateMachineInstance, ShiftDirection, SmInstance,
};
use embassy_rp::pio_instr_util;
use embassy_rp::relocate::RelocatedProgram;
use embassy_rp::{gpio, PeripheralRef};
pub struct Ws2812<P: PioInstance, S: SmInstance> {
sm: PioStateMachineInstance<P, S>,
dma: PeripheralRef<'static, AnyChannel>,
}
/// An Rgb value that can be safely transmuted to u32 for use with Ws2812.
#[repr(transparent)]
pub struct Rgb(u32);
impl<P: PioInstance, S: SmInstance> Ws2812<P, S> {
pub fn new(
mut sm: PioStateMachineInstance<P, S>,
dma: impl dma::Channel,
pin: gpio::AnyPin,
) -> Self {
// prepare the PIO program
let side_set = pio::SideSet::new(false, 1, false);
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
const T1: u8 = 2; // start bit
const T2: u8 = 5; // data bit
const T3: u8 = 3; // stop bit
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
let mut wrap_target = a.label();
let mut wrap_source = a.label();
let mut do_zero = a.label();
a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
a.bind(&mut wrap_target);
// Do stop bit
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
// Do start bit
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
// Do data bit = 1
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
a.bind(&mut do_zero);
// Do data bit = 0
a.nop_with_delay_and_side_set(T2 - 1, 0);
a.bind(&mut wrap_source);
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
let relocated = RelocatedProgram::new(&prg);
sm.write_instr(relocated.origin() as usize, relocated.code());
pio_instr_util::exec_jmp(&mut sm, relocated.origin());
// Pin config
let out_pin = sm.make_pio_pin(pin);
sm.set_set_pins(&[&out_pin]);
sm.set_sideset_base_pin(&out_pin);
sm.set_sideset_count(1);
// Clock config
// TODO CLOCK_FREQ should come from embassy_rp
const CLOCK_FREQ: u32 = 125_000_000;
const WS2812_FREQ: u32 = 800_000;
let bit_freq = WS2812_FREQ * CYCLES_PER_BIT;
let mut int = CLOCK_FREQ / bit_freq;
let rem = CLOCK_FREQ - (int * bit_freq);
let frac = (rem * 256) / bit_freq;
// 65536.0 is represented as 0 in the pio's clock divider
if int == 65536 {
int = 0;
}
sm.set_clkdiv((int << 8) | frac);
let pio::Wrap { source, target } = relocated.wrap();
sm.set_wrap(source, target);
// FIFO config
sm.set_autopull(true);
sm.set_fifo_join(FifoJoin::TxOnly);
sm.set_pull_threshold(24);
sm.set_out_shift_dir(ShiftDirection::Left);
sm.set_enable(true);
Self {
sm,
dma: PeripheralRef::new(dma.degrade()),
}
}
pub async fn write(&mut self, colors: &[Rgb]) {
let colors = rgbs_to_u32s(colors);
self.sm.dma_push(self.dma.reborrow(), colors).await;
}
}
#[inline(always)]
fn rgbs_to_u32s(rgbs: &[Rgb]) -> &[u32] {
// SAFETY: Rgb contains only a u32, and is #[repr(transparent)]
unsafe { transmute(rgbs) }
}
impl Rgb {
#[inline(always)]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self(u32::from_be_bytes([g, r, b, 0]))
}
#[inline(always)]
pub const fn components(&self) -> (u8, u8, u8) {
let [g, r, b, _] = self.0.to_be_bytes();
(r, g, b)
}
}
impl Debug for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (r, g, b) = self.components();
f.debug_tuple("Rgb").field(&r).field(&g).field(&b).finish()
}
}