acid/firmware/acid-firmware/src/peripherals/st7701s/mod.rs
2026-02-27 21:25:59 +01:00

1445 lines
34 KiB
Rust

use embassy_time::Timer;
use esp_hal::{
DriverMode,
gpio::{Flex, Level, Output},
lcd_cam::lcd::{
ClockMode, DelayMode, Phase, Polarity,
dpi::{Dpi, Format, FrameTiming},
},
ledc::{self, LowSpeed, channel::ChannelIFace, timer::TimerIFace},
time::Rate,
};
use lcd::spi_write;
use log::debug;
use ouroboros::self_referencing;
use paste::paste;
// use tinyvec::ArrayVec;
mod lcd;
pub trait Command {
fn address(&self) -> u8;
fn args_iter(&'_ self) -> core::slice::Iter<'_, u8>;
}
// pub struct ArrayCommand<const N: usize> {
// pub address: u8,
// pub args: ArrayVec<[u8; N]>,
// }
// impl<const N: usize> const ArrayCommand<N> {
// const fn from(command: impl Command) -> Self {
// Self {
// address: command.address(),
// args: command.args_iter().copied().collect(),
// }
// }
// }
// impl<const N: usize> Command for ArrayCommand<N> {
// fn address(&self) -> u8 {
// self.address
// }
// fn args_iter(&'_ self) -> core::slice::Iter<'_, u8> {
// self.args.iter()
// }
// }
pub struct CustomCommand {
pub address: u8,
pub args: &'static [u8],
}
impl Command for CustomCommand {
fn address(&self) -> u8 {
self.address
}
fn args_iter(&'_ self) -> core::slice::Iter<'_, u8> {
self.args.iter()
}
}
macro_rules! define_arg {
(@field ($field:ident: $min:literal..=$max:literal)) => {
paste! {
#[allow(unused, clippy::identity_op)]
pub const fn [<with_ $field>](mut self, val: u8) -> Self {
const MASK: u8 = ((1 << ($max - $min + 1)) - 1);
self.0 &= !(MASK << $min); // Clear bits
self.0 |= (val & MASK) << $min; // Assign bits
self
}
}
};
(@field ($field:ident: $index:literal)) => {
paste! {
#[allow(unused)]
pub const fn [<with_ $field>](mut self, val: bool) -> Self {
self.0 &= !(1 << $index); // Clear bit
self.0 |= (val as u8) << $index; // Assign bit
self
}
}
};
(@field ($field:ident)) => {
paste! {
#[allow(unused)]
pub const fn [<with_ $field>](mut self, val: u8) -> Self {
self.0 = val;
self
}
}
};
($visibility:vis struct $type:ident {
const INIT = $init:literal;
$($field:ident $(: $min:literal$(..=$max:literal)?)?),*$(,)?
}) => {
#[derive(Clone, Copy)]
$visibility struct $type(u8);
impl Default for $type {
fn default() -> Self {
Self::new()
}
}
#[allow(unused)]
impl $type {
pub const fn new() -> Self {
Self($init)
}
pub const fn into_byte(self) -> u8 {
self.0
}
$(
define_arg!(@field ($field $(: $min$(..=$max)?)?));
)*
}
};
($visibility:vis struct $type:ident {
$field:ident $(,)?
}) => {
#[derive(Clone, Copy)]
$visibility struct $type(u8);
impl Default for $type {
fn default() -> Self {
Self::new()
}
}
#[allow(unused)]
impl $type {
pub const fn new() -> Self {
Self(0)
}
pub const fn into_byte(self) -> u8 {
self.0
}
define_arg!(@field ($field));
}
};
}
macro_rules! define_commands {
($(
$(#[$docs:meta])*
$visibility:vis command $command:ident {
const ADDRESS = $address:literal;
$(
arg {
$($body:tt)*
}
)*
}
)*) => {
paste! {
$(
$(
define_arg!($visibility struct [< Cmd $command Arg ${index()}>] { $($body)* });
)*
#[repr(C)]
#[derive(Default)]
$(#[$docs])*
$visibility struct [< Cmd $command >]($(pub [< Cmd $command Arg ${index()} >], $(${ignore($body)})*)*);
impl Command for [< Cmd $command >] {
fn address(&self) -> u8 {
$address
}
fn args_iter(&'_ self) -> core::slice::Iter<'_, u8> {
self.into_iter()
}
}
impl<'a> IntoIterator for &'a [< Cmd $command >] {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
unsafe {
core::slice::from_raw_parts(
self as *const [< Cmd $command >] as *const u8,
0 $(+ 1 $(${ignore($body)})*)*,
)
}.iter()
}
}
)*
}
};
}
define_commands! {
// System function command table 1
#[doc="No operation"]
pub command Nop {
const ADDRESS = 0x00;
}
#[doc="Software reset"]
pub command Swreset {
const ADDRESS = 0x01;
}
#[doc="ID read"]
pub command Rddid {
const ADDRESS = 0x04;
arg { id1 }
arg { id2 }
arg { id3 }
}
#[doc="ID read"]
pub command Rdnumed {
const ADDRESS = 0x05;
arg {
const INIT = 0b0000_0000;
err: 0..=6,
err_over: 7,
}
}
#[doc="Read the first pixel of color R"]
pub command Rdred {
const ADDRESS = 0x06;
arg { r_1st }
}
#[doc="Read the first pixel of color G"]
pub command Rdgreen {
const ADDRESS = 0x07;
arg { g_1st }
}
#[doc="Read the first pixel of color B"]
pub command Rdblue {
const ADDRESS = 0x08;
arg { b_1st }
}
#[doc="Read display power mode"]
pub command Rddpm {
const ADDRESS = 0x0A;
arg {
const INIT = 0b0000_1000;
dison: 2,
slpout: 4,
bston: 7,
}
}
#[doc="Read display MADCTR"]
pub command Rddmadctl {
const ADDRESS = 0x0B;
arg {
const INIT = 0b0000_1000;
bgr: 3,
ml: 4,
}
}
#[doc="Read display pixel format"]
pub command Rddcolmod {
const ADDRESS = 0x0C;
arg {
const INIT = 0b0000_0000;
vipf: 5..=6,
}
}
#[doc="Read display image mode"]
pub command Rddim {
const ADDRESS = 0x0D;
arg {
const INIT = 0b0000_0000;
gcs: 0..=2,
alpxloff: 3,
alpxlon: 4,
invon: 5,
}
}
#[doc="Read display signal mode"]
pub command Rddsm {
const ADDRESS = 0x0E;
arg {
const INIT = 0b0000_0000;
telmd: 6,
teon: 7,
}
}
#[doc="Read display self-diagnostic result"]
pub command Rddsdr {
const ADDRESS = 0x0F;
arg {
const INIT = 0b0000_0000;
fund: 6,
rld: 7,
}
}
#[doc="Sleep in"]
pub command Slpin {
const ADDRESS = 0x10;
}
#[doc="Sleep out"]
pub command Slpout {
const ADDRESS = 0x11;
}
#[doc="Partial mode on"]
pub command Ptlon {
const ADDRESS = 0x12;
}
#[doc="Normal display mode on"]
pub command Noron {
const ADDRESS = 0x13;
}
#[doc="Display inversion off (normal)"]
pub command Invoff {
const ADDRESS = 0x20;
}
#[doc="Display inversion on"]
pub command Invon {
const ADDRESS = 0x21;
}
#[doc="All pixel off (black)"]
pub command Allpoff {
const ADDRESS = 0x22;
}
#[doc="All pixel on (white)"]
pub command Allpon {
const ADDRESS = 0x23;
}
#[doc="Gamma curve select"]
pub command Gamset {
const ADDRESS = 0x26;
arg {
const INIT = 0b0000_0000;
gc: 0..=3,
}
}
#[doc="Display off"]
pub command Dispoff {
const ADDRESS = 0x28;
}
#[doc="Display on"]
pub command Dispon {
const ADDRESS = 0x29;
}
#[doc="Tearing effect line off"]
pub command Teoff {
const ADDRESS = 0x34;
}
#[doc="Tearing effect line on"]
pub command Teon {
const ADDRESS = 0x35;
}
#[doc="Display data access control"]
pub command Madctl {
const ADDRESS = 0x36;
arg {
const INIT = 0b0000_0000;
bgr: 3,
ml: 4,
}
}
#[doc="Idle mode off"]
pub command Idmoff {
const ADDRESS = 0x38;
}
#[doc="Idle mode on"]
pub command Idmon {
const ADDRESS = 0x39;
}
#[doc="Interface pixel format"]
pub command Colmod {
const ADDRESS = 0x3A;
arg {
const INIT = 0b0000_0000;
vipf: 4..=6,
}
}
#[doc="Read tear line"]
pub command Gsl {
const ADDRESS = 0x45;
arg { tesl_msb }
arg { tesl_lsb }
}
#[doc="Write display brightness value"]
pub command Wrdibv {
const ADDRESS = 0x51;
arg { dbv }
}
#[doc="Read display brightness value"]
pub command Rddisbv {
const ADDRESS = 0x52;
arg { dbv }
}
#[doc="Write control display value"]
pub command Wrctrld {
const ADDRESS = 0x53;
arg {
const INIT = 0b0000_0000;
bl: 2,
dd: 3,
bctrl: 5,
}
}
#[doc="Read control display value"]
pub command Rrctrld {
const ADDRESS = 0x54;
arg {
const INIT = 0b0000_0000;
bl: 2,
dd: 3,
bctrl: 5,
}
}
#[doc="Write CABC mode"]
pub command Wrcabc {
const ADDRESS = 0x55;
arg {
const INIT = 0b0000_0000;
cabc_md: 0..=1,
ce_md: 4..=5,
ce_on: 7,
}
}
#[doc="Read CABC mode"]
pub command Rrcabc {
const ADDRESS = 0x56;
arg {
const INIT = 0b0000_0000;
cabc_md: 0..=1,
ce_md: 4..=5,
ce_on: 7,
}
}
#[doc="Write CABC minimum brightness"]
pub command Wrcabcmb {
const ADDRESS = 0x5E;
arg { cmb }
}
#[doc="Read CABC minimum brightness"]
pub command Rrcabcmb {
const ADDRESS = 0x5F;
arg { cmb }
}
#[doc="Read automatic brightness control self-diagnostic result"]
pub command Rdabcsd {
const ADDRESS = 0x68;
arg {
const INIT = 0b0000_0000;
fund: 6,
rld: 7,
}
}
#[doc="Read black/white low bits"]
pub command Rdbwlb {
const ADDRESS = 0x70;
arg {
const INIT = 0b0000_0000;
wy: 0..=1,
wx: 2..=3,
bky: 4..=5,
bkx: 6..=7,
}
}
#[doc="Read Bkx"]
pub command Rdbkx {
const ADDRESS = 0x71;
arg { bkx }
}
#[doc="Read Bky"]
pub command Rdbky {
const ADDRESS = 0x72;
arg { bky }
}
#[doc="Read Wx"]
pub command Rdwx {
const ADDRESS = 0x73;
arg { wx }
}
#[doc="Read Wy"]
pub command Rdwy {
const ADDRESS = 0x74;
arg { wy }
}
#[doc="Read red/green low bits"]
pub command Rdrglb {
const ADDRESS = 0x75;
arg {
const INIT = 0b0000_0000;
gy: 0..=1,
gx: 2..=3,
ry: 4..=5,
rx: 6..=7,
}
}
#[doc="Read Rx"]
pub command Rdrx {
const ADDRESS = 0x76;
arg { rx }
}
#[doc="Read Ry"]
pub command Rdry {
const ADDRESS = 0x77;
arg { ry }
}
#[doc="Read Gx"]
pub command Rdgx {
const ADDRESS = 0x78;
arg { gx }
}
#[doc="Read Gy"]
pub command Rdgy {
const ADDRESS = 0x79;
arg { gy }
}
#[doc="Read blue/a-color low bits"]
pub command Rdbalb {
const ADDRESS = 0x7A;
arg {
const INIT = 0b0000_0000;
ay: 0..=1,
ax: 2..=3,
by: 4..=5,
bx: 6..=7,
}
}
#[doc="Read Bx"]
pub command Rdbx {
const ADDRESS = 0x7B;
arg { bx }
}
#[doc="Read By"]
pub command Rdby {
const ADDRESS = 0x7C;
arg { by }
}
#[doc="Read Ax"]
pub command Rdax {
const ADDRESS = 0x7D;
arg { ax }
}
#[doc="Read Ay"]
pub command Rday {
const ADDRESS = 0x7E;
arg { ay }
}
#[doc="Read tthe DDB from the provided location"]
pub command Rdddbs {
const ADDRESS = 0xA1;
arg { const INIT = 0x88; }
arg { const INIT = 0x02; }
arg { mid_msb }
arg { mid_lsb }
arg { const INIT = 0xFF; }
}
#[doc="Continue reading the DDB from the last read location"]
pub command Rdddbc {
const ADDRESS = 0xA8;
arg { sid_msb }
arg { sid_lsb }
arg { mid_msb }
arg { mid_lsb }
arg { const INIT = 0xFF; }
}
#[doc="Read first checksum"]
pub command Rdfcs {
const ADDRESS = 0xAA;
arg { fcs }
}
#[doc="Read continue checksum"]
pub command Rdccs {
const ADDRESS = 0xAF;
arg { ccs }
}
#[doc="Read ID1"]
pub command Rdid1 {
const ADDRESS = 0xDA;
arg { id1 }
}
#[doc="Read ID2"]
pub command Rdid2 {
const ADDRESS = 0xDB;
arg { id2 }
}
#[doc="Read ID3"]
pub command Rdid3 {
const ADDRESS = 0xDC;
arg { id3 }
}
// System function command table 2
#[doc="Command2_BKx function selection"]
pub command Cn2bkxsel {
const ADDRESS = 0xFF;
arg { const INIT = 0b0111_0111; }
arg { const INIT = 0b0000_0001; }
arg { const INIT = 0b0000_0000; }
arg { const INIT = 0b0000_0000; }
arg {
const INIT = 0b0000_0000;
// The spec is ambiguous on the size of this field.
bksel: 0..=1,
cn2: 4,
}
}
// Command2_BK0
#[doc="Positive voltage gamma control"]
pub command Pvgamctrl {
const ADDRESS = 0xB0;
arg {
const INIT = 0b0000_0000;
vc0p: 0..=3,
aj0p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc4p: 0..=5,
aj1p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc8p: 0..=5,
aj2p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc16p: 0..=4,
}
arg {
const INIT = 0b0000_0000;
vc24p: 0..=4,
aj3p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc52p: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc80p: 0..=5,
}
arg {
const INIT = 0b0000_0000;
vc108p: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc147p: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc175p: 0..=5,
}
arg {
const INIT = 0b0000_0000;
vc203p: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc231p: 0..=4,
aj4p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc239p: 0..=4,
}
arg {
const INIT = 0b0000_0000;
vc247p: 0..=5,
aj5p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc251p: 0..=5,
aj6p: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc255p: 0..=4,
aj7p: 6..=7,
}
}
#[doc="Negative voltage gamma control"]
pub command Nvgamctrl {
const ADDRESS = 0xB1;
arg {
const INIT = 0b0000_0000;
vc0n: 0..=3,
aj0n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc4n: 0..=5,
aj1n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc8n: 0..=5,
aj2n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc16n: 0..=4,
}
arg {
const INIT = 0b0000_0000;
vc24n: 0..=4,
// TODO: This field is not documented in the spec,
// but it seems like it should be here?
aj3n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc52n: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc80n: 0..=5,
}
arg {
const INIT = 0b0000_0000;
vc108n: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc147n: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc175n: 0..=5,
}
arg {
const INIT = 0b0000_0000;
vc203n: 0..=3,
}
arg {
const INIT = 0b0000_0000;
vc231n: 0..=4,
aj4n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc239n: 0..=4,
}
arg {
const INIT = 0b0000_0000;
vc247n: 0..=5,
aj5n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc251n: 0..=5,
aj6n: 6..=7,
}
arg {
const INIT = 0b0000_0000;
vc255n: 0..=4,
aj7n: 6..=7,
}
}
#[doc="Digital gamma enable"]
pub command Dgmen {
const ADDRESS = 0xB8;
arg {
const INIT = 0b0000_0000;
dgm_on: 4,
}
}
// DGMLUTR, DGMLUTB not currently supported, because of its high number of parameters.
// It could be supported with a manual implementation of `Command`.
#[doc="PWM CLK select"]
pub command Pwmclk {
const ADDRESS = 0xBC;
arg {
const INIT = 0b0001_1000;
pwm_clk_sel: 0..=2,
}
}
#[doc="Display line setting"]
pub command Lneset {
const ADDRESS = 0xC0;
arg {
const INIT = 0b0000_0000;
bar: 0..=6,
lde_en: 7,
}
arg {
const INIT = 0b0000_0000;
line_delta: 0..=1,
}
}
#[doc="Porch control"]
pub command Porctrl {
const ADDRESS = 0xC1;
arg { vbp }
arg { vfp }
}
#[doc="Inversion selection & frame rate control"]
pub command Invsel {
const ADDRESS = 0xC2;
arg {
const INIT = 0b0011_0000;
nlinv: 0..=2,
}
arg {
const INIT = 0b0000_0000;
rtni: 0..=4,
}
}
#[doc="RGB control"]
pub command Rgbctrl {
const ADDRESS = 0xC3;
arg {
const INIT = 0b0000_0000;
ep: 0,
dp: 1,
hsp: 2,
vsp: 3,
de_hv: 7,
}
arg { hbp_hvrgb }
arg { vbp_hvrgb }
}
#[doc="Partial mode control"]
pub command Parctrl {
const ADDRESS = 0xC5;
arg { ptsa_lsb }
arg {
const INIT = 0b0000_0000;
ptsa_hsb: 0..=1,
}
arg { ptea_lsb }
arg {
const INIT = 0b0000_0000;
ptea_hsb: 0..=1,
}
}
#[doc="Source direction control"]
pub command Sdir {
const ADDRESS = 0xC7;
arg {
const INIT = 0b0000_0000;
ss: 2,
}
}
#[doc="Pseudo-dot inversion driving setting"]
pub command Pdotset {
const ADDRESS = 0xC8;
arg {
const INIT = 0b0000_0000;
z_gltor: 5,
z_sdm1s: 6,
z_en: 7,
}
}
#[doc="Color control"]
pub command Colctrl {
const ADDRESS = 0xCD;
arg {
const INIT = 0b0000_0000;
epf: 0..=2,
mdt: 3,
inv_led_on: 4,
inv_led_pwm: 5,
}
}
#[doc="Spread spectrum control"]
pub command Ssctrl {
const ADDRESS = 0xCE;
arg {
const INIT = 0b0000_0100;
dssrg: 4..=5,
dsse: 7,
}
}
#[doc="Sunlight readable enhancement"]
pub command Sectrl {
const ADDRESS = 0xE0;
arg {
const INIT = 0b0000_0000;
sre_alpha: 0..=3,
sre: 4,
}
}
#[doc="Noise reduce control"]
pub command Nrctrl {
const ADDRESS = 0xE1;
arg {
const INIT = 0b0000_0000;
nr_md: 0..=1,
nre: 4,
}
}
#[doc="Sharpness control (originally has a conflicting name SECTRL)"]
pub command Shctrl {
const ADDRESS = 0xE2;
arg {
const INIT = 0b0000_0000;
y_gain: 0..=3,
se: 4,
}
}
#[doc="Color calibration control"]
pub command Ccctrl {
const ADDRESS = 0xE3;
arg {
const INIT = 0b0000_0000;
cce: 0,
}
}
#[doc="Skin tone preservation control"]
pub command Skctrl {
const ADDRESS = 0xE4;
arg {
const INIT = 0b0000_0000;
skin_ce_mid: 0..=1,
ske: 4,
}
}
#[doc="NVM address setting enable"]
pub command Nvmsete {
const ADDRESS = 0xEA;
arg {
const INIT = 0b0000_0000;
aden: 0,
}
}
#[doc="CABC control"]
pub command Cabcctrl {
const ADDRESS = 0xEE;
arg {
const INIT = 0b0000_0000;
led_en: 0,
ledpwr_sel: 4,
}
}
// Command2_BK1
#[doc="Vop amplitude setting"]
pub command Vrhs {
const ADDRESS = 0xB0;
arg { vrha }
}
#[doc="VCOM amplitude setting"]
pub command Vcoms {
const ADDRESS = 0xB1;
arg { vcom }
}
#[doc="VGH voltage setting"]
pub command Vghss {
const ADDRESS = 0xB2;
arg {
const INIT = 0b0000_0000;
vghss: 0..=3,
}
}
#[doc="TEST command setting"]
pub command Tescmd {
const ADDRESS = 0xB3;
arg { const INIT = 0b1000_0000; }
}
#[doc="VGL voltage setting"]
pub command Vgls {
const ADDRESS = 0xB5;
arg {
const INIT = 0b0100_0000;
vgls: 0..=3,
}
}
#[doc="VRH_DV voltage setting"]
pub command Vrhdv {
const ADDRESS = 0xB6;
arg {
const INIT = 0b0000_0000;
vrh_dv: 0..=6,
}
}
#[doc="Power control 1"]
pub command Pwctrl1 {
const ADDRESS = 0xB7;
arg {
const INIT = 0b0000_0000;
apos: 0..=1,
apis: 2..=3,
ap: 6..=7,
}
}
#[doc="Power control 2"]
pub command Pwctrl2 {
const ADDRESS = 0xB8;
arg {
const INIT = 0b0000_0000;
avcl: 0..=1,
avdd: 4..=5,
}
}
#[doc="Power control 3"]
pub command Pwctrl3 {
const ADDRESS = 0xB9;
arg {
const INIT = 0b0000_0000;
svno_pum: 0..=1,
svpo_pum: 4..=5,
}
}
#[doc="Power pumping clk selection 1"]
pub command Pclks1 {
const ADDRESS = 0xBA;
arg {
const INIT = 0b0000_0000;
stp1cks: 0..=1,
stp4cks: 4..=5,
}
}
#[doc="Power pumping clk selection 2"]
pub command Pclks2 {
const ADDRESS = 0xBB;
arg {
const INIT = 0b0000_0000;
sbstcks: 0..=1,
}
}
#[doc="Power pumping clk selection 3"]
pub command Pclks3 {
const ADDRESS = 0xBC;
arg {
const INIT = 0b0000_0000;
stp2scks: 0..=1,
stp2pcks: 0..=1,
stp3cks: 4..=5,
}
}
#[doc="Source pre_drive timing set1 (also known as SPD1)"]
pub command Pdr1 {
const ADDRESS = 0xC1;
arg {
const INIT = 0b0111_0000;
t2d: 0..=3,
}
}
#[doc="Source pre_drive timing set2 (also known as SPD2)"]
pub command Pdr2 {
const ADDRESS = 0xC2;
arg {
const INIT = 0b0111_0000;
t3d: 0..=3,
}
}
#[doc="MIPI setting 1"]
pub command Mipiset1 {
const ADDRESS = 0xD0;
arg {
const INIT = 0b1000_0000;
err_sel: 0..=1,
eotp_en: 3,
}
}
#[doc="MIPI setting 2"]
pub command Mipiset2 {
const ADDRESS = 0xD1;
arg {
const INIT = 0b0000_0000;
mpc_tlpx0: 0..=3,
mpc_tlpx1: 4..=7,
}
arg {
const INIT = 0b0000_0000;
mpc_tlpx2: 0..=3,
mpc_txtimeadj: 4..=7,
}
arg {
const INIT = 0b0000_0000;
mpc_ttago: 0..=3,
}
arg {
const INIT = 0b0000_0000;
mpc_ttaget: 0..=3,
}
}
#[doc="MIPI setting 3"]
pub command Mipiset3 {
const ADDRESS = 0xD2;
arg {
const INIT = 0b0011_0000;
phy_ttasure: 0..=3,
}
}
#[doc="MIPI setting 4"]
pub command Mipiset4 {
const ADDRESS = 0xD3;
arg {
const INIT = 0b0000_0000;
phy_csk: 0..=2,
}
arg {
const INIT = 0b0000_0000;
phy_dsk0: 0..=2,
phy_dsk1: 4..=6,
}
}
// Command2_BK3
#[doc="NVM enable"]
pub command Nvmen {
const ADDRESS = 0xC8;
arg { const INIT = 0b0111_0111; }
arg { const INIT = 0b0000_0001; }
arg { const INIT = 0b1110_1110; }
arg { const INIT = 0b0000_0100; }
}
#[doc="NVM manual control setting"]
pub command Nvmset {
const ADDRESS = 0xCA;
arg {
const INIT = 0b0000_0000;
pa_msb: 0..=1,
}
arg { pa_lsb }
arg { pdin }
}
#[doc="NVM program active"]
pub command Promact {
const ADDRESS = 0xCC;
arg { const INIT = 0b1010_1010; }
}
}
#[self_referencing]
pub struct Backlight<'d> {
pub timer: ledc::timer::Timer<'d, LowSpeed>,
#[borrows(timer)]
#[covariant]
pub channel: ledc::channel::Channel<'this, LowSpeed>,
}
impl<'a> Backlight<'a> {
pub fn set_duty(&mut self, duty_pct: u8) -> Result<(), ledc::channel::Error> {
self.borrow_channel().set_duty(duty_pct)
}
}
pub struct St7701sController<'d> {
pub sck: Output<'d>,
pub mosi: Flex<'d>,
pub cs: Output<'d>,
pub backlight: Backlight<'d>,
}
impl<'d> St7701sController<'d> {
pub async fn send_init_sequence(&mut self) {
debug!("Writing ST7701S init sequence.");
for (subsequence, delay_ms) in &*lcd::INIT_SEQUENCE_COMMANDS {
for command in subsequence {
spi_write(
command.address(),
command.args_iter().copied(),
&mut self.mosi,
&mut self.sck,
&mut self.cs,
)
.await;
}
Timer::after_millis(*delay_ms).await;
}
}
pub async fn send(&mut self, command: impl Command) {
spi_write(
command.address(),
command.args_iter().copied(),
&mut self.mosi,
&mut self.sck,
&mut self.cs,
)
.await;
}
/// Puts the display into sleep mode and disables the backlight.
pub async fn sleep_on(&mut self) {
self.backlight.set_duty(0).unwrap();
self.send(CmdSlpin()).await;
}
/// Brings the display out of sleep mode and enables the backlight.
pub async fn sleep_off(&mut self) {
self.send(CmdSlpout()).await;
self.backlight.set_duty(100).unwrap();
}
}
pub struct St7701s<'d, Dm>
where
Dm: DriverMode,
{
pub controller: St7701sController<'d>,
pub dpi: Dpi<'d, Dm>,
}
impl<'d, Dm> St7701s<'d, Dm>
where
Dm: DriverMode,
{
/// Initializes an ST7701S display, and puts it to sleep.
/// To turn it on, invoke `St7701sController:sleep_off`.
pub async fn new(
mut sck: Output<'d>,
mut mosi: Flex<'d>,
mut cs: Output<'d>,
mut unconfigured_dpi: Dpi<'d, Dm>,
mut bl_timer: ledc::timer::Timer<'d, LowSpeed>,
bl_channel: ledc::channel::Channel<'d, LowSpeed>,
) -> Self {
sck.apply_config(&Default::default());
sck.set_high();
cs.apply_config(&Default::default());
cs.set_high();
mosi.apply_input_config(&Default::default());
mosi.apply_output_config(&Default::default());
mosi.set_input_enable(false);
mosi.set_output_enable(true);
let lcd_config = esp_hal::lcd_cam::lcd::dpi::Config::default()
// Internal memory can use the full 16 MHz, but when external PSRAM is used, it cannot keep up with the display.
// For that reason, we choose the highest value for which it doesn't glitch by showing black
// stripes on the screen.
//
// There are three knobs you can turn to improve the bandwidth situation.
// - increase psram frequency
// - decrease the peripheral's frequency
// - prevent flash from being used whilst your program is running. (There's a PR to make
// this easy to do)
// https://github.com/esp-rs/esp-hal/pull/3024
//
// Adafruit would use 11 MHz.
// I had lowered the frequency, so that `DmaBounce` could keep up.
.with_frequency(Rate::from_mhz(9)) // From Adafruit
.with_clock_mode(ClockMode {
polarity: Polarity::IdleLow, // From Adafruit
phase: Phase::ShiftHigh, // From Adafruit
})
.with_de_mode(DelayMode::RaisingEdge)
.with_hsync_mode(DelayMode::RaisingEdge)
.with_vsync_mode(DelayMode::RaisingEdge)
.with_output_bit_mode(DelayMode::RaisingEdge)
.with_format(Format {
enable_2byte_mode: true,
..Default::default()
})
.with_timing({
// Adafruit's config for this LCD:
// https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/bar240x960.py#L81-L97
// https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/__init__.py#L59-L62
// CircuitPython code handling Adafruit's config
// https://github.com/adafruit/circuitpython/blob/97c6617817e95b1f6aa2ce458778aaa8371de39b/ports/espressif/common-hal/dotclockframebuffer/DotClockFramebuffer.c#L63
// ESP-IDF peripheral configuration code:
// https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/esp_lcd/rgb/esp_lcd_panel_rgb.c#L556
// Espressif's docs:
// https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html#structures
// TODO: Investigate PORCTRL instruction in datasheet of ST7701
let horizontal_resolution: usize = 240;
let vertical_resolution = 960;
let overscan_left = 120;
let vsync_width = 8;
let hsync_width = 8;
let horizontal_blank_front_porch = 20;
let horizontal_blank_back_porch = 20;
let vertical_blank_front_porch = 20;
let vertical_blank_back_porch = 20;
let hsync_position = 0;
let horizontal_active_width =
(horizontal_resolution + overscan_left).div_ceil(16) * 16; // Round up to a multiple of 16.
let vertical_active_height = vertical_resolution;
FrameTiming {
horizontal_total_width: hsync_width
+ horizontal_blank_back_porch
+ horizontal_active_width
+ horizontal_blank_front_porch,
vertical_total_height: vsync_width
+ vertical_blank_back_porch
+ vertical_active_height
+ vertical_blank_front_porch,
horizontal_blank_front_porch: horizontal_blank_front_porch + hsync_width,
vertical_blank_front_porch: vertical_blank_front_porch + vsync_width,
horizontal_active_width,
vertical_active_height,
vsync_width,
hsync_width,
hsync_position,
}
})
.with_hsync_idle_level(Level::High)
.with_vsync_idle_level(Level::High)
.with_de_idle_level(Level::Low);
unconfigured_dpi.apply_config(&lcd_config).unwrap();
bl_timer
.configure(ledc::timer::config::Config {
duty: ledc::timer::config::Duty::Duty5Bit,
clock_source: ledc::timer::LSClockSource::APBClk,
frequency: Rate::from_khz(24),
})
.unwrap();
let backlight = Backlight::new(bl_timer, move |bl_timer| {
let mut bl_channel = bl_channel; // Forces bl_channel to be moved before it is mutated.
bl_channel
.configure(ledc::channel::config::Config {
timer: bl_timer,
drive_mode: esp_hal::gpio::DriveMode::PushPull,
duty_pct: 0,
})
.unwrap();
bl_channel
});
let mut lcd = Self {
controller: St7701sController {
sck,
mosi,
cs,
backlight,
},
dpi: unconfigured_dpi,
};
lcd.controller.send_init_sequence().await;
lcd.controller.sleep_on().await;
lcd
}
}