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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# xblink Project Status
|
# 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
|
**Last Updated**: 2026-03-05
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
- [x] Update `main.rs` with I2C init + GPIO EN + LP5562 direct PWM test
|
- [x] Update `main.rs` with I2C init + GPIO EN + LP5562 direct PWM test
|
||||||
- [x] Verify `cargo build --release` compiles
|
- [x] Verify `cargo build --release` compiles
|
||||||
- [x] Verify LP5562 I2C control via EVM USB-to-I2C bridge (`tools/lp5562_evm.py`)
|
- [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
|
### M2: Engine Patterns
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
|------|--------|-------|
|
|------|--------|-------|
|
||||||
| Seeed XIAO M0 | Available | Dev board MCU (SAMD21G18A) |
|
| Seeed XIAO M0 | Available | Dev board MCU (SAMD21G18A) |
|
||||||
| LP5562EVM | Available | TI eval module, RGBW LEDs, I2C addr 0x30 |
|
| 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 |
|
| 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 |
|
| Hall effect sensor | Not available | Need to source — TI DRV5032FB (SOT-23) for prototype |
|
||||||
| ACR1552 PCSC reader | Available | For ntag5sensor Python tooling |
|
| ACR1552 PCSC reader | Available | For ntag5sensor Python tooling |
|
||||||
|
|||||||
@@ -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.
|
**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`.
|
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).
|
||||||
|
|||||||
81
src/main.rs
81
src/main.rs
@@ -17,6 +17,16 @@ use pac::{CorePeripherals, Peripherals};
|
|||||||
mod led;
|
mod led;
|
||||||
use led::lp5562::{ClockSource, Lp5562, DEFAULT_ADDRESS};
|
use led::lp5562::{ClockSource, Lp5562, DEFAULT_ADDRESS};
|
||||||
|
|
||||||
|
/// Blink the on-board LED n times (active-low: low=on, high=off)
|
||||||
|
fn blink<D: embedded_hal::delay::DelayNs>(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]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
let mut peripherals = Peripherals::take().unwrap();
|
let mut peripherals = Peripherals::take().unwrap();
|
||||||
@@ -36,10 +46,14 @@ fn main() -> ! {
|
|||||||
let mut led: bsp::Led0 = pins.led0.into_push_pull_output();
|
let mut led: bsp::Led0 = pins.led0.into_push_pull_output();
|
||||||
led.set_high().unwrap(); // LED off (active low)
|
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
|
// LP5562 hardware enable on D0/A0 — drive high to power on
|
||||||
let mut lp_en = pins.a0.into_push_pull_output();
|
let mut lp_en = pins.a0.into_push_pull_output();
|
||||||
lp_en.set_high().unwrap();
|
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
|
// I2C on A4 (SDA) / A5 (SCL) at 400 kHz
|
||||||
let i2c = bsp::i2c_master(
|
let i2c = bsp::i2c_master(
|
||||||
@@ -53,35 +67,46 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let mut lp = Lp5562::new(i2c, DEFAULT_ADDRESS);
|
let mut lp = Lp5562::new(i2c, DEFAULT_ADDRESS);
|
||||||
|
|
||||||
// Enable chip, wait for startup (>500 us)
|
// Try to initialize LP5562 — blink error pattern on failure
|
||||||
lp.enable().unwrap();
|
let i2c_ok = (|| -> Result<(), led::lp5562::Error<_>> {
|
||||||
delay.delay_ms(1u16);
|
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
|
match i2c_ok {
|
||||||
lp.init_direct_control(ClockSource::Internal).unwrap();
|
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
|
// M1 smoke test: cycle through RGBW channels
|
||||||
lp.set_all_current(50, 50, 50, 50).unwrap();
|
let on: u8 = 255;
|
||||||
|
let step: u16 = 500;
|
||||||
|
|
||||||
// Board LED on = LP5562 initialized OK
|
loop {
|
||||||
led.set_low().unwrap();
|
lp.set_all_pwm(on, 0, 0, 0).ok(); // Blue
|
||||||
|
delay.delay_ms(step as u32);
|
||||||
// M1 smoke test: cycle through RGBW channels
|
lp.set_all_pwm(0, on, 0, 0).ok(); // Green
|
||||||
let on: u8 = 255;
|
delay.delay_ms(step as u32);
|
||||||
let step: u16 = 500;
|
lp.set_all_pwm(0, 0, on, 0).ok(); // Red
|
||||||
|
delay.delay_ms(step as u32);
|
||||||
loop {
|
lp.set_all_pwm(0, 0, 0, on).ok(); // White
|
||||||
lp.set_all_pwm(on, 0, 0, 0).unwrap(); // Blue
|
delay.delay_ms(step as u32);
|
||||||
delay.delay_ms(step);
|
lp.set_all_pwm(on, on, on, on).ok(); // All on
|
||||||
lp.set_all_pwm(0, on, 0, 0).unwrap(); // Green
|
delay.delay_ms(step as u32);
|
||||||
delay.delay_ms(step);
|
let _ = lp.all_off();
|
||||||
lp.set_all_pwm(0, 0, on, 0).unwrap(); // Red
|
delay.delay_ms(step as u32);
|
||||||
delay.delay_ms(step);
|
}
|
||||||
lp.set_all_pwm(0, 0, 0, on).unwrap(); // White
|
}
|
||||||
delay.delay_ms(step);
|
Err(_) => {
|
||||||
lp.set_all_pwm(on, on, on, on).unwrap(); // All on
|
// === DIAGNOSTIC: fast blink forever = I2C error ===
|
||||||
delay.delay_ms(step);
|
loop {
|
||||||
lp.all_off().unwrap();
|
blink(&mut led, &mut delay, 5, 80);
|
||||||
delay.delay_ms(step);
|
delay.delay_ms(500u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user