tm1637_embedded_hal/
formatters.rs

1//! Format numbers into byte arrays.
2//!
3//! Functions for converting numbers ([`i8`]s, [`i16`]s, [`i32`]s or [`f32`]s) into arrays of bytes
4//! that can be sent to a TM1637 display.
5//!
6//! There are versions of these functions that are meant for 4-digit displays
7//! and for 6-digit displays. The 6-digit versions take into account that the
8//! order of the bytes does not directly correlate with the order of the physical
9//! digits.
10//!
11//! All numbers are aligned to the right.
12//!
13//! # Example
14//!
15//! ```rust
16//! use tm1637_embedded_hal::{formatters::i16_to_4digits, mock::Noop, TM1637Builder};
17//!
18//! let mut tm = TM1637Builder::new(Noop, Noop, Noop).build_blocking::<4>();
19//!
20//! tm.init().ok();
21//!
22//! tm.display_slice(0, &i16_to_4digits(1234));
23//! ```
24
25use crate::mappings::{DigitBits, UpsideDownDigitBits};
26
27/// Formats a [`i16`] clamped between `-999` and `9999`, for a `4-digit display`.
28///
29/// # Example
30///
31/// A counter that goes from `-100` to `100`:
32///
33/// ```rust
34/// use tm1637_embedded_hal::{formatters::i16_to_4digits, mock::Noop, TM1637Builder};
35/// use embedded_hal::delay::DelayNs;
36///
37/// let mut delay = Noop;
38/// let mut tm = TM1637Builder::new(Noop, Noop, Noop).build_blocking::<4>();
39///
40/// tm.init().ok();
41///
42/// for i in -100..100 {
43///     let segments = i16_to_4digits(i);
44///     tm.display_slice(0, &segments).ok();
45///
46///     delay.delay_ms(100);
47/// }
48/// ```
49pub fn i16_to_4digits(n: i16) -> [u8; 4] {
50    let mut bytes: [u8; 4] = [0; 4];
51    let mut m: i16 = n.clamp(-999, 9999).abs();
52
53    for position in (0..4).rev() {
54        bytes[position as usize] = DigitBits::from_digit((m % 10) as u8) as u8;
55
56        m /= 10;
57
58        if m == 0 {
59            if n < 0 {
60                bytes[position as usize - 1] = 0b01000000; // minus sign
61            }
62            break;
63        };
64    }
65
66    bytes
67}
68
69/// Formats a [`i32`] clamped between `-99999` and `999999`, for a `6-digit display`.
70pub fn i32_to_6digits(n: i32) -> [u8; 6] {
71    let mut b: [u8; 6] = [0; 6];
72    let mut m: i32 = n.clamp(-99999, 999999).abs();
73
74    for position in (0..6).rev() {
75        b[position as usize] = DigitBits::from_digit((m % 10) as u8) as u8;
76
77        m /= 10;
78
79        if m == 0 {
80            if !n.is_positive() {
81                b[position as usize - 1] = 0b01000000; // minus sign
82            }
83            break;
84        };
85    }
86
87    // Swizzle bytes around to fit the order of 6-digit displays
88    [b[2], b[1], b[0], b[5], b[4], b[3]]
89}
90
91/// Formats a [`i8`] clamped between `-9` and `99`, appending the degrees symbol `(°)`
92/// and an `uppercase C`, for a `4-digit display`.
93pub fn celsius_to_4digits(n: i8) -> [u8; 4] {
94    let mut m: i8 = n.clamp(-9, 99);
95
96    // 3rd and 4th bytes are the degrees symbol (°) and uppercase C
97    let mut b: [u8; 4] = [0, 0, 0x63, 0x39];
98
99    for position in (0..2).rev() {
100        b[position as usize] = DigitBits::from_digit((m.abs() % 10) as u8) as u8;
101
102        m /= 10;
103
104        if m == 0 {
105            if !n.is_positive() {
106                b[position as usize - 1] = 0b01000000; // minus sign
107            }
108            break;
109        };
110    }
111    b
112}
113
114/// Formats a [`i16`] clamped between `-99` and `999`, appending the degrees symbol `(°)`,
115/// for a `4-digit display`.
116pub fn degrees_to_4digits(n: i16) -> [u8; 4] {
117    let mut m: i16 = n.clamp(-99, 999);
118
119    // 4th byte is the degrees symbol (°)
120    let mut b: [u8; 4] = [0, 0, 0, 0x63];
121
122    for position in (0..3).rev() {
123        b[position as usize] = DigitBits::from_digit((m.abs() % 10) as u8) as u8;
124
125        m /= 10;
126
127        if m == 0 {
128            if !n.is_positive() {
129                b[position as usize - 1] = 0b01000000; // minus sign
130            }
131            break;
132        };
133    }
134    b
135}
136
137/// Formats two [`u8`]s between `0` and `99`, with an optional colon between them.
138///
139/// This will only work for `4-digit displays` where there's a physical colon,
140/// and that colon acts as the decimal dot between the 2nd and 3rd digit.
141///
142/// # Example
143///
144/// Let's create a clock displaying `12:34` with a blinking colon:
145///
146/// ```rust
147/// use tm1637_embedded_hal::{formatters::clock_to_4digits, mock::Noop, TM1637Builder};
148/// use embedded_hal::delay::DelayNs;
149///
150/// let mut delay = Noop;
151///
152/// let mut tm = TM1637Builder::new(Noop, Noop, Noop).build_blocking::<4>();
153///
154/// tm.init().ok();
155///
156/// for hour in 12..24 {
157///     for minute in 34..60 {
158///         for second in 0..120 {
159///             let blink = second % 2 == 0;
160///             let segments = clock_to_4digits(hour, minute, blink);
161///
162///             tm.display_slice(0, &segments).ok();
163///
164///             delay.delay_ms(500);
165///         }
166///     }
167/// }
168/// ```
169pub fn clock_to_4digits(hour: u8, minute: u8, colon: bool) -> [u8; 4] {
170    let mut b: [u8; 4] = [0, 0, 0, 0];
171
172    if hour >= 10 {
173        b[0] = DigitBits::from_digit(hour / 10) as u8;
174    }
175    b[1] = DigitBits::from_digit(hour % 10) as u8;
176
177    if colon {
178        b[1] |= 0b1000_0000
179    }
180    b[2] = DigitBits::from_digit(minute / 10) as u8;
181    b[3] = DigitBits::from_digit(minute % 10) as u8;
182
183    b
184}
185
186/// Formats a [`i16`] clamped between `-999` and `9999`, for an `upside-down 4-digit display`.
187pub fn i16_to_upside_down_4digits(n: i16) -> [u8; 4] {
188    let mut bytes: [u8; 4] = [0; 4];
189    let mut m: i16 = n.clamp(-999, 9999).abs();
190
191    for position in 0..4 {
192        bytes[position as usize] = UpsideDownDigitBits::from_digit((m % 10) as u8) as u8;
193
194        m /= 10;
195
196        if m == 0 {
197            if !n.is_positive() {
198                bytes[position as usize + 1] = 0b01000000; // minus sign
199            }
200            break;
201        };
202    }
203
204    bytes
205}
206
207/// Formats a [`f32`] with the given amount of decimal digits, for a `6-digit display`.
208pub fn f32_to_6digits(n: f32, decimals: u8) -> [u8; 6] {
209    use ::core::ops::Mul;
210
211    let mut b: [u8; 6] = [0; 6];
212    let decimal_position = 5 - decimals;
213
214    let mut m: i32 = ((n.mul(10i32.pow(decimals as u32) as f32)
215        + if n.is_sign_positive() {
216            0.5_f32
217        } else {
218            -0.5_f32
219        }) as i32)
220        .clamp(-99999, 999999)
221        .abs();
222
223    for position in (0..6).rev() {
224        b[position as usize] = DigitBits::from_digit((m % 10) as u8) as u8;
225
226        m /= 10;
227
228        if position == decimal_position {
229            // Add a dot here
230            b[position as usize] |= 0b1000_0000;
231        }
232        if m == 0 && position <= decimal_position {
233            // Add the minus sign only when the digit with the decimal point
234            // has been done; do not break earlier.
235            if !n.is_sign_positive() {
236                b[position as usize - 1] = 0b01000000; // minus sign
237            }
238            break;
239        };
240    }
241
242    // Swizzle bytes around to fit the order of 6-digit displays
243    [b[2], b[1], b[0], b[5], b[4], b[3]]
244}