diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 72079af..7efafc2 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -2149,7 +2149,9 @@ name = "esp-hal-bounce-buffers" version = "0.1.0" dependencies = [ "document-features", + "embassy-sync 0.7.2", "esp-hal", + "log", "ouroboros", ] diff --git a/firmware/acid-firmware/Cargo.toml b/firmware/acid-firmware/Cargo.toml index aed0cb7..426252c 100644 --- a/firmware/acid-firmware/Cargo.toml +++ b/firmware/acid-firmware/Cargo.toml @@ -30,6 +30,8 @@ develop-usb = ["limit-fps", "usb-log", "no-usb", "ble"] probe = ["limit-fps", "rtt-log", "no-usb", "ble"] # Formats the EKV database on boot. format-db = [] +# Avoid entering the critical section for the whole duration of printing a message to console. +racy-logging = [] [dependencies] rmk = { version = "0.8.2", git = "https://github.com/Limeth/rmk", rev = "1661c55f5c21e7d80ea3f93255df483302c74b84", default-features = false, features = [ @@ -87,7 +89,7 @@ esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } indoc = "2.0.7" ouroboros = "0.18.5" -esp-hal-bounce-buffers = { git = "https://forgejo.limeth.cz/limeth/esp-hal-bounce-buffers", features = ["esp32s3"] } +esp-hal-bounce-buffers = { git = "https://forgejo.limeth.cz/limeth/esp-hal-bounce-buffers", rev = "8d3763a190368f476aed6d98777264c959bfdc2d", features = ["esp32s3", "log"] } # A fork of slint with patches for `allocator_api` support. # Don't forget to change `slint-build` in build dependencies, if this is changed. diff --git a/firmware/acid-firmware/src/logging.rs b/firmware/acid-firmware/src/logging.rs index 36fd092..028816b 100644 --- a/firmware/acid-firmware/src/logging.rs +++ b/firmware/acid-firmware/src/logging.rs @@ -69,21 +69,29 @@ pub mod usb { #[cfg(feature = "alt-log")] #[macro_use] pub mod uart { - use crate::util::MutexExt; - use super::*; use core::{cell::RefCell, fmt::Write}; - use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; + use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use esp_hal::{Blocking, uart::UartTx}; use log::{Log, info}; - static ALT_LOGGER_UART: Mutex< + #[cfg(feature = "racy-logging")] + static ALT_LOGGER_UART: embassy_sync::mutex::Mutex< CriticalSectionRawMutex, RefCell>>, - > = Mutex::new(RefCell::new(None)); + > = embassy_sync::mutex::Mutex::new(RefCell::new(None)); + #[cfg(not(feature = "racy-logging"))] + static ALT_LOGGER_UART: embassy_sync::blocking_mutex::Mutex< + CriticalSectionRawMutex, + RefCell>>, + > = embassy_sync::blocking_mutex::Mutex::new(RefCell::new(None)); + + #[cfg(feature = "racy-logging")] pub fn with_uart_tx(f: impl FnOnce(&'_ mut UartTx<'static, Blocking>) -> R) -> R { + use crate::util::MutexExt; + // Safety: // * The guard is not held across yield points. // * **CARE MUST BE TAKEN NOT TO INVOKE THIS FUNCTION FROM AN INTERRUPT HANDLER.** @@ -94,6 +102,16 @@ pub mod uart { (f)(uart) } + #[cfg(not(feature = "racy-logging"))] + pub fn with_uart_tx(f: impl FnOnce(&'_ mut UartTx<'static, Blocking>) -> R) -> R { + ALT_LOGGER_UART.lock(|uart| { + let mut uart = uart.borrow_mut(); + let uart = uart.as_mut().unwrap(); + + (f)(uart) + }) + } + #[allow(unused)] macro_rules! println { () => {{ @@ -156,11 +174,21 @@ pub mod uart { pub fn setup_logging(uart_tx: UartTx<'static, Blocking>) { { - // Safety: - // * The guard is not held across yield points. - // * This function is not invoked from an interrupt handler. - let uart = unsafe { ALT_LOGGER_UART.lock_blocking() }; - *uart.borrow_mut() = Some(uart_tx); + #[cfg(feature = "racy-logging")] + { + use crate::util::MutexExt; + + // Safety: + // * The guard is not held across yield points. + // * This function is not invoked from an interrupt handler. + let uart = unsafe { ALT_LOGGER_UART.lock_blocking() }; + *uart.borrow_mut() = Some(uart_tx); + } + + #[cfg(not(feature = "racy-logging"))] + ALT_LOGGER_UART.lock(move |uart| { + *uart.borrow_mut() = Some(uart_tx); + }); } unsafe { diff --git a/firmware/acid-firmware/src/main.rs b/firmware/acid-firmware/src/main.rs index c7c5932..8a95a94 100644 --- a/firmware/acid-firmware/src/main.rs +++ b/firmware/acid-firmware/src/main.rs @@ -47,7 +47,7 @@ use esp_hal::i2c::master::{I2c, I2cAddress}; use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl}; use esp_hal::lcd_cam::LcdCam; use esp_hal::lcd_cam::lcd::dpi::Dpi; -use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; +use esp_hal::ledc::{self, LSGlobalClkSource, Ledc, LowSpeed}; use esp_hal::peripherals::{DMA_CH0, SPI2}; use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock}; use esp_hal::ram; @@ -58,7 +58,9 @@ use esp_hal::system::Stack; use esp_hal::timer::timg::TimerGroup; use esp_hal::uart::{Uart, UartRx}; use esp_hal::{Blocking, interrupt}; -use esp_hal_bounce_buffers::{DmaBounce, Swapchain, allocate_dma_buffer_in}; +use esp_hal_bounce_buffers::{ + DmaBounce, RunningDmaBounceHandle, Swapchain, allocate_dma_buffer_in, +}; use esp_rtos::embassy::{Executor, InterruptExecutor}; use esp_storage::FlashStorage; use i_slint_core::software_renderer::TargetPixel; @@ -82,7 +84,7 @@ use static_cell::StaticCell; use {esp_alloc as _, esp_backtrace as _}; use crate::matrix::IoeMatrix; -use crate::peripherals::st7701s::St7701s; +use crate::peripherals::st7701s::{St7701s, St7701sController}; use crate::proxy::create_hid_report_interceptor; use crate::ui::backend::SlintBackend; use crate::ui::dpi::Framebuffer; @@ -144,12 +146,14 @@ async fn test_bounce_buffers_task( test_bounce_buffers(channel, peripheral, st7701s).await; } +#[allow(unused)] async fn test_bounce_buffers( channel: DMA_CH0<'static>, peripheral: SPI2<'static>, - st7701s: St7701s<'static, Blocking>, + mut st7701s: St7701s<'static, Blocking>, ) { error!("TEST BOUNCE BUFFERS SECTION ENTERED"); + const BYTES_PER_PIXEL: usize = core::mem::size_of::(); // Assume highest burst config setting. const EXTERNAL_BURST_CONFIG: ExternalBurstConfig = ExternalBurstConfig::Size32; @@ -215,11 +219,7 @@ async fn test_bounce_buffers( } } - warn!("FRONT_PORCH_SKIPPED_PIXELS: {FRONT_PORCH_SKIPPED_PIXELS}"); - warn!("WIDTH_STORED_PIXELS: {WIDTH_STORED_PIXELS}"); - warn!("ROWS_PER_WINDOW: {ROWS_PER_WINDOW}"); - - let buf = DmaBounce::new( + let mut dma_bounce = DmaBounce::new( Global, channel, AnySpi::from(peripheral), @@ -231,8 +231,18 @@ async fn test_bounce_buffers( burst_config, true, ); - buf.launch_interrupt_driven_task(); - error!("TEST BOUNCE BUFFERS SECTION DONE"); + let mut bb_controller = DmaBounceController::new(dma_bounce); + + error!("TEST BOUNCE BUFFERS TASK LAUNCHED"); + + loop { + bb_controller.start().await.unwrap(); + st7701s.controller.sleep_off().await; + Timer::after_secs(1).await; + st7701s.controller.sleep_on().await; + bb_controller.stop().await.unwrap(); + Timer::after_secs(1).await; + } } #[esp_rtos::main] @@ -313,10 +323,6 @@ async fn main(_spawner: Spawner) { // Enable antenna let _ = Output::new(peripherals.GPIO11, Level::Low, OutputConfig::default()); - // TODO: Use PWM to control the pwm_pin. - 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 sha_backend = ShaBackend::new(peripherals.SHA); let _sha_driver_handle = sha_backend.start(); @@ -341,6 +347,7 @@ async fn main(_spawner: Spawner) { // high_priority_task_spawner: interrupt_core_1_spawner, uart_rx, software_interrupt1: software_interrupt.software_interrupt1, + LEDC: peripherals.LEDC, RNG: peripherals.RNG, ADC1: peripherals.ADC1, USB0: peripherals.USB0, @@ -370,6 +377,7 @@ async fn main(_spawner: Spawner) { GPIO16: peripherals.GPIO16, GPIO19: peripherals.GPIO19, GPIO20: peripherals.GPIO20, + GPIO21: peripherals.GPIO21, GPIO34: peripherals.GPIO34, GPIO35: peripherals.GPIO35, GPIO36: peripherals.GPIO36, @@ -392,6 +400,7 @@ struct MainPeripherals { // high_priority_task_spawner: SendSpawner, uart_rx: UartRx<'static, Blocking>, software_interrupt1: SoftwareInterrupt<'static, 1>, + LEDC: esp_hal::peripherals::LEDC<'static>, RNG: esp_hal::peripherals::RNG<'static>, ADC1: esp_hal::peripherals::ADC1<'static>, USB0: esp_hal::peripherals::USB0<'static>, @@ -423,6 +432,7 @@ struct MainPeripherals { // GPIO18: esp_hal::peripherals::GPIO18<'static>, GPIO19: esp_hal::peripherals::GPIO19<'static>, GPIO20: esp_hal::peripherals::GPIO20<'static>, + GPIO21: esp_hal::peripherals::GPIO21<'static>, // GPIO33: esp_hal::peripherals::GPIO33<'static>, GPIO34: esp_hal::peripherals::GPIO34<'static>, GPIO35: esp_hal::peripherals::GPIO35<'static>, @@ -577,6 +587,11 @@ async fn main_task(peripherals: MainPeripherals) { info!("Flash memory configured!"); + let mut ledc = Ledc::new(peripherals.LEDC); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + let bl_timer = ledc.timer::(ledc::timer::Number::Timer0); + let bl_channel = ledc.channel::(ledc::channel::Number::Channel0, peripherals.GPIO21); + 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()); @@ -612,10 +627,11 @@ async fn main_task(peripherals: MainPeripherals) { .with_data5(peripherals.GPIO5) .with_data6(peripherals.GPIO12); - let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await; + let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi, bl_timer, bl_channel).await; info!("ST7701S-based LCD display initialized!"); + // Uncomment this to run bounce buffer test code instead. // test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s).await; // return; @@ -722,7 +738,7 @@ async fn main_task(peripherals: MainPeripherals) { 112, 368 - 112, 960, - 8, + 16, true, )); @@ -763,16 +779,11 @@ async fn main_task(peripherals: MainPeripherals) { info!("Second core started!"); - let mut user_controller = UserController::new(); + let bb_controller = DmaBounceController::new(framebuffer.bounce_buffers.take().unwrap()); + let mut user_controller = UserController::new(st7701s.controller, bb_controller); info!("Awaiting on all tasks..."); - framebuffer - .bounce_buffers - .take() - .unwrap() - .launch_interrupt_driven_task(); - // TODO: Probably want to select! instead and re-try. join_all![ run_alloc_stats_reporter(), @@ -835,19 +846,61 @@ async fn run_alloc_stats_reporter() { } } -struct UserController { - sub: ControllerSub, +enum DmaBounceControllerInner { + Idle(DmaBounce), + Transmitting(RunningDmaBounceHandle), } -impl UserController { - fn new() -> Self { +struct DmaBounceController { + inner: Option, +} + +impl DmaBounceController { + pub fn new(dma_bounce: DmaBounce) -> Self { + Self { + inner: Some(DmaBounceControllerInner::Idle(dma_bounce)), + } + } + + pub async fn start(&mut self) -> Result<(), ()> { + let DmaBounceControllerInner::Idle(dma_bounce) = self.inner.take().unwrap() else { + return Err(()); + }; + self.inner = Some(DmaBounceControllerInner::Transmitting( + dma_bounce.launch_interrupt_driven_task().await, + )); + Ok(()) + } + + pub async fn stop(&mut self) -> Result<(), ()> { + let DmaBounceControllerInner::Transmitting(running_dma_bounce) = self.inner.take().unwrap() + else { + return Err(()); + }; + self.inner = Some(DmaBounceControllerInner::Idle( + running_dma_bounce.stop().await, + )); + Ok(()) + } +} + +struct UserController<'a> { + sub: ControllerSub, + lcd_controller: St7701sController<'a>, + bb_controller: DmaBounceController, +} + +impl<'a> UserController<'a> { + fn new(lcd_controller: St7701sController<'a>, bb_controller: DmaBounceController) -> Self { Self { sub: CONTROLLER_CHANNEL.subscriber().unwrap(), + lcd_controller, + bb_controller, } } } -impl Controller for UserController { +impl<'a> Controller for UserController<'a> { type Event = ControllerEvent; async fn process_event(&mut self, event: Self::Event) { @@ -863,10 +916,14 @@ impl Controller for UserController { info!("Disabling LCD."); *rmk::channel::KEYBOARD_REPORT_SENDER.write().await = &rmk::channel::KEYBOARD_REPORT_RECEIVER; + self.lcd_controller.sleep_on().await; + self.bb_controller.stop().await.unwrap(); } true => { info!("Enabling LCD."); *rmk::channel::KEYBOARD_REPORT_SENDER.write().await = &KEYBOARD_REPORT_PROXY; + self.bb_controller.start().await.unwrap(); + self.lcd_controller.sleep_off().await; } } } diff --git a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs index b9a4b85..3af869d 100644 --- a/firmware/acid-firmware/src/peripherals/st7701s/mod.rs +++ b/firmware/acid-firmware/src/peripherals/st7701s/mod.rs @@ -6,10 +6,12 @@ use esp_hal::{ 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; @@ -1233,13 +1235,76 @@ define_commands! { } } +#[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 sck: Output<'d>, - pub mosi: Flex<'d>, - pub cs: Output<'d>, + pub controller: St7701sController<'d>, pub dpi: Dpi<'d, Dm>, } @@ -1247,11 +1312,15 @@ 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(); @@ -1276,7 +1345,7 @@ where // // 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_frequency(Rate::from_mhz(9)) // From Adafruit .with_clock_mode(ClockMode { polarity: Polarity::IdleLow, // From Adafruit phase: Phase::ShiftHigh, // From Adafruit @@ -1338,38 +1407,38 @@ where 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 { - sck, - mosi, - cs, + controller: St7701sController { + sck, + mosi, + cs, + backlight, + }, dpi: unconfigured_dpi, }; - lcd.send_init_sequence().await; + lcd.controller.send_init_sequence().await; + lcd.controller.sleep_on().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; - } - } } diff --git a/firmware/acid-firmware/src/ui/dpi.rs b/firmware/acid-firmware/src/ui/dpi.rs index 406bb11..cef0d55 100644 --- a/firmware/acid-firmware/src/ui/dpi.rs +++ b/firmware/acid-firmware/src/ui/dpi.rs @@ -1,67 +1,11 @@ -use core::{ - alloc::Layout, - cell::UnsafeCell, - ops::{Deref, DerefMut}, - sync::atomic::{self, AtomicBool}, -}; - -use alloc::{ - alloc::{Allocator, Global}, - boxed::Box, - sync::Arc, - vec, -}; -use bytemuck::{AnyBitPattern, NoUninit}; +use alloc::{alloc::Global, boxed::Box}; use esp_hal::{ - Blocking, - dma::{ - self, BurstConfig, DmaDescriptor, DmaTxBuffer, Mem2Mem, SimpleMem2Mem, - SimpleMem2MemTransfer, - }, - handler, - interrupt::{self, Priority}, - lcd_cam::lcd::dpi::{Dpi, DpiTransfer}, - peripherals::{DMA, DMA_CH0, Interrupt}, - ram, - spi::master::AnySpi, + Blocking, dma::BurstConfig, lcd_cam::lcd::dpi::Dpi, peripherals::DMA_CH0, spi::master::AnySpi, }; use esp_hal_bounce_buffers::{DmaBounce, Swapchain, SwapchainWriter, allocate_dma_buffer_in}; -use log::error; -use ouroboros::self_referencing; use crate::PSRAM_ALLOCATOR; -/// THIS IS TAKEN FROM https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/soc/esp32s3/mod.rs -/// Write back a specific range of data in the cache. -#[doc(hidden)] -#[unsafe(link_section = ".rwtext")] -pub unsafe fn cache_writeback_addr(addr: u32, size: u32) { - unsafe extern "C" { - fn rom_Cache_WriteBack_Addr(addr: u32, size: u32); - fn Cache_Suspend_DCache_Autoload() -> u32; - fn Cache_Resume_DCache_Autoload(value: u32); - } - // suspend autoload, avoid load cachelines being written back - unsafe { - let autoload = Cache_Suspend_DCache_Autoload(); - rom_Cache_WriteBack_Addr(addr, size); - Cache_Resume_DCache_Autoload(autoload); - } -} - -/// THIS IS TAKEN FROM https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/soc/esp32s3/mod.rs -/// Invalidate a specific range of addresses in the cache. -#[doc(hidden)] -#[unsafe(link_section = ".rwtext")] -pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { - unsafe extern "C" { - fn Cache_Invalidate_Addr(addr: u32, size: u32); - } - unsafe { - Cache_Invalidate_Addr(addr, size); - } -} - // TODO: Rename or get rid of. pub struct Framebuffer { pub width: u32, diff --git a/firmware/acid-firmware/src/util.rs b/firmware/acid-firmware/src/util.rs index c5ae610..f1107ad 100644 --- a/firmware/acid-firmware/src/util.rs +++ b/firmware/acid-firmware/src/util.rs @@ -2,7 +2,7 @@ use core::fmt::Display; use embassy_sync::{ blocking_mutex::raw::RawMutex, - mutex::{Mutex, MutexGuard, TryLockError}, + mutex::{Mutex, MutexGuard}, }; use embassy_time::Duration;