Glitchless LCD driving

This commit is contained in:
Jakub Hlusička 2026-01-01 01:24:52 +01:00
parent a9870fe133
commit f7fbe366f6
4 changed files with 59 additions and 18 deletions

3
firmware2/Cargo.lock generated
View file

@ -15,6 +15,7 @@ dependencies = [
"critical-section",
"embassy-embedded-hal",
"embassy-executor",
"embassy-sync 0.7.2",
"embassy-time",
"embedded-cli",
"embedded-io 0.6.1",
@ -1521,6 +1522,7 @@ dependencies = [
"document-features",
"embassy-executor-macros",
"embassy-executor-timer-queue",
"log",
]
[[package]]
@ -1599,6 +1601,7 @@ dependencies = [
"futures-core",
"futures-sink",
"heapless 0.8.0",
"log",
]
[[package]]

View file

@ -24,9 +24,10 @@ rmk = { version = "0.8.2", default-features = false, features = [
"storage",
"vial",
] }
embassy-executor = { version = "0.9" }
embassy-executor = { version = "0.9", features = ["log"] }
embassy-time = { version = "0.5.0", features = ["log"] }
embassy-embedded-hal = "0.5.0"
embassy-sync = { version = "0.7.2", features = ["log"] }
esp-backtrace = { version = "0.18", default-features = false, features = [
"esp32s3",
"println",

View file

@ -6,13 +6,18 @@ extern crate alloc;
use core::alloc::Layout;
use core::cell::RefCell;
use core::sync::atomic::AtomicBool;
use core::time::Duration;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::sync::Arc;
use alloc::vec;
use bt_hci::controller::ExternalController;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::{Delay, Timer};
use esp_alloc::{HeapRegion, MemoryCapability};
use esp_hal::clock::CpuClock;
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
@ -45,7 +50,7 @@ use rmk::storage::async_flash_wrapper;
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
use slint::platform::WindowAdapter;
use slint::platform::software_renderer::{MinimalSoftwareWindow, RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer};
use slint::{ComponentHandle, PhysicalSize, WindowSize};
use slint::{ComponentHandle, PhysicalSize, PlatformError, WindowSize};
use static_cell::StaticCell;
use ui::AppWindow;
use ui::window_adapter::SoftwareWindowAdapter;
@ -74,6 +79,12 @@ const LOG_LEVEL_FILTER: LevelFilter = LevelFilter::Info;
static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
/// Used to signal that MCU is ready to submit the framebuffer to the LCD.
static SIGNAL_LCD_SUBMIT: Signal<CriticalSectionRawMutex, ()> = Signal::new();
/// Used to signal that the MCU is ready to render the GUI.
static SIGNAL_UI_RENDER: Signal<CriticalSectionRawMutex, ()> = Signal::new();
#[esp_rtos::main]
async fn main(_spawner: Spawner) {
#[cfg(not(feature = "alt-log"))]
@ -199,8 +210,6 @@ async fn main(_spawner: Spawner) {
.with_data3(peripherals.GPIO41)
.with_data4(peripherals.GPIO42)
// Green
// .with_data5(peripherals.GPIO5) TODO
// .with_data6(peripherals.GPIO12)
.with_data7(peripherals.GPIO13)
.with_data8(peripherals.GPIO14)
.with_data9(peripherals.GPIO15)
@ -212,6 +221,12 @@ async fn main(_spawner: Spawner) {
.with_data14(peripherals.GPIO3)
.with_data15(peripherals.GPIO4);
#[cfg(not(feature = "alt-log"))]
let unconfigured_dpi = unconfigured_dpi
// Green
.with_data5(peripherals.GPIO5)
.with_data6(peripherals.GPIO12);
let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await;
// RMK config
@ -326,10 +341,23 @@ async fn run_renderer_task(backend: SlintBackend) {
slint::platform::set_platform(Box::new(backend))
.expect("backend already initialized");
loop {
let main = AppWindow::new().unwrap();
let main = AppWindow::new().unwrap();
main.run().unwrap();
// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
// async by having only one iteration of the loop run, and `await`ing here.
// The following block is analogous to `main.run()`.
{
main.show().unwrap();
loop {
slint::run_event_loop().unwrap();
SIGNAL_LCD_SUBMIT.signal(());
// Timer::after_millis(50).await;
SIGNAL_UI_RENDER.wait().await;
}
#[expect(unreachable_code)]
main.hide().unwrap();
}
}
@ -460,7 +488,8 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>, framebuffer: &'static
loop {
// Timer::after(Duration::from_millis(100)).await;
yield_now().await;
// yield_now().await;
SIGNAL_LCD_SUBMIT.wait().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
@ -477,16 +506,22 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>, framebuffer: &'static
Ok(transfer) => transfer,
};
while !transfer.is_done() {
// Timer::after_millis(1).await;
yield_now().await;
}
// This could be used to allow other tasks to be executed on the first core, but that causes
// the flash to be accessed, which interferes with the framebuffer transfer.
// For that reason, it is disabled, and this task blocks the first core, until the transfer
// is complete.
// while !transfer.is_done() {
// // Timer::after_millis(1).await;
// yield_now().await;
// }
let result;
let dma_buf;
(result, st7701s.dpi, dma_buf) = transfer.wait();
framebuffer.dma_buf = Some(dma_buf);
SIGNAL_UI_RENDER.signal(());
if let Err(error) = result {
error!(
"An error occurred while transferring framebuffer to the LCD display: {error:?}"

View file

@ -1,7 +1,7 @@
use core::{cell::RefCell, time::Duration};
use alloc::rc::Rc;
use esp_hal::time::Instant;
use esp_hal::{time::Instant};
use log::info;
use slint::{PhysicalSize, WindowSize, platform::software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}};
@ -36,21 +36,23 @@ impl slint::platform::Platform for SlintBackend {
}
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
loop {
slint::platform::update_timers_and_animations();
// Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
// in `crate::run_renderer_task`, where we can make use of `await`.
/* loop */ {
if let Some(window) = self.window.borrow().clone() {
// TODO: Event dispatch. Here or in `run_renderer_task`?
// window.try_dispatch_event(todo!())?;
window.draw_if_needed(|renderer| {
// TODO: Proper synchronization.
let framebuffer = unsafe { &mut *self.framebuffer.0 };
// TODO: Try using height to see if it rotates the screen correctly. Might need
// to swap dimensions elsewhere.
renderer.render(framebuffer, self.window_size[1] as usize);
info!("UI rendered.");
});
}
}
Ok(())
}
}