device_envoy_core/
lcd_text.rs1use embassy_time::Timer;
6
7#[derive(Clone, Copy, Debug)]
9#[doc(hidden)]
11pub struct LcdTextFrame<const MAX_CHARS: usize> {
12 pub width: usize,
14 pub height: usize,
16 pub cells: [u8; MAX_CHARS],
18}
19
20impl<const MAX_CHARS: usize> LcdTextFrame<MAX_CHARS> {
21 #[must_use]
23 pub const fn new_blank(width: usize, height: usize) -> Self {
24 assert!(
25 width * height <= MAX_CHARS,
26 "frame geometry exceeds capacity"
27 );
28 Self {
29 width,
30 height,
31 cells: [b' '; MAX_CHARS],
32 }
33 }
34
35 #[must_use]
37 pub fn from_rows<const W: usize, const H: usize>(rows: [[u8; W]; H]) -> Self {
38 let mut lcd_text_frame = Self::new_blank(W, H);
39 let mut row_index = 0;
40 while row_index < H {
41 let mut column_index = 0;
42 while column_index < W {
43 let flat_index = row_index * W + column_index;
44 lcd_text_frame.cells[flat_index] = rows[row_index][column_index];
45 column_index += 1;
46 }
47 row_index += 1;
48 }
49 lcd_text_frame
50 }
51
52 #[must_use]
54 pub fn cell(&self, row: usize, col: usize) -> u8 {
55 self.cells[row * self.width + col]
56 }
57}
58
59#[must_use]
68#[doc(hidden)]
70pub fn render_lcd_text_frame<const W: usize, const H: usize, const MAX_CHARS: usize>(
71 text: &str,
72) -> LcdTextFrame<MAX_CHARS> {
73 let mut rows = [[b' '; W]; H];
74
75 for (row_index, line) in text.split('\n').enumerate() {
76 if row_index >= H {
77 break;
78 }
79
80 for (column_index, ch) in line.chars().enumerate() {
81 if column_index >= W {
82 break;
83 }
84 rows[row_index][column_index] = if ch.is_ascii() { ch as u8 } else { b'?' };
85 }
86 }
87
88 LcdTextFrame::<MAX_CHARS>::from_rows(rows)
89}
90
91pub trait LcdText<const W: usize, const H: usize> {
122 const WIDTH: usize = W;
124 const HEIGHT: usize = H;
126 const ADDRESS: u8;
128
129 fn write_text(&self, text: impl AsRef<str>);
132}
133
134pub trait LcdTextWrite {
136 fn write(&mut self, address: u8, data: u8) -> crate::Result<()>;
138}
139
140const LCD_BACKLIGHT: u8 = 0x08;
142const LCD_ENABLE: u8 = 0x04;
143const LCD_RS: u8 = 0x01;
144
145#[doc(hidden)]
148pub struct LcdTextDriver {
149 address: u8,
150}
151
152impl LcdTextDriver {
153 #[must_use]
155 pub const fn new(address: u8) -> Self {
156 Self { address }
157 }
158
159 pub fn set_address(&mut self, address: u8) {
161 self.address = address;
162 }
163
164 pub async fn init(&mut self, lcd_text_write: &mut impl LcdTextWrite) -> crate::Result<()> {
166 Timer::after_millis(50).await;
167
168 self.write_nibble(lcd_text_write, 0x03, false).await?;
169 Timer::after_millis(5).await;
170 self.write_nibble(lcd_text_write, 0x03, false).await?;
171 Timer::after_micros(150).await;
172 self.write_nibble(lcd_text_write, 0x03, false).await?;
173 self.write_nibble(lcd_text_write, 0x02, false).await?;
174
175 self.write_byte(lcd_text_write, 0x28, false).await?;
177 self.write_byte(lcd_text_write, 0x0C, false).await?;
179 self.write_byte(lcd_text_write, 0x01, false).await?;
181 Timer::after_millis(2).await;
182 self.write_byte(lcd_text_write, 0x06, false).await?;
184 Ok(())
185 }
186
187 pub async fn write_frame<const MAX_CHARS: usize>(
189 &mut self,
190 lcd_text_write: &mut impl LcdTextWrite,
191 lcd_text_frame: &LcdTextFrame<MAX_CHARS>,
192 ) -> crate::Result<()> {
193 self.clear(lcd_text_write).await?;
194
195 for row_index in 0..lcd_text_frame.height {
196 self.set_cursor(lcd_text_write, row_index, 0).await?;
197 for column_index in 0..lcd_text_frame.width {
198 self.write_byte(
199 lcd_text_write,
200 lcd_text_frame.cell(row_index, column_index),
201 true,
202 )
203 .await?;
204 }
205 }
206
207 Ok(())
208 }
209
210 async fn write_nibble(
211 &mut self,
212 lcd_text_write: &mut impl LcdTextWrite,
213 nibble: u8,
214 rs: bool,
215 ) -> crate::Result<()> {
216 let rs_bit = if rs { LCD_RS } else { 0 };
217 let data = (nibble << 4) | LCD_BACKLIGHT | rs_bit;
218
219 lcd_text_write.write(self.address, data | LCD_ENABLE)?;
220 Timer::after_micros(1).await;
221 lcd_text_write.write(self.address, data)?;
222 Timer::after_micros(50).await;
223 Ok(())
224 }
225
226 async fn write_byte(
227 &mut self,
228 lcd_text_write: &mut impl LcdTextWrite,
229 byte: u8,
230 rs: bool,
231 ) -> crate::Result<()> {
232 self.write_nibble(lcd_text_write, (byte >> 4) & 0x0F, rs)
233 .await?;
234 self.write_nibble(lcd_text_write, byte & 0x0F, rs).await?;
235 Ok(())
236 }
237
238 async fn clear(&mut self, lcd_text_write: &mut impl LcdTextWrite) -> crate::Result<()> {
239 self.write_byte(lcd_text_write, 0x01, false).await?;
240 Timer::after_millis(2).await;
241 Ok(())
242 }
243
244 #[expect(
245 clippy::arithmetic_side_effects,
246 reason = "Row/column values are small"
247 )]
248 async fn set_cursor(
249 &mut self,
250 lcd_text_write: &mut impl LcdTextWrite,
251 row: usize,
252 col: u8,
253 ) -> crate::Result<()> {
254 let address = match row {
255 0 => col,
256 1 => 0x40_u8 + col,
257 2 => 0x14_u8 + col,
258 3 => 0x54_u8 + col,
259 _ => return Err(crate::Error::LcdRowOutOfBounds { row }),
260 };
261 self.write_byte(lcd_text_write, 0x80 | address, false)
262 .await?;
263 Ok(())
264 }
265}