dashboard/display_mod.rs
1//! Module for the Screen
2//!
3//! The [embedded_graphics](https://docs.rs/embedded-graphics/latest/embedded_graphics/) library
4//! is used to render 2D graphics to the screen. Examples for how to use the library can be
5//! found [here](https://docs.rs/embedded-graphics/latest/embedded_graphics/#shapes-and-text).
6//!
7//! The STM32G491KE has 512 Kbytes of Flash memory, and 112 Kbytes of SRAM. Because of the
8//! low memory constraints, a framebuffer cannot be used.
9//!
10//! # How rendering works
11//! The ILIxxxx IC drivers operate using commands and data. The command list can be found
12//! [here](https://www.displayfuture.com/Display/datasheet/controller/ILI9488.pdf#pages=140)
13//!
14//! What happens is the following:
15//!
16//! - A drawing window is prepared (with the 2 opposite corner coordinates), using three commands.
17//! - The [column address set](https://www.displayfuture.com/Display/datasheet/controller/ILI9488.pdf#pages=175)
18//! command.
19//! - The [page address set](https://www.displayfuture.com/Display/datasheet/controller/ILI9488.pdf#pages=177)
20//! command.
21//! - The [memory write](https://www.displayfuture.com/Display/datasheet/controller/ILI9488.pdf#pages=179)
22//! command begins the transmission of pixel data to the area defined by the column/page address set commands.
23//! - The starting point for drawint is the top left corner of this window
24//! - Every set of bytes received is intepreted as a pixel value in the current display format (Rgb666, Rgb565, etc.).
25//! How pixels are formatted into bytes depends on the display format and interface type. More information can be
26//! found in the [Display Data Format](https://www.displayfuture.com/Display/datasheet/controller/ILI9488.pdf#pages=119)
27//! section of the ILI9488 datasheet.
28//! - As soon as a pixel is received, an internal counter is incremented,
29//! and the next word will fill the next pixel (the adjacent on the right, or
30//! the first of the next row if the row ended)
31//!
32//! # Optimization Strategies
33//! 1. The hardware is optimized for drawing rectangles. So prefer rendering rectangles over other shapes.
34//! 1. If a text/gui element's state does not change between render frames, do not redraw it.
35//! 1. Numbers that are rendered on each frame (e.g speed, temperature) should use the seven-segment display font.
36//! The reason for this is because the seven-segment font is rendered using multiple horizontal/veritcal lines
37//! (rectangles), [source](https://github.com/embedded-graphics/eg-seven-segment/blob/master/src/segment.rs#L39).
38
39use defmt::{info, trace};
40use display_interface_spi::SPIInterface;
41use embassy_stm32::gpio::Output;
42use embassy_stm32::spi::Spi;
43use embassy_time::{Instant, Timer};
44use embedded_graphics::{
45 pixelcolor::Rgb666,
46 prelude::{Point, RgbColor},
47};
48use embedded_hal_bus::spi::ExclusiveDevice;
49use ili9488_rs::{Ili9488, Rgb666Mode};
50
51use crate::eco_can::RelayState;
52use crate::{
53 can_mod::RELAY_STATE,
54 mode::{
55 charging::render_charging_gui, init_charging::init_render_charging_gui,
56 init_running::init_render_running_gui, running::render_running_gui,
57 standby::render_standby_gui, startup::render_startup_gui,
58 },
59};
60
61/// Type Alias for ILI9488 driver, the current display driver
62pub type DisplayDevice = Ili9488<
63 SPIInterface<
64 ExclusiveDevice<
65 Spi<'static, embassy_stm32::mode::Async>,
66 Output<'static>,
67 embedded_hal_bus::spi::NoDelay,
68 >,
69 Output<'static>,
70 >,
71 Output<'static>,
72 Rgb666Mode,
73>;
74
75pub const DISPLAY_WIDTH: u32 = 480;
76pub const DISPLAY_HEIGHT: u32 = 320;
77pub const CENTER_POINT: Point = Point::new(DISPLAY_WIDTH as i32 / 2, DISPLAY_HEIGHT as i32 / 2);
78
79/// Responsible for rendering data to the display
80#[embassy_executor::task]
81pub async fn display_task(mut display: DisplayDevice) {
82 info!("Time taken to do a full screen clear:");
83 let start = Instant::now().as_millis();
84 display.clear_screen(Rgb666::WHITE).unwrap();
85 let end = Instant::now().as_millis();
86 info!("Full Screen Clear: {} ms", end - start);
87
88 let mut prev_relay_state = RelayState::RELAY_STRTP;
89
90 // Always render default startup screen
91 render_startup_gui(&mut display);
92
93 loop {
94 let relay_state_lock = RELAY_STATE.lock().await;
95 let relay_state = relay_state_lock.clone();
96 drop(relay_state_lock);
97
98 // Inialized display screen if switching relay state
99 if prev_relay_state != relay_state {
100 match relay_state {
101 RelayState::RELAY_STRTP => render_startup_gui(&mut display),
102 RelayState::RELAY_CHRGE => init_render_charging_gui(&mut display),
103 RelayState::RELAY_STBY => render_standby_gui(&mut display, true).await,
104 RelayState::RELAY_RUN => init_render_running_gui(&mut display),
105 }
106 // Update previous relay state
107 prev_relay_state = relay_state.clone();
108 }
109
110 // Update display with current relay state
111 match relay_state {
112 RelayState::RELAY_STRTP => (),
113 RelayState::RELAY_CHRGE => render_charging_gui(&mut display),
114 RelayState::RELAY_STBY => render_standby_gui(&mut display, false).await,
115 RelayState::RELAY_RUN => render_running_gui(&mut display),
116 }
117
118 trace!("Display Health check");
119 Timer::after_millis(2000).await;
120 }
121}