//! # Pimoroni Plasma 2040 WS2812 RGB LED Example //! //! Drives 3 WS2812 LEDs connected directly to the Pimoroni Plasma 2040 via its terminal block. //! Derived from the [pico_ws2812_led](../../rp-pico/examples/pico_ws2812_led.rs) example for the Raspberry Pi Pico. #![no_std] #![no_main] // Ensure we halt the program on panic (if we don't mention this crate it won't // be linked) use panic_halt as _; // Pull in any important traits use pimoroni_plasma_2040::hal::prelude::*; // A shorter alias for the Peripheral Access Crate, which provides low-level // register access use pimoroni_plasma_2040::hal::pac; // Import the Timer for Ws2812: use pimoroni_plasma_2040::hal::timer::Timer; // A shorter alias for the Hardware Abstraction Layer, which provides // higher-level drivers. use pimoroni_plasma_2040::hal; // PIOExt for the split() method that is needed to bring // PIO0 into useable form for Ws2812: use pimoroni_plasma_2040::hal::pio::PIOExt; // Import useful traits to handle the ws2812 LEDs: use smart_leds::{brightness, SmartLedsWrite, RGB8}; // Import the actual crate to handle the Ws2812 protocol: use ws2812_pio::Ws2812; // Currently 3 consecutive LEDs are driven by this example // to keep the power draw compatible with USB: const STRIP_LEN: usize = 3; /// Entry point to our bare-metal application. /// /// The `#[pimoroni_plasma_2040::entry]` macro ensures the Cortex-M start-up code calls this function /// as soon as all global variables and the spinlock are initialised. #[pimoroni_plasma_2040::entry] fn main() -> ! { // Grab our singleton objects let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); // Configure the clocks // // The default is to generate a 125 MHz system clock let clocks = hal::clocks::init_clocks_and_plls( pimoroni_plasma_2040::XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog, ) .ok() .unwrap(); // The single-cycle I/O block controls our GPIO pins let sio = hal::Sio::new(pac.SIO); // Set the pins up according to their function on this particular board let pins = pimoroni_plasma_2040::Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); // Setup a delay for the LED blink signals: let mut frame_delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); // Import the `sin` function for a smooth hue animation from the // Pico rp2040 ROM: let sin = hal::rom_data::float_funcs::fsin::ptr(); // Create a count down timer for the Ws2812 instance: let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); // Split the PIO state machine 0 into individual objects, so that // Ws2812 can use it: let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); // Instantiate a Ws2812 LED strip: let mut ws = Ws2812::new( pins.data.into_function(), &mut pio, sm0, clocks.peripheral_clock.freq(), timer.count_down(), ); let mut leds: [RGB8; STRIP_LEN] = [(0, 0, 0).into(); STRIP_LEN]; let mut t = 0.0; // Bring down the overall brightness of the strip to not blow // the USB power supply: every LED draws ~60mA, RGB means 3 LEDs per // ws2812 LED, for 3 LEDs that would be: 3 * 3 * 60mA, which is // already 540mA for just 3 white LEDs! let strip_brightness = 64u8; // Limit brightness to 64/256 // Slow down timer by this factor (0.1 will result in 10 seconds): let animation_speed = 0.1; loop { for (i, led) in leds.iter_mut().enumerate() { // An offset to give 3 consecutive LEDs a different color: let hue_offs = match i % 3 { 1 => 0.25, 2 => 0.5, _ => 0.0, }; let sin_11 = sin((t + hue_offs) * 2.0 * core::f32::consts::PI); // Bring -1..1 sine range to 0..1 range: let sin_01 = (sin_11 + 1.0) * 0.5; let hue = 360.0 * sin_01; let sat = 1.0; let val = 1.0; let rgb = hsv2rgb_u8(hue, sat, val); *led = rgb.into(); } // Here the magic happens and the `leds` buffer is written to the // ws2812 LEDs: ws.write(brightness(leds.iter().copied(), strip_brightness)) .unwrap(); // Wait a bit until calculating the next frame: frame_delay.delay_ms(16); // ~60 FPS // Increase the time counter variable and make sure it // stays inbetween 0.0 to 1.0 range: t += (16.0 / 1000.0) * animation_speed; while t > 1.0 { t -= 1.0; } } } pub fn hsv2rgb(hue: f32, sat: f32, val: f32) -> (f32, f32, f32) { let c = val * sat; let v = (hue / 60.0) % 2.0 - 1.0; let v = if v < 0.0 { -v } else { v }; let x = c * (1.0 - v); let m = val - c; let (r, g, b) = if hue < 60.0 { (c, x, 0.0) } else if hue < 120.0 { (x, c, 0.0) } else if hue < 180.0 { (0.0, c, x) } else if hue < 240.0 { (0.0, x, c) } else if hue < 300.0 { (x, 0.0, c) } else { (c, 0.0, x) }; (r + m, g + m, b + m) } pub fn hsv2rgb_u8(h: f32, s: f32, v: f32) -> (u8, u8, u8) { let r = hsv2rgb(h, s, v); ( (r.0 * 255.0) as u8, (r.1 * 255.0) as u8, (r.2 * 255.0) as u8, ) }