is31fl3743b_driver/
lib.rs

1//! This is a platform-agnostic Rust driver for the Lumissil IS31FL3743B LED
2//! Matrix Driver based on the [`embedded-hal`] traits.
3//!
4//! [`embedded-hal`]: https://docs.rs/embedded-hal
5//!
6//! For further details of the device architecture and operation, please refer
7//! to the official [`Datasheet`].
8//!
9//! [`Datasheet`]: https://www.lumissil.com/assets/pdf/core/IS31FL3743B_DS.pdf
10
11#![doc(html_root_url = "https://docs.rs/is31fl3743b-driver/latest")]
12#![doc = include_str!("../README.md")]
13#![cfg_attr(not(test), no_std)]
14
15use core::ops::{Deref, DerefMut, Index, IndexMut};
16
17#[cfg(feature = "is_blocking")]
18use embedded_hal::{
19    delay::DelayNs,
20    spi::{Operation, SpiDevice},
21};
22#[cfg(not(feature = "is_blocking"))]
23use embedded_hal_async::{
24    delay::DelayNs,
25    spi::{Operation, SpiDevice},
26};
27
28mod registers;
29pub use registers::*;
30
31const SW_MIN: u8 = 1;
32const SW_MAX: u8 = 11;
33const CS_MIN: u8 = 1;
34const CS_MAX: u8 = 18;
35const OPEN_REG_MIN: u8 = 0x03;
36const OPEN_REG_MAX: u8 = 0x23;
37const NUM_OPEN_REG: usize = ((OPEN_REG_MAX - OPEN_REG_MIN) + 1) as usize;
38const LED_REG_MIN: u8 = 0x01;
39const LED_REG_MAX: u8 = 0xC6;
40const NUM_LED_REG: usize = ((LED_REG_MAX - LED_REG_MIN) + 1) as usize;
41
42/// Switch column.
43#[derive(Clone, Copy, PartialEq, Eq)]
44pub enum SWx {
45    /// Switch column 1
46    SW1,
47    /// Switch column 2
48    SW2,
49    /// Switch column 3
50    SW3,
51    /// Switch column 4
52    SW4,
53    /// Switch column 5
54    SW5,
55    /// Switch column 6
56    SW6,
57    /// Switch column 7
58    SW7,
59    /// Switch column 8
60    SW8,
61    /// Switch column 9
62    SW9,
63    /// Switch column 10
64    SW10,
65    /// Switch column 11
66    SW11,
67}
68
69impl From<SWx> for u8 {
70    fn from(swx: SWx) -> Self {
71        match swx {
72            SWx::SW1 => 1,
73            SWx::SW2 => 2,
74            SWx::SW3 => 3,
75            SWx::SW4 => 4,
76            SWx::SW5 => 5,
77            SWx::SW6 => 6,
78            SWx::SW7 => 7,
79            SWx::SW8 => 8,
80            SWx::SW9 => 9,
81            SWx::SW10 => 10,
82            SWx::SW11 => 11,
83        }
84    }
85}
86
87impl TryFrom<u8> for SWx {
88    type Error = ();
89
90    fn try_from(value: u8) -> Result<Self, Self::Error> {
91        match value {
92            1 => Ok(SWx::SW1),
93            2 => Ok(SWx::SW2),
94            3 => Ok(SWx::SW3),
95            4 => Ok(SWx::SW4),
96            5 => Ok(SWx::SW5),
97            6 => Ok(SWx::SW6),
98            7 => Ok(SWx::SW7),
99            8 => Ok(SWx::SW8),
100            9 => Ok(SWx::SW9),
101            10 => Ok(SWx::SW10),
102            11 => Ok(SWx::SW11),
103            _ => Err(()),
104        }
105    }
106}
107
108/// Current sink row.
109#[derive(Clone, Copy, PartialEq, Eq)]
110pub enum CSy {
111    /// Current sink row 1
112    CS1,
113    /// Current sink row 2
114    CS2,
115    /// Current sink row 3
116    CS3,
117    /// Current sink row 4
118    CS4,
119    /// Current sink row 5
120    CS5,
121    /// Current sink row 6
122    CS6,
123    /// Current sink row 7
124    CS7,
125    /// Current sink row 8
126    CS8,
127    /// Current sink row 9
128    CS9,
129    /// Current sink row 10
130    CS10,
131    /// Current sink row 11
132    CS11,
133    /// Current sink row 12
134    CS12,
135    /// Current sink row 13
136    CS13,
137    /// Current sink row 14
138    CS14,
139    /// Current sink row 15
140    CS15,
141    /// Current sink row 16
142    CS16,
143    /// Current sink row 17
144    CS17,
145    /// Current sink row 18
146    CS18,
147}
148
149impl From<CSy> for u8 {
150    fn from(csy: CSy) -> Self {
151        match csy {
152            CSy::CS1 => 1,
153            CSy::CS2 => 2,
154            CSy::CS3 => 3,
155            CSy::CS4 => 4,
156            CSy::CS5 => 5,
157            CSy::CS6 => 6,
158            CSy::CS7 => 7,
159            CSy::CS8 => 8,
160            CSy::CS9 => 9,
161            CSy::CS10 => 10,
162            CSy::CS11 => 11,
163            CSy::CS12 => 12,
164            CSy::CS13 => 13,
165            CSy::CS14 => 14,
166            CSy::CS15 => 15,
167            CSy::CS16 => 16,
168            CSy::CS17 => 17,
169            CSy::CS18 => 18,
170        }
171    }
172}
173
174impl TryFrom<u8> for CSy {
175    type Error = ();
176
177    fn try_from(value: u8) -> Result<Self, Self::Error> {
178        match value {
179            1 => Ok(CSy::CS1),
180            2 => Ok(CSy::CS2),
181            3 => Ok(CSy::CS3),
182            4 => Ok(CSy::CS4),
183            5 => Ok(CSy::CS5),
184            6 => Ok(CSy::CS6),
185            7 => Ok(CSy::CS7),
186            8 => Ok(CSy::CS8),
187            9 => Ok(CSy::CS9),
188            10 => Ok(CSy::CS10),
189            11 => Ok(CSy::CS11),
190            12 => Ok(CSy::CS12),
191            13 => Ok(CSy::CS13),
192            14 => Ok(CSy::CS14),
193            15 => Ok(CSy::CS15),
194            16 => Ok(CSy::CS16),
195            17 => Ok(CSy::CS17),
196            18 => Ok(CSy::CS18),
197            _ => Err(()),
198        }
199    }
200}
201
202// Converts SWx and CSy coordinates to an LED register offset.
203// This is mainly used as an address offset to the PWM and Scaling register addresses.
204// Number is calculated based on diagram from Figure 10 in datasheet.
205fn led_reg_offset(swx: SWx, csy: CSy) -> u8 {
206    (u8::from(csy) + (u8::from(swx) - 1) * CS_MAX) - LED_REG_MIN
207}
208
209// Converts a percentage (from 0-100%) to a raw fraction of u8 max (0xFF).
210#[allow(clippy::cast_possible_truncation)]
211const fn raw_from_percent(percent: u8) -> u8 {
212    debug_assert!(percent <= 100);
213    ((0xFF * percent as u16) / 100) as u8
214}
215
216#[cfg(feature = "preserve_registers")]
217#[derive(Clone, Copy, Debug)]
218struct LedRegList([u8; NUM_LED_REG]);
219#[cfg(feature = "preserve_registers")]
220impl LedRegList {
221    fn inner_ref(&self) -> &[u8; NUM_LED_REG] {
222        &self.0
223    }
224}
225
226#[cfg(feature = "preserve_registers")]
227impl Index<usize> for LedRegList {
228    type Output = u8;
229    fn index(&self, index: usize) -> &Self::Output {
230        &self.0[index]
231    }
232}
233
234#[cfg(feature = "preserve_registers")]
235impl IndexMut<usize> for LedRegList {
236    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
237        &mut self.0[index]
238    }
239}
240
241#[cfg(feature = "preserve_registers")]
242impl Default for LedRegList {
243    fn default() -> Self {
244        Self([0x00; NUM_LED_REG])
245    }
246}
247
248// Maintains local, cached copies of device registers
249#[derive(Default, Clone, Copy, Debug)]
250struct CachedReg {
251    config: Configuration,
252    gcc: GlobalCurrentControl,
253    res_phase: PullDownUpResistorSelection,
254    temperature: TemperatureStatus,
255    spread_spectrum: SpreadSpectrum,
256    #[cfg(feature = "preserve_registers")]
257    pwm: LedRegList,
258    #[cfg(feature = "preserve_registers")]
259    scaling: LedRegList,
260}
261
262/// Contains the status of each LED after the last open/short detection test.
263///
264/// A new test must be performed to receive an updated status.
265#[derive(Clone, Copy, Debug)]
266pub struct OpenShortTestResult([u8; NUM_OPEN_REG]);
267
268impl OpenShortTestResult {
269    /// Returns true if every LED is functioning correctly (no shorts/open), false otherwise.
270    #[must_use]
271    pub fn all_leds_ok(&self) -> bool {
272        self.0.iter().all(|&r| r == 0)
273    }
274
275    /// Returns true if specified LED is functioning correctly (not shorted/open), false otherwise.
276    #[must_use]
277    pub fn led_ok(&self, swx: u8, csy: u8) -> bool {
278        debug_assert!((SW_MIN..=SW_MAX).contains(&swx) && (CS_MIN..=CS_MAX).contains(&csy));
279
280        // Calculations based on diagrams from Figure 12 and Table 9 in datasheet.
281        let idx = usize::from(((csy - 1) / 6) + ((swx - 1) * 3));
282        let bit = (csy - 1) % 6;
283
284        self.0[idx] & (1 << bit) == 0
285    }
286}
287
288/// IS31FL3743B device driver group
289#[derive(Clone, Copy, Debug)]
290pub struct Is31fl3743b<const N: usize, SPI>([Is31fl3743bDevice<SPI>; N]);
291
292impl<const N: usize, SPI: SpiDevice> Index<usize> for Is31fl3743b<N, SPI> {
293    type Output = Is31fl3743bDevice<SPI>;
294    fn index(&self, index: usize) -> &Self::Output {
295        &self.0[index]
296    }
297}
298
299impl<const N: usize, SPI: SpiDevice> IndexMut<usize> for Is31fl3743b<N, SPI> {
300    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
301        &mut self.0[index]
302    }
303}
304
305impl<const N: usize, SPI: SpiDevice> Deref for Is31fl3743b<N, SPI> {
306    type Target = Is31fl3743bDevice<SPI>;
307
308    fn deref(&self) -> &Self::Target {
309        &self.0[0]
310    }
311}
312
313impl<const N: usize, SPI: SpiDevice> DerefMut for Is31fl3743b<N, SPI> {
314    fn deref_mut(&mut self) -> &mut Self::Target {
315        &mut self.0[0]
316    }
317}
318
319#[maybe_async::maybe_async]
320impl<const N: usize, SPI: SpiDevice> Is31fl3743b<N, SPI> {
321    /// Create new SYNCd group of IS31FL3743B instances.
322    ///
323    /// This will automatically configure the master (index 0) and
324    /// slaves (index 1..N) into the proper SYNC mode (see [Is31fl3743bDevice::enable_sync]).
325    ///
326    /// The returned group of device instances can be indexed into using
327    /// the respective index of SPI instances passed in.
328    ///
329    /// If using a single device, prefer [Self::new].
330    ///
331    /// # Errors
332    ///
333    /// `SPI::Error` when SPI transaction fails
334    pub async fn new_with_sync(spi: [SPI; N]) -> Result<Self, SPI::Error> {
335        let mut devices = spi.map(|spi| Is31fl3743bDevice::new(spi));
336
337        // Slaves must be configured before master
338        for slave in devices.iter_mut().skip(1) {
339            slave.reset().await?;
340            slave.enable_sync(Sync::Slave).await?;
341            slave.enable().await?;
342        }
343
344        // Configure master
345        devices[0].reset().await?;
346        devices[0].enable_sync(Sync::Master).await?;
347        devices[0].enable().await?;
348
349        Ok(Self(devices))
350    }
351
352    /// Destroy the driver instances and return SPI instances.
353    pub fn destroy_with_sync(self) -> [SPI; N] {
354        self.0.map(Is31fl3743bDevice::destroy)
355    }
356}
357
358#[maybe_async::maybe_async]
359impl<SPI: SpiDevice> Is31fl3743b<1, SPI> {
360    /// Create new IS31FL3743B instance.
361    ///
362    /// If using multiple devices that will be tied together via SYNC pin,
363    /// prefer [Self::new_with_sync] for automatic configuration.
364    ///
365    /// # Errors
366    ///
367    /// `SPI::Error` when SPI transaction fails
368    pub async fn new(spi: SPI) -> Result<Self, SPI::Error> {
369        let mut devices = [Is31fl3743bDevice::new(spi); 1];
370        devices[0].reset().await?;
371        devices[0].enable().await?;
372
373        Ok(Self(devices))
374    }
375
376    /// Destroy the driver instance and return SPI instance.
377    #[allow(clippy::missing_panics_doc)]
378    pub fn destroy(self) -> SPI {
379        // Simply moving the first element out of the array is not allowed by the compiler.
380        // Convert to iterator to allow us to do this.
381        self.0
382            .into_iter()
383            .next()
384            .expect("Array is non-empty; this will never panic")
385            .destroy()
386    }
387}
388
389/// IS31FL3743B device driver
390#[derive(Clone, Copy, Debug)]
391pub struct Is31fl3743bDevice<SPI> {
392    /// The concrete SPI instance.
393    spi: SPI,
394
395    /// Local cached copy of device registers (since device does not support reads)
396    cached_reg: CachedReg,
397}
398
399#[maybe_async::maybe_async]
400impl<SPI: SpiDevice> Is31fl3743bDevice<SPI> {
401    /// Sets the brightness of an LED specified by given `SWx` and `CSy` coordinates as a percentage.
402    ///
403    /// This effectively modulates the PWM duty cycle by the given percentage
404    /// and sets the average current for the specified LED.
405    ///
406    /// For finer control, set the PWM register directly with [Self::set_pwm_reg].
407    ///
408    /// # Errors
409    ///
410    /// `SPI::Error` when SPI transaction fails
411    pub async fn set_led_brightness(
412        &mut self,
413        swx: SWx,
414        csy: CSy,
415        brightness_percentage: u8,
416    ) -> Result<(), SPI::Error> {
417        let raw = raw_from_percent(brightness_percentage);
418        self.set_led_brightness_bulk(swx, csy, &[raw]).await
419    }
420
421    /// Sets the brightness of several LEDs starting at the given `SWx` and `CSy` coordinates.
422    ///
423    /// Brightness must be passed as a raw value from 0 to 255, rather than as a percentage.
424    ///
425    /// # Errors
426    ///
427    /// `SPI::Error` when SPI transaction fails.
428    pub async fn set_led_brightness_bulk(
429        &mut self,
430        start_swx: SWx,
431        start_csy: CSy,
432        raw_brightness_values: &[u8],
433    ) -> Result<(), SPI::Error> {
434        let addr_offset = led_reg_offset(start_swx, start_csy);
435        self.write_multiple_with_addr_offset(Register::Pwm, addr_offset, raw_brightness_values)
436            .await
437    }
438
439    /// Sets the peak current of an LED specified by given `SWx` and `CSy` coordinates
440    /// as a percentage of global current.
441    ///
442    /// For finer control, set the Scaling register directly with [Self::set_scaling_reg].
443    ///
444    /// # Errors
445    ///
446    /// `SPI::Error` when SPI transaction fails
447    pub async fn set_led_peak_current(&mut self, swx: SWx, csy: CSy, scale_percentage: u8) -> Result<(), SPI::Error> {
448        let raw = raw_from_percent(scale_percentage);
449        self.set_led_peak_current_bulk(swx, csy, &[raw]).await
450    }
451
452    /// Sets the peak current of several LEDs starting at the given `SWx` and `CSy` coordinates.
453    ///
454    /// Scale value must be passed as a raw value from 0 to 255, rather than as a percentage.
455    ///
456    /// # Errors
457    ///
458    /// `SPI::Error` when SPI transaction fails
459    pub async fn set_led_peak_current_bulk(
460        &mut self,
461        start_swx: SWx,
462        start_csy: CSy,
463        raw_scale_values: &[u8],
464    ) -> Result<(), SPI::Error> {
465        let addr_offset = led_reg_offset(start_swx, start_csy);
466        self.write_multiple_with_addr_offset(Register::Scaling, addr_offset, raw_scale_values)
467            .await
468    }
469
470    /// Power on the device.
471    ///
472    /// # Errors
473    ///
474    /// `SPI::Error` when SPI transaction fails
475    pub async fn enable(&mut self) -> Result<(), SPI::Error> {
476        self.set_configuration_reg(self.cached_reg.config.with_ssd(true)).await
477    }
478
479    /// Power off the device.
480    ///
481    /// # Errors
482    ///
483    /// `SPI::Error` when SPI transaction fails
484    pub async fn disable(&mut self) -> Result<(), SPI::Error> {
485        self.set_configuration_reg(self.cached_reg.config.with_ssd(false)).await
486    }
487
488    /// Enables switch columns from SW1 up to the column specified.
489    ///
490    /// Also has the effect of changing the duty cycle (which is 1/11 when all columns active).
491    ///
492    /// e.g. Enabling up to SW3 changes duty cycle to 1/3.
493    ///
494    /// # Errors
495    ///
496    /// `SPI::Error` when SPI transaction fails
497    pub async fn switch_column_enable_upto(&mut self, switch_column: SwxSetting) -> Result<(), SPI::Error> {
498        self.set_configuration_reg(self.cached_reg.config.with_sws(switch_column))
499            .await
500    }
501
502    /// Sets the global current as a percentage of max current.
503    ///
504    /// Max current is determined by the external resistor connected to Iset and can be calculated as:
505    ///
506    /// `current = 343 / Riset`
507    ///
508    /// For finer control, set the GCC register directly with [Self::set_global_current_control_reg].
509    ///
510    /// # Errors
511    ///
512    /// `SPI::Error` when SPI transaction fails
513    pub async fn set_global_current(&mut self, scale_percentage: u8) -> Result<(), SPI::Error> {
514        let gcc = GlobalCurrentControl::from(raw_from_percent(scale_percentage));
515        self.set_global_current_control_reg(gcc).await
516    }
517
518    /// Sets the internal pull-up resistor to be used by all current sink rows.
519    ///
520    /// Primarily used for LED "de-ghosting". The default value (2k Ohms) is sufficient in most cases.
521    ///
522    /// # Errors
523    ///
524    /// `SPI::Error` when SPI transaction fails
525    pub async fn set_cs_pullup(&mut self, pur: Resistor) -> Result<(), SPI::Error> {
526        self.set_pdr_pur_resistor_reg(self.cached_reg.res_phase.with_cspur(pur))
527            .await
528    }
529
530    /// Sets the internal pull-down resistor to be used by all switch columns.
531    ///
532    /// Primarily used for "de-ghosting". The default value (2k Ohms) is sufficient in most cases.
533    ///
534    /// # Errors
535    ///
536    /// `SPI::Error` when SPI transaction fails
537    pub async fn set_sw_pulldown(&mut self, pdr: Resistor) -> Result<(), SPI::Error> {
538        self.set_pdr_pur_resistor_reg(self.cached_reg.res_phase.with_swpdr(pdr))
539            .await
540    }
541
542    /// Enable 180 degree phase delay.
543    ///
544    /// Primarily used to help reduce power noise.
545    ///
546    /// # Errors
547    ///
548    /// `SPI::Error` when SPI transaction fails
549    pub async fn enable_phase_delay(&mut self) -> Result<(), SPI::Error> {
550        self.set_pdr_pur_resistor_reg(self.cached_reg.res_phase.with_phc(true))
551            .await
552    }
553
554    /// Disable 180 degree phase delay.
555    ///
556    /// # Errors
557    ///
558    /// `SPI::Error` when SPI transaction fails
559    pub async fn disable_phase_delay(&mut self) -> Result<(), SPI::Error> {
560        self.set_pdr_pur_resistor_reg(self.cached_reg.res_phase.with_phc(false))
561            .await
562    }
563
564    /// Performs a short detection test and returns the test result.
565    ///
566    /// This test is relatively time-consuming and should ideally only
567    /// be performed once or periodically, and not in a tight loop.
568    ///
569    /// Overwrites the `PWM` and `Scaling` registers, so `preserve_registers` must be enabled
570    /// for the original values to be restored.
571    ///
572    /// # Errors
573    ///
574    /// `SPI::Error` when SPI transaction fails
575    pub async fn detect_shorts(&mut self, delay: impl DelayNs) -> Result<OpenShortTestResult, SPI::Error> {
576        self.open_short_test(Open::EnableShort, delay).await
577    }
578
579    /// Performs an open detection test and returns the test result.
580    ///
581    /// This test is relatively expensive and should ideally only
582    /// be performed once or periodically, and not in a tight loop.
583    ///
584    /// Overwrites the `PWM` and `Scaling` registers, so `preserve_registers` must be enabled
585    /// for the original values to be restored.
586    ///
587    /// # Errors
588    ///
589    /// `SPI::Error` when SPI transaction fails
590    pub async fn detect_opens(&mut self, delay: impl DelayNs) -> Result<OpenShortTestResult, SPI::Error> {
591        self.open_short_test(Open::EnableOpen, delay).await
592    }
593
594    /// Sets the thermal roll-off as a percentage of output current.
595    ///
596    /// # Errors
597    ///
598    /// `SPI::Error` when SPI transaction fails
599    pub async fn set_thermal_rolloff(&mut self, trof: ThermalRollOff) -> Result<(), SPI::Error> {
600        self.set_temperature_status_reg(self.cached_reg.temperature.with_trof(trof))
601            .await
602    }
603
604    /// Sets the temperature point (in degrees Celsius) of the IC above which thermal roll-off will begin.
605    ///
606    /// # Errors
607    ///
608    /// `SPI::Error` when SPI transaction fails
609    pub async fn set_temperature_point(&mut self, ts: TemperaturePoint) -> Result<(), SPI::Error> {
610        self.set_temperature_status_reg(self.cached_reg.temperature.with_ts(ts))
611            .await
612    }
613
614    /// Enables spread spectrum function.
615    ///
616    /// # Errors
617    ///
618    /// `SPI::Error` when SPI transaction fails
619    pub async fn enable_spread_spectrum(&mut self) -> Result<(), SPI::Error> {
620        self.set_spread_spectrum_reg(self.cached_reg.spread_spectrum.with_ssp(true))
621            .await
622    }
623
624    /// Disables spread spectrum function.
625    ///
626    /// # Errors
627    ///
628    /// `SPI::Error` when SPI transaction fails
629    pub async fn disable_spread_spectrum(&mut self) -> Result<(), SPI::Error> {
630        self.set_spread_spectrum_reg(self.cached_reg.spread_spectrum.with_ssp(false))
631            .await
632    }
633
634    /// Sets the spread spectrum cycle time in microseconds.
635    ///
636    /// # Errors
637    ///
638    /// `SPI::Error` when SPI transaction fails
639    pub async fn set_spread_spectrum_cycle_time(&mut self, clt: CycleTime) -> Result<(), SPI::Error> {
640        self.set_spread_spectrum_reg(self.cached_reg.spread_spectrum.with_clt(clt))
641            .await
642    }
643
644    /// Sets the spread spectrum cycle range.
645    ///
646    /// # Errors
647    ///
648    /// `SPI::Error` when SPI transaction fails
649    pub async fn set_spread_spectrum_range(&mut self, rng: Range) -> Result<(), SPI::Error> {
650        self.set_spread_spectrum_reg(self.cached_reg.spread_spectrum.with_rng(rng))
651            .await
652    }
653
654    /// Enables sync function configured in either master or slave mode.
655    ///
656    /// All slave chips should be set to operate in slave mode before configuring master chip.
657    ///
658    /// See Note 4 in datasheet.
659    ///
660    /// Prefer creating a SYNCd group of devices via [Is31fl3743b::new_with_sync]
661    /// as opposed to managing this manually for individual devices.
662    ///
663    /// # Errors
664    ///
665    /// `SPI::Error` when SPI transaction fails
666    pub async fn enable_sync(&mut self, mode: Sync) -> Result<(), SPI::Error> {
667        self.set_spread_spectrum_reg(self.cached_reg.spread_spectrum.with_sync(mode))
668            .await
669    }
670
671    /// Disables sync function which pulls SYNC pin low via internal 30k Ohm resistor.
672    ///
673    /// # Errors
674    ///
675    /// `SPI::Error` when SPI transaction fails
676    pub async fn disable_sync(&mut self) -> Result<(), SPI::Error> {
677        self.enable_sync(Sync::Disabled).await
678    }
679
680    /// Resets device, returning all registers to their default state.
681    ///
682    /// # Errors
683    ///
684    /// `SPI::Error` when SPI transaction fails
685    pub async fn reset(&mut self) -> Result<(), SPI::Error> {
686        self.write(Register::Reset, 0xAE).await?;
687        self.cached_reg = CachedReg::default();
688        Ok(())
689    }
690
691    /* See application note AN-107 for reference:
692     * https://www.lumissil.com/assets/pdf/support/app%20notes/AN-107%20OPEN%20SHORT%20TEST%20FUNCTION%20OF%20IS31FL3743B.pdf
693     */
694    async fn open_short_test(
695        &mut self,
696        test: Open,
697        mut delay: impl DelayNs,
698    ) -> Result<OpenShortTestResult, SPI::Error> {
699        // Backup cached registers to restore later
700        let tmp_gcc = self.cached_reg.gcc;
701        let tmp_res_phase = self.cached_reg.res_phase;
702
703        #[cfg(feature = "preserve_registers")]
704        let tmp_pwm = self.cached_reg.pwm;
705        #[cfg(feature = "preserve_registers")]
706        let tmp_scaling = self.cached_reg.scaling;
707
708        // Ensure OSDE field is clear before starting test (test only begins on transition from 0x00)
709        if self.cached_reg.config.osde() != Open::Disabled {
710            self.set_configuration_reg(self.cached_reg.config.with_osde(Open::Disabled))
711                .await?;
712        }
713
714        // Set PWM and Scaling registers to max value
715        self.write_multiple(Register::Pwm, &[0xFF; NUM_LED_REG]).await?;
716        #[cfg(feature = "preserve_registers")]
717        {
718            self.cached_reg.pwm = LedRegList([0xFF; NUM_LED_REG]);
719        }
720        self.write_multiple(Register::Scaling, &[0xFF; NUM_LED_REG]).await?;
721        #[cfg(feature = "preserve_registers")]
722        {
723            self.cached_reg.scaling = LedRegList([0xFF; NUM_LED_REG]);
724        }
725
726        // Write 0x0F (recommended value from application notes) to GCC before starting test
727        self.set_global_current_control_reg(GlobalCurrentControl::from(0x0F))
728            .await?;
729
730        // Clear pur/pdr register before starting test, as recommended by datasheet when GCC is 0x0F
731        self.set_pdr_pur_resistor_reg(PullDownUpResistorSelection::from(0b0000_0000))
732            .await?;
733
734        // Begin test by setting the OSDE field to the appropriate test type (Open or Short)
735        self.set_configuration_reg(self.cached_reg.config.with_osde(test))
736            .await?;
737
738        // Wait 1ms (datasheet only specifies AT LEAST 2 scan cycles; application note recommends 1ms)
739        delay.delay_ms(1).await;
740
741        // Restore previous GCC register value
742        self.set_global_current_control_reg(tmp_gcc).await?;
743
744        // Restore previous pur/pdr register value
745        self.set_pdr_pur_resistor_reg(tmp_res_phase).await?;
746
747        // Restore previous PWM and Scaling register values
748        #[cfg(feature = "preserve_registers")]
749        {
750            self.write_multiple(Register::Pwm, tmp_pwm.inner_ref()).await?;
751            self.cached_reg.pwm = tmp_pwm;
752
753            self.write_multiple(Register::Scaling, tmp_scaling.inner_ref()).await?;
754            self.cached_reg.scaling = tmp_scaling;
755        }
756
757        // Read all `Open` registers and collect into test result struct
758        Ok(OpenShortTestResult(
759            self.read_multiple_with_addr_offset::<NUM_OPEN_REG>(Register::Open, 0)
760                .await?,
761        ))
762    }
763
764    /// Sets the Nth (where 0x01 <= N <= 0xC6) PWM register.
765    ///
766    /// # Errors
767    ///
768    /// `SPI::Error` when SPI transaction fails
769    pub async fn set_pwm_reg(&mut self, n: u8, value: u8) -> Result<(), SPI::Error> {
770        debug_assert!((LED_REG_MIN..=LED_REG_MAX).contains(&n));
771
772        let offset = n - LED_REG_MIN;
773        self.write_with_addr_offset(Register::Pwm, offset, value).await?;
774        #[cfg(feature = "preserve_registers")]
775        {
776            self.cached_reg.pwm[offset as usize] = value;
777        }
778        Ok(())
779    }
780
781    /// Sets the Nth (where 0x01 <= N <= 0xC6) Scaling register.
782    ///
783    /// # Errors
784    ///
785    /// `SPI::Error` when SPI transaction fails
786    pub async fn set_scaling_reg(&mut self, n: u8, value: u8) -> Result<(), SPI::Error> {
787        debug_assert!((LED_REG_MIN..=LED_REG_MAX).contains(&n));
788
789        let offset = n - LED_REG_MIN;
790        self.write_with_addr_offset(Register::Scaling, offset, value).await?;
791        #[cfg(feature = "preserve_registers")]
792        {
793            self.cached_reg.scaling[offset as usize] = value;
794        }
795        Ok(())
796    }
797
798    /// Sets the configuration register.
799    ///
800    /// # Errors
801    ///
802    /// `SPI::Error` when SPI transaction fails
803    pub async fn set_configuration_reg(&mut self, value: Configuration) -> Result<(), SPI::Error> {
804        self.write(Register::Configuration, value.into()).await?;
805        self.cached_reg.config = value;
806        Ok(())
807    }
808
809    /// Sets the global current control register.
810    ///
811    /// # Errors
812    ///
813    /// `SPI::Error` when SPI transaction fails
814    pub async fn set_global_current_control_reg(&mut self, value: GlobalCurrentControl) -> Result<(), SPI::Error> {
815        self.write(Register::GlobalCurrentControl, value.into()).await?;
816        self.cached_reg.gcc = value;
817        Ok(())
818    }
819
820    /// Sets the pull down/up resistor selection register.
821    ///
822    /// # Errors
823    ///
824    /// `SPI::Error` when SPI transaction fails
825    pub async fn set_pdr_pur_resistor_reg(&mut self, value: PullDownUpResistorSelection) -> Result<(), SPI::Error> {
826        self.write(Register::PullDownUpResistorSelection, value.into()).await?;
827        self.cached_reg.res_phase = value;
828        Ok(())
829    }
830
831    /// Sets the temperature status register.
832    ///
833    /// # Errors
834    ///
835    /// `SPI::Error` when SPI transaction fails
836    pub async fn set_temperature_status_reg(&mut self, value: TemperatureStatus) -> Result<(), SPI::Error> {
837        self.write(Register::TemperatureStatus, value.into()).await?;
838        self.cached_reg.temperature = value;
839        Ok(())
840    }
841
842    /// Sets the spread spectrum register.
843    ///
844    /// # Errors
845    ///
846    /// `SPI::Error` when SPI transaction fails
847    pub async fn set_spread_spectrum_reg(&mut self, value: SpreadSpectrum) -> Result<(), SPI::Error> {
848        self.write(Register::SpreadSpectrum, value.into()).await?;
849        self.cached_reg.spread_spectrum = value;
850        Ok(())
851    }
852
853    /// Gets the Nth (where 0x03 <= N <= 0x23) open register.
854    ///
855    /// # Errors
856    ///
857    /// `SPI::Error` when SPI transaction fails
858    pub async fn open_reg(&mut self, n: u8) -> Result<u8, SPI::Error> {
859        debug_assert!((OPEN_REG_MIN..=OPEN_REG_MAX).contains(&n));
860        self.read_with_addr_offset(Register::Open, n - OPEN_REG_MIN).await
861    }
862
863    /// Gets the cached Nth (where 0x01 <= N <= 0xC6) PWM register.
864    #[cfg(feature = "preserve_registers")]
865    pub fn pwm_reg(&mut self, n: u8) -> u8 {
866        debug_assert!((LED_REG_MIN..=LED_REG_MAX).contains(&n));
867        self.cached_reg.pwm[(n - LED_REG_MIN) as usize]
868    }
869
870    /// Gets the cached Nth (where 0x01 <= N <= 0xC6) Scaling register.
871    #[cfg(feature = "preserve_registers")]
872    pub fn scaling_reg(&mut self, n: u8) -> u8 {
873        debug_assert!((LED_REG_MIN..=LED_REG_MAX).contains(&n));
874        self.cached_reg.scaling[(n - LED_REG_MIN) as usize]
875    }
876
877    /// Gets the cached configuration register.
878    pub fn configuration_reg(&mut self) -> Configuration {
879        self.cached_reg.config
880    }
881
882    /// Gets the global current control register.
883    pub fn global_current_control_reg(&mut self) -> GlobalCurrentControl {
884        self.cached_reg.gcc
885    }
886
887    /// Gets the cached pull down/up resistor selection register.
888    pub fn pdr_pur_resistor_reg(&mut self) -> PullDownUpResistorSelection {
889        self.cached_reg.res_phase
890    }
891
892    /// Gets the cached temperature status register.
893    pub fn temperature_status_reg(&mut self) -> TemperatureStatus {
894        self.cached_reg.temperature
895    }
896
897    /// Gets the cached spread spectrum register.
898    pub fn spread_spectrum_reg(&mut self) -> SpreadSpectrum {
899        self.cached_reg.spread_spectrum
900    }
901
902    async fn read_with_addr_offset(&mut self, reg: Register, offset: u8) -> Result<u8, SPI::Error> {
903        let data = self.read_multiple_with_addr_offset::<1>(reg, offset).await?;
904        Ok(data[0])
905    }
906
907    fn new(spi: SPI) -> Self {
908        Self {
909            spi,
910            cached_reg: CachedReg::default(),
911        }
912    }
913
914    fn destroy(self) -> SPI {
915        self.spi
916    }
917
918    async fn read_multiple_with_addr_offset<const N: usize>(
919        &mut self,
920        reg: Register,
921        offset: u8,
922    ) -> Result<[u8; N], SPI::Error> {
923        let cmd_byte: u8 = Command::default()
924            .with_rw(CommandType::Read)
925            .with_page(reg.page())
926            .into();
927
928        let write_buf = [cmd_byte, u8::from(reg) + offset];
929        let write_op = Operation::Write(&write_buf);
930
931        // Device will automatically increment register address for every sequential read
932        let mut data: [u8; N] = [0; N];
933        let read_op = Operation::Read(&mut data);
934
935        /* Read and write are combined in a single transaction since data is received
936         * immediately following the write of the command byte. This does not occur in
937         * full-duplex mode (aka simultaneously) hence the use of custom transaction
938         * over a `transfer` transaction.
939         */
940        self.spi.transaction(&mut [write_op, read_op]).await?;
941        Ok(data)
942    }
943
944    async fn write(&mut self, reg: Register, value: u8) -> Result<(), SPI::Error> {
945        self.write_with_addr_offset(reg, 0, value).await
946    }
947
948    async fn write_with_addr_offset(&mut self, reg: Register, offset: u8, value: u8) -> Result<(), SPI::Error> {
949        let cmd_byte: u8 = Command::default()
950            .with_rw(CommandType::Write)
951            .with_page(reg.page())
952            .into();
953        self.spi.write(&[cmd_byte, u8::from(reg) + offset, value]).await
954    }
955
956    async fn write_multiple(&mut self, reg: Register, data: &[u8]) -> Result<(), SPI::Error> {
957        self.write_multiple_with_addr_offset(reg, 0, data).await
958    }
959
960    async fn write_multiple_with_addr_offset(
961        &mut self,
962        reg: Register,
963        offset: u8,
964        data: &[u8],
965    ) -> Result<(), SPI::Error> {
966        let cmd_byte: u8 = Command::default()
967            .with_rw(CommandType::Write)
968            .with_page(reg.page())
969            .into();
970        let cmd_write_buf = [cmd_byte, u8::from(reg) + offset];
971
972        let cmd_write_op = Operation::Write(&cmd_write_buf);
973        let data_write_op = Operation::Write(data);
974        self.spi.transaction(&mut [cmd_write_op, data_write_op]).await
975    }
976}
977
978#[cfg(test)]
979mod tests {
980    use embedded_hal_mock::eh1::spi::{Mock, Transaction};
981
982    use super::*;
983
984    #[maybe_async::test(feature = "is_blocking", async(not(feature = "is_blocking"), tokio::test))]
985    async fn write_configuration_register() {
986        let expectations = vec![
987            // Initial reset
988            Transaction::transaction_start(),
989            Transaction::write_vec(vec![0x52, 0x2F, 0xAE]),
990            Transaction::transaction_end(),
991            // Initial power on
992            Transaction::transaction_start(),
993            Transaction::write_vec(vec![0x52, 0x00, 0x09]),
994            Transaction::transaction_end(),
995            // Write new configuration
996            Transaction::transaction_start(),
997            Transaction::write_vec(vec![0x52, 0x00, 0x69]),
998            Transaction::transaction_end(),
999        ];
1000
1001        let mock = Mock::new(&expectations);
1002
1003        let mut m = Is31fl3743b::new(mock).await.unwrap();
1004        m.switch_column_enable_upto(SwxSetting::Sw5).await.unwrap();
1005
1006        let mut mock = m.destroy();
1007        mock.done();
1008    }
1009
1010    #[maybe_async::test(feature = "is_blocking", async(not(feature = "is_blocking"), tokio::test))]
1011    async fn new_with_sync() {
1012        let expectations1 = vec![
1013            // Initial reset
1014            Transaction::transaction_start(),
1015            Transaction::write_vec(vec![0x52, 0x2F, 0xAE]),
1016            Transaction::transaction_end(),
1017            // Write master sync config
1018            Transaction::transaction_start(),
1019            Transaction::write_vec(vec![0x52, 0x25, 0xC0]),
1020            Transaction::transaction_end(),
1021            // Initial power on
1022            Transaction::transaction_start(),
1023            Transaction::write_vec(vec![0x52, 0x00, 0x09]),
1024            Transaction::transaction_end(),
1025        ];
1026        let expectations2 = vec![
1027            // Initial reset
1028            Transaction::transaction_start(),
1029            Transaction::write_vec(vec![0x52, 0x2F, 0xAE]),
1030            Transaction::transaction_end(),
1031            // Write slave sync config
1032            Transaction::transaction_start(),
1033            Transaction::write_vec(vec![0x52, 0x25, 0x80]),
1034            Transaction::transaction_end(),
1035            // Initial power on
1036            Transaction::transaction_start(),
1037            Transaction::write_vec(vec![0x52, 0x00, 0x09]),
1038            Transaction::transaction_end(),
1039        ];
1040
1041        let mock1 = Mock::new(&expectations1);
1042        let mock2 = Mock::new(&expectations2);
1043        let mocks = [mock1, mock2];
1044
1045        let m = Is31fl3743b::new_with_sync(mocks).await.unwrap();
1046
1047        let mocks = m.destroy_with_sync();
1048        mocks.into_iter().for_each(|mut mock| mock.done());
1049    }
1050
1051    #[test]
1052    fn led_register_calculation() {
1053        assert_eq!(led_reg_offset(SWx::SW1, CSy::CS1), 0x00);
1054        assert_eq!(led_reg_offset(SWx::SW4, CSy::CS16), 0x45);
1055        assert_eq!(led_reg_offset(SWx::SW9, CSy::CS2), 0x91);
1056        assert_eq!(led_reg_offset(SWx::SW11, CSy::CS18), 0xC5);
1057    }
1058}