Implement display sleep, backlight disabling and bounce buffer

transmission stopping
This commit is contained in:
Jakub Hlusička 2026-02-27 21:25:59 +01:00
parent 6f42bf7a7b
commit 682522d556
7 changed files with 232 additions and 130 deletions

2
firmware/Cargo.lock generated
View file

@ -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",
]

View file

@ -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.

View file

@ -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<Option<UartTx<'static, Blocking>>>,
> = 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<Option<UartTx<'static, Blocking>>>,
> = embassy_sync::blocking_mutex::Mutex::new(RefCell::new(None));
#[cfg(feature = "racy-logging")]
pub fn with_uart_tx<R>(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<R>(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 {

View file

@ -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::<u16>();
// 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::<LowSpeed>(ledc::timer::Number::Timer0);
let bl_channel = ledc.channel::<LowSpeed>(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<DmaBounceControllerInner>,
}
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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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,

View file

@ -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;