1#![no_std]
12
13use embedded_hal::{delay::DelayNs, i2c::I2c};
14use embedded_hal_compat::{Reverse, ReverseCompat};
15use ht16k33::{HT16K33, LedLocation};
16use phf::phf_map;
17
18pub use ht16k33::{Dimming, Display};
19
20mod error;
21pub use error::PhatError;
22
23const ALLOWED_POSITIONS: [u8; 4] = [0, 1, 2, 3];
32
33const ALL_LEDS: ([u8; 8], [u8; 6]) = ([0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5]);
34
35static CHARACTER_TO_COMMONS: phf::Map<char, (&'static [u8], &'static [u8])> = phf_map! {
36 ' ' => (&[], &[]),
37 '!' => (&[1, 2], &[]),
38 '"' => (&[5], &[1]),
39 '#' => (&[1, 2, 3, 6, 7], &[1, 4]),
40 '$' => (&[0, 2, 3, 5, 6, 7], &[1, 4]),
41 '£' => (&[3, 7], &[2, 3]),
42 '%' => (&[2, 5], &[2, 3]),
43 '&' => (&[0, 2, 3, 4, 6], &[0, 1, 5]),
44 '\'' => (&[], &[2]),
45 '(' => (&[], &[2, 5]),
46 ')' => (&[], &[0, 3]),
47 '*' => (&[6, 7], &[0, 1, 2, 3, 4, 5]),
48 '+' => (&[6, 7], &[1, 4]),
49 ',' => (&[], &[3]),
50 '-' => (&[6, 7], &[]),
51 '.' => (&[], &[]),
52 '/' => (&[], &[2, 3]),
53 '0' => (&[0, 1, 2, 3, 4, 5], &[2, 3]),
54 '1' => (&[1, 2], &[]),
55 '2' => (&[0, 1, 3, 4, 6, 7], &[]),
56 '3' => (&[0, 1, 2, 3, 7], &[]),
57 '4' => (&[1, 2, 5, 6, 7], &[]),
58 '5' => (&[0, 3, 5, 6], &[5]),
59 '6' => (&[0, 2, 3, 4, 5, 6, 7], &[]),
60 '7' => (&[0, 1, 2], &[]),
61 '8' => (&[0, 1, 2, 3, 4, 5, 6, 7], &[]),
62 '9' => (&[0, 1, 2, 3, 5, 6, 7], &[]),
63 ':' => (&[], &[1, 4]),
64 ';' => (&[], &[1, 3]),
65 '<' => (&[], &[2, 5]),
66 '=' => (&[3, 6, 7], &[]),
67 '>' => (&[], &[0, 3]),
68 '?' => (&[0, 1, 7], &[4]),
69 '@' => (&[0, 1, 3, 4, 5, 7], &[1]),
70 'A' => (&[0, 1, 2, 4, 5, 6, 7], &[]),
71 'B' => (&[0, 1, 2, 3, 7], &[1, 4]),
72 'C' => (&[0, 3, 4, 5], &[]),
73 'D' => (&[0, 1, 2, 3], &[1, 4]),
74 'E' => (&[0, 3, 4, 5, 6, 7], &[]),
75 'F' => (&[0, 4, 5, 6], &[]),
76 'G' => (&[0, 2, 3, 4, 5, 7], &[]),
77 'H' => (&[1, 2, 4, 5, 6, 7], &[]),
78 'I' => (&[], &[1, 4]),
79 'J' => (&[1, 2, 3, 4], &[]),
80 'K' => (&[4, 5, 6], &[2, 5]),
81 'L' => (&[3, 4, 5], &[]),
82 'M' => (&[1, 2, 4, 5], &[0, 2]),
83 'N' => (&[1, 2, 4, 5], &[0, 5]),
84 'O' => (&[0, 1, 2, 3, 4, 5], &[]),
85 'P' => (&[0, 1, 4, 5, 6, 7], &[]),
86 'Q' => (&[0, 1, 2, 3, 4, 5], &[5]),
87 'R' => (&[0, 1, 4, 5, 6, 7], &[5]),
88 'S' => (&[0, 2, 3, 5, 6, 7], &[]),
89 'T' => (&[0], &[1, 4]),
90 'U' => (&[1, 2, 3, 4, 5], &[]),
91 'V' => (&[4, 5], &[2, 3]),
92 'W' => (&[1, 2, 4, 5], &[3, 5]),
93 'X' => (&[], &[0, 2, 3, 5]),
94 'Y' => (&[], &[0, 2, 4]),
95 'Z' => (&[0, 3], &[2, 3]),
96 '[' => (&[0, 3, 4, 5], &[]),
97 '\\' => (&[], &[0, 5]),
98 ']' => (&[0, 1, 2, 3], &[]),
99 '^' => (&[0, 1], &[2, 3]),
100 '_' => (&[3], &[]),
101 '`' => (&[], &[0]),
102 'a' => (&[3, 4, 6], &[4]),
103 'b' => (&[3, 4, 5, 6], &[5]),
104 'c' => (&[3, 4, 6, 7], &[]),
105 'd' => (&[1, 2, 3, 7], &[3]),
106 'e' => (&[3, 4, 6], &[3]),
107 'f' => (&[0, 4, 5, 6], &[]),
108 'g' => (&[1, 2, 3, 7], &[2]),
109 'h' => (&[4, 5, 6], &[4]),
110 'i' => (&[], &[4]),
111 'j' => (&[1, 2, 3], &[]),
112 'k' => (&[], &[1, 2, 4, 5]),
113 'l' => (&[4, 5], &[]),
114 'm' => (&[2, 4, 6, 7], &[4]),
115 'n' => (&[4, 6], &[4]),
116 'o' => (&[2, 3, 4, 6, 7], &[]),
117 'p' => (&[4, 5, 6], &[0]),
118 'q' => (&[1, 2, 7], &[2]),
119 'r' => (&[4, 6], &[]),
120 's' => (&[3, 7], &[5]),
121 't' => (&[3, 4, 5, 6], &[]),
122 'u' => (&[2, 3, 4], &[]),
123 'v' => (&[2], &[5]),
124 'w' => (&[2, 4], &[3, 5]),
125 'x' => (&[6, 7], &[3, 5]),
126 'y' => (&[2, 3], &[5]),
127 'z' => (&[3, 6], &[3]),
128 '{' => (&[0, 3, 6], &[0, 3]),
129 '|' => (&[], &[1, 4]),
130 '}' => (&[0, 3, 7], &[2, 5]),
131 '~' => (&[5], &[0, 2]),
132};
133
134pub struct FourLetterPhat<I2C> {
139 driver: HT16K33<Reverse<I2C>>,
140}
141
142impl<I2C: I2c<Error = I2CE>, I2CE: core::fmt::Debug> FourLetterPhat<I2C> {
143 fn position_to_rows(position: u8) -> Result<(u8, u8), PhatError<I2CE>> {
144 if !ALLOWED_POSITIONS.contains(&position) {
145 return Err(PhatError::InvalidCharacter);
146 }
147
148 Ok((2 * position, 2 * position + 1))
149 }
150
151 pub fn new(i2c: I2C, address: u8) -> Result<FourLetterPhat<I2C>, PhatError<I2CE>> {
157 let i2c_hal_v0 = i2c.reverse();
158
159 let mut driver = HT16K33::new(i2c_hal_v0, address);
160 driver.initialize()?;
161 driver.set_display(Display::ON)?;
162 driver.set_dimming(Dimming::BRIGHTNESS_MAX)?;
163 Ok(FourLetterPhat { driver })
164 }
165
166 pub fn clear_display(&mut self) -> Result<(), PhatError<I2CE>> {
168 self.driver.clear_display_buffer();
169 self.driver.write_display_buffer()?;
170
171 Ok(())
172 }
173
174 pub fn clear_digit(&mut self, position: u8) -> Result<(), PhatError<I2CE>> {
181 let rows = Self::position_to_rows(position)?;
182
183 for common in ALL_LEDS.0.iter() {
184 self.driver.set_led(
185 LedLocation::new(rows.0, *common).map_err(PhatError::HT16K33Validation)?,
186 false,
187 )?;
188 }
189
190 for common in ALL_LEDS.1.iter() {
191 self.driver.set_led(
192 LedLocation::new(rows.1, *common).map_err(PhatError::HT16K33Validation)?,
193 false,
194 )?;
195 }
196
197 Ok(())
198 }
199
200 pub fn set_digit(&mut self, position: u8, character: char) -> Result<(), PhatError<I2CE>> {
208 let rows = Self::position_to_rows(position)?;
209
210 self.clear_digit(position)?;
211
212 if let Some(commons) = CHARACTER_TO_COMMONS.get(&character) {
213 for common in commons.0.iter() {
214 self.driver.update_display_buffer(
215 LedLocation::new(rows.0, *common).map_err(PhatError::HT16K33Validation)?,
216 true,
217 );
218 }
219
220 for common in commons.1.iter() {
221 self.driver.update_display_buffer(
222 LedLocation::new(rows.1, *common).map_err(PhatError::HT16K33Validation)?,
223 true,
224 );
225 }
226
227 self.driver.write_display_buffer()?;
228
229 Ok(())
230 } else {
231 Err(PhatError::InvalidCharacter)
232 }
233 }
234
235 pub fn clear_decimal(&mut self, position: u8) -> Result<(), PhatError<I2CE>> {
242 let row = Self::position_to_rows(position)?.1;
243
244 self.driver.set_led(
245 LedLocation::new(row, 6).map_err(PhatError::HT16K33Validation)?,
246 false,
247 )?;
248
249 Ok(())
250 }
251
252 pub fn set_decimal(&mut self, position: u8) -> Result<(), PhatError<I2CE>> {
259 let row = Self::position_to_rows(position)?.1;
260
261 self.driver.set_led(
262 LedLocation::new(row, 6).map_err(PhatError::HT16K33Validation)?,
263 true,
264 )?;
265
266 Ok(())
267 }
268
269 pub fn print_str(&mut self, message: &str) -> Result<(), PhatError<I2CE>> {
275 if message.len() > 4 {
276 return Err(PhatError::StringLength);
277 }
278
279 self.clear_display()?;
280
281 for (position, char) in message.chars().enumerate() {
282 self.set_digit(position as u8, char)?;
283 }
284
285 Ok(())
286 }
287
288 pub fn scroll_print_str(
298 &mut self,
299 message: &str,
300 mut delay: impl DelayNs,
301 tempo_ms: u32,
302 ) -> Result<(), PhatError<I2CE>> {
303 let frames_count = message.len().saturating_sub(3);
304
305 let chars = message.chars();
306
307 let mut iterators = [
308 #[allow(clippy::iter_skip_zero)]
309 chars.clone().skip(0),
310 chars.clone().skip(1),
311 chars.clone().skip(2),
312 chars.skip(3),
313 ];
314
315 for _ in 0..frames_count {
316 for (pos, iterator) in iterators.iter_mut().enumerate() {
317 self.set_digit(pos as u8, iterator.next().unwrap_or(' '))?;
318 }
319 delay.delay_ms(tempo_ms);
320 }
321
322 self.clear_display()?;
323
324 Ok(())
325 }
326
327 pub fn set_display(&mut self, display: Display) -> Result<(), PhatError<I2CE>> {
336 self.driver.set_display(display)?;
337
338 Ok(())
339 }
340
341 pub fn set_dimming(&mut self, dimming: Dimming) -> Result<(), PhatError<I2CE>> {
348 self.driver.set_dimming(dimming)?;
349
350 Ok(())
351 }
352}