From e91f3be26e2d82bb84ff3c3cf74f8adb597b2799 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sat, 4 Jul 2026 16:46:53 +0200 Subject: [PATCH] feat(firmware): blink on-board WS2812 alongside GPIO 2 The DevKitC-1 has no discrete LED on GPIO 2, so the Spike 1 blink was invisible without external wiring. Drive the on-board addressable LED (WS2812 on GPIO 48) each cycle for visible confirmation on the bench. --- firmware/src/main.rs | 53 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/firmware/src/main.rs b/firmware/src/main.rs index cc230f5..6417b63 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -1,6 +1,14 @@ +use std::time::Duration; + use esp_idf_svc::hal::delay::FreeRtos; use esp_idf_svc::hal::gpio::PinDriver; use esp_idf_svc::hal::peripherals::Peripherals; +use esp_idf_svc::hal::rmt::config::{TransmitConfig, TxChannelConfig}; +use esp_idf_svc::hal::rmt::encoder::CopyEncoder; +use esp_idf_svc::hal::rmt::{PinState, Symbol, TxChannelDriver}; +use esp_idf_svc::hal::units::Hertz; + +const WS2812_RESOLUTION: Hertz = Hertz(10_000_000); fn main() -> anyhow::Result<()> { // Required once before any esp-idf-svc call; some runtime patches @@ -11,16 +19,59 @@ fn main() -> anyhow::Result<()> { let peripherals = Peripherals::take()?; let mut led = PinDriver::output(peripherals.pins.gpio2)?; - log::info!("Typoena Spike 1 — Blink on GPIO 2"); + // On-board addressable LED (WS2812) — GPIO 48 on the DevKitC-1 v1.0; + // v1.1 boards moved it to GPIO 38. + let mut rgb = TxChannelDriver::new( + peripherals.pins.gpio48, + &TxChannelConfig { + resolution: WS2812_RESOLUTION, + ..Default::default() + }, + )?; + + log::info!("Typoena Spike 1 — Blink on GPIO 2 + on-board WS2812 (GPIO 48)"); let mut n: u32 = 0; loop { led.set_high()?; + ws2812_set(&mut rgb, 4, 0, 24)?; log::info!("blink {n}"); n = n.wrapping_add(1); FreeRtos::delay_ms(500); led.set_low()?; + ws2812_set(&mut rgb, 0, 0, 0)?; FreeRtos::delay_ms(500); } } + +/// Send one WS2812 GRB frame. The ≥50 µs low reset the LED needs between +/// frames is covered by the 500 ms idle gap between calls. +fn ws2812_set(tx: &mut TxChannelDriver, r: u8, g: u8, b: u8) -> anyhow::Result<()> { + // Bit timings from the WS2812 datasheet. + let zero = Symbol::new_with( + WS2812_RESOLUTION, + PinState::High, + Duration::from_nanos(350), + PinState::Low, + Duration::from_nanos(800), + )?; + let one = Symbol::new_with( + WS2812_RESOLUTION, + PinState::High, + Duration::from_nanos(700), + PinState::Low, + Duration::from_nanos(600), + )?; + + let mut symbols = [zero; 24]; + for (i, byte) in [g, r, b].into_iter().enumerate() { + for bit in 0..8 { + if byte & (0x80 >> bit) != 0 { + symbols[i * 8 + bit] = one; + } + } + } + tx.send_and_wait(CopyEncoder::new()?, &symbols, &TransmitConfig::default())?; + Ok(()) +}