From e45273bc71e086eeaddc9d780271f3d32a9a65e4 Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 5 Mar 2026 12:03:31 -0800 Subject: [PATCH] M1 complete: XIAO M0 drives LP5562 LEDs over I2C Add error-resilient firmware with diagnostic LED blink patterns (3 slow = alive, solid = I2C OK, fast burst = I2C error). Document EVM external MCU wiring: disconnect P6 EN jumper, power VDD from XIAO 5V, leave SCL/SDA jumpers in place with MSP430 unpowered. Co-Authored-By: Claude Opus 4.6 --- docs/STATUS.md | 6 +- docs/plans/2026-03-05-lp5562evm-protocol.md | 11 +++ src/main.rs | 81 ++++++++++++++------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index 0c1189c..2023844 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # xblink Project Status -**Current Milestone**: M1 — LED Smoke Test (I2C verified via EVM USB bridge, XIAO M0 flash pending) +**Current Milestone**: M2 — Engine Patterns **Last Updated**: 2026-03-05 --- @@ -24,7 +24,7 @@ - [x] Update `main.rs` with I2C init + GPIO EN + LP5562 direct PWM test - [x] Verify `cargo build --release` compiles - [x] Verify LP5562 I2C control via EVM USB-to-I2C bridge (`tools/lp5562_evm.py`) -- [ ] Flash XIAO M0 and verify RGBW LED channels with Rust firmware +- [x] Flash XIAO M0 and verify RGBW LED channels with Rust firmware ### M2: Engine Patterns @@ -142,7 +142,7 @@ |------|--------|-------| | Seeed XIAO M0 | Available | Dev board MCU (SAMD21G18A) | | LP5562EVM | Available | TI eval module, RGBW LEDs, I2C addr 0x30 | -| Mini-USB cable | Not on hand | Needed to flash XIAO M0 | +| Mini-USB cable | Available | USB-C, used for flashing XIAO M0 | | NTAG5 Link Click | Available, partially wired | Missing I2C jumper to XIAO | | Hall effect sensor | Not available | Need to source — TI DRV5032FB (SOT-23) for prototype | | ACR1552 PCSC reader | Available | For ntag5sensor Python tooling | diff --git a/docs/plans/2026-03-05-lp5562evm-protocol.md b/docs/plans/2026-03-05-lp5562evm-protocol.md index bacdd3d..af25586 100644 --- a/docs/plans/2026-03-05-lp5562evm-protocol.md +++ b/docs/plans/2026-03-05-lp5562evm-protocol.md @@ -88,3 +88,14 @@ The LP5562 EN pin is controlled by the MSP430's GPIO (EN-UC), connected through **Workaround**: Jumper EN directly to VDD (3.3V) on P1 or P6 header. This is acceptable because in the final xblink design, EN is controlled by a wired-AND circuit (MCU GPIO + hall sensor), not the EVM's MSP430. Without EN high, all I2C reads return status `02` (NAK) with data `00`. + +## Using EVM with External MCU (XIAO M0) + +When driving the LP5562 from an external MCU instead of the MSP430: + +1. **Disconnect EVM from USB** — the MSP430 must be unpowered to avoid I2C bus contention. +2. **P6 header**: Remove the **EN jumper** to disconnect EN-UC (MSP430's EN output). Leave SCL/SDA jumpers in place — the unpowered MSP430's I/O pins go high-impedance. +3. **P4 header**: Feed external VDD (e.g., XIAO 5V pin → P4 VDD). The on-board LP2985 3.3V LDO only runs from USB power. +4. **Wiring**: XIAO A0 → P6 EN (LP5562 side), A4 → SDA, A5 → SCL, GND → GND, 5V → VDD. + +The LP5562 VDD range is 2.7–5.5V. Using 5V ensures headroom for white LED forward voltage (~3.0V) plus LP5562 current sink saturation (~0.4V). diff --git a/src/main.rs b/src/main.rs index fb43f02..524b5e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,16 @@ use pac::{CorePeripherals, Peripherals}; mod led; use led::lp5562::{ClockSource, Lp5562, DEFAULT_ADDRESS}; +/// Blink the on-board LED n times (active-low: low=on, high=off) +fn blink(led: &mut bsp::Led0, delay: &mut D, times: u8, ms: u16) { + for _ in 0..times { + led.set_low().unwrap(); + delay.delay_ms(ms as u32); + led.set_high().unwrap(); + delay.delay_ms(ms as u32); + } +} + #[entry] fn main() -> ! { let mut peripherals = Peripherals::take().unwrap(); @@ -36,10 +46,14 @@ fn main() -> ! { let mut led: bsp::Led0 = pins.led0.into_push_pull_output(); led.set_high().unwrap(); // LED off (active low) + // === DIAGNOSTIC: 3 slow blinks = firmware is alive === + blink(&mut led, &mut delay, 3, 200); + delay.delay_ms(500u32); + // LP5562 hardware enable on D0/A0 — drive high to power on let mut lp_en = pins.a0.into_push_pull_output(); lp_en.set_high().unwrap(); - delay.delay_ms(1u16); // Let LP5562 power stabilize + delay.delay_ms(10u32); // Let LP5562 power stabilize // I2C on A4 (SDA) / A5 (SCL) at 400 kHz let i2c = bsp::i2c_master( @@ -53,35 +67,46 @@ fn main() -> ! { let mut lp = Lp5562::new(i2c, DEFAULT_ADDRESS); - // Enable chip, wait for startup (>500 us) - lp.enable().unwrap(); - delay.delay_ms(1u16); + // Try to initialize LP5562 — blink error pattern on failure + let i2c_ok = (|| -> Result<(), led::lp5562::Error<_>> { + lp.enable()?; + delay.delay_ms(1u32); // >500us startup + lp.init_direct_control(ClockSource::Internal)?; + lp.set_all_current(50, 50, 50, 50)?; + Ok(()) + })(); - // Direct I2C PWM control, internal oscillator - lp.init_direct_control(ClockSource::Internal).unwrap(); + match i2c_ok { + Ok(()) => { + // === DIAGNOSTIC: solid LED on = LP5562 init OK === + led.set_low().unwrap(); + delay.delay_ms(1000u32); - // 5 mA per channel (50 × 0.1 mA) — conservative for EH power budget - lp.set_all_current(50, 50, 50, 50).unwrap(); + // M1 smoke test: cycle through RGBW channels + let on: u8 = 255; + let step: u16 = 500; - // Board LED on = LP5562 initialized OK - led.set_low().unwrap(); - - // M1 smoke test: cycle through RGBW channels - let on: u8 = 255; - let step: u16 = 500; - - loop { - lp.set_all_pwm(on, 0, 0, 0).unwrap(); // Blue - delay.delay_ms(step); - lp.set_all_pwm(0, on, 0, 0).unwrap(); // Green - delay.delay_ms(step); - lp.set_all_pwm(0, 0, on, 0).unwrap(); // Red - delay.delay_ms(step); - lp.set_all_pwm(0, 0, 0, on).unwrap(); // White - delay.delay_ms(step); - lp.set_all_pwm(on, on, on, on).unwrap(); // All on - delay.delay_ms(step); - lp.all_off().unwrap(); - delay.delay_ms(step); + loop { + lp.set_all_pwm(on, 0, 0, 0).ok(); // Blue + delay.delay_ms(step as u32); + lp.set_all_pwm(0, on, 0, 0).ok(); // Green + delay.delay_ms(step as u32); + lp.set_all_pwm(0, 0, on, 0).ok(); // Red + delay.delay_ms(step as u32); + lp.set_all_pwm(0, 0, 0, on).ok(); // White + delay.delay_ms(step as u32); + lp.set_all_pwm(on, on, on, on).ok(); // All on + delay.delay_ms(step as u32); + let _ = lp.all_off(); + delay.delay_ms(step as u32); + } + } + Err(_) => { + // === DIAGNOSTIC: fast blink forever = I2C error === + loop { + blink(&mut led, &mut delay, 5, 80); + delay.delay_ms(500u32); + } + } } }