use core::ops::{Add, Mul}; use embedded_hal::{delay::DelayNs, spi::SpiBus}; use esp_hal::{DriverMode, gpio::Output, spi::master::Spi}; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Rgb565(pub u8, pub u8, pub u8); impl Rgb565 { pub fn as_color(&self) -> Color { let r = ((self.0 / 8) as u16) << 11; let g = ((self.1 / 4) as u16) << 5; let b = (self.2 / 8) as u16; Color::from(r | b | g) } pub fn black() -> Rgb565 { Rgb565(0, 0, 0) } pub fn white() -> Rgb565 { Rgb565(255, 255, 255) } pub fn red() -> Rgb565 { Rgb565(255, 0, 0) } pub fn green() -> Rgb565 { Rgb565(0, 255, 0) } pub fn blue() -> Rgb565 { Rgb565(0, 0, 255) } pub fn yellow() -> Rgb565 { Rgb565(255, 255, 0) } pub fn magenta() -> Rgb565 { Rgb565(255, 0, 255) } pub fn cyan() -> Rgb565 { Rgb565(0, 255, 255) } pub fn qoi_hash(&self) -> usize { ((self.0 as u32 * 3 + self.1 as u32 * 5 + self.2 as u32 * 7 + 255 * 11) % 64) as usize } } impl Mul for Rgb565 { type Output = Rgb565; fn mul(self, rhs: f32) -> Self::Output { Rgb565( (self.0 as f32 * rhs) as u8, (self.1 as f32 * rhs) as u8, (self.2 as f32 * rhs) as u8, ) } } impl Add for Rgb565 { type Output = Rgb565; fn add(self, rhs: Rgb565) -> Self::Output { Rgb565(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) } } #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Position { pub x: i16, pub y: i16, } impl Position { pub fn new(x: i16, y: i16) -> Position { Position { x, y } } } #[derive(Default, Clone, Copy, PartialEq, Eq)] pub struct Color { pub bytes: [u8; 2], } impl Color { pub fn from(number: u16) -> Color { Color { bytes: number.to_be_bytes(), } } } pub struct Display<'d, DM: DriverMode, T: DelayNs> { pub spi: Spi<'d, DM>, pub dc: Output<'d>, pub rst: Output<'d>, pub delay: T, } pub enum Command { NOP = 0x0, SoftReset = 0x1, RddID = 0x4, RddST = 0x9, SleepIn = 0x10, SleepOut = 0x11, PTLON = 0x12, NormalModeOn = 0x13, InversionOff = 0x20, InversionOn = 0x21, DisplayOff = 0x28, DisplayOn = 0x29, ColumnAlignmentSet = 0x2A, RowAlignmentSet = 0x2B, RamWR = 0x2C, RamRD = 0x2E, PTLAR = 0x30, VSCRDEF = 0x33, ColorMode = 0x3A, TearingOff = 0x34, TearingOn = 0x35, MADCTL = 0x36, VSCSAD = 0x37, // MadCTLMY = 0x80, // MadCTLMX = 0x40, // MadCTLMV = 0x20, // MadCTLML = 0x10, // MadCTLBGR = 0x08, // MadCTLMH = 0x04, // MadCTLRGB = 0x00, // RDID1 = 0xDA, // RDID2 = 0xDB, // RDID3 = 0xDC, // RDID4 = 0xDD, // ColorMode65K = 0x50, // ColorMode262K = 0x60, // ColorMode12BIT = 0x03, // ColorMode16BIT = 0x05, // ColorMode18BIT = 0x06, // ColorMode16M = 0x07, } enum ColorMode { ColorMode65K = 0x50, ColorMode262K = 0x60, ColorMode12BIT = 0x03, ColorMode16BIT = 0x05, ColorMode18BIT = 0x06, ColorMode16M = 0x07, } pub enum Writeable<'d> { Command(Command), Data(&'d [u8]), } #[derive(Default)] pub enum Rotation { #[default] Portrait, Landscape, InvertedPortrait, InvertedLandscape, } #[derive(Default)] pub enum ColorOrder { #[default] Rgb, Bgr, } #[derive(Default)] pub enum RefreshOrder { #[default] TopBottomLeftRight, TopBottomRightLeft, BottomTopLeftRight, BottomTopRightLeft, } #[derive(Default)] pub struct SetAddressMode { pub rotation: Rotation, pub color_order: ColorOrder, pub refresh_order: RefreshOrder, } pub enum TearingMode { Off, Horizontal, HorizontalAndVertical, } impl SetAddressMode { pub fn into_madctl(&self) -> u8 { let mut result = 0; result = match self.rotation { Rotation::Portrait => result, Rotation::Landscape => result | 0b0110_0000, Rotation::InvertedPortrait => result | 0b1100_0000, Rotation::InvertedLandscape => result | 0b1010_0000, }; result = match self.color_order { ColorOrder::Rgb => result, ColorOrder::Bgr => result | 0b0000_1000, }; result = match self.refresh_order { RefreshOrder::TopBottomLeftRight => result, RefreshOrder::TopBottomRightLeft => result | 0b0000_0100, RefreshOrder::BottomTopLeftRight => result | 0b0001_0000, RefreshOrder::BottomTopRightLeft => result | 0b0001_0100, }; result } } impl<'d, DM: DriverMode, T: DelayNs> Display<'d, DM, T> { pub fn write(&mut self, writeable: Writeable) { match writeable { Writeable::Command(cmd) => { self.dc.set_low(); SpiBus::write(&mut self.spi, &[cmd as u8]).unwrap(); self.dc.set_high(); } Writeable::Data(data) => { SpiBus::write(&mut self.spi, data).unwrap(); } } } pub fn init(&mut self, set_address_mode: SetAddressMode) { self.hard_reset(); self.delay.delay_ms(150); // self.soft_reset(); self.set_sleep(false); self.delay.delay_ms(50); self.write_madctl(set_address_mode); self.delay.delay_ms(150); self.set_color_mode((ColorMode::ColorMode65K as u8) | (ColorMode::ColorMode16BIT as u8)); self.delay.delay_ms(50); self.set_inversion(true); self.write(Writeable::Command(Command::NormalModeOn)); self.delay.delay_ms(50); self.write(Writeable::Command(Command::DisplayOn)); self.delay.delay_ms(500); self.draw_rect( Position { x: 0, y: 0 }, Position { x: 240, y: 240 }, Color::default(), ); } pub fn hard_reset(&mut self) { self.rst.set_high(); self.delay.delay_ms(50); self.rst.set_low(); self.delay.delay_ms(50); self.rst.set_high(); self.delay.delay_ms(150); } pub fn soft_reset(&mut self) { self.write(Writeable::Command(Command::SoftReset)); self.delay.delay_ms(150); } pub fn set_sleep(&mut self, sleep: bool) { match sleep { true => self.write(Writeable::Command(Command::SleepIn)), false => self.write(Writeable::Command(Command::SleepOut)), } } pub fn set_inversion(&mut self, inversion: bool) { match inversion { true => self.write(Writeable::Command(Command::InversionOn)), false => self.write(Writeable::Command(Command::InversionOff)), } } pub fn write_madctl(&mut self, set_address_mode: SetAddressMode) { self.write(Writeable::Command(Command::MADCTL)); self.write(Writeable::Data(&[set_address_mode.into_madctl()])); } pub fn set_color_mode(&mut self, mode: u8) { self.write(Writeable::Command(Command::ColorMode)); self.write(Writeable::Data(&[mode & 0x77])); } pub fn set_tearing(&mut self, tearing: TearingMode) { let cmd = match tearing { TearingMode::Off => Command::TearingOff, TearingMode::Horizontal => Command::TearingOn, TearingMode::HorizontalAndVertical => Command::TearingOn, }; self.write(Writeable::Command(cmd)); match tearing { TearingMode::Off => {} TearingMode::Horizontal => self.write(Writeable::Data(&[0])), TearingMode::HorizontalAndVertical => self.write(Writeable::Data(&[1])), } } fn set_columns(&mut self, start: u16, end: u16) { let [start1, start2] = start.to_be_bytes(); let [end1, end2] = end.to_be_bytes(); self.write(Writeable::Command(Command::ColumnAlignmentSet)); self.write(Writeable::Data(&[start1, start2, end1, end2])); } fn set_rows(&mut self, start: u16, end: u16) { let [start1, start2] = start.to_be_bytes(); let [end1, end2] = end.to_be_bytes(); self.write(Writeable::Command(Command::RowAlignmentSet)); self.write(Writeable::Data(&[start1, start2, end1, end2])); } pub fn set_window(&mut self, pos0: Position, pos1: Position) { self.set_columns(pos0.x.max(0) as u16, pos1.x.max(0) as u16); self.set_rows(pos0.y.min(240) as u16, pos1.y.min(240) as u16); self.write(Writeable::Command(Command::RamWR)); } pub fn pixel(&mut self, position: Position, color: Color) { self.set_window(position, position); self.write(Writeable::Data(&color.bytes)); } pub fn draw_rect(&mut self, pos0: Position, pos1: Position, color: Color) { self.set_window(pos0, pos1); let width = pos1.x as u16 - pos0.x as u16; let height = pos1.y as u16 - pos0.y as u16; let pixels = width * height; let chunks = pixels / 256; let mut full_buf = [0; 512]; let [col1, col2] = color.bytes; for i in 0..256 { full_buf[i * 2] = col1; full_buf[i * 2 + 1] = col2; } for _ in 0..chunks { self.write(Writeable::Data(&full_buf)); } for _ in 0..(pixels % 256) { self.write(Writeable::Data(&color.bytes)); } } pub fn clear(&mut self, color: Color) { self.draw_rect(Position::new(0, 0), Position::new(240, 240), color); } }