//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. //! See (https://www.sparkfun.com/categories/tags/ws2812) #![no_std] #![no_main] #![feature(type_alias_impl_trait)] use defmt::*; use embassy_executor::Spawner; use embassy_rp::dma::{AnyChannel, Channel}; use embassy_rp::peripherals::PIO0; use embassy_rp::pio::{ Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; use embassy_time::Timer; use fixed::types::U24F8; use fixed_macro::fixed; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { dma: PeripheralRef<'d, AnyChannel>, sm: StateMachine<'d, P, S>, } impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { pub fn new( pio: &mut Common<'d, P>, mut sm: StateMachine<'d, P, S>, dma: impl Peripheral

+ 'd, pin: impl PioPin, ) -> Self { into_ref!(dma); // Setup sm0 // 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 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(&pio.load_program(&prg), &[&out_pin]); // Clock config, measured in kHz to avoid overflows // TODO CLOCK_FREQ should come from embassy_rp let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); let ws2812_freq = fixed!(800: U24F8); 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.map_into(), sm, } } 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).await; } } /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { wheel_pos = 255 - wheel_pos; if wheel_pos < 85 { return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); } if wheel_pos < 170 { wheel_pos -= 85; return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); } wheel_pos -= 170; (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() } #[embassy_executor::main] async fn main(_spawner: Spawner) { info!("Start"); let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit // feather boards for the 2040 both have one built in. const NUM_LEDS: usize = 1; let mut data = [RGB8::default(); NUM_LEDS]; // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); // Loop forever making RGB values and pushing them out to the WS2812. loop { for j in 0..(256 * 5) { debug!("New Colors:"); for i in 0..NUM_LEDS { data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); } ws2812.write(&data).await; Timer::after_millis(10).await; } } }