st7735s-lcd 0.1.0

ST7735S TFT LCD driver with embedded-graphics support
Documentation
#![no_std]

//! This crate provides a ST7789 driver to connect to TFT displays.

pub mod command;

use crate::command::*;

use self::embedded_graphics_core::{
    draw_target::DrawTarget,
    pixelcolor::{
        raw::{RawData, RawU16},
        Rgb565,
    },
    prelude::*,
    primitives::Rectangle,
};

use embedded_graphics_core;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi;

/// ST7735S driver to connect to TFT displays.
/// W: width, H: height, DX, DY: image offset
pub struct ST7735S<SPI, DC, RST, const W: u16, const H: u16, const DX: u16, const DY: u16>
where
    SPI: spi::SpiDevice,
    DC: OutputPin,
    RST: OutputPin,
{
    /// SPI
    spi: SPI,
    /// Data/command pin.
    dc: DC,
    /// Reset pin.
    rst: RST,
}

/// Display orientation.
#[derive(Clone, Copy)]
pub enum Orientation {
    Portrait = 0x00,
    Landscape = 0x60,
    PortraitSwapped = 0xC0,
    LandscapeSwapped = 0xA0,
}

impl<SPI, DC, RST, const W: u16, const H: u16, const DX: u16, const DY: u16>
    ST7735S<SPI, DC, RST, W, H, DX, DY>
where
    SPI: spi::SpiDevice,
    DC: OutputPin,
    RST: OutputPin,
{
    /// Creates a new driver instance that uses hardware SPI.
    pub fn new(spi: SPI, dc: DC, rst: RST) -> Self {
        let display = ST7735S { spi, dc, rst };

        display
    }

    /// Runs commands to initialize the display.
    pub fn init<DELAY>(
        &mut self,
        delay: &mut DELAY,
        orientation: Orientation,
        rgb: bool,
        inverted: bool,
    ) -> Result<(), ()>
    where
        DELAY: DelayNs,
    {
        self.hard_reset(delay)?;
        self.write_command(Instruction::SLPOUT, &[])?;
        delay.delay_ms(120);

        self.write_command(Instruction::FRMCTR1, &[0x05, 0x3C, 0x3C])?;
        self.write_command(Instruction::FRMCTR2, &[0x05, 0x3C, 0x3C])?;
        self.write_command(Instruction::FRMCTR3, &[0x05, 0x3C, 0x3C, 0x05, 0x3C, 0x3C])?;
        self.write_command(Instruction::INVCTR, &[0x03])?;

        self.write_command(Instruction::PWCTR1, &[0x28, 0x08, 0x04])?;
        self.write_command(Instruction::PWCTR2, &[0xC0])?;
        self.write_command(Instruction::PWCTR3, &[0x0D, 0x00])?;
        self.write_command(Instruction::PWCTR4, &[0x8D, 0x2A])?;
        self.write_command(Instruction::PWCTR5, &[0x8D, 0xEE])?;
        self.write_command(Instruction::VMCTR1, &[0x1A])?;

        if rgb {
            self.write_command(Instruction::MADCTL, &[0xC0])?; //TODO
        } else {
            self.write_command(Instruction::MADCTL, &[0xC8])?;
        }

        self.write_command(
            Instruction::GMCTRP1,
            &[
                0x04, 0x22, 0x07, 0x0A, 0x2E, 0x30, 0x25, 0x2A, 0x28, 0x26, 0x2E, 0x3A, 0x00, 0x01,
                0x03, 0x13,
            ],
        )?;
        self.write_command(
            Instruction::GMCTRN1,
            &[
                0x04, 0x16, 0x06, 0x0D, 0x2D, 0x26, 0x23, 0x27, 0x27, 0x25, 0x2D, 0x3B, 0x00, 0x01,
                0x04, 0x13,
            ],
        )?;

        self.write_command(Instruction::COLMOD, &[0x05])?;

        //self.write_command(Instruction::GAMSET, &[0x02])?;

        if inverted {
            self.write_command(Instruction::INVON, &[])?;
        } else {
            self.write_command(Instruction::INVOFF, &[])?;
        }

        self.set_orientation(&orientation, rgb)?;
        //self.write_command(Instruction::NORON, &[])?;
        //self.write_command(Instruction::DISPON, &[])?;
        delay.delay_ms(200);

        Ok(())
    }

    pub fn on(&mut self) -> Result<(), ()> {
        self.write_command(Instruction::DISPON, &[])
    }

    pub fn off(&mut self) -> Result<(), ()> {
        self.write_command(Instruction::DISPOFF, &[])
    }

    pub fn hard_reset<DELAY>(&mut self, delay: &mut DELAY) -> Result<(), ()>
    where
        DELAY: DelayNs,
    {
        self.rst.set_low().map_err(|_| ())?;
        delay.delay_ms(100);
        self.rst.set_high().map_err(|_| ())?;
        delay.delay_ms(100);
        Ok(())
    }

    fn write_command(&mut self, command: Instruction, params: &[u8]) -> Result<(), ()> {
        self.dc.set_low().map_err(|_| ())?;
        self.spi.write(&[command as u8]).map_err(|_| ())?;
        if !params.is_empty() {
            self.start_data()?;
            self.write_data(params)?;
        }
        Ok(())
    }

    fn start_data(&mut self) -> Result<(), ()> {
        self.dc.set_high().map_err(|_| ())
    }

    fn write_data(&mut self, data: &[u8]) -> Result<(), ()> {
        self.spi.write(data).map_err(|_| ())
    }

    /// Writes a data word to the display.
    fn write_word(&mut self, value: u16) -> Result<(), ()> {
        self.write_data(&value.to_be_bytes())
    }

    fn write_words_buffered(&mut self, words: impl IntoIterator<Item = u16>) -> Result<(), ()> {
        let mut buffer = [0; 32];
        let mut index = 0;
        for word in words {
            let as_bytes = word.to_be_bytes();
            buffer[index] = as_bytes[0];
            buffer[index + 1] = as_bytes[1];
            index += 2;
            if index >= buffer.len() {
                self.write_data(&buffer)?;
                index = 0;
            }
        }
        self.write_data(&buffer[0..index])
    }

    pub fn set_orientation(&mut self, orientation: &Orientation, rgb: bool) -> Result<(), ()> {
        if rgb {
            self.write_command(Instruction::MADCTL, &[*orientation as u8])?;
        } else {
            self.write_command(Instruction::MADCTL, &[*orientation as u8 | 0x08])?;
        }

        Ok(())
    }

    /// Sets the address window for the display.
    pub fn set_address_window(&mut self, sx: u16, sy: u16, ex: u16, ey: u16) -> Result<(), ()> {
        self.write_command(Instruction::CASET, &[])?;
        self.start_data()?;
        self.write_word(sx + DX)?;
        self.write_word(ex + DX)?;
        self.write_command(Instruction::RASET, &[])?;
        self.start_data()?;
        self.write_word(sy + DY)?;
        self.write_word(ey + DY)
    }

    /// Sets a pixel color at the given coords.
    pub fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> {
        self.set_address_window(x, y, x, y)?;
        self.write_command(Instruction::RAMWR, &[])?;
        self.start_data()?;
        self.write_word(color)
    }

    /// Writes pixel colors sequentially into the current drawing window
    pub fn write_pixels<P: IntoIterator<Item = u16>>(&mut self, colors: P) -> Result<(), ()> {
        self.write_command(Instruction::RAMWR, &[])?;
        self.start_data()?;
        for color in colors {
            self.write_word(color)?;
        }
        Ok(())
    }
    pub fn write_pixels_buffered<P: IntoIterator<Item = u16>>(
        &mut self,
        colors: P,
    ) -> Result<(), ()> {
        self.write_command(Instruction::RAMWR, &[])?;
        self.start_data()?;
        self.write_words_buffered(colors)
    }

    /// Sets pixel colors at the given drawing window
    pub fn set_pixels<P: IntoIterator<Item = u16>>(
        &mut self,
        sx: u16,
        sy: u16,
        ex: u16,
        ey: u16,
        colors: P,
    ) -> Result<(), ()> {
        self.set_address_window(sx, sy, ex, ey)?;
        self.write_pixels(colors)
    }

    pub fn set_pixels_buffered<P: IntoIterator<Item = u16>>(
        &mut self,
        sx: u16,
        sy: u16,
        ex: u16,
        ey: u16,
        colors: P,
    ) -> Result<(), ()> {
        self.set_address_window(sx, sy, ex, ey)?;
        self.write_pixels_buffered(colors)
    }

    pub fn clear_screen(&mut self, color: Rgb565) -> Result<(), ()> {
        let wxh: usize = (W * H) as usize;
        self.set_pixels_buffered(
            0,
            0,
            W - 1,
            H - 1,
            core::iter::repeat(RawU16::from(color).into_inner()).take(wxh),
        )
    }
}

