use embedded_hal::digital::{self, OutputPin};
use embedded_hal_async::delay::DelayNs;
use crate::{
dcs::InterfaceExt,
interface::Interface,
models::{Model, ModelInitError},
options::{ColorInversion, ColorOrder, ModelOptions, Orientation, RefreshOrder},
Display,
};
pub struct Builder<DI, MODEL, RST>
where
DI: Interface,
MODEL: Model,
{
di: DI,
model: MODEL,
rst: Option<RST>,
options: ModelOptions,
}
impl<DI, MODEL> Builder<DI, MODEL, NoResetPin>
where
DI: Interface,
MODEL: Model,
{
#[must_use]
pub fn new(model: MODEL, di: DI) -> Self {
Self {
di,
model,
rst: None,
options: ModelOptions::full_size::<MODEL>(),
}
}
}
impl<DI, MODEL, RST> Builder<DI, MODEL, RST>
where
DI: Interface,
MODEL: Model,
RST: OutputPin,
{
#[must_use]
pub fn invert_colors(mut self, color_inversion: ColorInversion) -> Self {
self.options.invert_colors = color_inversion;
self
}
#[must_use]
pub fn color_order(mut self, color_order: ColorOrder) -> Self {
self.options.color_order = color_order;
self
}
#[must_use]
pub fn orientation(mut self, orientation: Orientation) -> Self {
self.options.orientation = orientation;
self
}
#[must_use]
pub fn refresh_order(mut self, refresh_order: RefreshOrder) -> Self {
self.options.refresh_order = refresh_order;
self
}
#[must_use]
pub fn display_size(mut self, width: u16, height: u16) -> Self {
self.options.display_size = (width, height);
self
}
#[must_use]
pub fn display_offset(mut self, x: u16, y: u16) -> Self {
self.options.display_offset = (x, y);
self
}
#[must_use]
pub fn reset_pin<RST2: OutputPin>(self, rst: RST2) -> Builder<DI, MODEL, RST2> {
Builder {
di: self.di,
model: self.model,
rst: Some(rst),
options: self.options,
}
}
pub async fn init(
mut self,
delay_source: &mut impl DelayNs,
) -> Result<Display<DI, MODEL, RST>, InitError<DI::Error, RST::Error>> {
let to_u32 = |(a, b)| (u32::from(a), u32::from(b));
let (width, height) = to_u32(self.options.display_size);
let (offset_x, offset_y) = to_u32(self.options.display_offset);
let (max_width, max_height) = to_u32(MODEL::FRAMEBUFFER_SIZE);
if width == 0 || height == 0 || width > max_width || height > max_height {
return Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplaySize,
));
}
if width + offset_x > max_width {
return Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplayOffset,
));
}
if height + offset_y > max_height {
return Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplayOffset,
));
}
match self.rst {
Some(ref mut rst) => {
rst.set_low().map_err(InitError::ResetPin)?;
delay_source.delay_us(MODEL::RESET_DURATION).await;
rst.set_high().map_err(InitError::ResetPin)?;
}
None => self
.di
.write_command(crate::dcs::SoftReset)
.await
.map_err(InitError::Interface)?,
}
let madctl = self
.model
.init(&mut self.di, delay_source, &self.options)
.await?;
let display = Display {
di: self.di,
model: self.model,
rst: self.rst,
options: self.options,
madctl,
sleeping: false, };
Ok(display)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError<DI, P> {
Interface(DI),
ResetPin(P),
InvalidConfiguration(ConfigurationError),
}
#[non_exhaustive]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigurationError {
UnsupportedInterface,
InvalidDisplaySize,
InvalidDisplayOffset,
}
impl<DiError, P> From<ModelInitError<DiError>> for InitError<DiError, P> {
fn from(value: ModelInitError<DiError>) -> Self {
match value {
ModelInitError::Interface(e) => Self::Interface(e),
ModelInitError::InvalidConfiguration(ce) => Self::InvalidConfiguration(ce),
}
}
}
pub enum NoResetPin {}
impl digital::OutputPin for NoResetPin {
fn set_low(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
impl digital::ErrorType for NoResetPin {
type Error = core::convert::Infallible;
}
#[cfg(test)]
mod tests {
use crate::{
_mock::{MockDelay, MockDisplayInterface, MockOutputPin},
models::ILI9341Rgb565,
};
use super::*;
#[test]
fn init_without_reset_pin() {
tokio_test::block_on(async {
let _: Display<_, _, NoResetPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
.init(&mut MockDelay)
.await
.unwrap();
});
}
#[test]
fn init_reset_pin() {
tokio_test::block_on(async {
let _: Display<_, _, MockOutputPin> = Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.init(&mut MockDelay)
.await
.unwrap();
});
}
#[test]
fn error_too_wide() {
tokio_test::block_on(async {
assert!(matches!(
Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.display_size(241, 320)
.init(&mut MockDelay)
.await,
Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplaySize
))
));
});
}
#[test]
fn error_too_tall() {
tokio_test::block_on(async {
assert!(matches!(
Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.display_size(240, 321)
.init(&mut MockDelay)
.await,
Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplaySize
)),
));
});
}
#[test]
fn error_offset_invalid_x() {
tokio_test::block_on(async {
assert!(matches!(
Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.display_size(240, 320)
.display_offset(1, 0)
.init(&mut MockDelay)
.await,
Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplayOffset
)),
));
});
}
#[test]
fn error_offset_invalid_y() {
tokio_test::block_on(async {
assert!(matches!(
Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.display_size(240, 310)
.display_offset(0, 11)
.init(&mut MockDelay)
.await,
Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplayOffset
)),
));
});
}
#[test]
fn error_zero_size() {
tokio_test::block_on(async {
assert!(matches!(
Builder::new(ILI9341Rgb565, MockDisplayInterface)
.reset_pin(MockOutputPin)
.display_size(0, 0)
.init(&mut MockDelay)
.await,
Err(InitError::InvalidConfiguration(
ConfigurationError::InvalidDisplaySize
)),
));
});
}
}