esp32-phone/src/display.rs
2026-05-16 22:03:34 +03:00

379 lines
9.4 KiB
Rust

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<f32> 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<Rgb565> 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);
}
}