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}