Files
xblink/src/led/lp5562.rs
michael e4195d2583 Add LP5562 driver, M1 smoke test, and milestone-based plan
- Integrate LP5562 driver from sibling project (ntag5-samd21-lp562)
  with full doc comments restored
- Update main.rs with complete M1 test: I2C init, GPIO EN, RGBW cycle
- Rewrite DEVELOPMENT_PLAN.md from waterfall to milestone-based groups
  organized by hardware availability (Groups A-E)
- Rewrite STATUS.md with milestone checklist tracking
- Add LP5562 timing constraints and flash script docs to CLAUDE.md
- Add flash_when_ready.sh for auto-flash dev workflow
- Add brainstorm design doc for plan redesign rationale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 10:26:24 -08:00

757 lines
22 KiB
Rust

//! LP5562 4-channel RGBW LED driver (I2C) — TI SNVS820B
//!
//! Complete `no_std` driver covering all LP5562 features:
//! direct PWM control, current setting, engine programming,
//! LED mapping, power-save, and clock configuration.
//!
//! Generic over `embedded_hal::i2c::I2c` (1.0).
use embedded_hal::i2c::I2c;
// ---------------------------------------------------------------------------
// I2C address constants (7-bit)
// ---------------------------------------------------------------------------
/// I2C address when ADDR_SEL1:0 = 00 (both pins low).
pub const ADDR_SEL_00: u8 = 0x30;
/// I2C address when ADDR_SEL1:0 = 01.
pub const ADDR_SEL_01: u8 = 0x31;
/// I2C address when ADDR_SEL1:0 = 10.
pub const ADDR_SEL_10: u8 = 0x32;
/// I2C address when ADDR_SEL1:0 = 11 (both pins high).
pub const ADDR_SEL_11: u8 = 0x33;
/// Default address (ADDR_SEL pins both low).
pub const DEFAULT_ADDRESS: u8 = ADDR_SEL_00;
// ---------------------------------------------------------------------------
// Register addresses
// ---------------------------------------------------------------------------
/// LP5562 register addresses (Table 26, datasheet page 30).
pub mod reg {
pub const ENABLE: u8 = 0x00;
pub const OP_MODE: u8 = 0x01;
pub const B_PWM: u8 = 0x02;
pub const G_PWM: u8 = 0x03;
pub const R_PWM: u8 = 0x04;
pub const B_CURRENT: u8 = 0x05;
pub const G_CURRENT: u8 = 0x06;
pub const R_CURRENT: u8 = 0x07;
pub const CONFIG: u8 = 0x08;
pub const ENG1_PC: u8 = 0x09;
pub const ENG2_PC: u8 = 0x0A;
pub const ENG3_PC: u8 = 0x0B;
pub const STATUS: u8 = 0x0C;
pub const RESET: u8 = 0x0D;
pub const W_PWM: u8 = 0x0E;
pub const W_CURRENT: u8 = 0x0F;
pub const LED_MAP: u8 = 0x70;
pub const ENG1_PROG_START: u8 = 0x10;
pub const ENG2_PROG_START: u8 = 0x30;
pub const ENG3_PROG_START: u8 = 0x50;
}
// ---------------------------------------------------------------------------
// Bit-field constants
// ---------------------------------------------------------------------------
// ENABLE register (0x00)
const ENABLE_LOG_EN: u8 = 1 << 7;
const ENABLE_CHIP_EN: u8 = 1 << 6;
const ENABLE_ENG1_EXEC_SHIFT: u8 = 4;
const ENABLE_ENG2_EXEC_SHIFT: u8 = 2;
const ENABLE_ENG3_EXEC_SHIFT: u8 = 0;
// OP_MODE register (0x01)
const OP_MODE_ENG1_SHIFT: u8 = 4;
const OP_MODE_ENG2_SHIFT: u8 = 2;
const OP_MODE_ENG3_SHIFT: u8 = 0;
// CONFIG register (0x08)
const CONFIG_PWM_HF: u8 = 1 << 6;
const CONFIG_PS_EN: u8 = 1 << 5;
// STATUS register (0x0C)
const STATUS_EXT_CLK_USED: u8 = 1 << 3;
const STATUS_ENG1_INT: u8 = 1 << 2;
const STATUS_ENG2_INT: u8 = 1 << 1;
const STATUS_ENG3_INT: u8 = 1 << 0;
// LED_MAP register (0x70)
const LED_MAP_W_SHIFT: u8 = 6;
const LED_MAP_R_SHIFT: u8 = 4;
const LED_MAP_G_SHIFT: u8 = 2;
const LED_MAP_B_SHIFT: u8 = 0;
const RESET_MAGIC: u8 = 0xFF;
// ---------------------------------------------------------------------------
// Enumerations
// ---------------------------------------------------------------------------
/// LED channel identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Channel {
Blue,
Green,
Red,
White,
}
impl Channel {
const fn pwm_reg(self) -> u8 {
match self {
Channel::Blue => reg::B_PWM,
Channel::Green => reg::G_PWM,
Channel::Red => reg::R_PWM,
Channel::White => reg::W_PWM,
}
}
const fn current_reg(self) -> u8 {
match self {
Channel::Blue => reg::B_CURRENT,
Channel::Green => reg::G_CURRENT,
Channel::Red => reg::R_CURRENT,
Channel::White => reg::W_CURRENT,
}
}
const fn led_map_shift(self) -> u8 {
match self {
Channel::White => LED_MAP_W_SHIFT,
Channel::Red => LED_MAP_R_SHIFT,
Channel::Green => LED_MAP_G_SHIFT,
Channel::Blue => LED_MAP_B_SHIFT,
}
}
}
/// Execution engine identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EngineId {
Engine1,
Engine2,
Engine3,
}
impl EngineId {
const fn exec_shift(self) -> u8 {
match self {
EngineId::Engine1 => ENABLE_ENG1_EXEC_SHIFT,
EngineId::Engine2 => ENABLE_ENG2_EXEC_SHIFT,
EngineId::Engine3 => ENABLE_ENG3_EXEC_SHIFT,
}
}
const fn mode_shift(self) -> u8 {
match self {
EngineId::Engine1 => OP_MODE_ENG1_SHIFT,
EngineId::Engine2 => OP_MODE_ENG2_SHIFT,
EngineId::Engine3 => OP_MODE_ENG3_SHIFT,
}
}
const fn pc_reg(self) -> u8 {
match self {
EngineId::Engine1 => reg::ENG1_PC,
EngineId::Engine2 => reg::ENG2_PC,
EngineId::Engine3 => reg::ENG3_PC,
}
}
const fn prog_start(self) -> u8 {
match self {
EngineId::Engine1 => reg::ENG1_PROG_START,
EngineId::Engine2 => reg::ENG2_PROG_START,
EngineId::Engine3 => reg::ENG3_PROG_START,
}
}
}
/// Engine execution state (ENABLE register, 2-bit field per engine).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum EngineExec {
/// Hold: finish current command then stop. PC is R/W in this state.
Hold = 0b00,
/// Step: execute one instruction, increment PC, return to Hold.
Step = 0b01,
/// Run: execute from current PC continuously.
Run = 0b10,
/// Execute current instruction once, then return to Hold.
ExecuteOnce = 0b11,
}
/// Engine operation mode (OP_MODE register, 2-bit field per engine).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum EngineMode {
/// Disabled: resets PC, mapped LED output = 0.
Disabled = 0b00,
/// Load: SRAM writable, all engines held.
Load = 0b01,
/// Run: execution controlled by EXEC bits in ENABLE.
Run = 0b10,
/// Direct: engine PWM from corresponding I2C PWM register.
Direct = 0b11,
}
/// LED-to-engine mapping (LED_MAP register, 2-bit field per channel).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum LedMapping {
/// Controlled via I2C PWM register.
I2c = 0b00,
/// Controlled by Engine 1.
Engine1 = 0b01,
/// Controlled by Engine 2.
Engine2 = 0b10,
/// Controlled by Engine 3.
Engine3 = 0b11,
}
/// Clock source (CONFIG register bits 1:0).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ClockSource {
/// External 32 kHz clock on CLK_32K pin.
External = 0b00,
/// Internal oscillator.
Internal = 0b01,
/// Automatic detection.
Auto = 0b10,
}
/// PWM output frequency.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PwmFrequency {
/// 256 Hz.
Hz256,
/// 558 Hz.
Hz558,
}
/// Prescaler for ramp/wait engine commands.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Prescale {
/// Divide by 16: 0.49 ms per step.
Fast = 0,
/// Divide by 512: 15.6 ms per step.
Slow = 1,
}
/// Ramp direction.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RampDirection {
Up,
Down,
}
// ---------------------------------------------------------------------------
// Status
// ---------------------------------------------------------------------------
/// Decoded STATUS register (0x0C, read-only). Reading clears interrupt bits.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Status {
pub ext_clk_used: bool,
pub engine1_int: bool,
pub engine2_int: bool,
pub engine3_int: bool,
}
impl Status {
fn from_reg(val: u8) -> Self {
Self {
ext_clk_used: val & STATUS_EXT_CLK_USED != 0,
engine1_int: val & STATUS_ENG1_INT != 0,
engine2_int: val & STATUS_ENG2_INT != 0,
engine3_int: val & STATUS_ENG3_INT != 0,
}
}
}
// ---------------------------------------------------------------------------
// Engine command builder
// ---------------------------------------------------------------------------
/// Builder for 16-bit engine program commands.
pub struct EngineCommand;
impl EngineCommand {
/// Ramp/Wait command.
///
/// - `prescale`: Fast (0.49ms/step) or Slow (15.6ms/step)
/// - `step_time`: 1..=63 (number of prescaled periods per step)
/// - `direction`: Up or Down
/// - `increment`: 0 = wait only, 1..=127 = number of PWM steps
pub const fn ramp_wait(
prescale: Prescale,
step_time: u8,
direction: RampDirection,
increment: u8,
) -> u16 {
let ps = (prescale as u16) << 14;
let st = ((step_time & 0x3F) as u16) << 8;
let sign = match direction {
RampDirection::Down => 1u16 << 7,
RampDirection::Up => 0,
};
let inc = (increment & 0x7F) as u16;
ps | st | sign | inc
}
/// Pure wait (ramp with increment = 0).
pub const fn wait(prescale: Prescale, step_time: u8) -> u16 {
Self::ramp_wait(prescale, step_time, RampDirection::Up, 0)
}
/// Set PWM to an absolute value (0-255).
pub const fn set_pwm(value: u8) -> u16 {
0x4000 | (value as u16)
}
/// Go to Start: reset PC to 0.
pub const fn go_to_start() -> u16 {
0x0000
}
/// Branch to `step_number` with `loop_count` (0 = infinite).
pub const fn branch(loop_count: u8, step_number: u8) -> u16 {
let base: u16 = 0b101 << 13;
let lc = ((loop_count & 0x3F) as u16) << 7;
let sn = (step_number & 0x0F) as u16;
base | lc | sn
}
/// End program. Optionally fire interrupt and/or reset PWM to 0.
pub const fn end(interrupt: bool, reset_pwm: bool) -> u16 {
let base: u16 = 0b110 << 13;
let int_bit = if interrupt { 1u16 << 12 } else { 0 };
let rst_bit = if reset_pwm { 1u16 << 11 } else { 0 };
base | int_bit | rst_bit
}
/// Trigger for engine synchronization.
///
/// Bitmask: bit0 = Engine1, bit1 = Engine2, bit2 = Engine3.
pub const fn trigger(wait_engines: u8, send_engines: u8) -> u16 {
let base: u16 = 0b111 << 13;
let wait = ((wait_engines & 0x07) as u16) << 8;
let send = ((send_engines & 0x07) as u16) << 1;
base | wait | send
}
}
// ---------------------------------------------------------------------------
// Engine program container
// ---------------------------------------------------------------------------
/// Maximum commands per engine.
pub const MAX_PROGRAM_LEN: usize = 16;
/// A program to load into engine SRAM (up to 16 commands).
#[derive(Clone, Debug)]
pub struct EngineProgram {
commands: [u16; MAX_PROGRAM_LEN],
len: usize,
}
impl EngineProgram {
pub const fn new() -> Self {
Self {
commands: [0u16; MAX_PROGRAM_LEN],
len: 0,
}
}
/// Create from a command slice. Panics if > 16 commands.
pub fn from_commands(cmds: &[u16]) -> Self {
assert!(cmds.len() <= MAX_PROGRAM_LEN);
let mut prog = Self::new();
let mut i = 0;
while i < cmds.len() {
prog.commands[i] = cmds[i];
i += 1;
}
prog.len = cmds.len();
prog
}
pub fn push(&mut self, cmd: u16) -> Result<(), ()> {
if self.len >= MAX_PROGRAM_LEN {
return Err(());
}
self.commands[self.len] = cmd;
self.len += 1;
Ok(())
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
/// Full 32-byte SRAM image (big-endian, unused slots = 0x0000).
pub fn as_bytes(&self) -> [u8; 32] {
let mut buf = [0u8; 32];
let mut i = 0;
while i < MAX_PROGRAM_LEN {
buf[i * 2] = (self.commands[i] >> 8) as u8;
buf[i * 2 + 1] = self.commands[i] as u8;
i += 1;
}
buf
}
}
// ---------------------------------------------------------------------------
// Error type
// ---------------------------------------------------------------------------
#[derive(Debug)]
pub enum Error<E> {
I2c(E),
ProgramTooLong,
}
impl<E> From<E> for Error<E> {
fn from(e: E) -> Self {
Error::I2c(e)
}
}
// ---------------------------------------------------------------------------
// Driver struct
// ---------------------------------------------------------------------------
/// LP5562 four-channel RGBW LED driver.
pub struct Lp5562<I2C> {
i2c: I2C,
addr: u8,
}
impl<I2C, E> Lp5562<I2C>
where
I2C: I2c<Error = E>,
{
// ---- Construction ----
pub fn new(i2c: I2C, addr: u8) -> Self {
Self { i2c, addr }
}
pub fn new_default(i2c: I2C) -> Self {
Self::new(i2c, DEFAULT_ADDRESS)
}
/// Consume the driver and return the I2C bus.
pub fn release(self) -> I2C {
self.i2c
}
// ---- Low-level register access ----
pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), E> {
self.i2c.write(self.addr, &[register, value])
}
pub fn read_register(&mut self, register: u8) -> Result<u8, E> {
let mut buf = [0u8];
self.i2c.write_read(self.addr, &[register], &mut buf)?;
Ok(buf[0])
}
fn modify_register<F>(&mut self, register: u8, f: F) -> Result<(), E>
where
F: FnOnce(u8) -> u8,
{
let val = self.read_register(register)?;
self.write_register(register, f(val))
}
// ---- Chip enable / disable / reset ----
/// Enable the chip (CHIP_EN = 1).
///
/// Caller MUST wait >= 500 us after this before issuing further commands.
pub fn enable(&mut self) -> Result<(), E> {
self.modify_register(reg::ENABLE, |v| v | ENABLE_CHIP_EN)
}
pub fn disable(&mut self) -> Result<(), E> {
self.modify_register(reg::ENABLE, |v| v & !ENABLE_CHIP_EN)
}
pub fn is_enabled(&mut self) -> Result<bool, E> {
let val = self.read_register(reg::ENABLE)?;
Ok(val & ENABLE_CHIP_EN != 0)
}
/// Software reset (write 0xFF to RESET register).
pub fn reset(&mut self) -> Result<(), E> {
self.write_register(reg::RESET, RESET_MAGIC)
}
// ---- PWM control ----
/// Set PWM duty cycle for a channel (0 = off, 255 = full).
pub fn set_pwm(&mut self, channel: Channel, value: u8) -> Result<(), E> {
self.write_register(channel.pwm_reg(), value)
}
pub fn get_pwm(&mut self, channel: Channel) -> Result<u8, E> {
self.read_register(channel.pwm_reg())
}
/// Set PWM for all channels. B/G/R use auto-increment, W separate.
pub fn set_all_pwm(&mut self, blue: u8, green: u8, red: u8, white: u8) -> Result<(), E> {
self.i2c.write(self.addr, &[reg::B_PWM, blue, green, red])?;
self.write_register(reg::W_PWM, white)
}
pub fn set_rgb(&mut self, red: u8, green: u8, blue: u8) -> Result<(), E> {
self.i2c.write(self.addr, &[reg::B_PWM, blue, green, red])
}
pub fn set_rgbw(&mut self, red: u8, green: u8, blue: u8, white: u8) -> Result<(), E> {
self.set_all_pwm(blue, green, red, white)
}
pub fn all_off(&mut self) -> Result<(), E> {
self.set_all_pwm(0, 0, 0, 0)
}
// ---- Current control ----
/// Set LED driver current (0-255 = 0.0-25.5 mA in 0.1 mA steps).
/// Default after reset: 0xAF = 17.5 mA.
pub fn set_current(&mut self, channel: Channel, value: u8) -> Result<(), E> {
self.write_register(channel.current_reg(), value)
}
pub fn get_current(&mut self, channel: Channel) -> Result<u8, E> {
self.read_register(channel.current_reg())
}
pub fn set_all_current(&mut self, blue: u8, green: u8, red: u8, white: u8) -> Result<(), E> {
self.i2c.write(self.addr, &[reg::B_CURRENT, blue, green, red])?;
self.write_register(reg::W_CURRENT, white)
}
// ---- Configuration ----
pub fn set_clock_source(&mut self, source: ClockSource) -> Result<(), E> {
self.modify_register(reg::CONFIG, |v| (v & !0x03) | (source as u8))
}
pub fn set_pwm_frequency(&mut self, freq: PwmFrequency) -> Result<(), E> {
self.modify_register(reg::CONFIG, |v| match freq {
PwmFrequency::Hz256 => v & !CONFIG_PWM_HF,
PwmFrequency::Hz558 => v | CONFIG_PWM_HF,
})
}
pub fn set_power_save(&mut self, enabled: bool) -> Result<(), E> {
self.modify_register(reg::CONFIG, |v| {
if enabled { v | CONFIG_PS_EN } else { v & !CONFIG_PS_EN }
})
}
pub fn set_log_mode(&mut self, enabled: bool) -> Result<(), E> {
self.modify_register(reg::ENABLE, |v| {
if enabled { v | ENABLE_LOG_EN } else { v & !ENABLE_LOG_EN }
})
}
/// Write the entire CONFIG register at once.
pub fn write_config(
&mut self,
clock: ClockSource,
pwm_hf: bool,
power_save: bool,
) -> Result<(), E> {
let mut val = clock as u8;
if pwm_hf { val |= CONFIG_PWM_HF; }
if power_save { val |= CONFIG_PS_EN; }
self.write_register(reg::CONFIG, val)
}
// ---- LED mapping ----
pub fn set_led_mapping(&mut self, channel: Channel, mapping: LedMapping) -> Result<(), E> {
let shift = channel.led_map_shift();
self.modify_register(reg::LED_MAP, |v| {
(v & !(0x03 << shift)) | ((mapping as u8) << shift)
})
}
pub fn set_all_led_mappings(
&mut self,
blue: LedMapping,
green: LedMapping,
red: LedMapping,
white: LedMapping,
) -> Result<(), E> {
let val = ((white as u8) << LED_MAP_W_SHIFT)
| ((red as u8) << LED_MAP_R_SHIFT)
| ((green as u8) << LED_MAP_G_SHIFT)
| ((blue as u8) << LED_MAP_B_SHIFT);
self.write_register(reg::LED_MAP, val)
}
/// Set all channels to direct I2C control.
pub fn set_all_i2c_controlled(&mut self) -> Result<(), E> {
self.write_register(reg::LED_MAP, 0x00)
}
// ---- Engine execution state ----
/// Set execution state for one engine.
///
/// Wait >= 488 us between consecutive ENABLE register writes.
pub fn set_engine_exec(&mut self, engine: EngineId, exec: EngineExec) -> Result<(), E> {
let shift = engine.exec_shift();
self.modify_register(reg::ENABLE, |v| {
(v & !(0x03 << shift)) | ((exec as u8) << shift)
})
}
pub fn set_all_engine_exec(
&mut self,
eng1: EngineExec,
eng2: EngineExec,
eng3: EngineExec,
) -> Result<(), E> {
self.modify_register(reg::ENABLE, |v| {
(v & 0xC0)
| ((eng1 as u8) << ENABLE_ENG1_EXEC_SHIFT)
| ((eng2 as u8) << ENABLE_ENG2_EXEC_SHIFT)
| ((eng3 as u8) << ENABLE_ENG3_EXEC_SHIFT)
})
}
// ---- Engine operation mode ----
/// Set operation mode for one engine.
///
/// Wait >= 153 us between consecutive OP_MODE register writes.
/// When transitioning from Run, first set exec to Hold.
pub fn set_engine_mode(&mut self, engine: EngineId, mode: EngineMode) -> Result<(), E> {
let shift = engine.mode_shift();
self.modify_register(reg::OP_MODE, |v| {
(v & !(0x03 << shift)) | ((mode as u8) << shift)
})
}
pub fn set_all_engine_modes(
&mut self,
eng1: EngineMode,
eng2: EngineMode,
eng3: EngineMode,
) -> Result<(), E> {
let val = ((eng1 as u8) << OP_MODE_ENG1_SHIFT)
| ((eng2 as u8) << OP_MODE_ENG2_SHIFT)
| ((eng3 as u8) << OP_MODE_ENG3_SHIFT);
self.write_register(reg::OP_MODE, val)
}
// ---- Engine program counter ----
/// Set PC (0-15). Engine exec must be Hold.
pub fn set_engine_pc(&mut self, engine: EngineId, pc: u8) -> Result<(), E> {
self.write_register(engine.pc_reg(), pc & 0x0F)
}
pub fn get_engine_pc(&mut self, engine: EngineId) -> Result<u8, E> {
let val = self.read_register(engine.pc_reg())?;
Ok(val & 0x0F)
}
// ---- Engine program loading ----
/// Load a program into engine SRAM.
///
/// Sets engine to Load mode internally. Caller must have set exec
/// to Hold first and must wait >= 153 us before the next OP_MODE write.
pub fn load_engine_program(
&mut self,
engine: EngineId,
program: &EngineProgram,
) -> Result<(), Error<E>> {
self.set_engine_mode(engine, EngineMode::Load)
.map_err(Error::I2c)?;
let prog_bytes = program.as_bytes();
let start = engine.prog_start();
let mut buf = [0u8; 33];
buf[0] = start;
buf[1..33].copy_from_slice(&prog_bytes);
self.i2c.write(self.addr, &buf).map_err(Error::I2c)?;
Ok(())
}
/// Load raw u16 commands into engine SRAM.
pub fn load_engine_commands(
&mut self,
engine: EngineId,
commands: &[u16],
) -> Result<(), Error<E>> {
if commands.len() > MAX_PROGRAM_LEN {
return Err(Error::ProgramTooLong);
}
let prog = EngineProgram::from_commands(commands);
self.load_engine_program(engine, &prog)
}
// ---- Status ----
/// Read and decode STATUS register. Reading clears interrupt flags.
pub fn read_status(&mut self) -> Result<Status, E> {
let val = self.read_register(reg::STATUS)?;
Ok(Status::from_reg(val))
}
pub fn read_status_raw(&mut self) -> Result<u8, E> {
self.read_register(reg::STATUS)
}
// ---- High-level convenience ----
/// Initialize for direct I2C PWM control.
///
/// Call `enable()` first, then wait >= 500 us, then call this.
pub fn init_direct_control(&mut self, clock: ClockSource) -> Result<(), E> {
self.write_config(clock, false, false)?;
self.set_all_i2c_controlled()
}
/// Load a program, set engine to Run mode and Run exec state.
///
/// Engine exec must be Hold before calling. Caller is responsible
/// for timing delays between register writes in production code.
pub fn run_engine(
&mut self,
engine: EngineId,
program: &EngineProgram,
) -> Result<(), Error<E>> {
self.load_engine_program(engine, program)?;
self.set_engine_mode(engine, EngineMode::Run)
.map_err(Error::I2c)?;
self.set_engine_exec(engine, EngineExec::Run)
.map_err(Error::I2c)?;
Ok(())
}
/// Stop an engine: set exec to Hold, then mode to Disabled.
pub fn stop_engine(&mut self, engine: EngineId) -> Result<(), E> {
self.set_engine_exec(engine, EngineExec::Hold)?;
self.set_engine_mode(engine, EngineMode::Disabled)
}
}