Skip to main content

display_driver_mipidcs/
lib.rs

1#![no_std]
2
3pub mod consts;
4pub mod dcs_types;
5pub mod display_bus;
6
7use core::marker::PhantomData;
8use display_driver::bus::DisplayBus;
9use display_driver::panel::{initseq::InitStep, reset::LCDResetOption, Orientation};
10use embedded_hal::digital::OutputPin;
11
12pub use crate::consts::*;
13pub use crate::dcs_types::*;
14
15/// A generic driver for MIPI DCS compliant displays.
16///
17/// This struct implements standard MIPI Display Command Set (MIPI DCS) operations such as setting address windows,
18/// controlling sleep modes, and handling pixel formats.
19/// It is designed to be embedded within specific panel drivers to handle the common DCS functionality.
20pub struct GenericMipidcs<B, S, RST>
21where
22    B: DisplayBus,
23    S: PanelSpec,
24    RST: OutputPin,
25{
26    pub reset_pin: LCDResetOption<RST>,
27    /// The current Address Mode (MADCTL) setting.
28    pub address_mode: AddressMode,
29    pub orientation: Orientation,
30    _phantom: PhantomData<(B, S)>,
31}
32
33impl<B, S, RST> GenericMipidcs<B, S, RST>
34where
35    B: DisplayBus,
36    S: PanelSpec,
37    RST: OutputPin,
38{
39    /// Creates a new generic MIPI DCS driver.
40    pub fn new(reset_pin: LCDResetOption<RST>) -> Self {
41        Self {
42            reset_pin,
43            address_mode: AddressMode::empty(),
44            orientation: Orientation::Deg0,
45            _phantom: PhantomData,
46        }
47    }
48
49    /// Returns the column (X) and page (Y) offsets based on the current orientation
50    /// and the `INVERT_TRANSPOSED_OFFSET` setting.
51    pub fn get_offset(&self) -> (u16, u16) {
52        match (self.orientation, S::INVERT_TRANSPOSED_OFFSET) {
53            (Orientation::Deg0, _) => (S::PHYSICAL_X_OFFSET, S::PHYSICAL_Y_OFFSET),
54            (Orientation::Deg180, _) => {
55                (S::PHYSICAL_X_OFFSET_ROTATED, S::PHYSICAL_Y_OFFSET_ROTATED)
56            }
57            (Orientation::Deg90, false) | (Orientation::Deg270, true) => {
58                (S::PHYSICAL_Y_OFFSET, S::PHYSICAL_X_OFFSET)
59            }
60            (Orientation::Deg270, false) | (Orientation::Deg90, true) => {
61                (S::PHYSICAL_Y_OFFSET_ROTATED, S::PHYSICAL_X_OFFSET_ROTATED)
62            }
63        }
64    }
65
66    /// Software reset on the display controller (Command 0x01).
67    pub async fn soft_reset(&self, bus: &mut B) -> Result<(), B::Error> {
68        bus.write_cmd(&[SOFT_RESET]).await
69    }
70
71    /// Enter Sleep Mode (Command 0x10).
72    pub async fn enter_sleep_mode(&self, bus: &mut B) -> Result<(), B::Error> {
73        bus.write_cmd(&[ENTER_SLEEP_MODE]).await
74    }
75
76    /// Exit Sleep Mode (Command 0x11).
77    pub async fn exit_sleep_mode(&self, bus: &mut B) -> Result<(), B::Error> {
78        bus.write_cmd(&[EXIT_SLEEP_MODE]).await
79    }
80
81    /// Turn the display panel OFF (Command 0x28).
82    pub async fn set_display_off(&self, bus: &mut B) -> Result<(), B::Error> {
83        bus.write_cmd(&[SET_DISPLAY_OFF]).await
84    }
85
86    /// Turn the display panel ON (Command 0x29).
87    pub async fn set_display_on(&self, bus: &mut B) -> Result<(), B::Error> {
88        bus.write_cmd(&[SET_DISPLAY_ON]).await
89    }
90
91    /// Set the column address window (Command 0x2A).
92    pub async fn set_column_address(
93        &self,
94        bus: &mut B,
95        start: u16,
96        end: u16,
97    ) -> Result<(), B::Error> {
98        let params = AddressRange::new_with_offset(start, end, S::PHYSICAL_X_OFFSET);
99        bus.write_cmd_with_params(&[SET_COLUMN_ADDRESS], params.as_bytes())
100            .await
101    }
102
103    /// Set the page (row) address window (Command 0x2B).
104    pub async fn set_page_address(
105        &self,
106        bus: &mut B,
107        start: u16,
108        end: u16,
109    ) -> Result<(), B::Error> {
110        let params = AddressRange::new_with_offset(start, end, S::PHYSICAL_Y_OFFSET);
111        bus.write_cmd_with_params(&[SET_PAGE_ADDRESS], params.as_bytes())
112            .await
113    }
114
115    pub async fn set_address_window(
116        &self,
117        bus: &mut B,
118        x0: u16,
119        y0: u16,
120        x1: u16,
121        y1: u16,
122    ) -> Result<(), B::Error> {
123        let (x_offset, y_offset) = self.get_offset();
124
125        bus.write_cmd_with_params(
126            &[SET_COLUMN_ADDRESS],
127            AddressRange::new_with_offset(x0, x1, x_offset).as_bytes(),
128        )
129        .await?;
130
131        bus.write_cmd_with_params(
132            &[SET_PAGE_ADDRESS],
133            AddressRange::new_with_offset(y0, y1, y_offset).as_bytes(),
134        )
135        .await
136    }
137
138    /// Set the Address Mode (Memory Data Access Control, aka. MADCTL - Command 0x36).
139    ///
140    /// # Arguments
141    ///
142    /// * `bus` - The display bus to write to.
143    /// * `mode` - The new address mode to set.
144    /// * `orientation_if_changed` - Set the orientation in state machine if it has changed
145    /// by your self for correct offset handling.
146    ///
147    /// # Note
148    ///
149    /// This function will not change `mode` and send it.
150    pub async fn set_address_mode(
151        &mut self,
152        bus: &mut B,
153        mode: AddressMode,
154        orientation_if_changed: Option<Orientation>,
155    ) -> Result<(), B::Error> {
156        self.address_mode = mode;
157        if let Some(orientation) = orientation_if_changed {
158            self.orientation = orientation;
159        }
160        bus.write_cmd_with_params(&[SET_ADDRESS_MODE], &[mode.bits()])
161            .await
162    }
163
164    /// Set the BGR/RGB order in Address Mode (MADCTL).
165    pub async fn set_bgr_order(&mut self, bus: &mut B, bgr: bool) -> Result<(), B::Error> {
166        self.address_mode.set(AddressMode::BGR, bgr);
167        bus.write_cmd_with_params(&[SET_ADDRESS_MODE], &[self.address_mode.bits()])
168            .await
169    }
170
171    /// Set the Pixel Format (Command 0x3A).
172    pub async fn set_pixel_format(&self, bus: &mut B, mode: PixelFormat) -> Result<(), B::Error> {
173        bus.write_cmd_with_params(&[SET_PIXEL_FORMAT], &[mode.0])
174            .await
175    }
176
177    /// Set Inversion Mode (Command 0x20 / 0x21).
178    ///
179    /// `true` enters Invert Mode (0x21), `false` exits Invert Mode (0x20).
180    pub async fn set_invert_mode(&self, bus: &mut B, inverted: bool) -> Result<(), B::Error> {
181        match inverted {
182            true => bus.write_cmd(&[ENTER_INVERT_MODE]).await,
183            false => bus.write_cmd(&[EXIT_INVERT_MODE]).await,
184        }
185    }
186
187    const INIT_STEPS: [InitStep<'_>; 6] = [
188        InitStep::SingleCommand(EXIT_SLEEP_MODE),
189        InitStep::DelayMs(120),
190        InitStep::select_cmd(S::INVERTED, ENTER_INVERT_MODE, EXIT_INVERT_MODE),
191        InitStep::CommandWithParams(
192            SET_ADDRESS_MODE,
193            &[if S::BGR { AddressMode::BGR.bits() } else { 0u8 }],
194        ),
195        // Power On
196        InitStep::SingleCommand(SET_DISPLAY_ON),
197        InitStep::DelayMs(20),
198    ];
199}
200
201/// Display Specification Trait.
202pub trait PanelSpec {
203    /// Screen width in pixels.
204    const PHYSICAL_WIDTH: u16;
205    /// Screen height in pixels.
206    const PHYSICAL_HEIGHT: u16;
207    /// Column(X) offset in pixels (default 0).
208    const PHYSICAL_X_OFFSET: u16 = 0;
209    /// Row(Y) offset in pixels (default 0).
210    const PHYSICAL_Y_OFFSET: u16 = 0;
211
212    /// Column(X) offset in pixels when the screen is rotated 180° or 270°.
213    /// Used for panels that are not physically centered within the frame.
214    /// If undefined, defaults to PHYSICAL_X_OFFSET.
215    const PHYSICAL_X_OFFSET_ROTATED: u16 = Self::PHYSICAL_X_OFFSET;
216
217    /// Row(Y) offset in pixels when the screen is rotated 180° or 270°.
218    /// Used for panels that are not physically centered within the frame.
219    /// If undefined, defaults to PHYSICAL_Y_OFFSET.
220    const PHYSICAL_Y_OFFSET_ROTATED: u16 = Self::PHYSICAL_Y_OFFSET;
221
222    /// Whether the offset is inverted when the screen is rotated 90° or 270°.
223    ///
224    /// Used for panels that are not physically centered within the frame
225    /// (PHYSICAL_*_OFFSET_ROTATED != PHYSICAL_*_OFFSET)
226    ///
227    /// Example: offset = (2, 1), offset_rotated = (2, 3)
228    /// INVERT_TRANSPOSED_OFFSET |  false |  true  |
229    /// Deg0                     | (2, 1) | (2, 1) |
230    /// Deg90(MV, MX)            | (1, 2) | (3, 2) |
231    /// Deg180(MX, MY)           | (2, 3) | (2, 3) |
232    /// Deg270(MV, MY)           | (3, 2) | (1, 2) |
233    ///
234    /// This issue likely relates to how the driver IC’s internal RAM is organized
235    /// or written to.
236    /// It is also possible that `true` actually represents the non-inverted state,
237    /// but in practice `false` appears to be the more common case.
238    const INVERT_TRANSPOSED_OFFSET: bool = false;
239
240    /// Whether the display is inverted (default false).
241    const INVERTED: bool = false;
242
243    /// Whether the display is BGR (default false).
244    const BGR: bool = false;
245}