sn3193/
lib.rs

1//! This Rust `embedded-hal`-based library is a simple way to control the SN3193 RGB LED driver.
2//! The SN3193 is a 3-channel LED driver with PWM control and breathing mode. It can drive up to
3//! 3 LEDs with a maximum current of 42 mA and is controlled via I2C. This driver is designed for the
4//! `no_std` environment, so it can be used in embedded systems.
5//! ## Usage
6//! Add this to your `Cargo.toml`:
7//! ```toml
8//! [dependencies]
9//! sn3193 = "0.1"
10//! ```
11//! Then to create a new driver:
12//! ```rust
13//! use embedded_hal::delay::DelayMs;
14//! use embedded_hal::i2c::I2c;
15//! use sn3193::SN3193Driver;
16//!
17//! // board setup
18//! let i2c = ...; // I2C peripheral
19//! let delay = ...; // DelayMs implementation
20//!
21//! // It is recommended that the `i2c` object be wrapped in an `embedded_hal_bus::i2c::CriticalSectionDevice` so that it can be shared between
22//! // multiple peripherals.
23//!
24//! // create and initialize the driver
25//! let mut driver = SN3193Driver::new(i2c, delay);
26//! if let Err(e) = driver.init() {
27//!    panic!("Error initializing SN3193 driver: {:?}", e);
28//! }
29//! ```
30//! ## Features
31//! ### PWM mode
32//! The driver can set the LEDs to PWM mode. This allows you to set the brightness of each LED individually.
33//! ```rust
34//! if let Err(e) = diver.set_led_mode(LEDModeSettings::PWM) {
35//!    panic!("Error setting LED mode to PWM: {:?}", e);
36//! }
37//! // check your device wiring to know which LED is which
38//! if let Err(e) = driver.set_pwm_levels(255, 128, 0) {
39//!   panic!("Error setting PWM levels: {:?}", e);
40//! }
41//! ```
42//! ### Breathing mode
43//! The driver can set the LEDs to breathing mode. This mode allows you to set the time it takes for the LED to
44//! ramp up to full brightness, hold at full brightness, ramp down to off, and hold at off. Each of these times
45//! can be set individually for each LED. Furthermore, the PWM levels can be set for each LED.
46//! ```rust
47//! // set the breathing times the same for all LEDs
48//! if let Err(e) = driver.set_breathing_times_for_led(
49//!     LEDId::ALL,
50//!     BreathingIntroTime::Time1p04s,
51//!     BreathingRampUpTime::Time4p16s,
52//!     BreathingHoldHighTime::Time1p04s,
53//!     BreathingRampDownTime::Time4p16s,
54//!     BreathingHoldLowTime::Time2p08s,
55//! ) {
56//!    panic!("Error setting breathing times: {:?}", e);
57//! }
58//! // enable breathing mode
59//! if let Err(e) = driver.set_led_mode(LEDModeSettings::Breathing) {
60//!   panic!("Error setting LED mode to breathing: {:?}", e);
61//! }
62//! ```
63//! The PWM levels and breathing times can be changed at any time. The driver will update the LEDs with the new settings.
64//!
65//! ### Function chaining
66//! The driver functions return a `Result` that contains the driver reference in the `Ok` value. This
67//! can be chained together to make the code more readable.
68//! ```rust
69//! driver.set_led_mode(LEDModeSettings::PWM)?
70//!     .set_current(CurrentSettings::Current17p5mA)?
71//!     .set_pwm_levels(255, 128, 0)?
72//!     .enable_leds(true, true, true)?;
73//! ```
74//! ## License
75//! This library is licensed under the MIT license.
76
77#![no_std]
78#![allow(dead_code, clippy::unusual_byte_groupings)]
79use embedded_hal::{delay::DelayNs, i2c};
80
81// Registers
82const REGISTER_SHUTDOWN: u8 = 0x00;
83const REGISTER_BREATHING_CONTROL: u8 = 0x01;
84const REGISTER_LED_MODE: u8 = 0x02;
85const REGISTER_CURRENT_SETTING: u8 = 0x03;
86const REGISTER_LED1_PWM: u8 = 0x04;
87const REGISTER_LED2_PWM: u8 = 0x05;
88const REGISTER_LED3_PWM: u8 = 0x06;
89const REGISTER_DATA_UPDATE: u8 = 0x07;
90const REGISTER_LED1_T0: u8 = 0x0A;
91const REGISTER_LED2_T0: u8 = 0x0B;
92const REGISTER_LED3_T0: u8 = 0x0C;
93const REGISTER_LED1_T1T2: u8 = 0x10;
94const REGISTER_LED2_T1T2: u8 = 0x11;
95const REGISTER_LED3_T1T2: u8 = 0x12;
96const REGISTER_LED1_T3T4: u8 = 0x16;
97const REGISTER_LED2_T3T4: u8 = 0x17;
98const REGISTER_LED3_T3T4: u8 = 0x18;
99const REGISTER_TIME_UPDATE: u8 = 0x1C;
100const REGISTER_LED_CONTROL: u8 = 0x1D;
101const REGISTER_RESET: u8 = 0x2F;
102
103// shutdown register
104const SHUTDOWN_CHANNEL_ENABLE: u8 = 0b00_1_0000_0;
105const SHUTDOWN_CHANNEL_DISABLE: u8 = 0b00_0_0000_0;
106const SOFTWARE_SHUTDOWN_MODE: u8 = 0b00_0_0000_0;
107const SOFTWARE_SHUTDOWN_NORMAL: u8 = 0b00_0_0000_1;
108
109#[derive(Debug, PartialEq)]
110pub enum CurrentSettings {
111    Current42mA = 0b000_000_00,
112    Current10mA = 0b000_001_00,
113    Current5mA = 0b000_010_00,
114    Current30mA = 0b000_011_00,
115    Current17p5mA = 0b000_100_00,
116}
117
118/// LED mode settings
119#[derive(Debug, PartialEq)]
120pub enum LEDModeSettings {
121    /// The LEDs are controlled by PWM settings
122    PWM = 0b00_0_00000,
123    /// The LEDs are controlled by breathing configuration
124    Breathing = 0b00_1_00000,
125}
126
127/// The time it takes to start breathing.
128/// The time is set in the T0 register.
129#[derive(Debug, PartialEq)]
130pub enum BreathingIntroTime {
131    Time0s = 0b0000_0000,
132    Time0p13s = 0b0001_0000,
133    Time0p26s = 0b0010_0000,
134    Time0p52s = 0b0011_0000,
135    Time1p04s = 0b0100_0000,
136    Time2p08s = 0b0101_0000,
137    Time4p16s = 0b0110_0000,
138    Time8p32s = 0b0111_0000,
139    Time16p64s = 0b1000_0000,
140    Time33p28s = 0b1001_0000,
141    Time66p56s = 0b1010_0000,
142}
143
144/// The time it takes to ramp up from low to high brightness.
145/// The time is set in the T1 register.
146#[derive(Debug, PartialEq)]
147pub enum BreathingRampUpTime {
148    Time0p13s = 0b000_0000_0,
149    Time0p26s = 0b001_0000_0,
150    Time0p52s = 0b010_0000_0,
151    Time1p04s = 0b011_0000_0,
152    Time2p08s = 0b100_0000_0,
153    Time4p16s = 0b101_0000_0,
154    Time8p32s = 0b110_0000_0,
155    Time16p64s = 0b111_0000_0,
156}
157
158/// The time that the high brightness is held.
159/// The time is set in the T2 register.
160#[derive(Debug, PartialEq)]
161pub enum BreathingHoldHighTime {
162    Time0s = 0b000_0000_0,
163    Time0p13s = 0b000_0001_0,
164    Time0p26s = 0b000_0010_0,
165    Time0p52s = 0b000_0011_0,
166    Time1p04s = 0b000_0100_0,
167    Time2p08s = 0b000_0101_0,
168    Time4p16s = 0b000_0110_0,
169    Time8p32s = 0b000_0111_0,
170    Time16p64s = 0b000_1000_0,
171}
172
173/// The time it takes to ramp down from high to low brightness.
174/// The time is set in the T3 register.
175#[derive(Debug, PartialEq)]
176pub enum BreathingRampDownTime {
177    Time0p13s = 0b000_0000_0,
178    Time0p26s = 0b001_0000_0,
179    Time0p52s = 0b010_0000_0,
180    Time1p04s = 0b011_0000_0,
181    Time2p08s = 0b100_0000_0,
182    Time4p16s = 0b101_0000_0,
183    Time8p32s = 0b110_0000_0,
184    Time16p64s = 0b111_0000_0,
185}
186
187/// The time that the low brightness is held.
188/// The time is set in the T4 register.
189#[derive(Debug, PartialEq)]
190pub enum BreathingHoldLowTime {
191    Time0s = 0b000_0000_0,
192    Time0p13s = 0b000_0001_0,
193    Time0p26s = 0b000_0010_0,
194    Time0p52s = 0b000_0011_0,
195    Time1p04s = 0b000_0100_0,
196    Time2p08s = 0b000_0101_0,
197    Time4p16s = 0b000_0110_0,
198    Time8p32s = 0b000_0111_0,
199    Time16p64s = 0b000_1000_0,
200    Time33p28s = 0b000_1001_0,
201    Time66p56s = 0b000_1010_0,
202}
203
204#[derive(Debug, PartialEq)]
205pub enum LEDId {
206    LED1 = 0b001,
207    LED2 = 0b010,
208    LED3 = 0b100,
209    ALL = 0b111,
210}
211/// Errors generated by the SN3193 driver
212#[derive(Debug)]
213pub enum SN3193Error<I2C>
214where
215    I2C: i2c::I2c,
216{
217    /// I2C bus error
218    I2CError(I2C::Error),
219}
220
221#[cfg(feature = "defmt")]
222impl<I2C> defmt::Format for SN3193Error<I2C>
223where
224    I2C: i2c::I2c,
225{
226    fn format(&self, fmt: defmt::Formatter) {
227        let msg = match self {
228            SN3193Error::I2CError(_) => "I2C error",
229        };
230        defmt::write!(fmt, "{}", msg);
231    }
232}
233
234#[cfg(feature = "ufmt")]
235impl<I2C> ufmt::uDisplay for SN3193Error<I2C>
236where
237    I2C: i2c::I2c,
238{
239    fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
240    where
241        W: ufmt::uWrite + ?Sized,
242    {
243        let msg = match self {
244            SN3193Error::I2CError(_) => "I2C error",
245        };
246        ufmt::uwrite!(w, "{}", msg)
247    }
248}
249
250pub struct SN3193Driver<I2C, DELAY>
251where
252    I2C: i2c::I2c,
253    DELAY: DelayNs,
254{
255    i2c: I2C,
256    address: u8,
257    delay: DELAY,
258}
259
260impl<I2C, DELAY> SN3193Driver<I2C, DELAY>
261where
262    I2C: i2c::I2c,
263    DELAY: DelayNs,
264{
265    /// Default address for the SN3193. This is the address when the AD pin is connected to GND.
266    pub fn default_address() -> u8 {
267        0x68
268    }
269
270    /// Create a new SN3193 driver with the default address (0x68)
271    pub fn new(i2c: I2C, delay: DELAY) -> Self {
272        Self::new_with_address(i2c, delay, Self::default_address())
273    }
274
275    /// Create a new SN3193 driver with a specific address.
276    /// The address can be changed by connecting the AD pin to GND, VDD, SCL, or SDA.
277    /// The set of available addresses are:
278    /// - 0x68 when AD is connected to GND
279    /// - 0x6B when AD is connected to VDD
280    /// - 0x69 when AD is connected to SCL
281    /// - 0x6A when AD is connected to SDA
282    pub fn new_with_address(i2c: I2C, delay: DELAY, address: u8) -> Self {
283        Self {
284            i2c,
285            address,
286            delay,
287        }
288    }
289
290    /// Get a mutable reference to the I2C bus used by the driver
291    pub fn i2c(&mut self) -> &mut I2C {
292        &mut self.i2c
293    }
294
295    /// Initialize the SN3193 driver. This will set the LED mode to PWM, the current to 17.5 mA, and enable all LEDs.
296    pub fn init(&mut self) -> Result<&mut Self, SN3193Error<I2C>> {
297        // start up sequence
298        // wait for power up
299        self.delay.delay_ms(10);
300        // reset
301        self.i2c
302            .write(self.address, &[REGISTER_RESET])
303            .map_err(SN3193Error::I2CError)?;
304
305        self.delay.delay_ms(10);
306        self.i2c
307            .write(
308                self.address,
309                &[
310                    REGISTER_SHUTDOWN,
311                    SHUTDOWN_CHANNEL_ENABLE | SOFTWARE_SHUTDOWN_MODE,
312                ],
313            )
314            .map_err(SN3193Error::I2CError)?;
315        self.delay.delay_ms(50);
316        // set mode 0 (PWM)
317        self.set_led_mode(LEDModeSettings::PWM)?;
318
319        // set current to 17.5 ma and enable all LEDs
320        self.set_current(CurrentSettings::Current17p5mA)?
321            .enable_leds(true, true, true)?;
322
323        Ok(self)
324    }
325
326    /// Set the mode of the LED, either PWM or Breathing.
327    pub fn set_led_mode(&mut self, mode: LEDModeSettings) -> Result<&mut Self, SN3193Error<I2C>> {
328        // things seem to work better with a small delay here, but it's not in the datasheet
329        self.delay.delay_ms(10);
330
331        self.i2c
332            .write(self.address, &[REGISTER_LED_MODE, mode as u8])
333            .map_err(SN3193Error::I2CError)?;
334        self.delay.delay_ms(10);
335        Ok(self)
336    }
337
338    /// Set the current for the LEDs.
339    pub fn set_current(&mut self, current: CurrentSettings) -> Result<&mut Self, SN3193Error<I2C>> {
340        // things seem to work better with a small delay here, but it's not in the datasheet
341        self.delay.delay_ms(1);
342
343        self.i2c
344            .write(self.address, &[REGISTER_CURRENT_SETTING, current as u8])
345            .map_err(SN3193Error::I2CError)?;
346        self.delay.delay_ms(5);
347        Ok(self)
348    }
349
350    /// Enable or disable the LEDs. The LEDs are numbered 1, 2, and 3.
351    pub fn enable_leds(
352        &mut self,
353        led1: bool,
354        led2: bool,
355        led3: bool,
356    ) -> Result<&mut Self, SN3193Error<I2C>> {
357        // things seem to work better with a small delay here, but it's not in the datasheet
358        self.delay.delay_ms(1);
359
360        let mut led_enable = 0;
361        if led1 {
362            led_enable |= 0b001;
363        }
364        if led2 {
365            led_enable |= 0b010;
366        }
367        if led3 {
368            led_enable |= 0b100;
369        }
370        self.i2c
371            .write(self.address, &[REGISTER_LED_CONTROL, led_enable])
372            .map_err(SN3193Error::I2CError)?;
373        self.load_register_data()
374    }
375
376    /// Set the PWM levels for the RGB LED. 255 is full on, 0 is off.
377    pub fn set_pwm_levels(
378        &mut self,
379        led1: u8,
380        led2: u8,
381        led3: u8,
382    ) -> Result<&mut Self, SN3193Error<I2C>> {
383        // things seem to work better with a small delay here, but it's not in the datasheet
384        self.delay.delay_ms(2);
385        self.i2c
386            .write(self.address, &[REGISTER_LED1_PWM, led1])
387            .map_err(SN3193Error::I2CError)?;
388        // things seem to work better with a small delay here, but it's not in the datasheet
389        self.delay.delay_ms(2);
390        self.i2c
391            .write(self.address, &[REGISTER_LED2_PWM, led2])
392            .map_err(SN3193Error::I2CError)?;
393        // things seem to work better with a small delay here, but it's not in the datasheet
394        self.delay.delay_ms(2);
395        self.i2c
396            .write(self.address, &[REGISTER_LED3_PWM, led3])
397            .map_err(SN3193Error::I2CError)?;
398        self.load_register_data()
399    }
400
401    /// Set the breathing times for a particular LED. The times are set in the T0, T1, T2, T3, and T4 registers.
402    /// The same values will be assigned to all LEDs if `LEDId::ALL` is used for the `led` parameter.
403    pub fn set_breathing_times_for_led(
404        &mut self,
405        led: LEDId,
406        intro: BreathingIntroTime,
407        ramp_up: BreathingRampUpTime,
408        hold_high: BreathingHoldHighTime,
409        ramp_down: BreathingRampDownTime,
410        hold_low: BreathingHoldLowTime,
411    ) -> Result<&mut Self, SN3193Error<I2C>> {
412        let t0_value = intro as u8;
413        let t1t2_value = (ramp_up as u8) | (hold_high as u8);
414        let t3t4_value = (ramp_down as u8) | (hold_low as u8);
415
416        if led == LEDId::LED1 || led == LEDId::ALL {
417            self.set_breathing_register(REGISTER_LED1_T0, t0_value)?;
418            self.set_breathing_register(REGISTER_LED1_T1T2, t1t2_value)?;
419            self.set_breathing_register(REGISTER_LED1_T3T4, t3t4_value)?;
420        }
421        if led == LEDId::LED2 || led == LEDId::ALL {
422            self.set_breathing_register(REGISTER_LED2_T0, t0_value)?;
423            self.set_breathing_register(REGISTER_LED2_T1T2, t1t2_value)?;
424            self.set_breathing_register(REGISTER_LED2_T3T4, t3t4_value)?;
425        }
426        if led == LEDId::LED3 || led == LEDId::ALL {
427            self.set_breathing_register(REGISTER_LED3_T0, t0_value)?;
428            self.set_breathing_register(REGISTER_LED3_T1T2, t1t2_value)?;
429            self.set_breathing_register(REGISTER_LED3_T3T4, t3t4_value)?;
430        }
431        self.load_register_time_data()?;
432        Ok(self)
433    }
434
435    /// Load the register data. This is used to update the LEDs after changing the PWM levels. Private method.
436    fn load_register_data(&mut self) -> Result<&mut Self, SN3193Error<I2C>> {
437        // things seem to work better with a small delay here, but it's not in the datasheet
438        self.delay.delay_ms(10);
439        self.i2c
440            .write(self.address, &[REGISTER_DATA_UPDATE, 0xFF])
441            .map_err(SN3193Error::I2CError)?;
442        self.delay.delay_ms(30);
443        Ok(self)
444    }
445
446    /// Load the breathing time data. This is used to update the LEDs after changing the breathing times. Private method.
447    fn load_register_time_data(&mut self) -> Result<&mut Self, SN3193Error<I2C>> {
448        // things seem to work better with a small delay here, but it's not in the datasheet
449        self.delay.delay_ms(10);
450        self.i2c
451            .write(self.address, &[REGISTER_TIME_UPDATE, 0xFF])
452            .map_err(SN3193Error::I2CError)?;
453        self.delay.delay_ms(30);
454        Ok(self)
455    }
456
457    /// Set a breathing register. Private method.
458    fn set_breathing_register(
459        &mut self,
460        register: u8,
461        value: u8,
462    ) -> Result<&mut Self, SN3193Error<I2C>> {
463        // things seem to work better with a small delay here, but it's not in the datasheet
464        self.delay.delay_ms(10);
465        self.i2c
466            .write(self.address, &[register, value])
467            .map_err(SN3193Error::I2CError)?;
468        self.delay.delay_ms(30);
469        Ok(self)
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    extern crate std;
476    use super::*;
477    use embedded_hal_mock::eh1::{
478        delay::NoopDelay,
479        i2c::{Mock as I2cMock, Transaction as I2cTransaction},
480    };
481
482    #[test]
483    fn test_current_settings_into() {
484        assert_eq!(CurrentSettings::Current42mA as u8, 0b000_000_00);
485        assert_eq!(CurrentSettings::Current10mA as u8, 0b000_001_00);
486        assert_eq!(CurrentSettings::Current5mA as u8, 0b000_010_00);
487        assert_eq!(CurrentSettings::Current30mA as u8, 0b000_011_00);
488        assert_eq!(CurrentSettings::Current17p5mA as u8, 0b000_100_00);
489    }
490
491    #[test]
492    fn test_led_mode_settings_into() {
493        assert_eq!(LEDModeSettings::PWM as u8, 0b00_0_00000);
494        assert_eq!(LEDModeSettings::Breathing as u8, 0b00_1_00000);
495    }
496
497    #[test]
498    fn test_set_led_mode() {
499        let expectations = [I2cTransaction::write(
500            0x68,
501            std::vec![REGISTER_LED_MODE, LEDModeSettings::PWM as u8],
502        )];
503        let i2c = I2cMock::new(&expectations);
504        let mut driver = SN3193Driver::new(i2c, NoopDelay);
505        driver.set_led_mode(LEDModeSettings::PWM).unwrap();
506        driver.i2c().done();
507    }
508
509    #[test]
510    fn test_set_current() {
511        let expectations = [
512            I2cTransaction::write(
513                0x68,
514                std::vec![
515                    REGISTER_CURRENT_SETTING,
516                    CurrentSettings::Current17p5mA as u8
517                ],
518            ),
519            I2cTransaction::write(
520                0x68,
521                std::vec![REGISTER_CURRENT_SETTING, CurrentSettings::Current42mA as u8],
522            ),
523        ];
524        let i2c = I2cMock::new(&expectations);
525        let mut driver = SN3193Driver::new(i2c, NoopDelay);
526        assert!(driver.set_current(CurrentSettings::Current17p5mA).is_ok());
527        assert!(driver.set_current(CurrentSettings::Current42mA).is_ok());
528        driver.i2c().done();
529    }
530
531    #[test]
532    fn test_enable_leds() {
533        let expectations = [
534            I2cTransaction::write(0x68, std::vec![REGISTER_LED_CONTROL, 0b111]),
535            I2cTransaction::write(0x68, std::vec![REGISTER_DATA_UPDATE, 0xFF]),
536            I2cTransaction::write(0x68, std::vec![REGISTER_LED_CONTROL, 0b101]),
537            I2cTransaction::write(0x68, std::vec![REGISTER_DATA_UPDATE, 0xFF]),
538            I2cTransaction::write(0x68, std::vec![REGISTER_LED_CONTROL, 0b011]),
539            I2cTransaction::write(0x68, std::vec![REGISTER_DATA_UPDATE, 0xFF]),
540        ];
541        let i2c = I2cMock::new(&expectations);
542        let mut driver = SN3193Driver::new(i2c, NoopDelay);
543        assert!(driver.enable_leds(true, true, true).is_ok());
544        assert!(driver.enable_leds(true, false, true).is_ok());
545        assert!(driver.enable_leds(true, true, false).is_ok());
546        driver.i2c().done();
547    }
548
549    #[test]
550    fn test_set_pwm_levels() {
551        let expectations = [
552            I2cTransaction::write(0x6B, std::vec![REGISTER_LED1_PWM, 255]),
553            I2cTransaction::write(0x6B, std::vec![REGISTER_LED2_PWM, 128]),
554            I2cTransaction::write(0x6B, std::vec![REGISTER_LED3_PWM, 0]),
555            I2cTransaction::write(0x6B, std::vec![REGISTER_DATA_UPDATE, 0xFF]),
556        ];
557        let i2c = I2cMock::new(&expectations);
558        let mut driver = SN3193Driver::new_with_address(i2c, NoopDelay, 0x6B);
559        assert!(driver.set_pwm_levels(255, 128, 0).is_ok());
560        driver.i2c().done();
561    }
562}