is31fl3728_rs/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4#[cfg(feature = "embedded-graphics")]
5mod embedded_graphics_support;
6
7use core::fmt::Debug;
8
9use embedded_hal::i2c::I2c;
10
11#[cfg(feature = "rtt-debug")]
12use rtt_target::debug_rprintln;
13
14//#[derive(Debug)]
15pub enum DriverError<E: Debug> {
16    I2C(E),
17    InvalidColumnNumber(u8, u8),
18    IncorrectMatrixSize,
19}
20
21impl<E: Debug> DriverError<E> {
22    fn invalid_column(actual: u8, max_column: u8) -> Self {
23        Self::InvalidColumnNumber(actual, max_column)
24    }
25}
26
27impl<E: Debug> Debug for DriverError<E> {
28    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29        match self {
30            Self::I2C(ref i2c_err) => f.debug_tuple("I2C").field(i2c_err).finish(),
31            Self::InvalidColumnNumber(ref actual, ref max) => {
32                write!(f, "OutOfBounds, actual = {}, max = {}", actual, max)
33            }
34            &Self::IncorrectMatrixSize => write!(
35                f,
36                "Incorrect matrix size. Draw bitmaps works only for 8x8 matrices."
37            ),
38        }
39    }
40}
41
42/// Enumeration of all supported sizes of matrices.
43#[derive(Clone, Copy, PartialEq, Eq)]
44#[repr(u8)]
45pub enum MatrixDimensions {
46    M8x8 = 0b00,
47    M7x9 = 0b01,
48    M6x10 = 0b10,
49    M5x11 = 0b11,
50}
51
52/// All supported lighting intensity.
53#[derive(Clone, Copy)]
54#[repr(u8)]
55pub enum LightingIntensity {
56    C05mA = 0b1000,
57    C10mA = 0b1001,
58    C15mA = 0b1010,
59    C20mA = 0b1011,
60    C25mA = 0b1100,
61    C30mA = 0b1101,
62    C35mA = 0b1110,
63    C40mA = 0b0000,
64    C45mA = 0b0001,
65    C50mA = 0b0010,
66    C55mA = 0b0011,
67    C60mA = 0b0100,
68    C65mA = 0b0101,
69    C70mA = 0b0110,
70    C75mA = 0b0111,
71}
72
73impl LightingIntensity {
74    pub fn next(&self) -> LightingIntensity {
75        match self {
76            &LightingIntensity::C05mA => LightingIntensity::C10mA,
77            &LightingIntensity::C10mA => LightingIntensity::C15mA,
78            &LightingIntensity::C15mA => LightingIntensity::C20mA,
79            &LightingIntensity::C20mA => LightingIntensity::C25mA,
80            &LightingIntensity::C25mA => LightingIntensity::C30mA,
81            &LightingIntensity::C30mA => LightingIntensity::C35mA,
82            &LightingIntensity::C35mA => LightingIntensity::C40mA,
83            &LightingIntensity::C40mA => LightingIntensity::C45mA,
84            &LightingIntensity::C45mA => LightingIntensity::C50mA,
85            &LightingIntensity::C50mA => LightingIntensity::C55mA,
86            &LightingIntensity::C55mA => LightingIntensity::C60mA,
87            &LightingIntensity::C60mA => LightingIntensity::C65mA,
88            &LightingIntensity::C65mA => LightingIntensity::C70mA,
89            &LightingIntensity::C70mA => LightingIntensity::C75mA,
90            &LightingIntensity::C75mA => LightingIntensity::C05mA,
91        }
92    }
93
94    pub fn prev(&self) -> LightingIntensity {
95        match self {
96            &LightingIntensity::C75mA => LightingIntensity::C70mA,
97            &LightingIntensity::C70mA => LightingIntensity::C65mA,
98            &LightingIntensity::C65mA => LightingIntensity::C60mA,
99            &LightingIntensity::C60mA => LightingIntensity::C55mA,
100            &LightingIntensity::C55mA => LightingIntensity::C50mA,
101            &LightingIntensity::C50mA => LightingIntensity::C45mA,
102            &LightingIntensity::C45mA => LightingIntensity::C40mA,
103            &LightingIntensity::C40mA => LightingIntensity::C35mA,
104            &LightingIntensity::C35mA => LightingIntensity::C30mA,
105            &LightingIntensity::C30mA => LightingIntensity::C25mA,
106            &LightingIntensity::C25mA => LightingIntensity::C20mA,
107            &LightingIntensity::C20mA => LightingIntensity::C15mA,
108            &LightingIntensity::C15mA => LightingIntensity::C10mA,
109            &LightingIntensity::C10mA => LightingIntensity::C05mA,
110            &LightingIntensity::C05mA => LightingIntensity::C75mA,
111        }
112    }
113}
114
115/// All supported Audio input gains
116#[derive(Clone, Copy)]
117#[repr(u8)]
118pub enum AudioInputGain {
119    G00dB = 0b0_000_0000,
120    G03dB = 0b0_001_0000,
121    G06dB = 0b0_010_0000,
122    G09dB = 0b0_011_0000,
123    G12dB = 0b0_100_0000,
124    G15dB = 0b0_101_0000,
125    G18dB = 0b0_110_0000,
126    GMinus6dB = 0b111,
127}
128
129/// Driver
130pub struct IS31FL3728<I2C> {
131    i2c: I2C,
132    address: u8,
133    dimensions: MatrixDimensions,
134    audio_input_enabled: bool,
135    rows_count: u8,
136    columns_count: u8,
137    configuration_register: u8,
138    lighting_effects_register: u8,
139}
140
141pub const MAX_COLUMNS: usize = 11;
142
143const CONFIGURATION_ADDRESS: u8 = 0x00;
144const UPDATE_COLUMN_ADDRESS: u8 = 0x0C;
145const LIGHTING_EFFECT_ADDRESS: u8 = 0x0D;
146const AUDIO_EQ_ADDRESS: u8 = 0x0F;
147
148pub const DEFAULT_LIGHTING_INTENSITY: LightingIntensity = LightingIntensity::C40mA;
149pub const DEFAULT_AUDIO_INPUT_GAIN: AudioInputGain = AudioInputGain::G00dB;
150
151const DEFAULT_CONFIGURATION_REGISTER: u8 = 0;
152const DEFAULT_LIGHTING_EFFECT_REGISTER: u8 =
153    (DEFAULT_AUDIO_INPUT_GAIN as u8) | (DEFAULT_LIGHTING_INTENSITY as u8);
154
155impl<I2C, E> IS31FL3728<I2C>
156where
157    I2C: I2c<Error = E>,
158    E: Debug,
159{
160    /// Create instance of driver
161    pub fn new(
162        i2c: I2C,
163        address: u8,
164        matrix_dimensions: MatrixDimensions,
165        audio_input_enabled: bool,
166    ) -> Result<IS31FL3728<I2C>, DriverError<E>> {
167        let (rows_count, columns_count) = match matrix_dimensions {
168            MatrixDimensions::M8x8 => (8, 8),
169            MatrixDimensions::M7x9 => (7, 9),
170            MatrixDimensions::M6x10 => (6, 10),
171            MatrixDimensions::M5x11 => (5, 11),
172        };
173
174        let mut driver = IS31FL3728 {
175            i2c,
176            address,
177            dimensions: matrix_dimensions,
178            audio_input_enabled,
179            rows_count,
180            columns_count,
181            configuration_register: DEFAULT_CONFIGURATION_REGISTER,
182            lighting_effects_register: DEFAULT_LIGHTING_EFFECT_REGISTER,
183        };
184
185        driver.init().unwrap();
186
187        Ok(driver)
188    }
189
190    fn debug(&self, msg: &str, data: u8) {
191        #[cfg(feature = "rtt-debug")]
192        debug_rprintln!("IS31FL3728[0x{:02x}]: {} = {:08b}", self.address, msg, data)
193    }
194
195    fn write_i2c(&mut self, write: &[u8]) -> Result<(), DriverError<E>> {
196        self.i2c
197            .write(self.address, write)
198            .map_err(DriverError::I2C)
199    }
200
201    fn write_config(&mut self, configuration: u8) -> Result<(), DriverError<E>> {
202        self.write_i2c(&[CONFIGURATION_ADDRESS, configuration])
203    }
204
205    /// Send configuration by I2C and persist a new configuration to this instance
206    fn update_lighting_effect(&mut self, configuration: u8) -> Result<(), DriverError<E>> {
207        if self.lighting_effects_register != configuration {
208            self.debug("lighting effect", configuration);
209            self.i2c
210                .write(self.address, &[LIGHTING_EFFECT_ADDRESS, configuration])
211                .map_err(DriverError::I2C)?;
212            self.lighting_effects_register = configuration
213        }
214        Ok(())
215    }
216
217    /// Init
218    fn init(&mut self) -> Result<(), DriverError<E>> {
219        let audio_input_mask = if self.audio_input_enabled {
220            0b0_0000_1_00
221        } else {
222            0
223        };
224
225        let configuration: u8 = 0b0_0000_0_00 | audio_input_mask | self.dimensions as u8;
226
227        if configuration != self.configuration_register {
228            self.debug("configuration", configuration);
229            self.write_config(configuration)?;
230            self.configuration_register = configuration;
231        }
232
233        Ok(())
234    }
235
236    /// Counts of rows.
237    pub fn rows_count(&self) -> u8 {
238        return self.rows_count;
239    }
240
241    /// Counts of columns.
242    pub fn columns_count(&self) -> u8 {
243        return self.columns_count;
244    }
245
246    /// Update column data registers from temporary data registers.
247    pub fn update(&mut self) -> Result<(), DriverError<E>> {
248        self.debug("update", 0 as u8);
249        self.i2c
250            .write(self.address, &[UPDATE_COLUMN_ADDRESS, 0])
251            .map_err(DriverError::I2C)
252    }
253
254    /// Send data to temporary registers.
255    /// <div class="warning">`row_number` starts from 1.</div>
256    pub fn send_column(&mut self, column_number: u8, column: u8) -> Result<(), DriverError<E>> {
257        if column_number > self.columns_count {
258            return Err(DriverError::invalid_column(
259                column_number,
260                self.columns_count,
261            ));
262        }
263        let msg = concat!("send column: ", stringify!(column_number));
264        self.debug(msg, column);
265        self.i2c
266            .write(self.address, &[column_number, column])
267            .map_err(DriverError::I2C)
268    }
269
270    /// Send data to temporary register and update columns registers.
271    /// <div class="warning">`row_number` starts from 1.</div>
272    pub fn draw_column(&mut self, column_number: u8, column: u8) -> Result<(), DriverError<E>> {
273        self.send_column(column_number, column)?;
274        self.update()
275    }
276
277    /// Send data to temporary registers and update columns registers.
278    /// Picture is array of columns.
279    pub fn draw(&mut self, picture: &[u8]) -> Result<(), DriverError<E>> {
280        for (column_idx, column) in picture.iter().enumerate() {
281            let column_number = (column_idx + 1) as u8;
282            self.send_column(column_number, *column)?
283        }
284        self.update()
285    }
286
287    /// Set intensity of led's matrix.
288    pub fn set_intensity(&mut self, intensity: LightingIntensity) -> Result<(), DriverError<E>> {
289        let mask = 0b1_111_0000;
290        let configuration = (self.lighting_effects_register & mask) | intensity as u8;
291
292        self.update_lighting_effect(configuration)?;
293
294        Ok(())
295    }
296
297    /// Set audio input gain
298    pub fn set_audio_input_gain(&mut self, gain: AudioInputGain) -> Result<(), DriverError<E>> {
299        let mask = 0b1_000_1111;
300        let configuration = (self.lighting_effects_register & mask) | gain as u8;
301
302        self.update_lighting_effect(configuration)?;
303
304        Ok(())
305    }
306
307    /// Enable audio equalize
308    pub fn audio_eq_enable(&mut self) -> Result<(), DriverError<E>> {
309        let configuration = 0b0_1_000000;
310        self.debug("Enable audio eq", configuration);
311        self.write_i2c(&[AUDIO_EQ_ADDRESS, configuration])
312    }
313
314    /// Disable audio equalize
315    pub fn audio_eq_disable(&mut self) -> Result<(), DriverError<E>> {
316        let configuration = 0b0_0_000000;
317        self.debug("Disable audio eq", configuration);
318        self.write_i2c(&[AUDIO_EQ_ADDRESS, configuration])
319    }
320
321    /// Send data to temporary registers and update columns registers.
322    /// Picture is array of rows.
323    ///
324    /// Use this method to simplify a work with led-matrix-editors like this one:
325    /// <https://xantorohara.github.io/led-matrix-editor/>
326    pub fn draw_bitmap(&mut self, picture: &[u8; 8]) -> Result<(), DriverError<E>> {
327        if self.dimensions != MatrixDimensions::M8x8 {
328            return Err(DriverError::IncorrectMatrixSize);
329        }
330
331        let mut column_mask: u8 = 0b1000_0000;
332        for column_idx in 0..=7 {
333            let mut column: u8 = 0;
334            for row_idx in 0..=7 {
335                let pixel = picture[row_idx] & column_mask;
336                let pixel_in_column = if column_idx < row_idx {
337                    pixel >> (row_idx - column_idx)
338                } else {
339                    pixel << (column_idx - row_idx)
340                };
341                column = column | pixel_in_column;
342            }
343            self.send_column((column_idx as u8) + 1, column)?;
344            column_mask = column_mask >> 1;
345        }
346
347        self.update()
348    }
349
350    /// Set all led's to off. If you want just turn off matrix without
351    /// changing picture, use `software_shutdown`
352    pub fn clear(&mut self) -> Result<(), DriverError<E>> {
353        for i in 1..=self.columns_count {
354            self.send_column(i, 0)?;
355        }
356        self.update()
357    }
358
359    /// Set all led's to on
360    pub fn fill(&mut self) -> Result<(), DriverError<E>> {
361        for i in 1..=self.columns_count {
362            // if rows count less then 8, older bit will be ignored
363            self.send_column(i, 0b1111_1111)?;
364        }
365        self.update()
366    }
367
368    /// Turn off matrix output with saving all registry.
369    /// Use `software_on` to return image
370    pub fn software_shutdown(&mut self) -> Result<(), DriverError<E>> {
371        let configuration = self.configuration_register | 0b1_0000_0_00;
372        self.write_config(configuration)
373    }
374
375    /// Turn on matrix output
376    pub fn software_on(&mut self) -> Result<(), DriverError<E>> {
377        let configuration = self.configuration_register & 0b0_1111_1_11;
378        self.write_config(configuration)
379    }
380}