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}