impl<SPI, DC, RST, const W: u16, const H: u16, const DX: u16, const DY: u16> DrawTarget
    for ST7735S<SPI, DC, RST, W, H, DX, DY>
where
    SPI: spi::SpiDevice,
    DC: OutputPin,
    RST: OutputPin,
{
    type Error = ();
    type Color = Rgb565;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        for Pixel(coord, color) in pixels.into_iter() {
            // Only draw pixels that would be on screen
            if coord.x >= 0 && coord.y >= 0 && coord.x < W as i32 && coord.y < H as i32 {
                self.set_pixel(
                    coord.x as u16,
                    coord.y as u16,
                    RawU16::from(color).into_inner(),
                )?;
            }
        }

        Ok(())
    }

    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Self::Color>,
    {
        // Clamp area to drawable part of the display target
        let drawable_area = area.intersection(&Rectangle::new(Point::zero(), self.size()));

        if drawable_area.size != Size::zero() {
            self.set_pixels_buffered(
                drawable_area.top_left.x as u16,
                drawable_area.top_left.y as u16,
                (drawable_area.top_left.x + (drawable_area.size.width - 1) as i32) as u16,
                (drawable_area.top_left.y + (drawable_area.size.height - 1) as i32) as u16,
                area.points()
                    .zip(colors)
                    .filter(|(pos, _color)| drawable_area.contains(*pos))
                    .map(|(_pos, color)| RawU16::from(color).into_inner()),
            )?;
        }

        Ok(())
    }
}

impl<SPI, DC, RST, const W: u16, const H: u16, const DX: u16, const DY: u16> OriginDimensions
    for ST7735S<SPI, DC, RST, W, H, DX, DY>
where
    SPI: spi::SpiDevice,
    DC: OutputPin,
    RST: OutputPin,
{
    fn size(&self) -> Size {
        Size::new(W as u32, H as u32)
    }
}