lm36011/
lib.rs

1#![no_std]
2
3use embedded_hal::blocking::i2c;
4use bitflags::bitflags;
5use core::fmt;
6
7/// Library for the Texas instruments LM36011 inductorless LED driver
8///
9/// https://www.ti.com/lit/ds/symlink/lm36011.pdf?ts=1694461699965&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FLM36011
10///
11/// This crate enables register only read / write, or complete register one-shot read/write based
12/// on the last known register values and the bitflags crate for updating specific features.
13
14/// Custom errors for the LM36011.
15#[derive(Debug)]
16pub enum LM36011Error<E> {
17    I2CError(E),
18    InvalidInput,
19    CurrentOutOfRange,
20    DeviceIDError,
21}
22
23/// Represents the configuration registers of the LM36011.
24pub enum Register {
25    /// Enable Register
26    EnableRegister = 0x01,
27    /// Configuration Register
28    ConfigurationRegister = 0x02,
29    /// LED Flash Brightness Register
30    LEDFlashBrightnessRegister = 0x03,
31    /// LED Torch Brightness Register
32    LEDTorchBrightnessRegister = 0x04,
33    /// Flags Register
34    FlagsRegister = 0x05,
35    /// Device ID Register
36    DeviceIdRegister = 0x06,
37}
38
39/// implement display trait for Register Enum to be used in printing out to serial if needed
40impl fmt::Display for Register {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match *self {
43            Register::EnableRegister => write!(f, "Enable Register"),
44            Register::ConfigurationRegister => write!(f, "Configuration Register"),
45            Register::LEDFlashBrightnessRegister => write!(f, "LED Flash Brightness Register"),
46            Register::LEDTorchBrightnessRegister => write!(f, "LED Torch Brightness Register"),
47            Register::FlagsRegister => write!(f, "Flags Register"),
48            Register::DeviceIdRegister => write!(f, "Device ID Register"),
49        }
50    }
51}
52
53// Bitflags for the Enable Register (0x01)
54bitflags! {
55    pub struct EnableRegisterFlags: u8 {
56        // Reserved for future use
57        const ENABLE_REGISTER_RFU           = 0b1110_0000;
58        // enables
59        const IVFM_ENABLE                   = 0b0001_0000;
60        const STROBE_TYPE_EDGE_TRIGGERED    = 0b0000_1000;
61        const STROBE_ENABLE                 = 0b0000_0100;
62        // mode settings
63        const MODE_IR_DRIVE                 = 0b0000_0001;
64        const MODE_TORCH                    = 0b0000_0010;
65        const MODE_FLASH                    = 0b0000_0011;
66        const MODE_MASK                     = 0b0000_0011;
67    }
68}
69
70// Bitflags for the Configuration Register (0x02)
71bitflags! {
72    pub struct ConfigurationRegisterFlags: u8 {
73        /// IVFM Levels (IVFM-D) [Bit 7-5]
74        const IVFM_2_9V         = 0b0000_0000;
75        const IVFM_3_0V         = 0b0010_0000;
76        const IVFM_3_1V         = 0b0100_0000;
77        const IVFM_3_2V         = 0b0110_0000;
78        const IVFM_3_3V         = 0b1000_0000;
79        const IVFM_3_4V         = 0b1010_0000;
80        const IVFM_3_5V         = 0b1100_0000;
81        const IVFM_3_6V         = 0b1110_0000;
82
83        /// Flash Time-out Duration [Bit 4-1]
84        const TIMEOUT_40MS      = 0b0000_0000;
85        const TIMEOUT_80MS      = 0b0000_0010;
86        const TIMEOUT_120MS     = 0b0000_0100;
87        const TIMEOUT_160MS     = 0b0000_0110;
88        const TIMEOUT_200MS     = 0b0000_1000;
89        const TIMEOUT_240MS     = 0b0000_1010;
90        const TIMEOUT_280MS     = 0b0000_1100;
91        const TIMEOUT_320MS     = 0b0000_1110;
92        const TIMEOUT_360MS     = 0b0001_0000;
93        const TIMEOUT_400MS     = 0b0001_0010;
94        const TIMEOUT_600MS     = 0b0001_0100;
95        const TIMEOUT_800MS     = 0b0001_0110;
96        const TIMEOUT_1000MS    = 0b0001_1000;
97        const TIMEOUT_1200MS    = 0b0001_1010;
98        const TIMEOUT_1400MS    = 0b0001_1100;
99        const TIMEOUT_1600MS    = 0b0001_1110;
100
101        /// Torch Ramp [Bit 0]
102        const TORCH_RAMP_OFF    = 0b0000_0000;
103        const TORCH_RAMP_1MS    = 0b0000_0001;
104    }
105}
106
107// Bitflags for the LED Flash Brightness Register (0x03)
108bitflags! {
109    pub struct LedFlashBrightnessFlags: u8 {
110        /// LED Flash Brightness Level [Bit 6:0]
111        const FLASH_11MA    = 0x00;
112        const FLASH_257MA   = 0x15;
113        const FLASH_750MA   = 0x3F;
114        const FLASH_1030MA  = 0x5F;
115        const FLASH_1200MA  = 0x66;
116        const FLASH_1500MA  = 0x7F;
117
118        /// Thermal Current Scale-Back [Bit 7]
119        const THERMAL_SCALEBACK_ENABLED = 0b1000_0000;
120    }
121}
122
123// Bitflags for the LED Torch Brightness Register (0x04)
124bitflags! {
125    pub struct LedTorchBrightnessFlags: u8 {
126        // Reserved for future use
127        const TORCH_BRIGHTNESS_RFU  = 0b1000_0000;
128        // Torch currents
129        const TORCH_2_4MA           = 0x00;
130        const TORCH_64MA            = 0x15;
131        const TORCH_188MA           = 0x3F;
132        const TORCH_258MA           = 0x5F;
133        const TORCH_302MA           = 0x66;
134        const TORCH_376MA           = 0x7F;
135    }
136}
137
138// Bitflags for the Flags Register (0x05)
139bitflags! {
140    pub struct FlagRegisterFlags: u8 {
141        // Reserved for future use
142        const FLAGS_REGISTER_RFU            = 0b1000_0000;
143        // Flags
144        const IVFM_TRIP                     = 0b0100_0000;
145        const VLED_SHORT_FAULT              = 0b0010_0000;
146        const THERMAL_CURRENT_SCALE_BACK    = 0b0000_1000;
147        const THERMAL_SHUTDOWN_FAULT        = 0b0000_0100;
148        const UVLO_FAULT                    = 0b0000_0010;
149        const FLASH_TIMEOUT_FLAG            = 0b0000_0001;
150    }
151}
152
153// Bitflags for the Device ID Register (0x06)
154bitflags! {
155    /// Represents the Device ID and RESET Register of the LM36011.
156    pub struct DeviceIdFlags: u8 {
157        // Software RESET
158        // 0 = Normal (default)
159        // 1 = Force device RESET
160        const SOFTWARE_RESET            = 0b1000_0000;
161
162        // Reserved for Future Use
163        const DEVICE_ID_RFU             = 0b0100_0000;
164
165        // Device ID
166        const DEVICE_ID_MASK            = 0b0011_1000;
167
168        // Silicon Revision Bits
169        const SILICON_REVISION_MASK     = 0b0000_0111;
170    }
171}
172
173/// I2C address for the LM36011 device.
174const LM36011_I2C_ADDRESS: u8 = 0x64;
175
176/// Represents the LM36011 device with an associated I2C interface.
177pub struct LM36011<I2C> {
178    /// The I2C interface used to communicate with the device.
179    i2c: I2C,
180    pub enable_flags: EnableRegisterFlags,
181    pub config_flags: ConfigurationRegisterFlags,
182    pub flash_brightness_flags: LedFlashBrightnessFlags,
183    pub torch_brightness_flags: LedTorchBrightnessFlags,
184    pub flag_register_flags: FlagRegisterFlags,
185    pub device_id: DeviceIdFlags,
186}
187
188impl<I2C> fmt::Display for LM36011<I2C> {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(
191            f,
192            "Enable Register: {:?}, \
193            Configuration Register: {:?}, \
194            LED Flash Brightness Register: {:?}, \
195            LED Torch Brightness Register: {:?}, \
196            Flags Register: {:?}, \
197            Device ID Register: {:?}",
198            self.enable_flags,
199            self.config_flags,
200            self.flash_brightness_flags,
201            self.torch_brightness_flags,
202            self.flag_register_flags,
203            self.device_id
204        )
205    }
206}
207
208impl<I2C, E> LM36011<I2C>
209    where
210        I2C: i2c::Write<Error=E> + i2c::WriteRead<Error=E>,
211{
212    /// Creates a new instance of the LM36011 with the provided I2C interface.
213    pub fn new(i2c: I2C) -> Self {
214        Self {
215            i2c,
216            enable_flags: EnableRegisterFlags::IVFM_ENABLE,
217            config_flags: ConfigurationRegisterFlags::IVFM_2_9V |
218                ConfigurationRegisterFlags::TIMEOUT_600MS |
219                ConfigurationRegisterFlags::TORCH_RAMP_1MS,
220            flash_brightness_flags: LedFlashBrightnessFlags::FLASH_11MA |
221                LedFlashBrightnessFlags::THERMAL_SCALEBACK_ENABLED,
222            torch_brightness_flags: LedTorchBrightnessFlags::TORCH_2_4MA,
223            flag_register_flags: FlagRegisterFlags::empty(),
224            device_id: DeviceIdFlags::empty(),
225        }
226    }
227
228    /// Sets the flash current of the LM36011 device.
229    ///
230    /// This function configures the flash current of the LM36011 by writing to the
231    /// `LEDFlashBrightnessRegister`. The desired current value is passed as an argument.
232    ///
233    /// # Arguments
234    ///
235    /// * `current` - The desired flash current value to be set. The exact range and interpretation
236    ///               of this value should be based on the LM36011 documentation.
237    ///
238    /// # Returns
239    ///
240    /// * `Ok(())` if the operation was successful.
241    /// * `Err(E)` if there was an error during the operation. The error type `E` is determined by the I2C interface.
242    ///
243    /// # Example
244    ///
245    /// ```
246    /// // Some initialization to get the device instance
247    /// //(I2C needs to be inititilized first)
248    /// let mut driver = lm36011::LM36011::new(i2c);; // Some initialization to get the device instance
249    /// match driver.set_flash_current(0x55) {
250    ///     Ok(_) => println!("Flash current set successfully"),
251    ///     Err(e) => eprintln!("Error setting flash current: {:?}", e),
252    /// }
253    /// ```
254    pub fn set_flash_current_hex(&mut self, current: u8) -> Result<(), LM36011Error<E>> {
255        if current > 0b1000_0000 {
256            return Err(LM36011Error::CurrentOutOfRange);
257        }
258
259        // Use the set_register function to set the flash current
260        self.set_register(Register::LEDFlashBrightnessRegister, current)
261    }
262
263    /// Sets the flash current of the LM36011 device.
264    ///
265    /// This function configures the flash current of the LM36011 by writing to the
266    /// `LEDFlashBrightnessRegister`. The desired current value is passed as an argument.
267    ///
268    /// # Arguments
269    ///
270    /// * `current` - The desired flash current value to be set. The input current in mA will be
271    /// divided by 11.7 and converted to a u8 byte.  Note: since the resolution of the driver is
272    /// 11.7mA, setting fractions of the current is likly overkill, but could be more accurate in a
273    /// very small subset of results.
274    ///
275    /// # Returns
276    ///
277    /// * `Ok(())` if the operation was successful.
278    /// * `Err(E)` if there was an error during the operation. The error type `E` is determined by the I2C interface.
279    ///
280    /// # Example
281    ///
282    /// ```
283    /// // Some initialization to get the device instance
284    /// //(I2C needs to be inititilized first)
285    /// let mut driver = lm36011::LM36011::new(i2c);; // Some initialization to get the device instance
286    /// match driver.set_flash_current(150.0) {
287    ///     Ok(_) => println!("Flash current set successfully"),
288    ///     Err(e) => eprintln!("Error setting flash current: {:?}", e),
289    /// }
290    /// ```
291    pub fn set_flash_current(&mut self, current: f32) -> Result<(), LM36011Error<E>> {
292        if current < 0.0 || current > 1500.0 {
293            return Err(LM36011Error::CurrentOutOfRange);
294        }
295        // take in the current in mA (f32) and convert it to a hex value
296        let brightness_flags: u8 = (current / 11.7) as u8;
297
298        // convert the u8 value to a LedFlashBrightnessFlags
299        let mut brightness_bitflags =
300            LedFlashBrightnessFlags::from_bits_truncate(brightness_flags);
301
302        // Ensure the thermal current scale-back bit remains set/not set
303        brightness_bitflags.set(
304            LedFlashBrightnessFlags::THERMAL_SCALEBACK_ENABLED,
305            self.flash_brightness_flags.contains(
306                LedFlashBrightnessFlags::THERMAL_SCALEBACK_ENABLED),
307        );
308
309        // Use the set_register function to set the flash current
310        self.set_register(Register::LEDFlashBrightnessRegister, brightness_flags)?;
311
312        // update internal struct state
313        self.flash_brightness_flags = brightness_bitflags;
314
315        Ok(())
316    }
317
318    /// Retrieves the device ID from the LM36011.
319    ///
320    /// This function reads the `DeviceIdRegister` of the LM36011 device to obtain its ID.
321    /// It uses the I2C `write_read` method to request and retrieve the device ID.
322    ///
323    /// # Returns
324    ///
325    /// * `Ok(u8)` containing the device ID if the read operation was successful.
326    /// * `Err(E)` if there was an error during the read operation. The error type `E` is determined by the I2C interface.
327    ///
328    /// # Example
329    ///
330    /// ```
331    /// // Some initialization to get the device instance
332    /// //(I2C needs to be inititilized first)
333    /// let mut driver = lm36011::LM36011::new(i2c);; // Some initialization to get the device instance
334    /// match driver.get_device_id() {
335    ///     Ok(id) => println!("LM36011 device ID: {}", id),
336    ///     Err(e) => eprintln!("Error reading device ID: {:?}", e),
337    /// }
338    /// ```
339    pub fn get_device_id(&mut self) -> Result<u8, E> {
340        let mut buffer = [0u8; 1];
341        self.i2c.write_read(LM36011_I2C_ADDRESS, &[Register::DeviceIdRegister as u8],
342                            &mut buffer)?;
343        Ok(buffer[0])
344    }
345
346    /// Retrieves the value of a specified register from the device.
347    ///
348    /// This function reads a byte of data from a specified register on the LM36011 device.
349    /// It uses the I2C `write_read` method to request and retrieve the data.
350    ///
351    /// # Arguments
352    ///
353    /// * `reg` - The register from which the data should be read. This is specified using the `Register` enum.
354    ///
355    /// # Returns
356    ///
357    /// * `Ok(u8)` containing the byte value read from the specified register if the read operation was successful.
358    /// * `Err(E)` if there was an error during the read operation. The error type `E` is determined by the I2C interface.
359    ///
360    /// # Example
361    ///
362    /// ```
363    /// // Some initialization to get the device instance
364    /// //(I2C needs to be inititilized first)
365    /// let mut driver = lm36011::LM36011::new(i2c);
366    /// match driver.get_register(Register::DeviceIdRegister) {
367    ///     Ok(value) => println!("Register value: {}", value),
368    ///     Err(e) => eprintln!("Error reading register: {:?}", e),
369    /// }
370    /// ```
371    pub fn get_register(&mut self, reg: Register) -> Result<u8, E> {
372        let mut buffer = [0u8; 1];
373        self.i2c.write_read(LM36011_I2C_ADDRESS, &[reg as u8], &mut buffer)?;
374        Ok(buffer[0])
375    }
376
377    /// Sets the value of a specified register on the device.
378    ///
379    /// This function writes a given data byte to a specified register on the LM36011 device.
380    /// It uses the I2C `write` method to send the data.
381    ///
382    /// # Arguments
383    ///
384    /// * `reg` - The register to which the data should be written. This is specified using the `Register` enum.
385    /// * `data` - The data byte to be written to the specified register.
386    ///
387    /// # Returns
388    ///
389    /// * `Ok(())` if the write operation was successful.
390    /// * `Err(E)` if there was an error during the write operation. The error type `E` is determined by the I2C interface.
391    ///
392    /// # Example
393    ///
394    /// ```
395    /// // Some initialization to get the device instance
396    /// //(I2C needs to be inititilized first)
397    /// let mut driver = lm36011::LM36011::new(i2c);
398    /// let result = driver.set_register(Register::DeviceIdRegister, 0x01);
399    /// if result.is_err() {
400    ///     // Handle the error
401    /// }
402    /// ```
403    pub fn set_register(&mut self, reg: Register, data: u8) -> Result<(), LM36011Error<E>> {
404        let buffer: [u8; 2] = [reg as u8, data];
405        self.i2c.write(LM36011_I2C_ADDRESS, &buffer).
406            map_err(LM36011Error::I2CError)
407    }
408
409    /// Reads all the registers of the LM36011 and saves the register states to the respective bitflag structs.
410    ///
411    /// This function performs a single I2C read operation starting from the `EnableRegister` and reads 6 bytes,
412    /// which correspond to the 6 registers of the LM36011. The read values are then saved to the respective
413    /// bitflag structs for easy access and manipulation.
414    ///
415    /// # Returns
416    ///
417    /// * `Ok(())` if the I2C read operation is successful.
418    /// * `Err(E)` if the I2C read operation fails, where `E` is the error type of the I2C operations.
419    ///
420    /// # Usage
421    ///
422    /// ```rust
423    /// let mut driver = LM36011::new(i2c_instance);
424    ///
425    /// if let Err(e) = driver.read_status() {
426    ///     // Handle the error `e` here.
427    /// }
428    /// ```
429    pub fn read_status(&mut self) -> Result<(), LM36011Error<E>> {
430        // Read all 6 LM36011 registers
431        let mut buffer = [0u8; 6];
432        self.i2c.write_read(LM36011_I2C_ADDRESS,
433                            &[Register::EnableRegister as u8], &mut buffer).
434            map_err(LM36011Error::I2CError)?;
435
436        // Save registers to the struct
437        self.enable_flags = EnableRegisterFlags::from_bits_truncate(buffer[0]);
438        self.config_flags = ConfigurationRegisterFlags::from_bits_truncate(buffer[1]);
439        self.flash_brightness_flags = LedFlashBrightnessFlags::from_bits_truncate(buffer[2]);
440        self.torch_brightness_flags = LedTorchBrightnessFlags::from_bits_truncate(buffer[3]);
441        self.flag_register_flags = FlagRegisterFlags::from_bits_truncate(buffer[4]);
442        self.device_id = DeviceIdFlags::from_bits_truncate(buffer[5]);
443
444        Ok(())
445    }
446
447    /// Writes the bitflags settings to the LM36011 device.
448    ///
449    /// This function will take the current settings stored in the bitflag structs and write them to the
450    /// respective registers on the LM36011 device using I2C.
451    ///
452    /// # Examples
453    ///
454    /// ```rust
455    /// // Assuming `i2c` is an initialized I2C instance`
456    /// let mut driver = LM36011::new(i2c_instance);
457    /// // Modify some settings
458    /// driver.enable_flags.insert(EnableRegisterFlags::MODE_TORCH);
459    /// driver.config_flags.insert(ConfigurationRegisterFlags::IVFM_3_4V);
460    ///
461    /// // Write the modified settings to the device
462    /// match lm36011.write_status() {
463    ///     Ok(_) => println!("Settings written successfully!"),
464    ///     Err(e) => println!("Failed to write settings: {:?}", e),
465    /// }
466    /// ```
467    ///
468    pub fn write_status(&mut self) -> Result<(), LM36011Error<E>> {
469        // create a buffer with all of the settings
470        let buffer = [0x01,
471            self.enable_flags.bits(),
472            self.config_flags.bits(),
473            self.flash_brightness_flags.bits(),
474            self.torch_brightness_flags.bits(),
475            //self.flag_register_flags.bits(),
476            //self.device_id.bits(),
477        ];
478
479        self.i2c.write(LM36011_I2C_ADDRESS, &buffer)
480            .map_err(LM36011Error::I2CError)
481    }
482
483    /// Performs a software reset on the LM36011 device.
484    ///
485    /// This function sends a specific command to the LM36011 device to initiate a software reset.
486    /// The reset command is sent to the address `0x06` with the data `0b1000_0000`.
487    ///
488    /// # Examples
489    ///
490    /// ```rust
491    /// let mut device = LM36011::new(i2c_instance);
492    ///
493    /// match device.software_reset() {
494    ///     Ok(_) => println!("Software reset successful!"),
495    ///     Err(e) => println!("Software reset failed with error: {:?}", e),
496    /// }
497    /// ```
498    ///
499    /// # Errors
500    ///
501    /// Returns an `Err` variant of `LM36011Error` if there's an I2C communication error.
502
503    pub fn software_reset(&mut self) -> Result<(), LM36011Error<E>> {
504        let buffer = [0x06,0b1000_0000];
505        self.i2c.write(LM36011_I2C_ADDRESS, &buffer)
506            .map_err(LM36011Error::I2CError)
507    }
508
509    /// Verifies the device ID of the LM36011.
510    ///
511    /// This function reads the current status of the LM36011, including its device ID,
512    /// and then checks if the silicon revision mask matches the expected value.
513    ///
514    /// # Returns
515    ///
516    /// * `Ok(true)` if the device ID matches the expected value.
517    /// * `Err(LM36011Error::InvalidInput)` if the device ID does not match the expected value.
518    /// * `Err(LM36011Error::I2CError(E))` if there's an error during the I2C communication.
519    ///
520    /// # Example
521    ///
522    /// ```rust
523    /// let mut driver = LM36011::new(i2c);
524    /// match driver.verify_device_id() {
525    ///     Ok(true) => println!("Device ID verified!"),
526    ///     Err(LM36011Error::DeviceIDError) => println!("Device ID does not match!"),
527    ///     Err(LM36011Error::I2CError(_)) => println!("Error verifying device ID due to I2C communication"),
528    ///     _ => println!("Some other error occurred"),
529    /// }
530    /// ```
531    pub fn verify_device_id(&mut self) -> Result<bool, LM36011Error<E>> {
532        match self.read_status() {
533            Ok(_) => (),
534            Err(e) => return Err(e),
535        }
536
537        // Check if the read value matches the expected device ID
538        if self.device_id & DeviceIdFlags::SILICON_REVISION_MASK ==
539            DeviceIdFlags::from_bits_truncate(0x01) {
540            Ok(true)
541        } else {
542            Err(LM36011Error::DeviceIDError)
543        }
544    }
545// similarly, you can add other methods with detailed documentation.
546}