Skip to main content

nt35510/
lib.rs

1#![cfg_attr(not(test), no_std)]
2//! Standalone `no_std` driver for NT35510 DSI LCD controller panels.
3//!
4//! The default configuration is tested on STM32F469I-DISCO:
5//! portrait mode, RGB565, 480x800.
6//! Landscape mode uses MADCTL rotation matching the OTM8009A pattern,
7//! but is currently untested.
8
9mod regs;
10
11pub use regs::*;
12
13use embedded_display_controller::dsi::{DsiHostCtrlIo, DsiReadCommand, DsiWriteCommand};
14use embedded_hal::delay::DelayNs;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum Error {
18    DsiRead,
19    DsiWrite,
20    ProbeMismatch(u8),
21    InvalidDimensions,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Mode {
26    /// Portrait orientation (480x800). Tested on STM32F469I-DISCO.
27    Portrait,
28    /// Landscape orientation (800x480). Untested.
29    Landscape,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum ColorFormat {
34    /// 16-bit RGB565. Tested on STM32F469I-DISCO.
35    Rgb565,
36    /// 24-bit RGB888. Tested on STM32F469I-DISCO.
37    Rgb888,
38}
39
40/// Configuration for the NT35510 panel.
41///
42/// Default values match the STM32F469I-DISCO board configuration
43/// (portrait mode, RGB565, 480x800).
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct Nt35510Config {
46    pub mode: Mode,
47    pub color_format: ColorFormat,
48    /// Display width in pixels (before rotation).
49    pub cols: u16,
50    /// Display height in pixels (before rotation).
51    pub rows: u16,
52}
53
54impl Default for Nt35510Config {
55    fn default() -> Self {
56        Self {
57            mode: Mode::Portrait,
58            color_format: ColorFormat::Rgb565,
59            cols: 480,
60            rows: 800,
61        }
62    }
63}
64
65pub struct Nt35510 {
66    initialized: bool,
67}
68
69impl Default for Nt35510 {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl Nt35510 {
76    pub const fn new() -> Self {
77        Self { initialized: false }
78    }
79
80    /// Probe whether an NT35510 is connected by reading its ID registers.
81    ///
82    /// Returns `Ok(())` if the panel responds with expected NT35510 IDs.
83    /// Returns `Err(Error::ProbeMismatch(id))` if a different panel responds.
84    /// Returns `Err(Error::DsiRead)` if DSI reads fail entirely.
85    pub fn probe<D: DelayNs>(
86        &mut self,
87        dsi_host: &mut impl DsiHostCtrlIo,
88        _delay: &mut D,
89    ) -> Result<(), Error> {
90        match self.read_id(dsi_host, NT35510_CMD_RDID2) {
91            Ok(id) if id == NT35510_ID2_EXPECTED => return Ok(()),
92            Ok(id) => return Err(Error::ProbeMismatch(id)),
93            Err(_) => {}
94        }
95
96        match self.read_id(dsi_host, NT35510_CMD_RDID1) {
97            Ok(id) if id == NT35510_ID1_EXPECTED => Ok(()),
98            Ok(id) => Err(Error::ProbeMismatch(id)),
99            Err(_) => Err(Error::DsiRead),
100        }
101    }
102
103    /// Check if an NT35510 panel is connected by reading ID registers.
104    /// Returns `Ok(true)` if NT35510 is detected and `Ok(false)` otherwise.
105    pub fn id_matches(&mut self, dsi_host: &mut impl DsiHostCtrlIo) -> Result<bool, Error> {
106        if let Ok(id) = self.read_id(dsi_host, NT35510_CMD_RDID2) {
107            return Ok(id == NT35510_ID2_EXPECTED);
108        }
109
110        match self.read_id(dsi_host, NT35510_CMD_RDID1) {
111            Ok(id) => Ok(id == NT35510_ID1_EXPECTED),
112            Err(_) => Err(Error::DsiRead),
113        }
114    }
115
116    /// Initialize the panel in RGB888 (24-bit) mode.
117    pub fn init<D: DelayNs>(
118        &mut self,
119        dsi_host: &mut impl DsiHostCtrlIo,
120        delay: &mut D,
121    ) -> Result<(), Error> {
122        let config = Nt35510Config {
123            color_format: ColorFormat::Rgb888,
124            ..Nt35510Config::default()
125        };
126        self.init_with_config(dsi_host, delay, config)
127    }
128
129    /// Initialize the panel in RGB565 (16-bit) mode.
130    pub fn init_rgb565<D: DelayNs>(
131        &mut self,
132        dsi_host: &mut impl DsiHostCtrlIo,
133        delay: &mut D,
134    ) -> Result<(), Error> {
135        self.init_with_config(dsi_host, delay, Nt35510Config::default())
136    }
137
138    /// Initialize the panel with an explicit configuration.
139    pub fn init_with_config<D: DelayNs>(
140        &mut self,
141        dsi_host: &mut impl DsiHostCtrlIo,
142        delay: &mut D,
143        config: Nt35510Config,
144    ) -> Result<(), Error> {
145        if self.initialized {
146            return Ok(());
147        }
148
149        if config.cols == 0 || config.rows == 0 {
150            return Err(Error::InvalidDimensions);
151        }
152
153        self.write_reg(
154            dsi_host,
155            NT35510_CMD_SETEXTC,
156            &[0x55, 0xAA, 0x52, 0x08, 0x01],
157        )?;
158        self.write_reg(dsi_host, NT35510_CMD_B0, &[0x03, 0x03, 0x03])?;
159        self.write_reg(dsi_host, NT35510_CMD_B6, &[0x46, 0x46, 0x46])?;
160        self.write_reg(dsi_host, NT35510_CMD_B1, &[0x03, 0x03, 0x03])?;
161        self.write_reg(dsi_host, NT35510_CMD_B7, &[0x36, 0x36, 0x36])?;
162        self.write_reg(dsi_host, NT35510_CMD_B2, &[0x00, 0x00, 0x02])?;
163        self.write_reg(dsi_host, NT35510_CMD_B8, &[0x26, 0x26, 0x26])?;
164        self.write_reg(dsi_host, NT35510_CMD_BF, &[0x01])?;
165        self.write_reg(dsi_host, NT35510_CMD_B3, &[0x09, 0x09, 0x09])?;
166        self.write_reg(dsi_host, NT35510_CMD_B9, &[0x36, 0x36, 0x36])?;
167        self.write_reg(dsi_host, NT35510_CMD_B5, &[0x08, 0x08, 0x08])?;
168        self.write_reg(dsi_host, NT35510_CMD_BA, &[0x26, 0x26, 0x26])?;
169        self.write_reg(dsi_host, NT35510_CMD_BC, &[0x00, 0x80, 0x00])?;
170        self.write_reg(dsi_host, NT35510_CMD_BD, &[0x00, 0x80, 0x00])?;
171        self.write_reg(dsi_host, NT35510_CMD_BE, &[0x00, 0x50])?;
172
173        self.write_reg(
174            dsi_host,
175            NT35510_CMD_SETEXTC,
176            &[0x55, 0xAA, 0x52, 0x08, 0x00],
177        )?;
178        self.write_reg(dsi_host, NT35510_CMD_B1, &[0xFC, 0x00])?;
179        self.write_reg(dsi_host, NT35510_CMD_B6, &[0x03, 0x03])?;
180        self.write_reg(dsi_host, NT35510_CMD_B5, &[0x50, 0x50])?;
181        self.write_reg(dsi_host, NT35510_CMD_B7, &[0x00, 0x00])?;
182        self.write_reg(dsi_host, NT35510_CMD_B8, &[0x01, 0x02, 0x02, 0x02])?;
183        self.write_reg(dsi_host, NT35510_CMD_BC, &[0x00, 0x00, 0x00])?;
184        self.write_reg(dsi_host, NT35510_CMD_CC, &[0x03, 0x00, 0x00])?;
185        self.write_reg(dsi_host, NT35510_CMD_BA, &[0x01, 0x01])?;
186
187        let colmod = match config.color_format {
188            ColorFormat::Rgb565 => NT35510_COLMOD_RGB565,
189            ColorFormat::Rgb888 => NT35510_COLMOD_RGB888,
190        };
191        let madctl = match config.mode {
192            Mode::Portrait => NT35510_MADCTL_PORTRAIT,
193            Mode::Landscape => NT35510_MADCTL_LANDSCAPE,
194        };
195
196        let last_col = (config.cols - 1).to_be_bytes();
197        let last_row = (config.rows - 1).to_be_bytes();
198        let caset = [0x00, 0x00, last_col[0], last_col[1]];
199        let raset = [0x00, 0x00, last_row[0], last_row[1]];
200
201        delay.delay_us(200_000);
202        self.write_cmd(dsi_host, NT35510_CMD_SLPOUT, 0x00)?;
203        delay.delay_us(120_000);
204        self.write_cmd(dsi_host, NT35510_CMD_COLMOD, colmod)?;
205        self.write_cmd(dsi_host, NT35510_CMD_MADCTL, madctl)?;
206        self.write_reg(dsi_host, NT35510_CMD_CASET, &caset)?;
207        self.write_reg(dsi_host, NT35510_CMD_RASET, &raset)?;
208        self.write_cmd(dsi_host, NT35510_CMD_WRDISBV, 0x7F)?;
209        self.write_cmd(dsi_host, NT35510_CMD_WRCTRLD, 0x2C)?;
210        self.write_cmd(dsi_host, NT35510_CMD_WRCABC, 0x00)?;
211        delay.delay_us(10_000);
212        self.write_cmd(dsi_host, NT35510_CMD_DISPON, 0x00)?;
213        delay.delay_us(10_000);
214        self.write_cmd(dsi_host, NT35510_CMD_RAMWR, 0x00)?;
215
216        self.initialized = true;
217        Ok(())
218    }
219
220    fn read_id(&self, dsi_host: &mut impl DsiHostCtrlIo, cmd: u8) -> Result<u8, Error> {
221        let mut id = [0u8; 1];
222        dsi_host
223            .read(DsiReadCommand::DcsShort { arg: cmd }, &mut id)
224            .map_err(|_| Error::DsiRead)?;
225        Ok(id[0])
226    }
227
228    fn write_cmd(
229        &self,
230        dsi_host: &mut impl DsiHostCtrlIo,
231        cmd: u8,
232        param: u8,
233    ) -> Result<(), Error> {
234        dsi_host
235            .write(DsiWriteCommand::DcsShortP1 {
236                arg: cmd,
237                data: param,
238            })
239            .map_err(|_| Error::DsiWrite)
240    }
241
242    fn write_reg(
243        &self,
244        dsi_host: &mut impl DsiHostCtrlIo,
245        reg: u8,
246        data: &[u8],
247    ) -> Result<(), Error> {
248        if data.is_empty() {
249            self.write_cmd(dsi_host, reg, 0)
250        } else if data.len() == 1 {
251            self.write_cmd(dsi_host, reg, data[0])
252        } else {
253            dsi_host
254                .write(DsiWriteCommand::DcsLongWrite { arg: reg, data })
255                .map_err(|_| Error::DsiWrite)
256        }
257    }
258}