lcd_async/
models.rs

1//! Display models.
2
3use crate::{
4    dcs::{self, InterfaceExt, SetAddressMode},
5    interface::Interface,
6    options::{self, ModelOptions, Rotation},
7    ConfigurationError,
8};
9use embedded_graphics_core::prelude::RgbColor;
10use embedded_hal_async::delay::DelayNs;
11
12// existing model implementations
13mod gc9107;
14mod gc9a01;
15mod ili9225;
16mod ili9341;
17mod ili9342c;
18mod ili934x;
19mod ili9486;
20mod ili9488;
21mod ili948x;
22mod rm67162;
23mod st7735s;
24mod st7789;
25mod st7796;
26
27pub use gc9107::*;
28pub use gc9a01::*;
29pub use ili9225::*;
30pub use ili9341::*;
31pub use ili9342c::*;
32pub use ili9486::*;
33pub use ili9488::*;
34pub use rm67162::*;
35pub use st7735s::*;
36pub use st7789::*;
37pub use st7796::*;
38
39/// Display model.
40pub trait Model {
41    /// The color format.
42    type ColorFormat: RgbColor;
43
44    /// The framebuffer size in pixels.
45    const FRAMEBUFFER_SIZE: (u16, u16);
46
47    /// Duration of the active low reset pulse in µs.
48    const RESET_DURATION: u32 = 10;
49
50    /// Initializes the display for this model with MADCTL from [crate::Display]
51    /// and returns the value of MADCTL set by init
52    fn init<DELAY, DI>(
53        &mut self,
54        di: &mut DI,
55        delay: &mut DELAY,
56        options: &ModelOptions,
57    ) -> impl core::future::Future<Output = Result<SetAddressMode, ModelInitError<DI::Error>>>
58    where
59        DELAY: DelayNs,
60        DI: Interface;
61
62    /// Updates the address window of the display.
63    fn update_address_window<DI>(
64        di: &mut DI,
65        _rotation: Rotation,
66        sx: u16,
67        sy: u16,
68        ex: u16,
69        ey: u16,
70    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
71    where
72        DI: Interface,
73    {
74        async move {
75            di.write_command(dcs::SetColumnAddress::new(sx, ex)).await?;
76            di.write_command(dcs::SetPageAddress::new(sy, ey)).await
77        }
78    }
79
80    ///
81    /// Need to call [Self::wake] before issuing other commands
82    ///
83    fn sleep<DI, DELAY>(
84        di: &mut DI,
85        delay: &mut DELAY,
86    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
87    where
88        DI: Interface,
89        DELAY: DelayNs,
90    {
91        async move {
92            di.write_command(dcs::EnterSleepMode).await?;
93            // All supported models requires a 120ms delay before issuing other commands
94            delay.delay_us(120_000).await;
95            Ok(())
96        }
97    }
98    ///
99    /// Wakes the display after it's been set to sleep via [Self::sleep]
100    ///
101    fn wake<DI, DELAY>(
102        di: &mut DI,
103        delay: &mut DELAY,
104    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
105    where
106        DI: Interface,
107        DELAY: DelayNs,
108    {
109        async move {
110            di.write_command(dcs::ExitSleepMode).await?;
111            // ST7789 and st7735s have the highest minimal delay of 120ms
112            delay.delay_us(120_000).await;
113            Ok(())
114        }
115    }
116    ///
117    /// We need WriteMemoryStart befor write pixel
118    ///
119    fn write_memory_start<DI>(
120        di: &mut DI,
121    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
122    where
123        DI: Interface,
124    {
125        async move { di.write_command(dcs::WriteMemoryStart).await }
126    }
127    ///
128    /// SoftReset
129    ///
130    fn software_reset<DI>(di: &mut DI) -> impl core::future::Future<Output = Result<(), DI::Error>>
131    where
132        DI: Interface,
133    {
134        async move { di.write_command(dcs::SoftReset).await }
135    }
136    ///
137    /// This function will been called if user update options
138    ///
139    fn update_options<DI>(
140        &self,
141        di: &mut DI,
142        options: &ModelOptions,
143    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
144    where
145        DI: Interface,
146    {
147        async move {
148            let madctl = SetAddressMode::from(options);
149            di.write_command(madctl).await
150        }
151    }
152
153    ///
154    /// Configures the tearing effect output.
155    ///
156    fn set_tearing_effect<DI>(
157        di: &mut DI,
158        tearing_effect: options::TearingEffect,
159        _options: &ModelOptions,
160    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
161    where
162        DI: Interface,
163    {
164        async move {
165            di.write_command(dcs::SetTearingEffect::new(tearing_effect))
166                .await
167        }
168    }
169
170    /// Sets the vertical scroll region.
171    ///
172    /// The `top_fixed_area` and `bottom_fixed_area` arguments can be used to
173    /// define an area on the top and/or bottom of the display which won't be
174    /// affected by scrolling.
175    ///
176    /// Note that this method is not affected by the current display orientation
177    /// and will always scroll vertically relative to the default display
178    /// orientation.
179    ///
180    /// The combined height of the fixed area must not larger than the
181    /// height of the framebuffer height in the default orientation.
182    ///
183    /// After the scrolling region is defined the [`set_vertical_scroll_offset`](Self::set_vertical_scroll_offset) can be
184    /// used to scroll the display.
185    fn set_vertical_scroll_region<DI>(
186        di: &mut DI,
187        top_fixed_area: u16,
188        bottom_fixed_area: u16,
189    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
190    where
191        DI: Interface,
192    {
193        async move {
194            let rows = Self::FRAMEBUFFER_SIZE.1;
195
196            let vscrdef = if top_fixed_area + bottom_fixed_area > rows {
197                dcs::SetScrollArea::new(rows, 0, 0)
198            } else {
199                dcs::SetScrollArea::new(
200                    top_fixed_area,
201                    rows - top_fixed_area - bottom_fixed_area,
202                    bottom_fixed_area,
203                )
204            };
205
206            di.write_command(vscrdef).await
207        }
208    }
209
210    /// Sets the vertical scroll offset.
211    ///
212    /// Setting the vertical scroll offset shifts the vertical scroll region
213    /// upwards by `offset` pixels.
214    ///
215    /// Use [`set_vertical_scroll_region`](Self::set_vertical_scroll_region) to setup the scroll region, before
216    /// using this method.
217    fn set_vertical_scroll_offset<DI>(
218        di: &mut DI,
219        offset: u16,
220    ) -> impl core::future::Future<Output = Result<(), DI::Error>>
221    where
222        DI: Interface,
223    {
224        async move {
225            let vscad = dcs::SetScrollStart::new(offset);
226            di.write_command(vscad).await
227        }
228    }
229}
230
231/// Error returned by [`Model::init`].
232///
233/// This error type is used internally by implementations of the [`Model`]
234/// trait.
235pub enum ModelInitError<DiError> {
236    /// Error caused by the display interface.
237    Interface(DiError),
238
239    /// Invalid configuration error.
240    ///
241    /// This error is returned when the configuration passed to the builder is
242    /// invalid. For example, when the combination of bit depth and interface
243    /// kind isn't supported by the selected model.
244    InvalidConfiguration(ConfigurationError),
245}
246
247impl<DiError> From<DiError> for ModelInitError<DiError> {
248    fn from(value: DiError) -> Self {
249        Self::Interface(value)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use embedded_graphics::pixelcolor::Rgb565;
256
257    use crate::{
258        Builder,
259        _mock::{MockDelay, MockDisplayInterface},
260        dcs::SetAddressMode,
261        interface::InterfaceKind,
262        ConfigurationError, InitError,
263    };
264
265    use super::*;
266
267    struct OnlyOneKindModel(InterfaceKind);
268
269    impl Model for OnlyOneKindModel {
270        type ColorFormat = Rgb565;
271
272        const FRAMEBUFFER_SIZE: (u16, u16) = (16, 16);
273
274        async fn init<DELAY, DI>(
275            &mut self,
276            _di: &mut DI,
277            _delay: &mut DELAY,
278            _options: &ModelOptions,
279        ) -> Result<SetAddressMode, ModelInitError<DI::Error>>
280        where
281            DELAY: DelayNs,
282            DI: Interface,
283        {
284            if DI::KIND != self.0 {
285                return Err(ModelInitError::InvalidConfiguration(
286                    ConfigurationError::UnsupportedInterface,
287                ));
288            }
289
290            Ok(SetAddressMode::default())
291        }
292    }
293
294    #[test]
295    fn test_assert_interface_kind_serial() {
296        tokio_test::block_on(async {
297            Builder::new(
298                OnlyOneKindModel(InterfaceKind::Serial4Line),
299                MockDisplayInterface,
300            )
301            .init(&mut MockDelay)
302            .await
303            .unwrap();
304        });
305    }
306
307    #[test]
308    fn test_assert_interface_kind_parallel() {
309        tokio_test::block_on(async {
310            assert!(matches!(
311                Builder::new(
312                    OnlyOneKindModel(InterfaceKind::Parallel8Bit),
313                    MockDisplayInterface,
314                )
315                .init(&mut MockDelay)
316                .await,
317                Err(InitError::InvalidConfiguration(
318                    ConfigurationError::UnsupportedInterface
319                ))
320            ));
321        });
322    }
323}