Skip to main content

lcd_async/interface/
spi.rs

1//!
2//! Async SPI interface for MIPI DCS displays.
3//!
4//! This module provides an async implementation of the [`Interface`] trait for SPI-based TFT displays.
5//! It is designed for use with async runtimes and drivers, and does not require an internal buffer—
6//! pixel data is sent directly from the provided slice.
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use lcd_async::interface::SpiInterface;
12//! use embedded_hal_async::spi::SpiDevice;
13//! use embedded_hal::digital::OutputPin;
14//!
15//! let spi = /* your async SPI device */;
16//! let dc = /* your DC OutputPin */;
17//! let mut iface = SpiInterface::new(spi, dc);
18//! // Use iface with the display driver
19//! ```
20
21use embedded_hal::digital::OutputPin;
22use embedded_hal_async::spi::SpiDevice;
23
24use super::{Interface, InterfaceKind};
25
26/// Error type for the async SPI interface.
27///
28/// Wraps errors from the SPI bus or the data/command (DC) pin.
29#[derive(Clone, Copy, Debug)]
30#[cfg_attr(feature = "defmt", derive(defmt::Format))]
31pub enum SpiError<SPI, DC> {
32    /// SPI bus error
33    Spi(SPI),
34    /// Data/command pin error
35    Dc(DC),
36}
37
38/// Async SPI interface for MIPI DCS displays.
39///
40/// This struct implements the [`Interface`] trait for SPI-based displays, using an async [`SpiDevice`]
41/// and a data/command (DC) output pin. Unlike the original mipidsi version, this async variant does not
42/// use an internal buffer; all pixel data is sent directly from the provided slice.
43///
44/// Use [`SpiInterface::new`] to construct, and [`SpiInterface::release`] to deconstruct and recover the SPI and DC resources.
45pub struct SpiInterface<SPI, DC> {
46    spi: SPI,
47    dc: DC,
48}
49
50impl<SPI, DC> SpiInterface<SPI, DC>
51where
52    SPI: SpiDevice,
53    DC: OutputPin,
54{
55    /// Create a new async SPI interface from an SPI device and DC pin.
56    pub fn new(spi: SPI, dc: DC) -> Self {
57        Self { spi, dc }
58    }
59
60    /// Release the DC pin and SPI peripheral back, deconstructing the interface.
61    pub fn release(self) -> (SPI, DC) {
62        (self.spi, self.dc)
63    }
64}
65
66impl<SPI, DC> Interface for SpiInterface<SPI, DC>
67where
68    SPI: SpiDevice, // Assuming async
69    DC: OutputPin,  // Ensure OutputPin methods are compatible with your async context
70{
71    type Word = u8; // For SPI, Word is u8. send_data_slice will take &[u8]
72    type Error = SpiError<SPI::Error, DC::Error>;
73
74    const KIND: InterfaceKind = InterfaceKind::Serial4Line;
75
76    /// Send a command and its arguments to the display controller.
77    ///
78    /// The DC pin is set low for the command byte, then high for the argument bytes.
79    async fn send_command(&mut self, command: u8, args: &[u8]) -> Result<(), Self::Error> {
80        self.dc.set_low().map_err(SpiError::Dc)?;
81        self.spi.write(&[command]).await.map_err(SpiError::Spi)?;
82        self.dc.set_high().map_err(SpiError::Dc)?;
83        self.spi.write(args).await.map_err(SpiError::Spi)?;
84        Ok(())
85    }
86
87    /// Send a slice of pixel or data bytes to the display controller.
88    ///
89    /// The data is sent as-is over SPI, with the DC pin assumed to be high.
90    async fn send_data_slice(&mut self, data: &[Self::Word]) -> Result<(), Self::Error> {
91        // data is &[u8] because Self::Word = u8
92        // Directly send the user's framebuffer slice.
93        // The underlying SPI driver might do its own buffering/chunking if necessary.
94        self.spi.write(data).await.map_err(SpiError::Spi)?;
95        Ok(())
96    }
97}