lcd_async/lib.rs
1#![no_std]
2// associated re-typing not supported in rust yet
3#![allow(clippy::type_complexity)]
4#![warn(missing_docs)]
5
6//!
7//! # lcd-async
8//!
9//! An **async-first** display driver for TFT displays implementing the [MIPI Display Command Set](https://www.mipi.org/specifications/display-command-set).
10//!
11//! This crate is a fork of [mipidsi](https://github.com/almindor/mipidsi), redesigned for modern, async-native embedded Rust. It features a framebuffer-centric workflow, enabling efficient, non-blocking rendering and seamless integration with async runtimes like [embassy](https://embassy.dev/).
12//!
13//! ## Key Features
14//!
15//! - **Fully Asynchronous:** All display I/O is performed via async traits, making it ideal for async runtimes and DMA-driven workflows.
16//! - **Framebuffer-Based Drawing:** Draw your scene into an in-memory buffer using `embedded-graphics`, then send the entire frame to the display in one efficient async operation.
17//! - **Separation of Concerns:** Drawing is synchronous and CPU-bound; sending to the display is async and I/O-bound. This enables double buffering and advanced rendering patterns.
18//! - **Multiple Interface Support:**
19//! - SPI ([`interface::SpiInterface`])
20//! - 8080-style parallel via GPIO ([`interface::ParallelInterface`])
21//!
22//! ## Supported Models
23//!
24//! - GC9107
25//! - GC9A01
26//! - ILI9225
27//! - ILI9341
28//! - ILI9342C
29//! - ILI9486
30//! - ILI9488
31//! - RM67162
32//! - ST7735
33//! - ST7789
34//! - ST7796
35//!
36//! ## Example: Minimal Framebuffer Workflow
37//!
38//! ```rust
39//! use embedded_graphics::prelude::*;
40//! use embedded_graphics::pixelcolor::Rgb565;
41//! use embedded_graphics::primitives::{Circle, PrimitiveStyle};
42//! use lcd_async::raw_framebuf::RawFrameBuf;
43//!
44//! const WIDTH: usize = 240;
45//! const HEIGHT: usize = 240;
46//! let mut buffer = [0u8; WIDTH * HEIGHT * 2];
47//! let mut fbuf = RawFrameBuf::<Rgb565, _>::new(&mut buffer[..], WIDTH, HEIGHT);
48//! fbuf.clear(Rgb565::BLACK).unwrap();
49//! Circle::new(Point::new(120, 120), 80)
50//! .into_styled(PrimitiveStyle::with_fill(Rgb565::GREEN))
51//! .draw(&mut fbuf)
52//! .unwrap();
53//! // See the examples/ directory for full async display usage
54//! ```
55//!
56//! ## Troubleshooting
57//!
58//! Refer to the [troubleshooting guide](_troubleshooting)
59//! if you experience problems like a blank screen or incorrect colors.
60//!
61//! ## License
62//!
63//! Licensed under MIT, same as the original mipidsi crate.
64
65use dcs::SetAddressMode;
66
67pub mod interface;
68
69use embedded_hal::digital::OutputPin;
70use embedded_hal_async::delay::DelayNs;
71
72pub mod options;
73use options::MemoryMapping;
74
75mod builder;
76pub use builder::*;
77
78pub mod dcs;
79
80pub mod models;
81pub mod raw_framebuf;
82use models::Model;
83
84mod graphics;
85
86mod test_image;
87pub use test_image::TestImage;
88
89pub mod _troubleshooting;
90
91///
92/// Display driver to connect to TFT displays.
93///
94pub struct Display<DI, MODEL, RST>
95where
96 DI: interface::Interface,
97 MODEL: Model,
98 RST: OutputPin,
99{
100 // DCS provider
101 di: DI,
102 // Model
103 model: MODEL,
104 // Reset pin
105 rst: Option<RST>,
106 // Model Options, includes current orientation
107 options: options::ModelOptions,
108 // Current MADCTL value copy for runtime updates
109 #[allow(dead_code)]
110 madctl: SetAddressMode,
111 // State monitor for sleeping TODO: refactor to a Model-connected state machine
112 sleeping: bool,
113}
114
115impl<DI, M, RST> Display<DI, M, RST>
116where
117 DI: interface::Interface,
118 M: Model,
119 RST: OutputPin,
120{
121 ///
122 /// Returns currently set [options::Orientation]
123 ///
124 pub fn orientation(&self) -> options::Orientation {
125 self.options.orientation
126 }
127
128 ///
129 /// Sets display [options::Orientation] with mirror image parameter
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use lcd_async::options::{Orientation, Rotation};
135 ///
136 /// # tokio_test::block_on(async {
137 /// # let mut display = lcd_async::_mock::new_mock_display().await;
138 /// display.set_orientation(Orientation::default().rotate(Rotation::Deg180)).await.unwrap();
139 /// # });
140 /// ```
141 pub async fn set_orientation(
142 &mut self,
143 orientation: options::Orientation,
144 ) -> Result<(), DI::Error> {
145 self.options.orientation = orientation;
146 self.model.update_options(&mut self.di, &self.options).await
147 }
148
149 /// Sends a raw pixel data slice to the specified rectangular region of the display.
150 pub async fn show_raw_data<DW>(
151 &mut self,
152 x: u16,
153 y: u16,
154 width: u16,
155 height: u16,
156 pixel_data: &[DW],
157 ) -> Result<(), DI::Error>
158 where
159 DI: interface::Interface<Word = DW>,
160 DW: Copy,
161 {
162 let ex = x + width - 1;
163 let ey = y + height - 1;
164
165 self.set_address_window(x, y, ex, ey).await?;
166 M::write_memory_start(&mut self.di).await?;
167 self.di.send_data_slice(pixel_data).await
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 pub async fn set_vertical_scroll_region(
186 &mut self,
187 top_fixed_area: u16,
188 bottom_fixed_area: u16,
189 ) -> Result<(), DI::Error> {
190 M::set_vertical_scroll_region(&mut self.di, top_fixed_area, bottom_fixed_area).await
191 }
192
193 /// Sets the vertical scroll offset.
194 ///
195 /// Setting the vertical scroll offset shifts the vertical scroll region
196 /// upwards by `offset` pixels.
197 ///
198 /// Use [`set_vertical_scroll_region`](Self::set_vertical_scroll_region) to setup the scroll region, before
199 /// using this method.
200 pub async fn set_vertical_scroll_offset(&mut self, offset: u16) -> Result<(), DI::Error> {
201 M::set_vertical_scroll_offset(&mut self.di, offset).await
202 }
203
204 ///
205 /// Release resources allocated to this driver back.
206 /// This returns the display interface, reset pin and and the model deconstructing the driver.
207 ///
208 pub fn release(self) -> (DI, M, Option<RST>) {
209 (self.di, self.model, self.rst)
210 }
211
212 // Sets the address window for the display.
213 async fn set_address_window(
214 &mut self,
215 sx: u16,
216 sy: u16,
217 ex: u16,
218 ey: u16,
219 ) -> Result<(), DI::Error> {
220 // add clipping offsets if present
221 let mut offset = self.options.display_offset;
222 let mapping = MemoryMapping::from(self.options.orientation);
223 if mapping.reverse_columns {
224 offset.0 = M::FRAMEBUFFER_SIZE.0 - (self.options.display_size.0 + offset.0);
225 }
226 if mapping.reverse_rows {
227 offset.1 = M::FRAMEBUFFER_SIZE.1 - (self.options.display_size.1 + offset.1);
228 }
229 if mapping.swap_rows_and_columns {
230 offset = (offset.1, offset.0);
231 }
232
233 let (sx, sy, ex, ey) = (sx + offset.0, sy + offset.1, ex + offset.0, ey + offset.1);
234
235 M::update_address_window(
236 &mut self.di,
237 self.options.orientation.rotation,
238 sx,
239 sy,
240 ex,
241 ey,
242 )
243 .await
244 }
245
246 ///
247 /// Configures the tearing effect output.
248 ///
249 pub async fn set_tearing_effect(
250 &mut self,
251 tearing_effect: options::TearingEffect,
252 ) -> Result<(), DI::Error> {
253 M::set_tearing_effect(&mut self.di, tearing_effect, &self.options).await
254 }
255
256 ///
257 /// Returns `true` if display is currently set to sleep.
258 ///
259 pub fn is_sleeping(&self) -> bool {
260 self.sleeping
261 }
262
263 ///
264 /// Puts the display to sleep, reducing power consumption.
265 /// Need to call [Self::wake] before issuing other commands
266 ///
267 pub async fn sleep<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DI::Error> {
268 M::sleep(&mut self.di, delay).await?;
269 self.sleeping = true;
270 Ok(())
271 }
272
273 ///
274 /// Wakes the display after it's been set to sleep via [Self::sleep]
275 ///
276 pub async fn wake<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DI::Error> {
277 M::wake(&mut self.di, delay).await?;
278 self.sleeping = false;
279 Ok(())
280 }
281
282 /// Returns the DCS interface for sending raw commands.
283 ///
284 /// # Safety
285 ///
286 /// Sending raw commands to the controller can lead to undefined behaviour,
287 /// because the rest of the code isn't aware of any state changes that were caused by sending raw commands.
288 /// The user must ensure that the state of the controller isn't altered in a way that interferes with the normal
289 /// operation of this crate.
290 pub unsafe fn dcs(&mut self) -> &mut DI {
291 &mut self.di
292 }
293}
294
295/// Mock implementations of embedded-hal and interface traits for async architecture.
296///
297/// Do not use types in this module outside of doc tests.
298#[doc(hidden)]
299pub mod _mock {
300 use core::convert::Infallible;
301
302 use embedded_hal::{digital, spi};
303 use embedded_hal_async::delay::DelayNs;
304
305 use crate::{
306 interface::{Interface, InterfaceKind},
307 models::ILI9341Rgb565,
308 Builder, Display, NoResetPin,
309 };
310
311 pub async fn new_mock_display() -> Display<MockDisplayInterface, ILI9341Rgb565, NoResetPin> {
312 Builder::new(ILI9341Rgb565, MockDisplayInterface)
313 .init(&mut MockDelay)
314 .await
315 .unwrap()
316 }
317
318 pub struct MockOutputPin;
319
320 impl digital::OutputPin for MockOutputPin {
321 fn set_low(&mut self) -> Result<(), Self::Error> {
322 Ok(())
323 }
324
325 fn set_high(&mut self) -> Result<(), Self::Error> {
326 Ok(())
327 }
328 }
329
330 impl digital::ErrorType for MockOutputPin {
331 type Error = core::convert::Infallible;
332 }
333
334 pub struct MockSpi;
335
336 impl spi::SpiDevice for MockSpi {
337 fn transaction(
338 &mut self,
339 _operations: &mut [spi::Operation<'_, u8>],
340 ) -> Result<(), Self::Error> {
341 Ok(())
342 }
343 }
344
345 impl spi::ErrorType for MockSpi {
346 type Error = core::convert::Infallible;
347 }
348
349 pub struct MockDelay;
350
351 impl DelayNs for MockDelay {
352 async fn delay_ns(&mut self, _ns: u32) {}
353 }
354
355 pub struct MockDisplayInterface;
356
357 impl Interface for MockDisplayInterface {
358 type Word = u8;
359 type Error = Infallible;
360
361 const KIND: InterfaceKind = InterfaceKind::Serial4Line;
362
363 async fn send_command(&mut self, _command: u8, _args: &[u8]) -> Result<(), Self::Error> {
364 Ok(())
365 }
366
367 async fn send_data_slice(&mut self, _data: &[Self::Word]) -> Result<(), Self::Error> {
368 Ok(())
369 }
370 }
371}