The example spins too fast so it doesn't appear to change; it's delaying for microseconds instead of milliseconds. This commit slows it down and adds a comment noting the pin mapping for the Adafruit feather rp2040+RFM95 LoRA module, which has its Neopixel on pin 4 instead of 16.
		
			
				
	
	
		
			160 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! 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::{Duration, 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<PIO0>;
 | 
						|
});
 | 
						|
 | 
						|
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<P = impl Channel> + '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(Duration::from_millis(10)).await;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |