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
14pub 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#[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#[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#[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
129pub 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 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 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 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 pub fn rows_count(&self) -> u8 {
238 return self.rows_count;
239 }
240
241 pub fn columns_count(&self) -> u8 {
243 return self.columns_count;
244 }
245
246 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 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 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 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 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 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 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 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 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 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 pub fn fill(&mut self) -> Result<(), DriverError<E>> {
361 for i in 1..=self.columns_count {
362 self.send_column(i, 0b1111_1111)?;
364 }
365 self.update()
366 }
367
368 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 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}