Skip to main content

display_driver/
lib.rs

1#![no_std]
2
3pub mod area;
4pub mod bus;
5pub mod color;
6pub mod panel;
7
8pub use crate::area::Area;
9pub use crate::bus::{
10    BusBytesIo, BusHardwareFill, DisplayBus, FrameControl, Metadata, SimpleDisplayBus,
11};
12pub use color::{ColorFormat, ColorType, SolidColor};
13pub use panel::{reset::LCDResetOption, Orientation, Panel, PanelSetBrightness};
14
15use embedded_hal_async::delay::DelayNs;
16
17/// Error type for display operations.
18#[derive(Debug)]
19pub enum DisplayError<E> {
20    /// Error propagated from the underlying bus.
21    BusError(E),
22    /// The requested operation is not supported by the display or driver.
23    Unsupported,
24    /// Parameter is out of valid range.
25    OutOfRange,
26    /// Invalid arguments.
27    InvalidArgs,
28    /// The area is unaligned.
29    UnalignedArea,
30}
31
32impl<E> From<E> for DisplayError<E> {
33    fn from(error: E) -> Self {
34        Self::BusError(error)
35    }
36}
37
38/// A builder for configuring and initializing a [`DisplayDriver`].
39///
40/// Use [`DisplayDriver::builder`] to create a builder, then chain configuration methods
41/// and call [`init`](DisplayDriverBuilder::init) to complete initialization.
42///
43/// # Example
44/// ```ignore
45/// let mut display = DisplayDriver::builder(bus, panel)
46///     .with_color_format(ColorFormat::RGB565)
47///     .with_orientation(Orientation::Deg270)
48///     .init(&mut delay).await.unwrap();
49/// ```
50pub struct DisplayDriverBuilder<B: DisplayBus, P: Panel<B>> {
51    bus: B,
52    panel: P,
53    color_format: Option<ColorFormat>,
54    orientation: Option<Orientation>,
55}
56
57impl<B: DisplayBus, P: Panel<B>> DisplayDriverBuilder<B, P> {
58    /// Creates a new builder with the given bus and panel.
59    fn new(bus: B, panel: P) -> Self {
60        Self {
61            bus,
62            panel,
63            color_format: None,
64            orientation: None,
65        }
66    }
67
68    /// Sets the color format to be applied during initialization.
69    pub fn with_color_format(mut self, color_format: ColorFormat) -> Self {
70        self.color_format = Some(color_format);
71        self
72    }
73
74    /// Sets the orientation to be applied during initialization.
75    pub fn with_orientation(mut self, orientation: Orientation) -> Self {
76        self.orientation = Some(orientation);
77        self
78    }
79
80    /// Initializes the display and returns the configured [`DisplayDriver`].
81    ///
82    /// This method:
83    /// 1. Calls the panel's initialization sequence
84    /// 2. Applies the color format if configured
85    /// 3. Applies the orientation if configured
86    pub async fn init<D: DelayNs>(
87        mut self,
88        delay: &mut D,
89    ) -> Result<DisplayDriver<B, P>, DisplayError<B::Error>> {
90        self.panel
91            .init(&mut self.bus, delay)
92            .await
93            .map_err(DisplayError::BusError)?;
94
95        if let Some(color_format) = self.color_format {
96            self.panel
97                .set_color_format(&mut self.bus, color_format)
98                .await?;
99        }
100
101        if let Some(orientation) = self.orientation {
102            self.panel
103                .set_orientation(&mut self.bus, orientation)
104                .await?;
105        }
106
107        Ok(DisplayDriver {
108            bus: self.bus,
109            panel: self.panel,
110        })
111    }
112}
113
114/// The high-level driver that orchestrates drawing operations.
115///
116/// This struct acts as the "glue" between the logical [`Panel`] implementation (which knows the command set)
117/// and the [`DisplayBus`] (which handles the physical transport). It exposes user-friendly methods
118/// for drawing pixels, filling rectangles, and managing the display state.
119pub struct DisplayDriver<B: DisplayBus, P: Panel<B>> {
120    /// The underlying bus interface used for communication.
121    pub bus: B,
122    /// The panel.
123    pub panel: P,
124}
125
126impl<B: DisplayBus, P: Panel<B>> DisplayDriver<B, P> {
127    /// Creates a builder for configuring and initializing a display driver.
128    ///
129    /// # Example
130    /// ```ignore
131    /// let mut display = DisplayDriver::builder(bus, panel)
132    ///     .with_color_format(ColorFormat::RGB565)
133    ///     .with_orientation(Orientation::Deg270)
134    ///     .init(&mut delay).await.unwrap();
135    /// ```
136    pub fn builder(bus: B, panel: P) -> DisplayDriverBuilder<B, P> {
137        DisplayDriverBuilder::new(bus, panel)
138    }
139
140    /// Creates a new display driver directly (without builder).
141    ///
142    /// Use [`builder`](Self::builder) for a fluent initialization API.
143    pub fn new(bus: B, panel: P) -> Self {
144        Self { bus, panel }
145    }
146
147    /// Initializes the display.
148    pub async fn init(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError<B::Error>> {
149        self.panel
150            .init(&mut self.bus, delay)
151            .await
152            .map_err(DisplayError::BusError)
153    }
154
155    /// Sets the window.
156    ///
157    /// Use `write_pixels` or `write_frame` if you just want to draw a buffer.
158    /// Use `fill_solid_xxx` if you just want to fill an Area.
159    pub async fn set_window(&mut self, area: Area) -> Result<(), DisplayError<B::Error>> {
160        if self.panel.x_alignment() > 1 || self.panel.y_alignment() > 1 {
161            if area.x % self.panel.x_alignment() != 0
162                || area.y % self.panel.y_alignment() != 0
163                || area.w % self.panel.x_alignment() != 0
164                || area.h % self.panel.y_alignment() != 0
165            {
166                return Err(DisplayError::UnalignedArea);
167            }
168        }
169
170        let (x1, y1) = area.bottom_right();
171        self.panel
172            .set_window(&mut self.bus, area.x, area.y, x1, y1)
173            .await
174    }
175
176    /// Sets the pixel color format.
177    pub async fn set_color_format(
178        &mut self,
179        color_format: ColorFormat,
180    ) -> Result<(), DisplayError<B::Error>> {
181        self.panel
182            .set_color_format(&mut self.bus, color_format)
183            .await
184    }
185
186    /// Sets the display orientation.
187    pub async fn set_orientation(
188        &mut self,
189        orientation: Orientation,
190    ) -> Result<(), DisplayError<B::Error>> {
191        self.panel.set_orientation(&mut self.bus, orientation).await
192    }
193
194    /// Writes pixels to the specified area.
195    pub async fn write_pixels(
196        &mut self,
197        area: Area,
198        frame_control: FrameControl,
199        buffer: &[u8],
200    ) -> Result<(), DisplayError<B::Error>> {
201        self.set_window(area).await?;
202        let cmd = &P::PIXEL_WRITE_CMD[0..P::CMD_LEN];
203        let metadata = Metadata {
204            area: Some(area),
205            frame_control,
206        };
207        self.bus.write_pixels(cmd, buffer, metadata).await
208    }
209
210    /// Writes the entire buffer to the display.
211    pub async fn write_frame(&mut self, buffer: &[u8]) -> Result<(), DisplayError<B::Error>> {
212        self.write_pixels(
213            Area::from_origin_size(self.panel.size()),
214            FrameControl::new_standalone(),
215            buffer,
216        )
217        .await
218    }
219}
220
221impl<B: DisplayBus, P: Panel<B> + PanelSetBrightness<B>> DisplayDriver<B, P> {
222    /// Sets the display brightness (if supported by the panel).
223    pub async fn set_brightness(&mut self, brightness: u8) -> Result<(), DisplayError<B::Error>> {
224        self.panel.set_brightness(&mut self.bus, brightness).await
225    }
226}
227
228impl<B: DisplayBus + BusHardwareFill, P: Panel<B>> DisplayDriver<B, P> {
229    /// Fills the area with a solid color using bus auto-fill.
230    pub async fn fill_solid_via_bus(
231        &mut self,
232        color: SolidColor,
233        area: Area,
234    ) -> Result<(), DisplayError<B::Error>> {
235        self.set_window(area).await?;
236        let cmd = &P::PIXEL_WRITE_CMD[0..P::CMD_LEN];
237        self.bus.fill_solid(cmd, color, area).await
238    }
239
240    /// Fills the entire screen with a solid color using bus auto-fill.
241    pub async fn fill_screen_via_bus(
242        &mut self,
243        color: SolidColor,
244    ) -> Result<(), DisplayError<B::Error>> {
245        self.fill_solid_via_bus(color, Area::from_origin_size(self.panel.size()))
246            .await
247    }
248}
249
250impl<B, P> DisplayDriver<B, P>
251where
252    B: DisplayBus + BusBytesIo,
253    P: Panel<B>,
254{
255    /// Fills the area with a solid color.
256    pub async fn fill_solid_batch<const N: usize>(
257        &mut self,
258        color: SolidColor,
259        area: Area,
260    ) -> Result<(), DisplayError<B::Error>> {
261        self.set_window(area).await?;
262        let cmd = &P::PIXEL_WRITE_CMD[0..P::CMD_LEN];
263
264        self.bus
265            .write_cmd_bytes(cmd)
266            .await
267            .map_err(DisplayError::BusError)?;
268
269        let pixel_size = color.format.size_bytes() as usize;
270        let total_pixels = area.total_pixels();
271        let mut remaining_pixels = total_pixels;
272
273        let mut buffer = [0u8; N];
274
275        // Calculate how many full pixels fit in the buffer
276        let pixels_per_chunk = buffer.len() / pixel_size;
277
278        // Extract the raw bytes for the color based on its size
279        let color_bytes = &color.raw[..pixel_size];
280
281        // Pre-fill the buffer with the color pattern
282        for i in 0..pixels_per_chunk {
283            buffer[i * pixel_size..(i + 1) * pixel_size].copy_from_slice(color_bytes);
284        }
285
286        while remaining_pixels > 0 {
287            let current_pixels = remaining_pixels.min(pixels_per_chunk);
288            let byte_count = current_pixels * pixel_size;
289            self.bus
290                .write_data_bytes(&buffer[0..byte_count])
291                .await
292                .map_err(DisplayError::BusError)?;
293            remaining_pixels -= current_pixels;
294        }
295
296        Ok(())
297    }
298
299    /// Fills the entire screen with a solid color.
300    pub async fn fill_screen_batch<const N: usize>(
301        &mut self,
302        color: SolidColor,
303    ) -> Result<(), DisplayError<B::Error>> {
304        self.fill_solid_batch::<N>(color, Area::from_origin_size(self.panel.size()))
305            .await
306    }
307}