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    // Current orientation
42    orientation: Orientation,
43    offset_x: u16,
44    offset_y: u16,
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            orientation: Orientation::default(),
120        }
121    }
122    pub fn set_offset(&mut self, offset_x: u16, offset_y: u16) {
123        self.offset_x = offset_x;
124        self.offset_y = offset_y;
125    }
126
127    ///
128    /// Runs commands to initialize the display
129    ///
130    /// # Arguments
131    ///
132    /// * `delay_source` - mutable reference to a delay provider
133    ///
134    pub fn init(&mut self, delay_source: &mut impl DelayUs<u32>) -> Result<(), Error<PinE>> {
135        self.hard_reset(delay_source)?;
136        if let Some(bl) = self.bl.as_mut() {
137            bl.set_low().map_err(Error::Pin)?;
138            delay_source.delay_us(10_000);
139            bl.set_high().map_err(Error::Pin)?;
140        }
141
142        self.write_command(Instruction::SWRESET)?; // reset display
143        delay_source.delay_us(150_000);
144        self.write_command(Instruction::SLPOUT)?; // turn off sleep
145        delay_source.delay_us(10_000);
146        self.write_command(Instruction::INVOFF)?; // turn off invert
147        self.write_command(Instruction::VSCRDER)?; // vertical scroll definition
148        self.write_data(&[0u8, 0u8, 0x14u8, 0u8, 0u8, 0u8])?; // 0 TSA, 320 VSA, 0 BSA
149        self.write_command(Instruction::MADCTL)?; // left -> right, bottom -> top RGB
150        self.write_data(&[0b0000_0000])?;
151        self.write_command(Instruction::COLMOD)?; // 16bit 65k colors
152        self.write_data(&[0b0101_0101])?;
153        self.write_command(Instruction::INVON)?; // hack?
154        delay_source.delay_us(10_000);
155        self.write_command(Instruction::NORON)?; // turn on display
156        delay_source.delay_us(10_000);
157        self.write_command(Instruction::DISPON)?; // turn on display
158        delay_source.delay_us(10_000);
159        Ok(())
160    }
161
162    ///
163    /// Performs a hard reset using the RST pin sequence
164    ///
165    /// # Arguments
166    ///
167    /// * `delay_source` - mutable reference to a delay provider
168    ///
169    pub fn hard_reset(&mut self, delay_source: &mut impl DelayUs<u32>) -> Result<(), Error<PinE>> {
170        if let Some(rst) = self.rst.as_mut() {
171            rst.set_high().map_err(Error::Pin)?;
172            delay_source.delay_us(10); // ensure the pin change will get registered
173            rst.set_low().map_err(Error::Pin)?;
174            delay_source.delay_us(10); // ensure the pin change will get registered
175            rst.set_high().map_err(Error::Pin)?;
176            delay_source.delay_us(10); // ensure the pin change will get registered
177        }
178
179        Ok(())
180    }
181
182    pub fn set_backlight(
183        &mut self,
184        state: BacklightState,
185        delay_source: &mut impl DelayUs<u32>,
186    ) -> Result<(), Error<PinE>> {
187        if let Some(bl) = self.bl.as_mut() {
188            match state {
189                BacklightState::On => bl.set_high().map_err(Error::Pin)?,
190                BacklightState::Off => bl.set_low().map_err(Error::Pin)?,
191            }
192            delay_source.delay_us(10); // ensure the pin change will get registered
193        }
194        Ok(())
195    }
196
197    ///
198    /// Returns currently set orientation
199    ///
200    pub fn orientation(&self) -> Orientation {
201        self.orientation
202    }
203
204    ///
205    /// Sets display orientation
206    ///
207    pub fn set_orientation(&mut self, orientation: Orientation) -> Result<(), Error<PinE>> {
208        self.write_command(Instruction::MADCTL)?;
209        self.write_data(&[orientation as u8])?;
210        self.orientation = orientation;
211        Ok(())
212    }
213
214    ///
215    /// Sets a pixel color at the given coords.
216    ///
217    /// # Arguments
218    ///
219    /// * `x` - x coordinate
220    /// * `y` - y coordinate
221    /// * `color` - the Rgb565 color value
222    ///
223    pub fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), Error<PinE>> {
224        self.set_address_window(x, y, x, y)?;
225        self.write_command(Instruction::RAMWR)?;
226        self.di
227            .send_data(U16BEIter(&mut once(color)))
228            .map_err(|_| Error::DisplayError)?;
229
230        Ok(())
231    }
232
233    ///
234    /// Sets pixel colors in given rectangle bounds.
235    ///
236    /// # Arguments
237    ///
238    /// * `sx` - x coordinate start
239    /// * `sy` - y coordinate start
240    /// * `ex` - x coordinate end
241    /// * `ey` - y coordinate end
242    /// * `colors` - anything that can provide `IntoIterator<Item = u16>` to iterate over pixel data
243    ///
244    pub fn set_pixels<T>(
245        &mut self,
246        sx: u16,
247        sy: u16,
248        ex: u16,
249        ey: u16,
250        colors: T,
251    ) -> Result<(), Error<PinE>>
252    where
253        T: IntoIterator<Item = u16>,
254    {
255        self.set_address_window(sx, sy, ex, ey)?;
256        self.write_command(Instruction::RAMWR)?;
257        self.di
258            .send_data(U16BEIter(&mut colors.into_iter()))
259            .map_err(|_| Error::DisplayError)
260    }
261
262    ///
263    /// Sets scroll offset "shifting" the displayed picture
264    /// # Arguments
265    ///
266    /// * `offset` - scroll offset in pixels
267    ///
268    pub fn set_scroll_offset(&mut self, offset: u16) -> Result<(), Error<PinE>> {
269        self.write_command(Instruction::VSCAD)?;
270        self.write_data(&offset.to_be_bytes())
271    }
272
273    ///
274    /// Release resources allocated to this driver back.
275    /// This returns the display interface and the RST pin deconstructing the driver.
276    ///
277    pub fn release(self) -> (DI, Option<RST>, Option<BL>) {
278        (self.di, self.rst, self.bl)
279    }
280
281    fn write_command(&mut self, command: Instruction) -> Result<(), Error<PinE>> {
282        self.di
283            .send_commands(U8Iter(&mut once(command as u8)))
284            .map_err(|_| Error::DisplayError)?;
285        Ok(())
286    }
287
288    fn write_data(&mut self, data: &[u8]) -> Result<(), Error<PinE>> {
289        self.di
290            .send_data(U8Iter(&mut data.iter().cloned()))
291            .map_err(|_| Error::DisplayError)
292    }
293
294    // Sets the address window for the display.
295    fn set_address_window(
296        &mut self,
297        sx: u16,
298        sy: u16,
299        ex: u16,
300        ey: u16,
301    ) -> Result<(), Error<PinE>> {
302        let (sx, sy, ex, ey) = match self.orientation() {
303            Orientation::Landscape | Orientation::LandscapeSwapped => (
304                sx + self.offset_x,
305                sy + self.offset_y,
306                ex + self.offset_x,
307                ey + self.offset_y,
308            ),
309            Orientation::Portrait | Orientation::PortraitSwapped => (
310                sy + self.offset_x,
311                sx + self.offset_y,
312                ey + self.offset_x,
313                ex + self.offset_y,
314            ),
315        };
316
317        self.write_command(Instruction::CASET)?;
318        self.write_data(&sx.to_be_bytes())?;
319        self.write_data(&ex.to_be_bytes())?;
320        self.write_command(Instruction::RASET)?;
321        self.write_data(&sy.to_be_bytes())?;
322        self.write_data(&ey.to_be_bytes())
323    }
324
325    ///
326    /// Configures the tearing effect output.
327    ///
328    pub fn set_tearing_effect(&mut self, tearing_effect: TearingEffect) -> Result<(), Error<PinE>> {
329        match tearing_effect {
330            TearingEffect::Off => self.write_command(Instruction::TEOFF),
331            TearingEffect::Vertical => {
332                self.write_command(Instruction::TEON)?;
333                self.write_data(&[0])
334            }
335            TearingEffect::HorizontalAndVertical => {
336                self.write_command(Instruction::TEON)?;
337                self.write_data(&[1])
338            }
339        }
340    }
341}