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