From a67991027d4e9ee4b4ce77abeb39e44148668fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hlusi=C4=8Dka?= Date: Sat, 27 Dec 2025 21:03:52 +0100 Subject: [PATCH] Incremental improvements --- firmware2/.cargo/config.toml | 12 +- firmware2/Cargo.lock | 77 ++- firmware2/Cargo.toml | 7 +- firmware2/src/keymap.rs | 2 +- firmware2/src/macros.rs | 12 - firmware2/src/main.rs | 461 ++++++++---------- firmware2/src/matrix.rs | 23 +- firmware2/src/peripherals/mod.rs | 1 + .../src/{ => peripherals/st7701s}/lcd.rs | 71 +-- .../st7701s/mod.rs} | 157 +++++- 10 files changed, 460 insertions(+), 363 deletions(-) delete mode 100644 firmware2/src/macros.rs create mode 100644 firmware2/src/peripherals/mod.rs rename firmware2/src/{ => peripherals/st7701s}/lcd.rs (91%) rename firmware2/src/{st7701s.rs => peripherals/st7701s/mod.rs} (81%) diff --git a/firmware2/.cargo/config.toml b/firmware2/.cargo/config.toml index cab417d..a2cc239 100644 --- a/firmware2/.cargo/config.toml +++ b/firmware2/.cargo/config.toml @@ -16,4 +16,14 @@ ESP_LOG = "info" # Xtensa only: # Needed for nightly, until llvm upstream has support for Rust Xtensa. [unstable] -build-std = ["alloc", "core"] \ No newline at end of file +build-std = ["alloc", "core"] + +# [patch.crates-io] +# esp-backtrace = { path = "../../../rust/esp-hal/esp-backtrace" } +# esp-hal = { path = "../../../rust/esp-hal/esp-hal" } +# esp-storage = { path = "../../../rust/esp-hal/esp-storage" } +# esp-alloc = { path = "../../../rust/esp-hal/esp-alloc" } +# esp-println = { path = "../../../rust/esp-hal/esp-println" } +# esp-radio = { path = "../../../rust/esp-hal/esp-radio" } +# esp-rtos = { path = "../../../rust/esp-hal/esp-rtos" } +# esp-bootloader-esp-idf = { path = "../../../rust/esp-hal/esp-bootloader-esp-idf" } diff --git a/firmware2/Cargo.lock b/firmware2/Cargo.lock index 4a7ce2d..bf56c5e 100644 --- a/firmware2/Cargo.lock +++ b/firmware2/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "cc", "const-gen", "embassy-executor", + "embassy-time", "embuild", "esp-alloc", "esp-backtrace", @@ -20,6 +21,7 @@ dependencies = [ "esp-radio", "esp-rtos", "esp-storage", + "itertools", "json", "lazy_static", "log", @@ -247,9 +249,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "shlex", @@ -592,6 +594,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -732,6 +740,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "futures-core", + "log", ] [[package]] @@ -1042,6 +1051,7 @@ dependencies = [ "esp32s3", "fugit", "instability", + "log", "nb 1.1.0", "paste", "portable-atomic", @@ -1331,9 +1341,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fnv" @@ -1665,16 +1675,25 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.15" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "a87d9b8105c23642f50cbbae03d1f75d8422c5cb98ce7ee9271f7ff7505be6b8" dependencies = [ "jiff-static", "log", @@ -1685,9 +1704,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "b787bebb543f8969132630c51fd0afab173a86c6abae56ff3b9e5e3e3f9f6e58" dependencies = [ "proc-macro2", "quote", @@ -1738,9 +1757,9 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -1936,9 +1955,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -2032,9 +2051,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -2087,9 +2106,9 @@ checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -2138,9 +2157,9 @@ dependencies = [ [[package]] name = "riscv-rt-macros" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c3138fdd8d128b2d81829842a3e0ce771b3712f7b6318ed1476b0695e7d330" +checksum = "def519ddeeb5e43c2b4fc3952c27b3a86782fc05192f322b2309125cd85b1fc3" dependencies = [ "proc-macro2", "quote", @@ -2312,9 +2331,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scopeguard" @@ -2426,15 +2445,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -3197,3 +3216,9 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d6085d62852e35540689d1f97ad663e3971fc19cf5eceab364d62c646ea167" diff --git a/firmware2/Cargo.toml b/firmware2/Cargo.toml index 2811ed8..7d8998f 100644 --- a/firmware2/Cargo.toml +++ b/firmware2/Cargo.toml @@ -8,22 +8,24 @@ repository = "https://github.com/haobogu/rmk" edition = "2024" [features] +default = ["ble"] no_usb = ["rmk/_no_usb"] +ble = ["rmk/esp32s3_ble"] [dependencies] rmk = { version = "0.8.2", default-features = false, features = [ - "esp32s3_ble", "log", "storage", "vial", ] } embassy-executor = { version = "0.9" } +embassy-time = { version = "0.5.0", features = ["log"] } esp-backtrace = { version = "0.18", features = [ "esp32s3", "panic-handler", "println", ] } -esp-hal = { version = "1.0", features = ["esp32s3", "unstable", "psram"] } +esp-hal = { version = "1.0", features = ["esp32s3", "unstable", "psram", "log-04"] } esp-storage = { version = "0.8.0", features = ["esp32s3"] } esp-alloc = { version = "0.9.0" } esp-println = { version = "0.16.0", features = ["esp32s3", "log-04"] } @@ -37,6 +39,7 @@ lazy_static = { version = "1.5.0", features = ["spin_no_std"], default-features log = "0.4.29" bitflags = "2.10.0" paste = "1.0.15" +itertools = { version = "0.14.0", default-features = false } [build-dependencies] xz2 = "0.1.7" diff --git a/firmware2/src/keymap.rs b/firmware2/src/keymap.rs index 8c45512..6afbcbc 100644 --- a/firmware2/src/keymap.rs +++ b/firmware2/src/keymap.rs @@ -1,5 +1,5 @@ use rmk::types::action::KeyAction; -use rmk::{a, k, layer, mo}; +use rmk::{a, k, layer}; use crate::matrix::{MATRIX_COLS, MATRIX_ROWS}; diff --git a/firmware2/src/macros.rs b/firmware2/src/macros.rs deleted file mode 100644 index 5501345..0000000 --- a/firmware2/src/macros.rs +++ /dev/null @@ -1,12 +0,0 @@ -macro_rules! config_matrix_pins_esp { - (peripherals: $p:ident, input: [$($in_pin:ident), *], output: [$($out_pin:ident), +]) => { - { - let mut output_pins = [$(Output::new($p.$out_pin, Level::Low, OutputConfig::default())), +]; - let input_pins = [$(Input::new($p.$in_pin, InputConfig::default().with_pull(Pull::Down))), +]; - output_pins.iter_mut().for_each(|p| { - let _ = p.set_low(); - }); - (input_pins, output_pins) - } - }; -} diff --git a/firmware2/src/main.rs b/firmware2/src/main.rs index f3414c4..e127528 100644 --- a/firmware2/src/main.rs +++ b/firmware2/src/main.rs @@ -5,59 +5,53 @@ extern crate alloc; mod keymap; -#[macro_use] -mod macros; -mod lcd; mod matrix; -mod st7701s; +mod peripherals; mod vial; use core::alloc::Layout; -use core::ptr::addr_of_mut; use alloc::boxed::Box; use alloc::vec; use bt_hci::controller::ExternalController; use embassy_executor::Spawner; use esp_alloc::{HeapRegion, MemoryCapability}; -use esp_hal::clock::{CpuClock, RtcClock}; -use esp_hal::delay::Delay; +use esp_hal::clock::CpuClock; use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig}; -use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; +use esp_hal::gpio::{Flex, Input, InputConfig, Io, Level, Output, OutputConfig, Pull}; +use esp_hal::handler; use esp_hal::i2c::master::{I2c, I2cAddress}; +use esp_hal::interrupt::Priority; +use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::lcd_cam::LcdCam; -use esp_hal::lcd_cam::lcd::dpi::{Dpi, Format, FrameTiming}; -use esp_hal::lcd_cam::lcd::{ClockMode, Phase, Polarity}; +use esp_hal::lcd_cam::lcd::dpi::Dpi; use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; -use esp_hal::otg_fs::Usb; -use esp_hal::otg_fs::asynch::{Config, Driver}; -// use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock}; +use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock}; use esp_hal::rng::TrngSource; -use esp_hal::rtc_cntl::sleep::RtcSleepConfig; -use esp_hal::rtc_cntl::{Rtc, sleep}; -use esp_hal::time::Rate; -use esp_hal::timer::systimer::SystemTimer; +use esp_hal::system::Stack; use esp_hal::timer::timg::TimerGroup; +use esp_hal::uart::Uart; +use esp_hal::{Blocking, ram}; use esp_radio::Controller; use esp_radio::ble::controller::BleConnector; +use esp_rtos::embassy::InterruptExecutor; use esp_storage::FlashStorage; -use log::{LevelFilter, info}; -use rmk::ble::build_ble_stack; +use itertools::chain; +use log::{error, info}; use rmk::channel::EVENT_CHANNEL; use rmk::config::{BehaviorConfig, PositionalConfig, RmkConfig, StorageConfig, VialConfig}; use rmk::debounce::default_debouncer::DefaultDebouncer; -use rmk::futures::future::join3; +use rmk::embassy_futures::yield_now; use rmk::input_device::Runnable; +use rmk::join_all; use rmk::keyboard::Keyboard; -use rmk::matrix::Matrix; use rmk::storage::async_flash_wrapper; -use rmk::{HostResources, initialize_keymap_and_storage, run_devices, run_rmk}; +use rmk::{initialize_keymap_and_storage, run_devices, run_rmk}; use static_cell::StaticCell; use {esp_alloc as _, esp_backtrace as _}; -use crate::keymap::*; -use crate::lcd::spi_write; use crate::matrix::IoeMatrix; +use crate::peripherals::st7701s::St7701s; use crate::vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID}; // This creates a default app-descriptor required by the esp-idf bootloader. @@ -67,17 +61,18 @@ esp_bootloader_esp_idf::esp_app_desc!(); static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); #[esp_rtos::main] -async fn main(spawner: Spawner) { +async fn main(_spawner: Spawner) { esp_println::logger::init_logger_from_env(); info!("Logger initialized!"); - let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); - // .with_psram(PsramConfig { - // size: PsramSize::AutoDetect, - // core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m), - // flash_frequency: FlashFreq::default(), - // ram_frequency: SpiRamFreq::Freq80m, - // }); + let config = esp_hal::Config::default() + .with_cpu_clock(CpuClock::max()) + .with_psram(PsramConfig { + size: PsramSize::AutoDetect, + core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m), + flash_frequency: FlashFreq::default(), + ram_frequency: SpiRamFreq::Freq80m, + }); let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config); info!("System initialized!"); @@ -85,52 +80,20 @@ async fn main(spawner: Spawner) { esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 64 * 1024); info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats()); - // let timer0 = SystemTimer::new(peripherals.SYSTIMER); - // esp_hal_embassy::init(timer0.alarm0); - // info!("Embassy initialized!"); + // Initialize the PSRAM allocator. + { + let (psram_offset, psram_size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM); + unsafe { + PSRAM_ALLOCATOR.add_region(HeapRegion::new( + psram_offset, + psram_size, + MemoryCapability::External.into(), + )); + } + } - // // Initialize the PSRAM allocator. - // { - // let (psram_offset, psram_size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM); - // unsafe { - // PSRAM_ALLOCATOR.add_region(HeapRegion::new( - // psram_offset, - // psram_size, - // MemoryCapability::External.into(), - // )); - // } - // } - // const BUFFER_LEN: usize = core::mem::size_of::() - // * (360 + /* TODO: Figure out why more bytes are needed: */ 8) - // * 960; - // // Note: We just leak this buffer. - // let buffer_ptr = unsafe { - // // ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM - // // have extra alignment requirements. The address and size of the buffer pointed to by each - // // descriptor must be a multiple of the cache line (block) size. This is 32 bytes on - // // ESP32-S3. - // PSRAM_ALLOCATOR.alloc_caps( - // MemoryCapability::External.into(), - // Layout::from_size_align(BUFFER_LEN, 32).unwrap(), - // ) - // }; - // let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_ptr, BUFFER_LEN) }; - // let burst_config: BurstConfig = ExternalBurstConfig::Size16.into(); - - // info!( - // "PSRAM SPI burst config: max_compatible_chunk_size={}", - // burst_config.max_compatible_chunk_size() - // ); - - // let dma_buf_descs_len = - // esp_hal::dma::descriptor_count(BUFFER_LEN, burst_config.max_compatible_chunk_size(), false); - // // Descriptors are initialized by `DmaTxBuf::new`. - // let dma_buf_descs = Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice()); - // let mut dma_buf = DmaTxBuf::new(dma_buf_descs, buffer).unwrap(); - // // let mut dma_buf = dma_tx_buffer!(BUFFER_LEN).unwrap(); - - // TODO: Spawn some tasks - let _ = spawner; + let mut io = Io::new(peripherals.IO_MUX); + io.set_interrupt_handler(interrupt_handler); // Enable pull-up on GPIO0 to prevent booting into download mode. let gpio0 = Output::new( @@ -149,20 +112,12 @@ async fn main(spawner: Spawner) { let mut _pwm = McPwm::new(peripherals.MCPWM0, PeripheralClockConfig::with_prescaler(1)); let mut _pwm_pin = Output::new(peripherals.GPIO21, Level::High, OutputConfig::default()); - let mut matrix_interrupt = Input::new( - peripherals.GPIO7, - InputConfig::default().with_pull(Pull::Up), - ); - - // esp_alloc::heap_allocator!(size: 72 * 1024); let timg0 = TimerGroup::new(peripherals.TIMG0); - #[cfg(target_arch = "riscv32")] let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); esp_rtos::start( - timg0.timer0, - #[cfg(target_arch = "riscv32")] - software_interrupt.software_interrupt0, + timg0.timer0, /*, software_interrupt.software_interrupt0 */ ); + // Enable the TRNG source, so `Trng` can be constructed. let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1); let mut rng = esp_hal::rng::Trng::try_new().unwrap(); static RADIO: StaticCell> = StaticCell::new(); @@ -171,12 +126,19 @@ async fn main(spawner: Spawner) { let connector = BleConnector::new(radio, bluetooth, Default::default()).unwrap(); let controller: ExternalController<_, 20> = ExternalController::new(connector); let central_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7]; - let mut host_resources = HostResources::new(); - let stack = build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await; + #[cfg(feature = "ble")] + let mut host_resources = rmk::HostResources::new(); + #[cfg(feature = "ble")] + let stack = + rmk::ble::build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await; - // // Initialize USB + // Initialize USB #[cfg(not(feature = "no_usb"))] let usb_driver = { + use core::ptr::addr_of_mut; + use esp_hal::otg_fs::Usb; + use esp_hal::otg_fs::asynch::{Config, Driver}; + static mut EP_MEMORY: [u8; 1024] = [0; 1024]; let usb = Usb::new(peripherals.USB0, peripherals.GPIO20, peripherals.GPIO19); // Create the driver, from the HAL. @@ -188,102 +150,12 @@ async fn main(spawner: Spawner) { let flash = FlashStorage::new(peripherals.FLASH); let flash = async_flash_wrapper(flash); - let mut sck = Output::new(peripherals.GPIO36, Level::High, OutputConfig::default()); - let mut mosi = Flex::new(peripherals.GPIO35); - let mut cs = Output::new(peripherals.GPIO6, Level::High, OutputConfig::default()); + let sck = Output::new(peripherals.GPIO36, Level::High, OutputConfig::default()); + let mosi = Flex::new(peripherals.GPIO35); + let cs = Output::new(peripherals.GPIO6, Level::High, OutputConfig::default()); - mosi.set_input_enable(false); - mosi.set_output_enable(true); - - // info!("init sequence writing"); - - for (subsequence, delay_ms) in &*lcd::INIT_SEQUENCE_COMMANDS { - for command in subsequence { - spi_write( - command.address(), - command.args_iter().copied(), - &mut mosi, - &mut sck, - &mut cs, - ); - // info!("COMM 0x{:02X}", command.address()); - // for arg in command.args_iter() { - // info!("DATA 0x{arg:02X}"); - // } - } - Delay::new().delay_millis(*delay_ms as u32); // TODO: async? - // Timer::after_millis(*delay_ms).await - } - - // info!("init sequence written"); - - let mut lcd = LcdCam::new(peripherals.LCD_CAM).into_async().lcd; - 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 - .with_frequency(Rate::from_mhz(11)) // From Adafruit - .with_clock_mode(ClockMode { - polarity: Polarity::IdleLow, // From Adafruit - phase: Phase::ShiftHigh, // From Adafruit - }) - .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); - let mut dpi = Dpi::new(lcd, peripherals.DMA_CH2, lcd_config) + let lcd = LcdCam::new(peripherals.LCD_CAM).lcd; + let unconfigured_dpi = Dpi::new(lcd, peripherals.DMA_CH2, Default::default()) .unwrap() .with_de(peripherals.GPIO37) .with_pclk(peripherals.GPIO34) @@ -309,6 +181,8 @@ async fn main(spawner: Spawner) { .with_data14(peripherals.GPIO3) .with_data15(peripherals.GPIO4); + let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await; + // RMK config let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]); let storage_config = StorageConfig { @@ -345,7 +219,10 @@ async fn main(spawner: Spawner) { .with_sda(peripherals.GPIO8) .with_scl(peripherals.GPIO9); + let matrix_interrupt_low = Input::new(peripherals.GPIO7, InputConfig::default()); + let mut matrix = IoeMatrix::new( + matrix_interrupt_low, i2c.into_async(), DefaultDebouncer::new(), [I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT], @@ -353,7 +230,31 @@ async fn main(spawner: Spawner) { .await; let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller - join3( + // spawner.must_spawn(run_lcd(st7701s)); + static SECOND_CORE_STACK: StaticCell> = StaticCell::new(); + let second_core_stack = SECOND_CORE_STACK.init(Stack::new()); + esp_rtos::start_second_core( + peripherals.CPU_CTRL, + software_interrupt.software_interrupt0, + software_interrupt.software_interrupt1, + second_core_stack, + move || { + static EXECUTOR: StaticCell> = StaticCell::new(); + let exec = EXECUTOR.init(InterruptExecutor::new( + software_interrupt.software_interrupt2, + )); + let spawner = exec.start(Priority::Priority3); + spawner.must_spawn(run_lcd_task(st7701s)); + // static EXECUTOR: StaticCell = StaticCell::new(); + // let executor: &mut Executor = EXECUTOR.init(Executor::new()); + // executor.run(|spawner| { + // let task = run_lcd_task(st7701s); + // spawner.must_spawn(task); + // }); + }, + ); + + join_all![ run_devices! ( (matrix) => EVENT_CHANNEL, ), @@ -362,102 +263,120 @@ async fn main(spawner: Spawner) { &keymap, #[cfg(not(feature = "no_usb"))] usb_driver, + #[cfg(feature = "ble")] &stack, &mut storage, rmk_config, - ), - ) + ) // run_lcd(st7701s) + ] .await; } -// #[esp_rtos::main] -// async fn main(_s: Spawner) { -// // Initialize the peripherals and bluetooth controller -// esp_println::logger::init_logger(LevelFilter::max()); -// let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); -// esp_alloc::heap_allocator!(size: 72 * 1024); -// let timg0 = TimerGroup::new(peripherals.TIMG0); -// #[cfg(target_arch = "riscv32")] -// let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); -// esp_rtos::start( -// timg0.timer0, -// #[cfg(target_arch = "riscv32")] -// software_interrupt.software_interrupt0, -// ); -// let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1); -// let mut rng = esp_hal::rng::Trng::try_new().unwrap(); -// static RADIO: StaticCell> = StaticCell::new(); -// let radio = RADIO.init(esp_radio::init().unwrap()); -// let bluetooth = peripherals.BT; -// let connector = BleConnector::new(radio, bluetooth, Default::default()).unwrap(); -// let controller: ExternalController<_, 20> = ExternalController::new(connector); -// let central_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7]; -// let mut host_resources = HostResources::new(); -// let stack = build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await; +#[embassy_executor::task] +async fn run_lcd_task(st7701s: St7701s<'static, Blocking>) { + run_lcd(st7701s).await +} -// // Initialize the flash -// let flash = FlashStorage::new(peripherals.FLASH); -// let flash = async_flash_wrapper(flash); +async fn run_lcd(mut st7701s: St7701s<'static, Blocking>) { + const PADDING_LEFT: usize = 121; + const PADDING_RIGHT: usize = 7; + const COLORS_WIDTH: usize = 240; + const COLORS_HEIGHT: usize = 960; -// // Initialize the IO pins -// let (row_pins, col_pins) = config_matrix_pins_esp!(peripherals: peripherals, input: [GPIO6, GPIO7, GPIO21, GPIO35], output: [GPIO3, GPIO4, GPIO5]); + const MAX_RED: u8 = (1 << 5) - 1; + const MAX_GREEN: u8 = (1 << 6) - 1; + const MAX_BLUE: u8 = (1 << 5) - 1; -// // RMK config -// let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]); -// let storage_config = StorageConfig { -// start_addr: 0x3f0000, -// num_sectors: 16, -// ..Default::default() -// }; -// let rmk_config = RmkConfig { -// vial_config, -// storage_config, -// ..Default::default() -// }; + fn rgb(r: u8, g: u8, b: u8) -> u16 { + (((r & MAX_RED) as u16) << 11) | (((g & MAX_GREEN) as u16) << 5) | ((b & MAX_BLUE) as u16) + } -// // Initialze keyboard stuffs -// // Initialize the storage and keymap -// let mut default_keymap = keymap::get_default_keymap(); -// let mut behavior_config = BehaviorConfig::default(); -// let mut per_key_config = PositionalConfig::default(); -// let (keymap, mut storage) = initialize_keymap_and_storage( -// &mut default_keymap, -// flash, -// &storage_config, -// &mut behavior_config, -// &mut per_key_config, -// ) -// .await; + fn row(edge: u16) -> impl Iterator + Clone { + chain![ + core::iter::repeat_n(rgb(0, 0, 0xFF), PADDING_LEFT), + core::iter::once(rgb(0xFF, 0xFF, 0xFF)), + core::iter::repeat_n(edge, COLORS_WIDTH - 2), + core::iter::once(rgb(0xFF, 0xFF, 0xFF)), + core::iter::repeat_n(rgb(0, 0, 0xFF), PADDING_RIGHT), + ] + } -// // Initialize the matrix and keyboard -// let debouncer = DefaultDebouncer::new(); -// let mut i2c = I2c::new( -// peripherals.I2C0, -// esp_hal::i2c::master::Config::default().with_frequency(Rate::from_khz(400)), -// ) -// .unwrap() -// .with_sda(peripherals.GPIO8) -// .with_scl(peripherals.GPIO9); + let mut colors = chain![ + row(rgb(0xFF, 0, 0)), + core::iter::repeat_n(row(0), COLORS_HEIGHT - 2).flatten(), + row(rgb(0xFF, 0xFF, 0)), + ]; -// const I2C_ADDR_MATRIX_LEFT: I2cAddress = I2cAddress::SevenBit(0b0100000); -// const I2C_ADDR_MATRIX_RIGHT: I2cAddress = I2cAddress::SevenBit(0b0100001); + const BUFFER_LEN: usize = core::mem::size_of::() + * (360 + /* TODO: Figure out why more bytes are needed: */ 8) + * 960; + // Allocate the framebuffer in the external PSRAM memory. + // Note: We just leak this buffer. + let buffer_ptr = unsafe { + // ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM + // have extra alignment requirements. The address and size of the buffer pointed to by each + // descriptor must be a multiple of the cache line (block) size. This is 32 bytes on ESP32-S3. + PSRAM_ALLOCATOR.alloc_caps( + MemoryCapability::External.into(), + Layout::from_size_align(BUFFER_LEN, 32).unwrap(), + ) + }; + let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_ptr, BUFFER_LEN) }; + let burst_config: BurstConfig = ExternalBurstConfig::Size16.into(); -// let mut matrix = IoeMatrix::new( -// i2c.into_async(), -// debouncer, -// [I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT], -// ) -// .await; -// // let mut matrix = Matrix::<_, _, _, ROW, COL, true>::new(row_pins, col_pins, debouncer); -// // let mut matrix = rmk::matrix::TestMatrix::::new(); -// let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller + info!( + "PSRAM SPI burst config: max_compatible_chunk_size={}", + burst_config.max_compatible_chunk_size() + ); + let dma_buf_descs_len = + esp_hal::dma::descriptor_count(BUFFER_LEN, burst_config.max_compatible_chunk_size(), false); + // Descriptors are initialized by `DmaTxBuf::new`. + let dma_buf_descs = Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice()); + let mut dma_buf = DmaTxBuf::new(dma_buf_descs, buffer).unwrap(); -// join3( -// run_devices! ( -// (matrix) => EVENT_CHANNEL, -// ), -// keyboard.run(), // Keyboard is special -// run_rmk(&keymap, &stack, &mut storage, rmk_config), -// ) -// .await; -// } + { + for (chunk, color) in dma_buf.as_mut_slice().chunks_mut(2).zip(&mut colors) { + chunk.copy_from_slice(&color.to_le_bytes()); + } + + info!("chunk addr: {}", dma_buf.as_slice().as_ptr() as usize); + // colors.next(); // Shift colors + } + + loop { + // Timer::after(Duration::from_millis(100)).await; + yield_now().await; + + // TODO: Use bounce buffers: + // https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32s3/api-reference/peripherals/lcd.html#bounce-buffer-with-single-psram-frame-buffer + // They need to be implemented in esp-hal. + let transfer = match st7701s.dpi.send(false, dma_buf) { + Err((error, result_dpi, result_dma_buf)) => { + error!( + "An error occurred while initiating transfer of the framebuffer to the LCD display: {error:?}" + ); + st7701s.dpi = result_dpi; + dma_buf = result_dma_buf; + continue; + } + Ok(transfer) => transfer, + }; + let result; + (result, st7701s.dpi, dma_buf) = transfer.wait(); + + if let Err(error) = result { + error!( + "An error occurred while transferring framebuffer to the LCD display: {error:?}" + ); + } + } +} + +#[handler] +#[ram] // TODO: Is this necessary? +fn interrupt_handler() { + esp_println::println!( + "GPIO Interrupt with priority {}", + esp_hal::xtensa_lx::interrupt::get_level() + ); +} diff --git a/firmware2/src/matrix.rs b/firmware2/src/matrix.rs index 8fec4b9..a0704e7 100644 --- a/firmware2/src/matrix.rs +++ b/firmware2/src/matrix.rs @@ -1,11 +1,13 @@ use esp_hal::{ Async, + gpio::{Input, InputConfig, Pull}, i2c::master::{BusTimeout, I2c, I2cAddress, SoftwareTimeout}, time::Rate, }; use log::info; use rmk::{ debounce::{DebounceState, DebouncerTrait}, + embassy_futures::yield_now, event::{Event, KeyboardEvent}, input_device::InputDevice, matrix::{KeyState, MatrixTrait}, @@ -40,6 +42,8 @@ pub struct IoeMatrix where D: DebouncerTrait, { + /// Goes low when the matrix was changed and should be read. + matrix_interrupt_low: Input<'static>, i2c: I2c<'static, Async>, /// IO expander addresses. ioe_addresses: [I2cAddress; 2], @@ -60,10 +64,12 @@ where D: DebouncerTrait, { pub async fn new( + mut matrix_interrupt_low: Input<'static>, mut i2c: I2c<'static, Async>, debouncer: D, ioe_addresses: [I2cAddress; 2], ) -> Self { + matrix_interrupt_low.apply_config(&InputConfig::default().with_pull(Pull::Up)); i2c.apply_config( &esp_hal::i2c::master::Config::default() .with_frequency(Rate::from_khz(400)) @@ -73,12 +79,19 @@ where .unwrap(); for addr in ioe_addresses { - i2c.write_async(addr, &[0b10010000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) - .await - .unwrap(); + loop { + let Err(error) = i2c + .write_async(addr, &[0b10010000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + .await + else { + break; + }; + info!("I2C timed out when writing the inversion mask to an IO expander: {error:?}"); + } } Self { + matrix_interrupt_low, i2c, ioe_addresses, debouncer, @@ -98,6 +111,10 @@ where async fn read_event(&mut self) -> Event { loop { if self.scan_pos == 0 { + // TODO: Only execute if interrupt received. + // Timer::after(Duration::from_millis(100)).await; + yield_now().await; + // Load data from IO expanders. let mut input_register_banks = [0_u8; 10]; diff --git a/firmware2/src/peripherals/mod.rs b/firmware2/src/peripherals/mod.rs new file mode 100644 index 0000000..5901968 --- /dev/null +++ b/firmware2/src/peripherals/mod.rs @@ -0,0 +1 @@ +pub mod st7701s; diff --git a/firmware2/src/lcd.rs b/firmware2/src/peripherals/st7701s/lcd.rs similarity index 91% rename from firmware2/src/lcd.rs rename to firmware2/src/peripherals/st7701s/lcd.rs index b03322e..2a4c4a2 100644 --- a/firmware2/src/lcd.rs +++ b/firmware2/src/peripherals/st7701s/lcd.rs @@ -1,95 +1,98 @@ use alloc::{boxed::Box, vec, vec::Vec}; use core::iter::once; -use esp_hal::{ - delay::Delay, - gpio::{Flex, Level, Output}, -}; +use embassy_time::{Duration, Timer}; +use esp_hal::gpio::{Flex, Level, Output}; use lazy_static::lazy_static; -use crate::st7701s::Command; +use crate::peripherals::st7701s::Command; -fn spi_delay() { - Delay::new().delay_micros(1); // TODO: Async? +async fn spi_delay() { + Timer::after(Duration::from_micros(1)).await; } -fn spi_dummy_bit(sck: &mut Output) { +async fn spi_dummy_bit(sck: &mut Output<'_>) { sck.set_low(); - spi_delay(); + spi_delay().await; sck.set_high(); - spi_delay(); + spi_delay().await; } -fn spi_write_bit(bit: bool, mosi: &mut Flex, sck: &mut Output) { +async fn spi_write_bit(bit: bool, mosi: &mut Flex<'_>, sck: &mut Output<'_>) { mosi.set_level(if bit { Level::High } else { Level::Low }); sck.set_low(); - spi_delay(); + spi_delay().await; sck.set_high(); - spi_delay(); + spi_delay().await; } -fn spi_read_bit(mosi: &mut Flex, sck: &mut Output) -> bool { +async fn spi_read_bit(mosi: &mut Flex<'_>, sck: &mut Output<'_>) -> bool { sck.set_low(); - spi_delay(); + spi_delay().await; sck.set_high(); - spi_delay(); + spi_delay().await; mosi.is_high() } -fn spi_write_bits(bits: impl Iterator, mosi: &mut Flex, sck: &mut Output) { +async fn spi_write_bits( + bits: impl Iterator, + mosi: &mut Flex<'_>, + sck: &mut Output<'_>, +) { for bit in bits { - spi_write_bit(bit, mosi, sck); + spi_write_bit(bit, mosi, sck).await; } } -fn spi_write_word(is_param: bool, data: u8, mosi: &mut Flex, sck: &mut Output) { +async fn spi_write_word(is_param: bool, data: u8, mosi: &mut Flex<'_>, sck: &mut Output<'_>) { assert!(sck.is_set_high()); spi_write_bits( once(is_param).chain((0..8).map(|i| (data >> i) & 1 != 0).rev()), mosi, sck, - ); + ) + .await; } -pub fn spi_write( +pub async fn spi_write( command: u8, args: impl IntoIterator, - mosi: &mut Flex, - sck: &mut Output, - cs: &mut Output, + mosi: &mut Flex<'_>, + sck: &mut Output<'_>, + cs: &mut Output<'_>, ) { cs.set_low(); - spi_write_word(false, command, mosi, sck); + spi_write_word(false, command, mosi, sck).await; for arg in args { - spi_write_word(true, arg, mosi, sck); + spi_write_word(true, arg, mosi, sck).await; } cs.set_high(); } -fn spi_read( +pub async fn spi_read( command: u8, dummy_cycle: bool, - mosi: &mut Flex, - sck: &mut Output, - cs: &mut Output, + mosi: &mut Flex<'_>, + sck: &mut Output<'_>, + cs: &mut Output<'_>, output_buffer: &mut [u8], ) { output_buffer.fill(0); cs.set_low(); - spi_write_word(false, command, mosi, sck); + spi_write_word(false, command, mosi, sck).await; mosi.set_output_enable(false); mosi.set_input_enable(true); if dummy_cycle { - spi_dummy_bit(sck); + spi_dummy_bit(sck).await; } for output_byte in output_buffer { for i in (0..8).rev() { - if spi_read_bit(mosi, sck) { + if spi_read_bit(mosi, sck).await { *output_byte |= 1 << i; } } @@ -101,7 +104,7 @@ fn spi_read( cs.set_high(); } -use crate::st7701s::*; +use crate::peripherals::st7701s::*; lazy_static! { pub static ref INIT_SEQUENCE_COMMANDS: Vec<(Vec>, u64)> = vec![ diff --git a/firmware2/src/st7701s.rs b/firmware2/src/peripherals/st7701s/mod.rs similarity index 81% rename from firmware2/src/st7701s.rs rename to firmware2/src/peripherals/st7701s/mod.rs index 587c2cf..4b0c23d 100644 --- a/firmware2/src/st7701s.rs +++ b/firmware2/src/peripherals/st7701s/mod.rs @@ -1,7 +1,19 @@ -use bitflags::bitflags; -use esp_hal::gpio::OutputPin; +use embassy_time::Timer; +use esp_hal::{ + DriverMode, + gpio::{Flex, Level, Output}, + lcd_cam::lcd::{ + ClockMode, Phase, Polarity, + dpi::{Dpi, Format, FrameTiming}, + }, + time::Rate, +}; +use lcd::spi_write; +use log::debug; use paste::paste; +mod lcd; + pub trait Command { fn address(&self) -> u8; fn args_iter(&'_ self) -> core::slice::Iter<'_, u8>; @@ -1196,17 +1208,136 @@ define_commands! { } } -pub struct St7701s {} +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 St7701s { - pub fn new() -> Self { - let _ = CmdNop::default(); - let x = CmdGsl( - CmdGslArg0::new().with_tesl_msb(1), - CmdGslArg1::new().with_tesl_lsb(0), - ); - x.address(); - x.args_iter(); - todo!() +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 + .with_frequency(Rate::from_mhz(11)) // From Adafruit + .with_clock_mode(ClockMode { + polarity: Polarity::IdleLow, // From Adafruit + phase: Phase::ShiftHigh, // From Adafruit + }) + .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; + } } }