use embassy_time::Timer; use esp_hal::{ DriverMode, gpio::{Flex, Level, Output}, lcd_cam::lcd::{ ClockMode, DelayMode, Phase, Polarity, dpi::{Dpi, Format, FrameTiming}, }, time::Rate, }; use lcd::spi_write; use log::debug; 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 { // pub address: u8, // pub args: ArrayVec<[u8; N]>, // } // impl const ArrayCommand { // const fn from(command: impl Command) -> Self { // Self { // address: command.address(), // args: command.args_iter().copied().collect(), // } // } // } // impl Command for ArrayCommand { // 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 [](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 [](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 [](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; } } } pub struct St7701s<'d, Dm> where Dm: DriverMode, { pub sck: Output<'d>, pub mosi: Flex<'d>, pub cs: Output<'d>, pub dpi: Dpi<'d, Dm>, } impl<'d, Dm> St7701s<'d, Dm> where Dm: DriverMode, { pub async fn new( mut sck: Output<'d>, mut mosi: Flex<'d>, mut cs: Output<'d>, mut unconfigured_dpi: Dpi<'d, Dm>, ) -> 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(6)) // 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(); let mut lcd = Self { sck, mosi, cs, dpi: unconfigured_dpi, }; lcd.send_init_sequence().await; lcd } 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; // info!("COMM 0x{:02X}", command.address()); // for arg in command.args_iter() { // info!("DATA 0x{arg:02X}"); // } } Timer::after_millis(*delay_ms).await; } } }