From 2b49bc6b604548270a424228053aa6a67d8df0f6 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Sun, 9 Jan 2022 15:27:29 +0100 Subject: [PATCH] Add Raspberry Pi Pico WS2812 LED example --- boards/rp-pico/Cargo.toml | 2 + boards/rp-pico/examples/pico_ws2812_led.rs | 224 +++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 boards/rp-pico/examples/pico_ws2812_led.rs diff --git a/boards/rp-pico/Cargo.toml b/boards/rp-pico/Cargo.toml index 13e636d..4e47d6c 100644 --- a/boards/rp-pico/Cargo.toml +++ b/boards/rp-pico/Cargo.toml @@ -29,6 +29,8 @@ nb = "1.0" i2c-pio = { git = "https://github.com/ithinuel/i2c-pio-rs", rev = "df06e4ac94a5b2c985d6a9426dc4cc9be0d535c0" } heapless = "0.7.9" embedded-sdmmc = { git = "https://github.com/rust-embedded-community/embedded-sdmmc-rs.git" } +smart-leds = "0.3.0" +ws2812-pio = { git = "https://github.com/ithinuel/ws2812-pio-rs", rev = "4f0d81e594ea9934f9c4c38ed9824ad0cce4ebb5" } defmt = "0.2.0" defmt-rtt = "0.2.0" diff --git a/boards/rp-pico/examples/pico_ws2812_led.rs b/boards/rp-pico/examples/pico_ws2812_led.rs new file mode 100644 index 0000000..1aac83b --- /dev/null +++ b/boards/rp-pico/examples/pico_ws2812_led.rs @@ -0,0 +1,224 @@ +//! # Pico WS2812 RGB LED Example +//! +//! Drives 3 WS2812 LEDs connected directly to the Raspberry Pi Pico. +//! This assumes you drive the Raspberry Pi Pico via USB power, so that VBUS +//! delivers the 5V and at least enough amperes to drive the LEDs. +//! +//! For a more large scale and longer strips you should use an extra power +//! supply for the LED strip (or know what you are doing ;-) ). +//! +//! The example also comes with an utility function to calculate the colors +//! from HSV color space. It also limits the brightness a bit to save a +//! few amperes. +//! +//! The example assumes you connected the data input to pin 6 of the +//! Raspberry Pi Pico, which is GPIO4 of the rp2040. Here is a circuit +//! diagram that shows the assumed setup: +//! +//! ```text +//! _______________ /----------------------\ +//! |+5V /---\ +5V|----/ _|USB|_ | +//! |DO <-|LED|<- DI|-\ |1 R 40|-VBUS-/ +//! |GND \---/ GND|--+---\ |2 P 39| +//! """"""""""""""" | \-GND-|3 38| +//! | |4 P 37| +//! | |5 I 36| +//! \------GP4-|6 C | +//! |7 O | +//! | | +//! ......... +//! |20 21| +//! """"""" +//! Symbols: +//! - (+) crossing lines, not connected +//! - (o) connected lines +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use cortex_m_rt::entry; + +// 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 rp_pico::hal::prelude::*; + +// Embed the `Hz` function/trait: +use embedded_time::rate::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use rp_pico::hal::pac; + +// Import the Timer for Ws2812: +use rp_pico::hal::timer::Timer; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use rp_pico::hal; + +// PIOExt for the split() method that is needed to bring +// PIO0 into useable form for Ws2812: +use rp_pico::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] +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( + rp_pico::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 = rp_pico::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().integer()); + + // Import the `sin` function for a smooth hue animation from the + // Pico rp2040 ROM: + let sin = unsafe { + core::mem::transmute::<_, fn(f32) -> f32>(rp_pico::hal::rom_data::float_funcs::fsin()) + }; + + // Create a count down timer for the Ws2812 instance: + let timer = Timer::new(pac.TIMER, &mut pac.RESETS); + + // 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); + + // Instanciate a Ws2812 LED strip: + let mut ws = Ws2812::new( + // Use pin 6 on the Raspberry Pi Pico (which is GPIO4 of the rp2040 chip) + // for the LED data output: + pins.gpio4.into_mode(), + &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 save + // a few amperes (every LED draws ~60mA, RGB means 3 LEDs per + // ws2812 LED, for 3 LEDs that would be: 3 * 3 * 60mA, which is + // already 540mA! + 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 (0.0..60.0).contains(&hue) { + (c, x, 0.0) + } else if (60.0..120.0).contains(&hue) { + (x, c, 0.0) + } else if (120.0..180.0).contains(&hue) { + (0.0, c, x) + } else if (180.0..240.0).contains(&hue) { + (0.0, x, c) + } else if (240.0..300.0).contains(&hue) { + (x, 0.0, c) + } else { + // if hue >= 300.0 && hue < 360.0 { + (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, + ) +}