1use embedded_hal::delay::DelayNs;
4
5use crate::{
6 command::{Font, LineMode, MoveDirection, RAMType, ShiftType, State},
7 state::LcdState,
8};
9
10mod init;
11
12pub use init::Config;
13
14mod impls;
15
16pub struct Lcd<'a, 'b, Sender, Delayer, const READABLE: bool>
18where
19 Delayer: DelayNs,
20{
21 sender: &'a mut Sender,
22 delayer: &'b mut Delayer,
23 state: LcdState,
24 poll_interval_us: u32,
25}
26
27#[derive(Default)]
29pub struct CGRAMGraph {
30 pub upper: [u8; 8],
33 pub lower: Option<[u8; 3]>,
38}
39
40#[allow(missing_docs)]
42pub trait Basic {
43 fn write_byte_to_cur(&mut self, byte: u8);
44
45 fn write_graph_to_cgram(&mut self, index: u8, graph_data: &CGRAMGraph);
48
49 fn clean_display(&mut self);
50
51 fn return_home(&mut self);
52
53 fn set_line_mode(&mut self, line: LineMode);
54
55 fn get_line_mode(&self) -> LineMode;
56
57 fn set_font(&mut self, font: Font);
58
59 fn get_font(&self) -> Font;
60
61 fn set_display_state(&mut self, display: State);
62
63 fn get_display_state(&self) -> State;
64
65 fn set_cursor_state(&mut self, cursor: State);
66
67 fn get_cursor_state(&self) -> State;
68
69 fn get_ram_type(&self) -> RAMType;
70
71 fn set_cursor_blink_state(&mut self, blink: State);
72
73 fn get_cursor_blink_state(&self) -> State;
74
75 fn set_direction(&mut self, dir: MoveDirection);
76
77 fn get_direction(&self) -> MoveDirection;
78
79 fn set_shift_type(&mut self, shift: ShiftType);
80
81 fn get_shift_type(&self) -> ShiftType;
82
83 fn set_cursor_pos(&mut self, pos: (u8, u8));
84
85 fn set_cgram_addr(&mut self, addr: u8);
86
87 fn get_cursor_pos(&self) -> (u8, u8);
88
89 fn shift_cursor_or_display(&mut self, shift_type: ShiftType, dir: MoveDirection);
90
91 fn get_display_offset(&self) -> u8;
92
93 fn set_poll_interval(&mut self, interval_us: u32);
94
95 fn get_poll_interval_us(&self) -> u32;
96
97 fn get_line_capacity(&self) -> u8;
98
99 fn set_backlight(&mut self, backlight: State);
102
103 fn get_backlight(&self) -> State;
104
105 fn calculate_pos_by_offset(&self, start: (u8, u8), offset: (i8, i8)) -> (u8, u8);
106
107 fn delay_ms(&mut self, ms: u32);
109
110 fn delay_us(&mut self, us: u32);
112}
113
114#[allow(missing_docs)]
116pub trait BasicRead: Basic {
117 fn read_u8_from_cur(&mut self) -> u8;
118}
119
120pub trait Ext: Basic {
122 fn toggle_display(&mut self) {
124 match self.get_display_state() {
125 State::Off => self.set_display_state(State::On),
126 State::On => self.set_display_state(State::Off),
127 }
128 }
129
130 fn write_char_to_cur(&mut self, char: char) {
134 assert!(
135 self.get_ram_type() == RAMType::DDRam,
136 "Current in CGRAM, use .set_cursor_pos() to change to DDRAM"
137 );
138
139 let out_byte = match char.is_ascii() {
141 true if (0x20 <= char as u8) && (char as u8 <= 0x7D) => char as u8,
142 _ => 0xFF,
143 };
144
145 self.write_byte_to_cur(out_byte);
146 }
147
148 fn write_str_to_cur(&mut self, str: &str) {
150 str.chars().for_each(|char| self.write_char_to_cur(char));
151 }
152
153 fn write_byte_to_pos(&mut self, byte: u8, pos: (u8, u8)) {
155 self.set_cursor_pos(pos);
156
157 self.write_byte_to_cur(byte);
158 }
159
160 fn write_char_to_pos(&mut self, char: char, pos: (u8, u8)) {
162 self.set_cursor_pos(pos);
163 self.write_char_to_cur(char);
164 }
165
166 fn write_str_to_pos(&mut self, str: &str, pos: (u8, u8)) {
168 self.set_cursor_pos(pos);
169 self.write_str_to_cur(str);
170 }
171
172 fn write_graph_to_cur(&mut self, index: u8) {
177 match self.get_font() {
178 Font::Font5x8 => assert!(index < 8, "index too big, should less than 8 for 5x8 Font"),
179 Font::Font5x11 => assert!(index < 4, "index too big, should less than 4 for 5x11 Font"),
180 }
181
182 self.write_byte_to_cur(match self.get_font() {
183 Font::Font5x8 => index,
184 Font::Font5x11 => index << 1,
185 });
186 }
187
188 fn write_graph_to_pos(&mut self, index: u8, pos: (u8, u8)) {
193 match self.get_font() {
194 Font::Font5x8 => assert!(index < 8, "Only 8 graphs allowed in CGRAM for 5x8 Font"),
195 Font::Font5x11 => assert!(index < 4, "Only 4 graphs allowed in CGRAM for 5x11 Font"),
196 }
197
198 self.write_byte_to_pos(
199 match self.get_font() {
200 Font::Font5x8 => index,
201 Font::Font5x11 => index << 1,
202 },
203 pos,
204 );
205 }
206
207 fn offset_cursor_pos(&mut self, offset: (i8, i8)) {
209 self.set_cursor_pos(self.calculate_pos_by_offset(self.get_cursor_pos(), offset));
210 }
211}
212
213pub trait ExtRead: Ext + BasicRead {
215 fn read_byte_from_pos(&mut self, pos: (u8, u8)) -> u8 {
217 let original_pos = self.get_cursor_pos();
218 self.set_cursor_pos(pos);
219 let data = self.read_u8_from_cur();
220 self.set_cursor_pos(original_pos);
221 data
222 }
223
224 fn read_graph_from_cgram(&mut self, index: u8) -> CGRAMGraph {
229 match self.get_font() {
230 Font::Font5x8 => assert!(index < 8, "index too big, should less than 8 for 5x8 Font"),
231 Font::Font5x11 => assert!(index < 4, "index too big, should less than 4 for 5x11 Font"),
232 }
233
234 self.set_cgram_addr(
236 index
237 .checked_shl(match self.get_font() {
238 Font::Font5x8 => 3,
239 Font::Font5x11 => 4,
240 })
241 .unwrap(),
242 );
243
244 let mut graph = CGRAMGraph::default();
245
246 graph
247 .upper
248 .iter_mut()
249 .for_each(|line| *line = self.read_u8_from_cur());
250
251 graph.lower = Some([0u8; 3]);
252
253 graph
254 .lower
255 .as_mut()
256 .unwrap()
257 .iter_mut()
258 .for_each(|line| *line = self.read_u8_from_cur());
259
260 graph
261 }
262}
263
264pub enum MoveStyle {
266 ForceMoveLeft,
268 ForceMoveRight,
270 NoCrossBoundary,
272 Shortest,
274}
275
276pub enum FlipStyle {
278 Sequential,
280 Simultaneous,
282}
283
284pub trait Anim: Ext {
286 fn full_display_blink(&mut self, count: u32, interval_us: u32) {
293 match count == 0 {
294 true => loop {
295 self.delay_us(interval_us);
296 self.toggle_display();
297 },
298 false => {
299 (0..count * 2).for_each(|_| {
300 self.delay_us(interval_us);
301 self.toggle_display();
302 });
303 }
304 }
305 }
306
307 fn typewriter_write(&mut self, str: &str, delay_us: u32) {
314 str.chars().for_each(|char| {
315 self.delay_us(delay_us);
316 self.write_char_to_cur(char);
317 })
318 }
319
320 fn split_flap_write(
330 &mut self,
331 str: &str,
332 fs: FlipStyle,
333 max_flip_cnt: Option<u8>,
334 per_flip_delay_us: u32,
335 per_char_flip_delay_us: Option<u32>,
336 ) {
337 let test_result = str
339 .chars()
340 .all(|char| char.is_ascii() && (0x20 <= char as u8) && (char as u8 <= 0x7D));
341
342 assert!(test_result, "Currently only support ASCII 0x20 to 0x7D");
343
344 let mut cursor_state_changed = false;
345
346 if self.get_cursor_state() != State::Off {
348 self.set_cursor_state(State::Off);
349 cursor_state_changed = true;
350 }
351
352 match fs {
353 FlipStyle::Sequential => {
354 assert!(
355 per_char_flip_delay_us.is_some(),
356 "Should set some per char delay in Sequential Mode"
357 );
358 str.chars().for_each(|char| {
359 let cur_byte = char as u8;
360
361 let flap_start_byte = match max_flip_cnt {
362 None => 0x20,
363 Some(max_flip_cnt) => {
364 if cur_byte - max_flip_cnt < 0x20 {
365 0x20
366 } else {
367 cur_byte - max_flip_cnt
368 }
369 }
370 };
371
372 let cur_pos = self.get_cursor_pos();
373
374 self.delay_us(per_char_flip_delay_us.unwrap());
375 (flap_start_byte..=cur_byte).for_each(|byte| {
376 self.delay_us(per_flip_delay_us);
377 self.write_byte_to_pos(byte, cur_pos);
378 });
379 })
380 }
381 FlipStyle::Simultaneous => {
382 let min_char_byte = str.chars().min().unwrap() as u8;
383 let max_char_byte = str.chars().max().unwrap() as u8;
384 let str_len = str.chars().count();
385
386 let flap_start_byte = match max_flip_cnt {
387 None => 0x20,
388 Some(max_flip_cnt) => {
389 if max_char_byte - min_char_byte > max_flip_cnt {
390 min_char_byte
391 } else if max_char_byte - max_flip_cnt < 0x20 {
392 0x20
393 } else {
394 max_char_byte - max_flip_cnt
395 }
396 }
397 };
398
399 let start_pos = self.get_cursor_pos();
400
401 (flap_start_byte..=max_char_byte).for_each(|cur_byte| {
402 self.delay_us(per_flip_delay_us);
403
404 str.char_indices()
405 .filter(|&(_, target_char)| cur_byte <= target_char as u8) .for_each(|(index, _)| {
407 let cur_pos = match self.get_direction() {
408 MoveDirection::RightToLeft => {
409 self.calculate_pos_by_offset(start_pos, (-(index as i8), 0))
410 }
411 MoveDirection::LeftToRight => {
412 self.calculate_pos_by_offset(start_pos, (index as i8, 0))
413 }
414 };
415 self.write_byte_to_pos(cur_byte, cur_pos);
416 });
417 });
418
419 let end_pos = match self.get_direction() {
422 MoveDirection::RightToLeft => {
423 self.calculate_pos_by_offset(start_pos, (-((str_len) as i8), 0))
424 }
425 MoveDirection::LeftToRight => {
426 self.calculate_pos_by_offset(start_pos, ((str_len as i8), 0))
427 }
428 };
429 self.set_cursor_pos(end_pos);
430 }
431 }
432
433 if cursor_state_changed {
435 self.set_cursor_state(State::On);
436 }
437 }
438
439 fn shift_display_to_pos(
448 &mut self,
449 target_pos: u8,
450 ms: MoveStyle,
451 display_state_when_shift: State,
452 delay_us_per_step: u32,
453 ) {
454 let before_pos = self.get_display_offset();
455
456 if before_pos == target_pos {
458 return;
459 }
460
461 let line_capacity = self.get_line_capacity();
462
463 let before_state = self.get_display_state();
464
465 self.set_display_state(display_state_when_shift);
466
467 let (distance, direction) = match ms {
469 MoveStyle::ForceMoveLeft => {
470 if target_pos < before_pos {
471 (before_pos - target_pos, MoveDirection::RightToLeft)
472 } else {
473 (
474 line_capacity - (target_pos - before_pos),
475 MoveDirection::RightToLeft,
476 )
477 }
478 }
479
480 MoveStyle::ForceMoveRight => {
481 if target_pos > before_pos {
482 (target_pos - before_pos, MoveDirection::LeftToRight)
483 } else {
484 (
485 line_capacity - (before_pos - target_pos),
486 MoveDirection::LeftToRight,
487 )
488 }
489 }
490
491 MoveStyle::NoCrossBoundary => {
492 if target_pos > before_pos {
493 (target_pos - before_pos, MoveDirection::LeftToRight)
494 } else {
495 (before_pos - target_pos, MoveDirection::RightToLeft)
496 }
497 }
498
499 MoveStyle::Shortest => {
500 if target_pos > before_pos {
501 if target_pos - before_pos <= line_capacity / 2 {
502 (target_pos - before_pos, MoveDirection::LeftToRight)
503 } else {
504 (
505 line_capacity - (target_pos - before_pos),
506 MoveDirection::RightToLeft,
507 )
508 }
509 } else {
510 #[allow(clippy::collapsible_else_if)]
511 if before_pos - target_pos <= line_capacity / 2 {
512 (before_pos - target_pos, MoveDirection::RightToLeft)
513 } else {
514 (
515 line_capacity - (before_pos - target_pos),
516 MoveDirection::LeftToRight,
517 )
518 }
519 }
520 }
521 };
522
523 (0..(distance)).for_each(|_| {
524 self.delay_us(delay_us_per_step);
525 self.shift_cursor_or_display(ShiftType::CursorAndDisplay, direction);
526 });
527
528 self.set_display_state(before_state);
530 }
531}