Glitchless LCD driving
This commit is contained in:
parent
a9870fe133
commit
f7fbe366f6
3
firmware2/Cargo.lock
generated
3
firmware2/Cargo.lock
generated
|
|
@ -15,6 +15,7 @@ dependencies = [
|
||||||
"critical-section",
|
"critical-section",
|
||||||
"embassy-embedded-hal",
|
"embassy-embedded-hal",
|
||||||
"embassy-executor",
|
"embassy-executor",
|
||||||
|
"embassy-sync 0.7.2",
|
||||||
"embassy-time",
|
"embassy-time",
|
||||||
"embedded-cli",
|
"embedded-cli",
|
||||||
"embedded-io 0.6.1",
|
"embedded-io 0.6.1",
|
||||||
|
|
@ -1521,6 +1522,7 @@ dependencies = [
|
||||||
"document-features",
|
"document-features",
|
||||||
"embassy-executor-macros",
|
"embassy-executor-macros",
|
||||||
"embassy-executor-timer-queue",
|
"embassy-executor-timer-queue",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1599,6 +1601,7 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"heapless 0.8.0",
|
"heapless 0.8.0",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,10 @@ rmk = { version = "0.8.2", default-features = false, features = [
|
||||||
"storage",
|
"storage",
|
||||||
"vial",
|
"vial",
|
||||||
] }
|
] }
|
||||||
embassy-executor = { version = "0.9" }
|
embassy-executor = { version = "0.9", features = ["log"] }
|
||||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
embassy-time = { version = "0.5.0", features = ["log"] }
|
||||||
embassy-embedded-hal = "0.5.0"
|
embassy-embedded-hal = "0.5.0"
|
||||||
|
embassy-sync = { version = "0.7.2", features = ["log"] }
|
||||||
esp-backtrace = { version = "0.18", default-features = false, features = [
|
esp-backtrace = { version = "0.18", default-features = false, features = [
|
||||||
"esp32s3",
|
"esp32s3",
|
||||||
"println",
|
"println",
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,18 @@ extern crate alloc;
|
||||||
|
|
||||||
use core::alloc::Layout;
|
use core::alloc::Layout;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
|
use core::sync::atomic::AtomicBool;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
|
use alloc::sync::Arc;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use bt_hci::controller::ExternalController;
|
use bt_hci::controller::ExternalController;
|
||||||
use embassy_executor::Spawner;
|
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_alloc::{HeapRegion, MemoryCapability};
|
||||||
use esp_hal::clock::CpuClock;
|
use esp_hal::clock::CpuClock;
|
||||||
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
|
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 rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
|
||||||
use slint::platform::WindowAdapter;
|
use slint::platform::WindowAdapter;
|
||||||
use slint::platform::software_renderer::{MinimalSoftwareWindow, RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer};
|
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 static_cell::StaticCell;
|
||||||
use ui::AppWindow;
|
use ui::AppWindow;
|
||||||
use ui::window_adapter::SoftwareWindowAdapter;
|
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();
|
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]
|
#[esp_rtos::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
#[cfg(not(feature = "alt-log"))]
|
#[cfg(not(feature = "alt-log"))]
|
||||||
|
|
@ -199,8 +210,6 @@ async fn main(_spawner: Spawner) {
|
||||||
.with_data3(peripherals.GPIO41)
|
.with_data3(peripherals.GPIO41)
|
||||||
.with_data4(peripherals.GPIO42)
|
.with_data4(peripherals.GPIO42)
|
||||||
// Green
|
// Green
|
||||||
// .with_data5(peripherals.GPIO5) TODO
|
|
||||||
// .with_data6(peripherals.GPIO12)
|
|
||||||
.with_data7(peripherals.GPIO13)
|
.with_data7(peripherals.GPIO13)
|
||||||
.with_data8(peripherals.GPIO14)
|
.with_data8(peripherals.GPIO14)
|
||||||
.with_data9(peripherals.GPIO15)
|
.with_data9(peripherals.GPIO15)
|
||||||
|
|
@ -212,6 +221,12 @@ async fn main(_spawner: Spawner) {
|
||||||
.with_data14(peripherals.GPIO3)
|
.with_data14(peripherals.GPIO3)
|
||||||
.with_data15(peripherals.GPIO4);
|
.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;
|
let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await;
|
||||||
|
|
||||||
// RMK config
|
// RMK config
|
||||||
|
|
@ -326,10 +341,23 @@ async fn run_renderer_task(backend: SlintBackend) {
|
||||||
slint::platform::set_platform(Box::new(backend))
|
slint::platform::set_platform(Box::new(backend))
|
||||||
.expect("backend already initialized");
|
.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 {
|
loop {
|
||||||
// Timer::after(Duration::from_millis(100)).await;
|
// Timer::after(Duration::from_millis(100)).await;
|
||||||
yield_now().await;
|
// yield_now().await;
|
||||||
|
SIGNAL_LCD_SUBMIT.wait().await;
|
||||||
|
|
||||||
// TODO: Use bounce buffers:
|
// 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
|
// 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,
|
Ok(transfer) => transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
while !transfer.is_done() {
|
// This could be used to allow other tasks to be executed on the first core, but that causes
|
||||||
// Timer::after_millis(1).await;
|
// the flash to be accessed, which interferes with the framebuffer transfer.
|
||||||
yield_now().await;
|
// 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 result;
|
||||||
let dma_buf;
|
let dma_buf;
|
||||||
(result, st7701s.dpi, dma_buf) = transfer.wait();
|
(result, st7701s.dpi, dma_buf) = transfer.wait();
|
||||||
framebuffer.dma_buf = Some(dma_buf);
|
framebuffer.dma_buf = Some(dma_buf);
|
||||||
|
|
||||||
|
SIGNAL_UI_RENDER.signal(());
|
||||||
|
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
error!(
|
error!(
|
||||||
"An error occurred while transferring framebuffer to the LCD display: {error:?}"
|
"An error occurred while transferring framebuffer to the LCD display: {error:?}"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use core::{cell::RefCell, time::Duration};
|
use core::{cell::RefCell, time::Duration};
|
||||||
|
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use esp_hal::time::Instant;
|
use esp_hal::{time::Instant};
|
||||||
use log::info;
|
use log::info;
|
||||||
use slint::{PhysicalSize, WindowSize, platform::software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}};
|
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> {
|
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
|
||||||
loop {
|
// Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
|
||||||
slint::platform::update_timers_and_animations();
|
// in `crate::run_renderer_task`, where we can make use of `await`.
|
||||||
|
|
||||||
|
/* loop */ {
|
||||||
if let Some(window) = self.window.borrow().clone() {
|
if let Some(window) = self.window.borrow().clone() {
|
||||||
|
// TODO: Event dispatch. Here or in `run_renderer_task`?
|
||||||
// window.try_dispatch_event(todo!())?;
|
// window.try_dispatch_event(todo!())?;
|
||||||
|
|
||||||
window.draw_if_needed(|renderer| {
|
window.draw_if_needed(|renderer| {
|
||||||
// TODO: Proper synchronization.
|
// TODO: Proper synchronization.
|
||||||
let framebuffer = unsafe { &mut *self.framebuffer.0 };
|
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);
|
renderer.render(framebuffer, self.window_size[1] as usize);
|
||||||
info!("UI rendered.");
|
info!("UI rendered.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue