lcd_async/
builder.rs

1//! [super::Display] builder module
2
3use embedded_hal::digital::{self, OutputPin};
4use embedded_hal_async::delay::DelayNs;
5
6use crate::{
7    dcs::InterfaceExt,
8    interface::Interface,
9    models::{Model, ModelInitError},
10    options::{ColorInversion, ColorOrder, ModelOptions, Orientation, RefreshOrder},
11    Display,
12};
13
14/// Builder for [Display] instances.
15///
16/// Exposes all possible display options.
17///
18/// # Examples
19///
20/// ```
21/// use lcd_async::{Builder, options::ColorOrder, models::ILI9342CRgb565};
22///
23/// # tokio_test::block_on(async {
24/// # let di = lcd_async::_mock::MockDisplayInterface;
25/// # let rst = lcd_async::_mock::MockOutputPin;
26/// # let mut delay = lcd_async::_mock::MockDelay;
27/// let mut display = Builder::new(ILI9342CRgb565, di)
28///     .reset_pin(rst)
29///     .color_order(ColorOrder::Bgr)
30///     .display_size(320, 240)
31///     .init(&mut delay).await.unwrap();
32/// # });
33/// ```
34pub struct Builder<DI, MODEL, RST>
35where
36    DI: Interface,
37    MODEL: Model,
38{
39    di: DI,
40    model: MODEL,
41    rst: Option<RST>,
42    options: ModelOptions,
43}
44
45impl<DI, MODEL> Builder<DI, MODEL, NoResetPin>
46where
47    DI: Interface,
48    MODEL: Model,
49{
50    ///
51    /// Constructs a new builder for given [Model].
52    ///
53    #[must_use]
54    pub fn new(model: MODEL, di: DI) -> Self {
55        Self {
56            di,
57            model,
58            rst: None,
59            options: ModelOptions::full_size::<MODEL>(),
60        }
61    }
62}
63
64impl<DI, MODEL, RST> Builder<DI, MODEL, RST>
65where
66    DI: Interface,
67    MODEL: Model,
68    RST: OutputPin,
69{
70    ///
71    /// Sets the invert color flag
72    ///
73    #[must_use]
74    pub fn invert_colors(mut self, color_inversion: ColorInversion) -> Self {
75        self.options.invert_colors = color_inversion;
76        self
77    }
78
79    ///
80    /// Sets the [ColorOrder]
81    ///
82    #[must_use]
83    pub fn color_order(mut self, color_order: ColorOrder) -> Self {
84        self.options.color_order = color_order;
85        self
86    }
87
88    ///
89    /// Sets the [Orientation]
90    ///
91    #[must_use]
92    pub fn orientation(mut self, orientation: Orientation) -> Self {
93        self.options.orientation = orientation;
94        self
95    }
96
97    ///
98    /// Sets refresh order
99    ///
100    #[must_use]
101    pub fn refresh_order(mut self, refresh_order: RefreshOrder) -> Self {
102        self.options.refresh_order = refresh_order;
103        self
104    }
105
106    /// Sets the display size.
107    ///
108    ///
109    #[must_use]
110    pub fn display_size(mut self, width: u16, height: u16) -> Self {
111        self.options.display_size = (width, height);
112        self
113    }
114
115    ///
116    /// Sets the display offset
117    ///
118    #[must_use]
119    pub fn display_offset(mut self, x: u16, y: u16) -> Self {
120        self.options.display_offset = (x, y);
121        self
122    }
123
124    /// Sets the reset pin.
125    ///
126    /// ### WARNING
127    /// The reset pin needs to be in *high* state in order for the display to operate.
128    /// If it wasn't provided the user needs to ensure this is the case.
129    ///
130    #[must_use]
131    pub fn reset_pin<RST2: OutputPin>(self, rst: RST2) -> Builder<DI, MODEL, RST2> {
132        Builder {
133            di: self.di,
134            model: self.model,
135            rst: Some(rst),
136            options: self.options,
137        }
138    }
139
140    ///
141    /// Consumes the builder to create a new [Display] with an optional reset [OutputPin].
142    /// Blocks using the provided [DelayNs] `delay_source` to perform the display initialization.
143    /// The display will be awake ready to use, no need to call [Display::wake] after init.
144    ///
145    /// Returns [InitError] if the area defined by the [`display_size`](Self::display_size)
146    /// and [`display_offset`](Self::display_offset) settings is (partially) outside the framebuffer.
147    pub async fn init(
148        mut self,
149        delay_source: &mut impl DelayNs,
150    ) -> Result<Display<DI, MODEL, RST>, InitError<DI::Error, RST::Error>> {
151        let to_u32 = |(a, b)| (u32::from(a), u32::from(b));
152        let (width, height) = to_u32(self.options.display_size);
153        let (offset_x, offset_y) = to_u32(self.options.display_offset);
154        let (max_width, max_height) = to_u32(MODEL::FRAMEBUFFER_SIZE);
155
156        if width == 0 || height == 0 || width > max_width || height > max_height {
157            return Err(InitError::InvalidConfiguration(
158                ConfigurationError::InvalidDisplaySize,
159            ));
160        }
161
162        if width + offset_x > max_width {
163            return Err(InitError::InvalidConfiguration(
164                ConfigurationError::InvalidDisplayOffset,
165            ));
166        }
167
168        if height + offset_y > max_height {
169            return Err(InitError::InvalidConfiguration(
170                ConfigurationError::InvalidDisplayOffset,
171            ));
172        }
173
174        match self.rst {
175            Some(ref mut rst) => {
176                rst.set_low().map_err(InitError::ResetPin)?;
177                delay_source.delay_us(MODEL::RESET_DURATION).await;
178                rst.set_high().map_err(InitError::ResetPin)?;
179            }
180            None => self
181                .di
182                .write_command(crate::dcs::SoftReset)
183                .await
184                .map_err(InitError::Interface)?,
185        }
186
187        let madctl = self
188            .model
189            .init(&mut self.di, delay_source, &self.options)
190            .await?;
191
192        let display = Display {
193            di: self.di,
194            model: self.model,
195            rst: self.rst,
196            options: self.options,
197            madctl,
198            sleeping: false, // TODO: init should lock state
199        };
200
201        Ok(display)
202    }
203}
204
205/// Error returned by [`Builder::init`].
206#[derive(Debug)]
207pub enum InitError<DI, P> {
208    /// Error caused by the display interface.
209    Interface(DI),
210
211    /// Error caused by the reset pin's [`OutputPin`](embedded_hal::digital::OutputPin) implementation.
212    ResetPin(P),
213
214    /// Invalid configuration error.
215    ///
216    /// This error is returned when the configuration passed to the builder is
217    /// invalid. For example, when the combination of bit depth and interface
218    /// kind isn't supported by the selected model.
219    InvalidConfiguration(ConfigurationError),
220}
221
222/// Specifics of [InitError::InvalidConfiguration] if configuration was found invalid
223#[non_exhaustive]
224#[derive(Debug)]
225pub enum ConfigurationError {
226    /// Unsupported interface kind.
227    ///
228    /// The chosen interface isn't supported by the selected model. Note that
229    /// some controller models don't support all combinations of physical
230    /// interface and color formats. To resolve this, try to use another color
231    /// format if available (e.g. [`ILI9486Rgb666`](crate::models::ILI9486Rgb666) instead of
232    /// [`ILI9486Rgb565`](crate::models::ILI9486Rgb565) if you use a SPI connection)
233    UnsupportedInterface,
234    /// Invalid display size
235    ///
236    /// Display dimensions provided in [Builder::display_size] were invalid, e.g. width or height of 0
237    InvalidDisplaySize,
238    /// Invalid display offset.
239    ///
240    /// The active display area, defined by [`display_size`](Builder::display_size) and
241    /// [`display_offset`](Builder::display_offset), extends beyond the boundaries of
242    /// the controller's framebuffer. To resolve this, reduce the offset to a maximum value of
243    /// [`FRAMEBUFFER_SIZE`](Model::FRAMEBUFFER_SIZE) minus [`display_size`](Builder::display_size).
244    InvalidDisplayOffset,
245}
246
247impl<DiError, P> From<ModelInitError<DiError>> for InitError<DiError, P> {
248    fn from(value: ModelInitError<DiError>) -> Self {
249        match value {
250            ModelInitError::Interface(e) => Self::Interface(e),
251            ModelInitError::InvalidConfiguration(ce) => Self::InvalidConfiguration(ce),
252        }
253    }
254}
255
256/// Marker type for no reset pin.
257pub enum NoResetPin {}
258
259impl digital::OutputPin for NoResetPin {
260    fn set_low(&mut self) -> Result<(), Self::Error> {
261        Ok(())
262    }
263
264    fn set_high(&mut self) -> Result<(), Self::Error> {
265        Ok(())
266    }
267}
268
269impl digital::ErrorType for NoResetPin {
270    type Error = core::convert::Infallible;
271}
272
273#[cfg(test)]
274mod tests {
275    use crate::{
276        _mock::{MockDelay, MockDisplayInterface, MockOutputPin},
277        models::ILI9341Rgb565,
278    };
279
280    use super::*;
281
282    #[test]
283    fn init_without_reset_pin() {
284        tokio_test::block_on(async {
285            let _: Display<_, _, NoResetPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
286                .init(&mut MockDelay)
287                .await
288                .unwrap();
289        });
290    }
291
292    #[test]
293    fn init_reset_pin() {
294        tokio_test::block_on(async {
295            let _: Display<_, _, MockOutputPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
296                .reset_pin(MockOutputPin)
297                .init(&mut MockDelay)
298                .await
299                .unwrap();
300        });
301    }
302
303    #[test]
304    fn error_too_wide() {
305        tokio_test::block_on(async {
306            assert!(matches!(
307                Builder::new(ILI9341Rgb565, MockDisplayInterface)
308                    .reset_pin(MockOutputPin)
309                    .display_size(241, 320)
310                    .init(&mut MockDelay)
311                    .await,
312                Err(InitError::InvalidConfiguration(
313                    ConfigurationError::InvalidDisplaySize
314                ))
315            ));
316        });
317    }
318
319    #[test]
320    fn error_too_tall() {
321        tokio_test::block_on(async {
322            assert!(matches!(
323                Builder::new(ILI9341Rgb565, MockDisplayInterface)
324                    .reset_pin(MockOutputPin)
325                    .display_size(240, 321)
326                    .init(&mut MockDelay)
327                    .await,
328                Err(InitError::InvalidConfiguration(
329                    ConfigurationError::InvalidDisplaySize
330                )),
331            ));
332        });
333    }
334
335    #[test]
336    fn error_offset_invalid_x() {
337        tokio_test::block_on(async {
338            assert!(matches!(
339                Builder::new(ILI9341Rgb565, MockDisplayInterface)
340                    .reset_pin(MockOutputPin)
341                    .display_size(240, 320)
342                    .display_offset(1, 0)
343                    .init(&mut MockDelay)
344                    .await,
345                Err(InitError::InvalidConfiguration(
346                    ConfigurationError::InvalidDisplayOffset
347                )),
348            ));
349        });
350    }
351
352    #[test]
353    fn error_offset_invalid_y() {
354        tokio_test::block_on(async {
355            assert!(matches!(
356                Builder::new(ILI9341Rgb565, MockDisplayInterface)
357                    .reset_pin(MockOutputPin)
358                    .display_size(240, 310)
359                    .display_offset(0, 11)
360                    .init(&mut MockDelay)
361                    .await,
362                Err(InitError::InvalidConfiguration(
363                    ConfigurationError::InvalidDisplayOffset
364                )),
365            ));
366        });
367    }
368
369    #[test]
370    fn error_zero_size() {
371        tokio_test::block_on(async {
372            assert!(matches!(
373                Builder::new(ILI9341Rgb565, MockDisplayInterface)
374                    .reset_pin(MockOutputPin)
375                    .display_size(0, 0)
376                    .init(&mut MockDelay)
377                    .await,
378                Err(InitError::InvalidConfiguration(
379                    ConfigurationError::InvalidDisplaySize
380                )),
381            ));
382        });
383    }
384}