1use 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
14pub 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 #[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 #[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 #[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 #[must_use]
92 pub fn orientation(mut self, orientation: Orientation) -> Self {
93 self.options.orientation = orientation;
94 self
95 }
96
97 #[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 #[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 #[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 #[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 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, };
200
201 Ok(display)
202 }
203}
204
205#[derive(Debug)]
207#[cfg_attr(feature = "defmt", derive(defmt::Format))]
208pub enum InitError<DI, P> {
209 Interface(DI),
211
212 ResetPin(P),
214
215 InvalidConfiguration(ConfigurationError),
221}
222
223#[non_exhaustive]
225#[derive(Debug)]
226#[cfg_attr(feature = "defmt", derive(defmt::Format))]
227pub enum ConfigurationError {
228 UnsupportedInterface,
236 InvalidDisplaySize,
240 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
258pub 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}