Skip to main content

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)]
207#[cfg_attr(feature = "defmt", derive(defmt::Format))]
208pub enum InitError<DI, P> {
209    /// Error caused by the display interface.
210    Interface(DI),
211
212    /// Error caused by the reset pin's [`OutputPin`](embedded_hal::digital::OutputPin) implementation.
213    ResetPin(P),
214
215    /// Invalid configuration error.
216    ///
217    /// This error is returned when the configuration passed to the builder is
218    /// invalid. For example, when the combination of bit depth and interface
219    /// kind isn't supported by the selected model.
220    InvalidConfiguration(ConfigurationError),
221}
222
223/// Specifics of [InitError::InvalidConfiguration] if configuration was found invalid
224#[non_exhaustive]
225#[derive(Debug)]
226#[cfg_attr(feature = "defmt", derive(defmt::Format))]
227pub enum ConfigurationError {
228    /// Unsupported interface kind.
229    ///
230    /// The chosen interface isn't supported by the selected model. Note that
231    /// some controller models don't support all combinations of physical
232    /// interface and color formats. To resolve this, try to use another color
233    /// format if available (e.g. [`ILI9486Rgb666`](crate::models::ILI9486Rgb666) instead of
234    /// [`ILI9486Rgb565`](crate::models::ILI9486Rgb565) if you use a SPI connection)
235    UnsupportedInterface,
236    /// Invalid display size
237    ///
238    /// Display dimensions provided in [Builder::display_size] were invalid, e.g. width or height of 0
239    InvalidDisplaySize,
240    /// Invalid display offset.
241    ///
242    /// The active display area, defined by [`display_size`](Builder::display_size) and
243    /// [`display_offset`](Builder::display_offset), extends beyond the boundaries of
244    /// the controller's framebuffer. To resolve this, reduce the offset to a maximum value of
245    /// [`FRAMEBUFFER_SIZE`](Model::FRAMEBUFFER_SIZE) minus [`display_size`](Builder::display_size).
246    InvalidDisplayOffset,
247}
248
249impl<DiError, P> From<ModelInitError<DiError>> for InitError<DiError, P> {
250    fn from(value: ModelInitError<DiError>) -> Self {
251        match value {
252            ModelInitError::Interface(e) => Self::Interface(e),
253            ModelInitError::InvalidConfiguration(ce) => Self::InvalidConfiguration(ce),
254        }
255    }
256}
257
258/// Marker type for no reset pin.
259pub enum NoResetPin {}
260
261impl digital::OutputPin for NoResetPin {
262    fn set_low(&mut self) -> Result<(), Self::Error> {
263        Ok(())
264    }
265
266    fn set_high(&mut self) -> Result<(), Self::Error> {
267        Ok(())
268    }
269}
270
271impl digital::ErrorType for NoResetPin {
272    type Error = core::convert::Infallible;
273}
274
275#[cfg(test)]
276mod tests {
277    use crate::{
278        _mock::{MockDelay, MockDisplayInterface, MockOutputPin},
279        models::ILI9341Rgb565,
280    };
281
282    use super::*;
283
284    #[test]
285    fn init_without_reset_pin() {
286        tokio_test::block_on(async {
287            let _: Display<_, _, NoResetPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
288                .init(&mut MockDelay)
289                .await
290                .unwrap();
291        });
292    }
293
294    #[test]
295    fn init_reset_pin() {
296        tokio_test::block_on(async {
297            let _: Display<_, _, MockOutputPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
298                .reset_pin(MockOutputPin)
299                .init(&mut MockDelay)
300                .await
301                .unwrap();
302        });
303    }
304
305    #[test]
306    fn error_too_wide() {
307        tokio_test::block_on(async {
308            assert!(matches!(
309                Builder::new(ILI9341Rgb565, MockDisplayInterface)
310                    .reset_pin(MockOutputPin)
311                    .display_size(241, 320)
312                    .init(&mut MockDelay)
313                    .await,
314                Err(InitError::InvalidConfiguration(
315                    ConfigurationError::InvalidDisplaySize
316                ))
317            ));
318        });
319    }
320
321    #[test]
322    fn error_too_tall() {
323        tokio_test::block_on(async {
324            assert!(matches!(
325                Builder::new(ILI9341Rgb565, MockDisplayInterface)
326                    .reset_pin(MockOutputPin)
327                    .display_size(240, 321)
328                    .init(&mut MockDelay)
329                    .await,
330                Err(InitError::InvalidConfiguration(
331                    ConfigurationError::InvalidDisplaySize
332                )),
333            ));
334        });
335    }
336
337    #[test]
338    fn error_offset_invalid_x() {
339        tokio_test::block_on(async {
340            assert!(matches!(
341                Builder::new(ILI9341Rgb565, MockDisplayInterface)
342                    .reset_pin(MockOutputPin)
343                    .display_size(240, 320)
344                    .display_offset(1, 0)
345                    .init(&mut MockDelay)
346                    .await,
347                Err(InitError::InvalidConfiguration(
348                    ConfigurationError::InvalidDisplayOffset
349                )),
350            ));
351        });
352    }
353
354    #[test]
355    fn error_offset_invalid_y() {
356        tokio_test::block_on(async {
357            assert!(matches!(
358                Builder::new(ILI9341Rgb565, MockDisplayInterface)
359                    .reset_pin(MockOutputPin)
360                    .display_size(240, 310)
361                    .display_offset(0, 11)
362                    .init(&mut MockDelay)
363                    .await,
364                Err(InitError::InvalidConfiguration(
365                    ConfigurationError::InvalidDisplayOffset
366                )),
367            ));
368        });
369    }
370
371    #[test]
372    fn error_zero_size() {
373        tokio_test::block_on(async {
374            assert!(matches!(
375                Builder::new(ILI9341Rgb565, MockDisplayInterface)
376                    .reset_pin(MockOutputPin)
377                    .display_size(0, 0)
378                    .init(&mut MockDelay)
379                    .await,
380                Err(InitError::InvalidConfiguration(
381                    ConfigurationError::InvalidDisplaySize
382                )),
383            ));
384        });
385    }
386}