embassy/examples/rp/src/bin/pio_ws2812.rs
pennae cbc8871a0b rp: relocate programs implicitly during load
this removed the RelocatedProgram construction step from pio uses.
there's not all that much to be said for the extra step because the
origin can be set on the input program itself, and the remaining
information exposed by RelocatedProgram can be exposed from
LoadedProgram instead (even though it's already available on the pio_asm
programs, albeit perhaps less convenient). we do lose access to the
relocated instruction iterator, but we also cannot think of anything
this iterator would actually be useful for outside of program loading.
2023-07-28 19:33:02 +02:00

159 lines
5.1 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];
// For the thing plus, use pin 8
// For the feather, use pin 16
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_micros(5)).await;
}
}
}