tm1637_embedded_hal/device.rs
1//! Device definition and implementation.
2
3use duplicate::duplicate_item;
4
5/// Identity trait.
6///
7/// Used to trick the compiler while using [`duplicate_item`] to implement `async` and `blocking` versions of the same module.
8/// Using this trait, we can write normal rust code that can also be formatted by `rustfmt`.
9#[cfg(any(feature = "async", feature = "blocking"))]
10trait Identity: Sized {
11 fn identity(self) -> Self {
12 self
13 }
14}
15
16#[cfg(any(feature = "async", feature = "blocking"))]
17impl<T: Sized> Identity for T {}
18
19#[duplicate_item(
20 feature_ module async await delay_trait;
21 ["async"] [asynch] [async] [await.identity()] [embedded_hal_async::delay::DelayNs];
22 ["blocking"] [blocking] [] [identity()] [embedded_hal::delay::DelayNs];
23)]
24pub mod module {
25 //! Device definition and implementation.
26
27 #[cfg(feature=feature_)]
28 mod inner {
29 use super::super::Identity;
30 use crate::brightness::{Brightness, DisplayState};
31 use embedded_hal::digital::OutputPin;
32
33 /// `TM1637` 7-segment display builder.
34 #[derive(Clone)]
35 #[cfg_attr(feature = "impl-defmt-format", derive(defmt::Format))]
36 #[cfg_attr(feature = "impl-debug", derive(core::fmt::Debug))]
37 pub struct TM1637Builder<CLK, DIO, DELAY> {
38 /// The inner [`TM1637`] instance.
39 inner: TM1637<CLK, DIO, DELAY>,
40 }
41
42 impl<CLK, DIO, DELAY> TM1637Builder<CLK, DIO, DELAY> {
43 /// Create a new [`TM1637Builder`] instance.
44 pub fn new(clk: CLK, dio: DIO, delay: DELAY) -> Self {
45 Self {
46 inner: TM1637 {
47 clk,
48 dio,
49 delay,
50 brightness: Brightness::L0,
51 delay_us: 10,
52 num_positions: 4,
53 },
54 }
55 }
56
57 /// Set the brightness level.
58 pub fn brightness(mut self, brightness: Brightness) -> Self {
59 self.inner.brightness = brightness;
60 self
61 }
62
63 /// Set the delay in microseconds.
64 pub fn delay_us(mut self, delay_us: u32) -> Self {
65 self.inner.delay_us = delay_us;
66 self
67 }
68
69 /// Set the number of positions on the display.
70 pub fn num_positions(mut self, num_positions: u8) -> Self {
71 self.inner.num_positions = num_positions;
72 self
73 }
74
75 /// Build the [`TM1637`] instance.
76 pub fn build(self) -> TM1637<CLK, DIO, DELAY> {
77 self.inner
78 }
79 }
80
81 /// `TM1637` 7-segment display driver.
82 #[derive(Clone)]
83 #[cfg_attr(feature = "impl-defmt-format", derive(defmt::Format))]
84 #[cfg_attr(feature = "impl-debug", derive(core::fmt::Debug))]
85 pub struct TM1637<CLK, DIO, DELAY> {
86 /// Clock.
87 clk: CLK,
88 /// Data input/output.
89 dio: DIO,
90 /// Delay provider.
91 delay: DELAY,
92 /// Brightness level.
93 brightness: Brightness,
94 /// The delay in microseconds.
95 ///
96 /// Experiment with this value to find the best value for your display.
97 delay_us: u32,
98 /// The number of positions on the display.
99 num_positions: u8,
100 }
101
102 impl<CLK, DIO, DELAY, ERR> TM1637<CLK, DIO, DELAY>
103 where
104 CLK: OutputPin<Error = ERR>,
105 DIO: OutputPin<Error = ERR>,
106 DELAY: delay_trait,
107 {
108 /// Create a new [`TM1637`] instance.
109 pub fn new(
110 clk: CLK,
111 dio: DIO,
112 delay: DELAY,
113 brightness: Brightness,
114 delay_us: u32,
115 num_positions: u8,
116 ) -> Self {
117 Self {
118 clk,
119 dio,
120 delay,
121 brightness,
122 delay_us,
123 num_positions,
124 }
125 }
126
127 /// Create a new [`TM1637Builder`] instance.
128 pub fn builder(clk: CLK, dio: DIO, delay: DELAY) -> TM1637Builder<CLK, DIO, DELAY> {
129 TM1637Builder::new(clk, dio, delay)
130 }
131
132 /// Send a byte to the display and wait for the ACK.
133 async fn write_byte(&mut self, byte: u8) -> Result<(), ERR> {
134 let mut rest = byte;
135
136 for _ in 0..8 {
137 self.bit_delay().await;
138 tri!(self.clk.set_low());
139 self.bit_delay().await;
140
141 match rest & 0x01 {
142 1 => tri!(self.dio.set_high()),
143 _ => tri!(self.dio.set_low()),
144 }
145
146 self.bit_delay().await;
147 tri!(self.clk.set_high());
148 self.bit_delay().await;
149
150 rest >>= 1;
151 }
152
153 tri!(self.clk.set_low());
154 tri!(self.dio.set_high());
155 self.bit_delay().await;
156
157 tri!(self.clk.set_high());
158 self.bit_delay().await;
159
160 tri!(self.clk.set_low());
161 self.bit_delay().await;
162
163 Ok(())
164 }
165
166 /// Write the `cmd` to the display.
167 async fn write_cmd_raw(&mut self, cmd: u8) -> Result<(), ERR> {
168 tri!(self.start().await);
169 tri!(self.write_byte(cmd).await);
170 tri!(self.stop().await);
171
172 Ok(())
173 }
174
175 /// Start the communication with the display.
176 async fn start(&mut self) -> Result<(), ERR> {
177 tri!(self.dio.set_low());
178 self.bit_delay().await;
179 tri!(self.clk.set_low());
180 self.bit_delay().await;
181
182 Ok(())
183 }
184
185 /// Stop the communication with the display.
186 async fn stop(&mut self) -> Result<(), ERR> {
187 tri!(self.dio.set_low());
188 self.bit_delay().await;
189 tri!(self.clk.set_high());
190 self.bit_delay().await;
191 tri!(self.dio.set_high());
192 self.bit_delay().await;
193
194 Ok(())
195 }
196
197 /// Delay for [`TM1637::delay_us`] microseconds using [`TM1637::delay`] provider.
198 async fn bit_delay(&mut self) {
199 self.delay.delay_us(self.delay_us).await;
200 }
201
202 /// Initialize the display.
203 ///
204 /// Clear the display and set the brightness level.
205 pub async fn init(&mut self) -> Result<(), ERR> {
206 tri!(self.clear().await);
207 self.write_cmd_raw(self.brightness as u8).await
208 }
209
210 /// Turn the display on.
211 pub async fn on(&mut self) -> Result<(), ERR> {
212 self.write_cmd_raw(self.brightness as u8).await
213 }
214
215 /// Turn the display off.
216 pub async fn off(&mut self) -> Result<(), ERR> {
217 self.write_cmd_raw(DisplayState::Off as u8).await
218 }
219
220 /// Clear the display.
221 pub async fn clear(&mut self) -> Result<(), ERR> {
222 self.write_segments_raw_iter(
223 0,
224 core::iter::repeat(0).take(self.num_positions as usize),
225 )
226 .await
227 }
228
229 /// Write the given bytes to the display starting from the given position.
230 ///
231 /// See [`TM1637::write_segments_raw_iter`].
232 pub async fn write_segments_raw(
233 &mut self,
234 position: u8,
235 bytes: &[u8],
236 ) -> Result<(), ERR> {
237 self.write_segments_raw_iter(position, bytes.iter().copied())
238 .await
239 }
240
241 /// Write the given bytes to the display starting from the given position.
242 ///
243 /// # Notes:
244 /// - Positions greater than [`TM1637::num_positions`] will be ignored.
245 /// - Bytes with index greater than [`TM1637::num_positions`] will be ignored.
246 ///
247 /// Brightness level will not be written to the device on each call. Make sure to call [`TM1637::write_brightness`] or [`TM1637::init`] to set the brightness level.
248 pub async fn write_segments_raw_iter<ITER: Iterator<Item = u8>>(
249 &mut self,
250 position: u8,
251 bytes: ITER,
252 ) -> Result<(), ERR> {
253 #[cfg(not(feature = "disable-checks"))]
254 if position >= self.num_positions {
255 return Ok(());
256 }
257
258 // COMM1
259 tri!(self.write_cmd_raw(0x40).await);
260
261 // COMM2
262 tri!(self.start().await);
263 tri!(self.write_byte(0xc0 | (position & 0x03)).await);
264
265 #[cfg(not(feature = "disable-checks"))]
266 let bytes = bytes.take(self.num_positions as usize - position as usize);
267
268 for byte in bytes {
269 tri!(self.write_byte(byte).await);
270 }
271
272 tri!(self.stop().await);
273
274 Ok(())
275 }
276
277 /// Set [`TM1637::brightness`] and write the brightness level to the display.
278 pub async fn write_brightness(&mut self, brightness: Brightness) -> Result<(), ERR> {
279 self.brightness = brightness;
280 self.write_cmd_raw(brightness as u8).await
281 }
282
283 /// Move all segments across the display starting and ending at `position`.
284 ///
285 /// If the length of the bytes is less than or equal to [`TM1637::num_positions`] - `position`, the bytes will only be written to the display.
286 ///
287 /// `N` is the size of the internal window used to move the segments. Please make sure that `N` is equal to [`TM1637::num_positions`].
288 /// [`TM1637::num_positions`] will be removed in the future in favor of a constant generic parameter representing the number of positions.
289 pub async fn move_segments_raw<const N: usize>(
290 &mut self,
291 position: u8,
292 bytes: &[u8],
293 delay_ms: u32,
294 ) -> Result<(), ERR> {
295 let num_positions = self.num_positions as usize;
296
297 if bytes.len() <= num_positions - position as usize {
298 return self.write_segments_raw(position, bytes).await;
299 }
300
301 for i in 0..=bytes.len() {
302 let mut window = [0u8; N];
303 for j in 0..num_positions {
304 window[j] = bytes[(i + j) % bytes.len()];
305 }
306
307 tri!(self.write_segments_raw(position, &window).await);
308
309 self.delay.delay_ms(delay_ms).await;
310 }
311
312 Ok(())
313 }
314
315 /// Convert an `ASCII` string to a byte iterator using [`from_ascii_byte`](crate::mappings::from_ascii_byte) and write the segments to the display using [`TM1637::write_segments_raw_iter`].
316 ///
317 /// Only available when the `mappings` feature of this library is activated.
318 ///
319 /// # Example
320 ///
321 /// Write the string `"Err"` to the display:
322 ///
323 /// ```rust, ignore
324 /// let mut tm = TM1637::builder(clk_pin, dio_pin, delay)
325 /// .brightness(Brightness::L3)
326 /// .build();
327 ///
328 /// tm.init().ok();
329 ///
330 /// tm.write_ascii_str(0, "Err").ok();
331 /// ```
332 ///
333 /// On a `4-digit display`, this will look like this:
334 ///
335 /// ```text
336 /// +---+ +---+ +---+ +---+
337 /// | E | | r | | r | | |
338 /// +---+ +---+ +---+ +---+
339 /// ```
340 #[cfg(feature = "mappings")]
341 pub async fn write_ascii_str(
342 &mut self,
343 position: u8,
344 ascii_str: &str,
345 ) -> Result<(), ERR> {
346 let iter = ascii_str
347 .as_bytes()
348 .iter()
349 .take(self.num_positions as usize - position as usize)
350 .copied()
351 .map(crate::mappings::from_ascii_byte);
352
353 self.write_segments_raw_iter(position, iter).await
354 }
355
356 /// Convert an `ASCII` string to a byte array using [`from_ascii_byte`](crate::mappings::from_ascii_byte) and move the segments across the display using [`TM1637::move_segments_raw`].
357 ///
358 /// - `N` is the size of the internal window used to move the segments. See [`TM1637::move_segments_raw`] for more information.
359 /// - `M` is the maximum number of bytes that can be converted from the `ASCII` string.
360 ///
361 /// Only available when the `mappings` feature of this library is activated.
362 ///
363 /// # Example
364 ///
365 /// Move the string `"HELLO "` across a `4-digit display`:
366 ///
367 /// ```rust, ignore
368 /// let mut tm = TM1637::builder(clk_pin, dio_pin, delay)
369 /// .brightness(Brightness::L3)
370 /// .build();
371 ///
372 /// tm.init().ok();
373 ///
374 /// // 4 is the actual number of positions on the display.
375 /// // 6 is the maximum number of bytes that can be converted from the ASCII string.
376 /// // In case of "HELLO ", all six bytes will be converted.
377 /// tm.move_ascii_str::<4, 6>(0, "HELLO ", 500).ok();
378 /// ```
379 ///
380 /// On a `4-digit display`, this will look like this:
381 ///
382 /// ```text
383 /// +---+ +---+ +---+ +---+
384 /// | H | | E | | L | | L |
385 /// +---+ +---+ +---+ +---+
386 ///
387 /// +---+ +---+ +---+ +---+
388 /// | E | | L | | L | | O |
389 /// +---+ +---+ +---+ +---+
390 ///
391 /// +---+ +---+ +---+ +---+
392 /// | L | | L | | O | | |
393 /// +---+ +---+ +---+ +---+
394 ///
395 /// +---+ +---+ +---+ +---+
396 /// | L | | O | | | | H |
397 /// +---+ +---+ +---+ +---+
398 ///
399 /// +---+ +---+ +---+ +---+
400 /// | O | | | | H | | E |
401 /// +---+ +---+ +---+ +---+
402 ///
403 /// +---+ +---+ +---+ +---+
404 /// | | | H | | E | | L |
405 /// +---+ +---+ +---+ +---+
406 ///
407 /// +---+ +---+ +---+ +---+
408 /// | H | | E | | L | | L |
409 /// +---+ +---+ +---+ +---+
410 /// ```
411 #[cfg(feature = "mappings")]
412 pub async fn move_ascii_str<const N: usize, const M: usize>(
413 &mut self,
414 position: u8,
415 ascii_str: &str,
416 delay_ms: u32,
417 ) -> Result<(), ERR> {
418 let mut bytes = [0u8; M];
419 ascii_str
420 .as_bytes()
421 .iter()
422 .take(M)
423 .enumerate()
424 .for_each(|(i, &byte)| bytes[i] = crate::mappings::from_ascii_byte(byte));
425
426 self.move_segments_raw::<N>(position, &bytes, delay_ms)
427 .await
428 }
429 }
430 }
431
432 #[cfg(feature=feature_)]
433 pub use inner::*;
434}