//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) use embassy_time::Timer; use fixed::types::U24F8; use smart_leds::RGB8; use crate::clocks::clk_sys_freq; use crate::dma::{AnyChannel, Channel}; use crate::pio::{ Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; use crate::Peri; 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; /// This struct represents a ws2812 program loaded into pio instruction memory. pub struct PioWs2812Program<'a, PIO: Instance> { prg: LoadedProgram<'a, PIO>, } impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { /// Load the ws2812 program into the given pio pub fn new(common: &mut Common<'a, PIO>) -> Self { let side_set = pio::SideSet::new(false, 1, false); let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); 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 prg = common.load_program(&prg); Self { prg } } } /// Pio backed ws2812 driver /// Const N is the number of ws2812 leds attached to this pin pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { dma: Peri<'d, AnyChannel>, sm: StateMachine<'d, P, S>, } impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { /// Configure a pio state machine to use the loaded ws2812 program. pub fn new( pio: &mut Common<'d, P>, mut sm: StateMachine<'d, P, S>, dma: Peri<'d, impl Channel>, pin: Peri<'d, impl PioPin>, program: &PioWs2812Program<'d, P>, ) -> Self { // Setup sm0 let mut cfg = Config::default(); // Pin config let out_pin = pio.make_pio_pin(pin); cfg.set_out_pins(&[&out_pin]); cfg.set_set_pins(&[&out_pin]); cfg.use_program(&program.prg, &[&out_pin]); // Clock config, measured in kHz to avoid overflows let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); let ws2812_freq = U24F8::from_num(800); let bit_freq = ws2812_freq * CYCLES_PER_BIT; cfg.clock_divider = clock_freq / bit_freq; // FIFO config cfg.fifo_join = FifoJoin::TxOnly; cfg.shift_out = ShiftConfig { auto_fill: true, threshold: 24, direction: ShiftDirection::Left, }; sm.set_config(&cfg); sm.set_enable(true); Self { dma: dma.into(), sm } } /// Write a buffer of [smart_leds::RGB8] to the ws2812 string pub async fn write(&mut self, colors: &[RGB8; N]) { // Precompute the word bytes from the colors let mut words = [0u32; N]; for i in 0..N { let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); words[i] = word; } // DMA transfer self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; Timer::after_micros(55).await; } }