lp50xx_async/hl/
mod.rs

1#[cfg(test)]
2mod test;
3
4use core::{marker::PhantomData, ops::Deref};
5use device_driver::AsyncBufferInterface;
6use embedded_hal_async::i2c::I2c;
7
8use crate::ll::{self, DeviceError};
9
10pub use crate::ll::MaxCurrentOption;
11
12/// I2C address used to address the device.
13#[derive(Debug, Default, Clone, Copy)]
14pub enum Address {
15    /// Devices configured with `GND, GND` on addr0 and addr1.
16    #[default]
17    Address0,
18    /// Devices configured with `GND, VCC` on addr0 and addr1.
19    Address1,
20    /// Devices configured with `VCC, GND` on addr0 and addr1.
21    Address2,
22    /// Devices configured with `VCC, VCC` on addr0 and addr1.
23    Address3,
24    /// Broadcast address to address all similar devices on the I2C bus.
25    ///
26    /// Is only valid if all devices would respond to the same commands in a similar way.
27    /// In other words: if all devices are configured identically.
28    Broadcast,
29}
30
31#[derive(Debug, Clone, PartialEq)]
32#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
33pub enum Error<T> {
34    /// The underlying I2C interface returned an error.
35    Interface(T),
36    /// A LED or RGB LED was indexed incorrectly.
37    ///
38    /// For example: when you index RGB LED #11 for the LP5030,
39    /// which only has up to RGB LED #9.
40    Index,
41}
42
43impl<T> From<DeviceError<T>> for Error<T> {
44    fn from(value: DeviceError<T>) -> Self {
45        match value {
46            DeviceError::Interface(e) => Error::Interface(e),
47            DeviceError::BufferTooSmall => unreachable!(), // Should never happen.
48        }
49    }
50}
51
52/// Color value for an RGB LED, with each `u8` representing the 8-bit value for
53/// the Red, Green and Blue channels.
54#[derive(Debug, Clone, Copy, PartialEq)]
55#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
56pub struct Rgb(pub [u8; 3]);
57
58impl core::ops::Deref for Rgb {
59    type Target = [u8; 3];
60
61    fn deref(&self) -> &Self::Target {
62        &self.0
63    }
64}
65
66impl From<[u8; 3]> for Rgb {
67    fn from(value: [u8; 3]) -> Self {
68        Rgb(value)
69    }
70}
71
72impl From<(u8, u8, u8)> for Rgb {
73    fn from(value: (u8, u8, u8)) -> Self {
74        Rgb(value.into())
75    }
76}
77
78/// Markers used to indicated the typestate of the device.
79pub mod marker {
80    pub struct Standby;
81    pub struct Normal;
82
83    trait Sealed {}
84
85    #[allow(private_bounds)]
86    pub trait Marker: Sealed {}
87
88    macro_rules! impl_marker {
89        ($struct:ty) => {
90            impl Sealed for $struct {}
91            impl Marker for $struct {}
92        };
93    }
94
95    impl_marker!(Standby);
96    impl_marker!(Normal);
97}
98
99/// High level generic driver for the LP50xx family of devices.
100///
101/// See [LP50xx] on how to instantiate the device.
102///
103/// The channels can be configured per OUT and per RGB LED.
104/// Bank-mode is not (yet) supported.
105pub struct Driver<VARIANT: LP50xx, T: I2c, STATE: marker::Marker> {
106    device: ll::Device<ll::i2c::DeviceInterface<T>>,
107    marker: PhantomData<VARIANT>,
108    state: PhantomData<STATE>,
109}
110
111/// Generic configuration for an LP50xx device.
112pub struct Config {
113    /// Use logarithmic scaling.
114    pub log_scale: bool,
115    /// Turn IC automatically into power save mode when all LEDs are effectively off (after 30ms).
116    pub power_save: bool,
117    /// Enable dithering mode, stretching the resolution from 9 bits to 12 bits.
118    pub pwm_dithering: bool,
119    /// The maximum amount of current for a single LED channel.
120    ///
121    /// 35mA only valid when `Vcc >= 3.3V`.
122    pub max_current: ll::MaxCurrentOption,
123}
124
125impl Default for Config {
126    fn default() -> Self {
127        Self {
128            log_scale: true,
129            power_save: true,
130            pwm_dithering: true,
131            max_current: ll::MaxCurrentOption::Current25MA5,
132        }
133    }
134}
135
136impl<VARIANT: LP50xx, T: I2c> Driver<VARIANT, T, marker::Standby> {
137    fn new(interface: T, address: Address) -> Self {
138        let address = match address {
139            Address::Address0 => VARIANT::I2C_ADDRESS_BASE,
140            Address::Address1 => VARIANT::I2C_ADDRESS_BASE | 0b01,
141            Address::Address2 => VARIANT::I2C_ADDRESS_BASE | 0b10,
142            Address::Address3 => VARIANT::I2C_ADDRESS_BASE | 0b11,
143            Address::Broadcast => VARIANT::I2C_ADDRESS_BROADCAST,
144        };
145
146        Self {
147            device: ll::Device::new(ll::i2c::DeviceInterface::new(interface, address)),
148            marker: PhantomData,
149            state: PhantomData,
150        }
151    }
152
153    /// Enable the device, turning on the constant current sinks
154    /// (if any are configured to have a non-zero duty cycle).
155    ///
156    /// This will consume up to 10mA of current, unless power saving is enabled.
157    pub async fn enable(
158        mut self,
159    ) -> Result<Driver<VARIANT, T, marker::Normal>, DeviceError<T::Error>> {
160        self.device
161            .device_config_0()
162            .write_async(|w| w.set_chip_en(true))
163            .await?;
164
165        Ok(Driver {
166            device: self.device,
167            marker: PhantomData,
168            state: PhantomData,
169        })
170    }
171}
172
173impl<VARIANT: LP50xx, T: I2c> Driver<VARIANT, T, marker::Normal> {
174    /// Disable the device, putting it into Standby mode.
175    ///
176    /// All register values will be retained, but the constant current sinks will no longer
177    /// be functional, turning off the LEDs.
178    ///
179    /// Consumes up to 12uA of current, depending on the device type.
180    pub async fn disable(
181        mut self,
182    ) -> Result<Driver<VARIANT, T, marker::Standby>, DeviceError<T::Error>> {
183        self.device
184            .device_config_0()
185            .write_async(|w| w.set_chip_en(false))
186            .await?;
187
188        Ok(Driver {
189            device: self.device,
190            marker: PhantomData,
191            state: PhantomData,
192        })
193    }
194}
195
196impl<VARIANT: LP50xx, T: I2c, MARKER: marker::Marker> Driver<VARIANT, T, MARKER> {
197    /// Set the general configuration parameters of the device.
198    pub async fn configure(&mut self, config: &Config) -> Result<(), DeviceError<T::Error>> {
199        self.device
200            .device_config_1()
201            .modify_async(|w| {
202                w.set_log_scale_en(config.log_scale);
203                w.set_max_current_option(config.max_current);
204                w.set_power_save_en(config.power_save);
205                w.set_pwm_dithering_en(config.pwm_dithering);
206            })
207            .await?;
208        Ok(())
209    }
210
211    /// Set the specific OUT channel to a specific color value.
212    ///
213    /// Will return the [Error::Index] if the device does not have the indexed channel.
214    pub async fn set_channel(&mut self, channel_i: u8, value: u8) -> Result<(), Error<T::Error>> {
215        if channel_i > VARIANT::LED_COUNT {
216            return Err(Error::Index);
217        }
218
219        self.device
220            .interface()
221            .write(VARIANT::OUT_START_ADDRESS + channel_i, &[value])
222            .await?;
223        Ok(())
224    }
225
226    /// Set the RGB LED color values.
227    ///
228    /// Will return the [Error::Index] if the device does not have the indexed RGB LED.
229    pub async fn set_rgb(
230        &mut self,
231        rgb_i: u8,
232        value: impl Into<Rgb>,
233    ) -> Result<(), Error<T::Error>> {
234        if rgb_i > VARIANT::RGB_COUNT {
235            return Err(Error::Index);
236        }
237
238        // Note: auto incrementing is enabled.
239        self.device
240            .interface()
241            .write(VARIANT::OUT_START_ADDRESS + rgb_i * 3, value.into().deref())
242            .await?;
243        Ok(())
244    }
245
246    /// Set the brightness of a RGB LED (not the color).
247    ///
248    /// Will return the [Error::Index] if the device does not have the indexed RGB LED
249    pub async fn set_rgb_brightness(
250        &mut self,
251        rgb_i: u8,
252        value: u8,
253    ) -> Result<(), Error<T::Error>> {
254        if rgb_i > VARIANT::RGB_COUNT {
255            return Err(Error::Index);
256        }
257
258        self.device
259            .interface()
260            .write(VARIANT::LED_START_ADDRESS + rgb_i, &[value])
261            .await?;
262        Ok(())
263    }
264
265    /// Set the brightness of all RGB LEDs (not the color) in one call.
266    pub async fn set_all_brightness(&mut self, value: u8) -> Result<(), Error<T::Error>> {
267        let mut buf: heapless::Vec<u8, 36> = heapless::Vec::new();
268        buf.extend(core::iter::repeat_n(value, VARIANT::RGB_COUNT as usize));
269
270        self.device
271            .interface()
272            .write(VARIANT::LED_START_ADDRESS, &buf)
273            .await?;
274        Ok(())
275    }
276}
277
278/// Trait for all variants of the LP50xx family of IC's.
279pub trait LP50xx: Sized {
280    const LED_COUNT: u8;
281    const RGB_COUNT: u8 = Self::LED_COUNT / 3;
282
283    const I2C_ADDRESS_BASE: u8;
284    const I2C_ADDRESS_BROADCAST: u8;
285
286    /// Register address of `LED0_BRIGHTNESS`.
287    const LED_START_ADDRESS: u8;
288    /// Register address of `OUT0_COLOR`.
289    const OUT_START_ADDRESS: u8 = Self::LED_START_ADDRESS + Self::RGB_COUNT;
290
291    /// Construct the high level driver for a specific IC variant.
292    fn new<T: I2c>(interface: T, address: Address) -> Driver<Self, T, marker::Standby> {
293        Driver::new(interface, address)
294    }
295}
296
297pub struct LP5009;
298pub struct LP5012;
299pub struct LP5018;
300pub struct LP5024;
301pub struct LP5030;
302pub struct LP5036;
303
304impl LP50xx for LP5009 {
305    const LED_COUNT: u8 = 9;
306    const I2C_ADDRESS_BASE: u8 = 0b0110000;
307    const I2C_ADDRESS_BROADCAST: u8 = 0b0011100;
308    const LED_START_ADDRESS: u8 = 0x07;
309    const OUT_START_ADDRESS: u8 = 0x0b;
310}
311
312impl LP50xx for LP5012 {
313    const LED_COUNT: u8 = 12;
314    const I2C_ADDRESS_BASE: u8 = 0b0110000;
315    const I2C_ADDRESS_BROADCAST: u8 = 0b0011100;
316    const LED_START_ADDRESS: u8 = 0x07;
317}
318
319impl LP50xx for LP5018 {
320    const LED_COUNT: u8 = 18;
321    const I2C_ADDRESS_BASE: u8 = 0b0101000;
322    const I2C_ADDRESS_BROADCAST: u8 = 0b0111100;
323    const LED_START_ADDRESS: u8 = 0x07;
324    const OUT_START_ADDRESS: u8 = 0x0f;
325}
326
327impl LP50xx for LP5024 {
328    const LED_COUNT: u8 = 24;
329    const I2C_ADDRESS_BASE: u8 = 0b0101000;
330    const I2C_ADDRESS_BROADCAST: u8 = 0b0111100;
331    const LED_START_ADDRESS: u8 = 0x07;
332}
333
334impl LP50xx for LP5030 {
335    const LED_COUNT: u8 = 30;
336    const I2C_ADDRESS_BASE: u8 = 0b0110000;
337    const I2C_ADDRESS_BROADCAST: u8 = 0b0011100;
338    const LED_START_ADDRESS: u8 = 0x08;
339    const OUT_START_ADDRESS: u8 = 0x14;
340}
341
342impl LP50xx for LP5036 {
343    const LED_COUNT: u8 = 36;
344    const I2C_ADDRESS_BASE: u8 = 0b0110000;
345    const I2C_ADDRESS_BROADCAST: u8 = 0b0011100;
346    const LED_START_ADDRESS: u8 = 0x08;
347}