st7789_driver/
lib.rs

1#![no_std]
2// associated re-typing not supported in rust yet
3#![allow(clippy::type_complexity)]
4
5//! This crate provides a ST7789 driver to connect to TFT displays.
6
7pub mod instruction;
8
9use crate::instruction::Instruction;
10use core::iter::once;
11
12use display_interface::DataFormat::{U16BEIter, U8Iter};
13use display_interface::WriteOnlyDataCommand;
14use embedded_hal::blocking::delay::DelayUs;
15use embedded_hal::digital::v2::OutputPin;
16
17#[cfg(feature = "graphics")]
18mod graphics;
19
20#[cfg(feature = "batch")]
21mod batch;
22
23///
24/// ST7789 driver to connect to TFT displays.
25///
26pub struct ST7789<DI, RST, BL>
27where
28    DI: WriteOnlyDataCommand,
29    RST: OutputPin,
30    BL: OutputPin,
31{
32    // Display interface
33    di: DI,
34    // Reset pin.
35    rst: Option<RST>,
36    // Backlight pin,
37    bl: Option<BL>,
38    // Visible size (x, y)
39    size_x: u16,
40    size_y: u16,
41    offset_x: u16,
42    offset_y: u16,
43    // Current orientation
44    orientation: Orientation,
45}
46
47///
48/// Display orientation.
49///
50#[repr(u8)]
51#[derive(Copy, Clone)]
52pub enum Orientation {
53    Portrait = 0b0000_0000,         // no inverting
54    Landscape = 0b0110_0000,        // invert column and page/column order
55    PortraitSwapped = 0b1100_0000,  // invert page and column order
56    LandscapeSwapped = 0b1010_0000, // invert page and page/column order
57}
58
59impl Default for Orientation {
60    fn default() -> Self {
61        Self::Portrait
62    }
63}
64
65///
66/// Tearing effect output setting.
67///
68#[derive(Copy, Clone)]
69pub enum TearingEffect {
70    /// Disable output.
71    Off,
72    /// Output vertical blanking information.
73    Vertical,
74    /// Output horizontal and vertical blanking information.
75    HorizontalAndVertical,
76}
77
78#[derive(Copy, Clone, Debug)]
79pub enum BacklightState {
80    On,
81    Off,
82}
83
84///
85/// An error holding its source (pins or SPI)
86///
87#[derive(Debug)]
88pub enum Error<PinE> {
89    DisplayError,
90    Pin(PinE),
91}
92
93impl<DI, RST, BL, PinE> ST7789<DI, RST, BL>
94where
95    DI: WriteOnlyDataCommand,
96    RST: OutputPin<Error = PinE>,
97    BL: OutputPin<Error = PinE>,
98{
99    ///
100    /// Creates a new ST7789 driver instance
101    ///
102    /// # Arguments
103    ///
104    /// * `di` - a display interface for talking with the display
105    /// * `rst` - display hard reset pin
106    /// * `bl` - backlight pin
107    /// * `size_x` - x axis resolution of the display in pixels
108    /// * `size_y` - y axis resolution of the display in pixels
109    ///
110    pub fn new(di: DI, rst: Option<RST>, bl: Option<BL>, size_x: u16, size_y: u16) -> Self {
111        Self {
112            di,
113            rst,
114            bl,
115            size_x,
116            size_y,
117            offset_x: 0,
118            offset_y: 0,
119
120            orientation: Orientation::default(),
121        }
122    }
123    pub fn set_offset(&mut self, offset_x: u16, offset_y: u16) {
124        self.offset_x = offset_x;
125        self.offset_y = offset_y;
126    }
127
128    ///
129    /// Runs commands to initialize the display
130    ///
131    /// # Arguments
132    ///
133    /// * `delay_source` - mutable reference to a delay provider
134    ///
135    pub fn init(&mut self, delay_source: &mut impl DelayUs<u32>) -> Result<(), Error<PinE>> {
136        self.hard_reset(delay_source)?;
137        if let Some(bl) = self.bl.as_mut() {
138            bl.set_low().map_err(Error::Pin)?;
139            delay_source.delay_us(10_000);
140            bl.set_high().map_err(Error::Pin)?;
141        }
142
143        self.write_command(Instruction::SWRESET)?; // reset display
144        delay_source.delay_us(150_000);
145        self.write_command(Instruction::SLPOUT)?; // turn off sleep
146        delay_source.delay_us(10_000);
147        self.write_command(Instruction::INVOFF)?; // turn off invert
148        self.write_command(Instruction::VSCRDER)?; // vertical scroll definition
149        self.write_data(&[0u8, 0u8, 0x14u8, 0u8, 0u8, 0u8])?; // 0 TSA, 320 VSA, 0 BSA
150        self.write_command(Instruction::MADCTL)?; // left -> right, bottom -> top RGB
151        self.write_data(&[0b0000_0000])?;
152        self.write_command(Instruction::COLMOD)?; // 16bit 65k colors
153        self.write_data(&[0b0101_0101])?;
154        self.write_command(Instruction::INVON)?; // hack?
155        delay_source.delay_us(10_000);
156        self.write_command(Instruction::NORON)?; // turn on display
157        delay_source.delay_us(10_000);
158        self.write_command(Instruction::DISPON)?; // turn on display
159        delay_source.delay_us(10_000);
160        Ok(())
161    }
162
163    ///
164    /// Performs a hard reset using the RST pin sequence
165    ///
166    /// # Arguments
167    ///
168    /// * `delay_source` - mutable reference to a delay provider
169    ///
170    pub fn hard_reset(&mut self, delay_source: &mut impl DelayUs<u32>) -> Result<(), Error<PinE>> {
171        if let Some(rst) = self.rst.as_mut() {
172            rst.set_high().map_err(Error::Pin)?;
173            delay_source.delay_us(10); // ensure the pin change will get registered
174            rst.set_low().map_err(Error::Pin)?;
175            delay_source.delay_us(10); // ensure the pin change will get registered
176            rst.set_high().map_err(Error::Pin)?;
177            delay_source.delay_us(10); // ensure the pin change will get registered
178        }
179
180        Ok(())
181    }
182
183    pub fn set_backlight(
184        &mut self,
185        state: BacklightState,
186        delay_source: &mut impl DelayUs<u32>,
187    ) -> Result<(), Error<PinE>> {
188        if let Some(bl) = self.bl.as_mut() {
189            match state {
190                BacklightState::On => bl.set_high().map_err(Error::Pin)?,
191                BacklightState::Off => bl.set_low().map_err(Error::Pin)?,
192            }
193            delay_source.delay_us(10); // ensure the pin change will get registered
194        }
195        Ok(())
196    }
197
198    ///
199    /// Returns currently set orientation
200    ///
201    pub fn orientation(&self) -> Orientation {
202        self.orientation
203    }
204
205    ///
206    /// Sets display orientation
207    ///
208    pub fn set_orientation(&mut self, orientation: Orientation) -> Result<(), Error<PinE>> {
209        self.write_command(Instruction::MADCTL)?;
210        self.write_data(&[orientation as u8])?;
211        self.orientation = orientation;
212        Ok(())
213    }
214
215    ///
216    /// Sets a pixel color at the given coords.
217    ///
218    /// # Arguments
219    ///
220    /// * `x` - x coordinate
221    /// * `y` - y coordinate
222    /// * `color` - the Rgb565 color value
223    ///
224    pub fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), Error<PinE>> {
225        self.set_address_window(x, y, x, y)?;
226        self.write_command(Instruction::RAMWR)?;
227        self.di
228            .send_data(U16BEIter(&mut once(color)))
229            .map_err(|_| Error::DisplayError)?;
230
231        Ok(())
232    }
233
234    ///
235    /// Sets pixel colors in given rectangle bounds.
236    ///
237    /// # Arguments
238    ///
239    /// * `sx` - x coordinate start
240    /// * `sy` - y coordinate start
241    /// * `ex` - x coordinate end
242    /// * `ey` - y coordinate end
243    /// * `colors` - anything that can provide `IntoIterator<Item = u16>` to iterate over pixel data
244    ///
245    pub fn set_pixels<T>(
246        &mut self,
247        sx: u16,
248        sy: u16,
249        ex: u16,
250        ey: u16,
251        colors: T,
252    ) -> Result<(), Error<PinE>>
253    where
254        T: IntoIterator<Item = u16>,
255    {
256        self.set_address_window(sx, sy, ex, ey)?;
257        self.write_command(Instruction::RAMWR)?;
258        self.di
259            .send_data(U16BEIter(&mut colors.into_iter()))
260            .map_err(|_| Error::DisplayError)
261    }
262
263    ///
264    /// Sets scroll offset "shifting" the displayed picture
265    /// # Arguments
266    ///
267    /// * `offset` - scroll offset in pixels
268    ///
269    pub fn set_scroll_offset(&mut self, offset: u16) -> Result<(), Error<PinE>> {
270        self.write_command(Instruction::VSCAD)?;
271        self.write_data(&offset.to_be_bytes())
272    }
273
274    ///
275    /// Release resources allocated to this driver back.
276    /// This returns the display interface and the RST pin deconstructing the driver.
277    ///
278    pub fn release(self) -> (DI, Option<RST>, Option<BL>) {
279        (self.di, self.rst, self.bl)
280    }
281
282    fn write_command(&mut self, command: Instruction) -> Result<(), Error<PinE>> {
283        self.di
284            .send_commands(U8Iter(&mut once(command as u8)))
285            .map_err(|_| Error::DisplayError)?;
286        Ok(())
287    }
288
289    fn write_data(&mut self, data: &[u8]) -> Result<(), Error<PinE>> {
290        self.di
291            .send_data(U8Iter(&mut data.iter().cloned()))
292            .map_err(|_| Error::DisplayError)
293    }
294
295    // Sets the address window for the display.
296    fn set_address_window(
297        &mut self,
298        sx: u16,
299        sy: u16,
300        ex: u16,
301        ey: u16,
302    ) -> Result<(), Error<PinE>> {
303        let mut offset = (self.offset_x, self.offset_y);
304        // let mut offset: (i16, i16) = (self.offset_x as i16, self.offset_y as i16);
305
306        match self.orientation {
307            Orientation::Portrait | Orientation::PortraitSwapped => {
308                offset = (offset.0, offset.1);
309            }
310            Orientation::Landscape | Orientation::LandscapeSwapped => {
311                // offset = (offset.0, offset.1);
312                offset = (offset.1, offset.0);
313            }
314        }
315        let (sx, sy, ex, ey) = (sx + offset.0, sy + offset.1, ex + offset.0, ey + offset.1);
316        self.write_command(Instruction::CASET)?;
317        self.write_data(&sx.to_be_bytes())?;
318        self.write_data(&ex.to_be_bytes())?;
319        self.write_command(Instruction::RASET)?;
320        self.write_data(&sy.to_be_bytes())?;
321        self.write_data(&ey.to_be_bytes())
322    }
323
324    ///
325    /// Configures the tearing effect output.
326    ///
327    pub fn set_tearing_effect(&mut self, tearing_effect: TearingEffect) -> Result<(), Error<PinE>> {
328        match tearing_effect {
329            TearingEffect::Off => self.write_command(Instruction::TEOFF),
330            TearingEffect::Vertical => {
331                self.write_command(Instruction::TEON)?;
332                self.write_data(&[0])
333            }
334            TearingEffect::HorizontalAndVertical => {
335                self.write_command(Instruction::TEON)?;
336                self.write_data(&[1])
337            }
338        }
339    }
340}