UI rendering beginnings; Add debugging serial interface on GPIO5 & 12

This commit is contained in:
Jakub Hlusička 2025-12-29 19:36:00 +01:00
parent b9789b89ac
commit 47340a63e5
8 changed files with 4765 additions and 106 deletions

4316
firmware2/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,15 @@ repository = "https://github.com/haobogu/rmk"
edition = "2024"
[features]
default = ["ble"]
no_usb = ["rmk/_no_usb"]
default = ["ble", "usb-log"]
no-usb = ["rmk/_no_usb"]
ble = ["rmk/esp32s3_ble"]
# Use alternative logging via GPIO5 as RX and GPIO12 as TX.
# Disables default logging via USB.
# Does not support esp-println's `println!`.
alt-log = []
# Standard logging implementation over USB.
usb-log = ["esp-backtrace/panic-handler"]
[dependencies]
rmk = { version = "0.8.2", default-features = false, features = [
@ -20,9 +26,9 @@ rmk = { version = "0.8.2", default-features = false, features = [
] }
embassy-executor = { version = "0.9" }
embassy-time = { version = "0.5.0", features = ["log"] }
esp-backtrace = { version = "0.18", features = [
embassy-embedded-hal = "0.5.0"
esp-backtrace = { version = "0.18", default-features = false, features = [
"esp32s3",
"panic-handler",
"println",
] }
esp-hal = { version = "1.0", features = ["esp32s3", "unstable", "psram", "log-04"] }
@ -40,6 +46,9 @@ log = "0.4.29"
bitflags = "2.10.0"
paste = "1.0.15"
itertools = { version = "0.14.0", default-features = false }
bytemuck = "1.24.0"
slint = { version = "1.14.1", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software"]}
critical-section = "1.2.0"
[build-dependencies]
xz2 = "0.1.7"
@ -47,6 +56,7 @@ json = "0.12"
const-gen = "1.6"
embuild = "0.33"
cc = "1.2.9"
slint-build = "1.14.1"
[[bin]]
name = "acid-firmware"

View file

@ -4,6 +4,7 @@ use std::path::Path;
use std::{env, fs};
use const_gen::*;
use slint_build::{CompilerConfiguration, EmbedResourcesKind};
use xz2::read::XzEncoder;
fn main() {
@ -15,6 +16,11 @@ fn main() {
// Set the extra linker script from defmt
// println!("cargo:rustc-link-arg=-Tdefmt.x");
let slint_config = CompilerConfiguration::new()
.embed_resources(EmbedResourcesKind::EmbedForSoftwareRenderer);
slint_build::compile_with_config("ui/main.slint", slint_config).expect("Slint build failed");
slint_build::print_rustc_flags().unwrap()
}
fn generate_vial_config() {

37
firmware2/src/console.rs Normal file
View file

@ -0,0 +1,37 @@
use esp_hal::{Async, uart::UartRx};
use log::{debug, info, error};
pub async fn run_console(mut uart_rx: UartRx<'_, Async>) {
let mut buf = [0_u8; 32];
info!("Debugging console opened.");
loop {
use log::warn;
let len = match uart_rx.read_async(&mut buf[..]).await {
Ok(len) => len,
Err(error) => {
error!("Failed to read from the alt UART port: {error:?}");
continue;
}
};
let mut read_data = match str::from_utf8(&buf[0..len]) {
Ok(utf8) => utf8,
Err(error) => {
error!("Failed to parse data from the UART port as UTF-8: {error:?}");
continue;
}
};
debug!("Read from alt UART: {read_data:?}");
match read_data.trim() {
"reset" | "reboot" | "rst" | "r" => {
info!("Performing software reset.");
esp_hal::system::software_reset();
}
_ => warn!("Command not recognized: {read_data:?}"),
}
}
}

154
firmware2/src/logging.rs Normal file
View file

@ -0,0 +1,154 @@
use core::alloc::Layout;
use core::cell::{OnceCell, RefCell};
use core::fmt::Write;
use core::time::Duration;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec;
use bt_hci::controller::ExternalController;
use critical_section::Mutex;
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_time::Timer;
use esp_alloc::{HeapRegion, MemoryCapability};
use esp_hal::clock::CpuClock;
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
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;
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
use esp_hal::peripherals::Peripherals;
use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
use esp_hal::rng::TrngSource;
use esp_hal::system::Stack;
use esp_hal::time::Instant;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::uart::{Uart, UartTx};
use esp_hal::usb_serial_jtag::{UsbSerialJtag, UsbSerialJtagTx};
use esp_hal::{Blocking, ram};
use esp_radio::Controller;
use esp_radio::ble::controller::BleConnector;
use esp_rtos::embassy::{Executor, InterruptExecutor};
use esp_storage::FlashStorage;
use itertools::chain;
use log::{LevelFilter, Log, error, info};
use rmk::channel::EVENT_CHANNEL;
use rmk::config::{BehaviorConfig, PositionalConfig, RmkConfig, StorageConfig, VialConfig};
use rmk::debounce::default_debouncer::DefaultDebouncer;
use rmk::embassy_futures::yield_now;
use rmk::input_device::Runnable;
use rmk::{join_all};
use rmk::keyboard::Keyboard;
use rmk::storage::async_flash_wrapper;
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
use slint::platform::software_renderer::{Rgb565Pixel, TargetPixel};
use slint::{ComponentHandle, PhysicalSize, WindowSize};
use slint::platform::Platform;
use static_cell::StaticCell;
static ALT_LOGGER_UART: Mutex<RefCell<Option<UartTx<'static, Blocking>>>> = Mutex::new(RefCell::new(None));
struct AlternativeLogger;
impl Log for AlternativeLogger {
#[allow(unused)]
fn enabled(&self, _: &log::Metadata) -> bool {
// Filtered by `log` already
true
}
#[allow(unused)]
fn log(&self, record: &log::Record) {
critical_section::with(|cs| {
let mut uart = ALT_LOGGER_UART.borrow(cs).borrow_mut();
let mut uart = uart.as_mut().unwrap();
print_log_record(&mut uart, record);
})
}
fn flush(&self) {}
}
const RESET: &str = "\u{001B}[0m";
const RED: &str = "\u{001B}[31m";
const GREEN: &str = "\u{001B}[32m";
const YELLOW: &str = "\u{001B}[33m";
const BLUE: &str = "\u{001B}[34m";
const CYAN: &str = "\u{001B}[35m";
#[cfg(feature = "alt-log")]
macro_rules! println {
() => {{
do_print(Default::default());
}};
($($arg:tt)*) => {{
do_print(::core::format_args!($($arg)*));
}};
}
#[cfg(not(feature = "alt-log"))]
use esp_println::println;
fn do_print(args: core::fmt::Arguments<'_>) {
critical_section::with(|cs| {
let mut uart = ALT_LOGGER_UART.borrow(cs).borrow_mut();
let mut uart = uart.as_mut().unwrap();
uart.write_fmt(args).unwrap();
uart.write_str("\n").unwrap();
uart.flush().unwrap();
})
}
fn print_log_record(uart: &mut UartTx<'_, Blocking>, record: &log::Record) {
let color = match record.level() {
log::Level::Error => RED,
log::Level::Warn => YELLOW,
log::Level::Info => GREEN,
log::Level::Debug => BLUE,
log::Level::Trace => CYAN,
};
let reset = RESET;
let args = format_args!("{}{:>5} - {}{}\n", color, record.level(), record.args(), reset);
uart.write_fmt(args).unwrap();
uart.flush().unwrap();
}
#[cfg(feature = "alt-log")]
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
use esp_backtrace::Backtrace;
println!("{RED}");
println!("====================== PANIC ======================");
println!("{info}{RESET}");
println!("");
println!("Backtrace:");
println!("");
let backtrace = Backtrace::capture();
for frame in backtrace.frames() {
println!("0x{:x}", frame.program_counter());
}
loop {}
}
pub fn setup_alternative_logging(alt_uart: UartTx<'static, Blocking>, level_filter: LevelFilter)
{
critical_section::with(|cs| {
*ALT_LOGGER_UART.borrow(cs).borrow_mut() = Some(alt_uart);
});
unsafe {
log::set_logger_racy(&AlternativeLogger).unwrap();
log::set_max_level_racy(level_filter);
}
}

View file

@ -8,13 +8,23 @@ mod keymap;
mod matrix;
mod peripherals;
mod vial;
mod ui;
mod logging;
mod console;
use core::alloc::Layout;
use core::cell::{OnceCell, RefCell};
use core::fmt::Write;
use core::time::Duration;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec;
use bt_hci::controller::ExternalController;
use critical_section::Mutex;
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_time::Timer;
use esp_alloc::{HeapRegion, MemoryCapability};
use esp_hal::clock::CpuClock;
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
@ -26,28 +36,35 @@ use esp_hal::interrupt::software::SoftwareInterruptControl;
use esp_hal::lcd_cam::LcdCam;
use esp_hal::lcd_cam::lcd::dpi::Dpi;
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
use esp_hal::peripherals::Peripherals;
use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
use esp_hal::rng::TrngSource;
use esp_hal::system::Stack;
use esp_hal::time::Instant;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::uart::Uart;
use esp_hal::usb_serial_jtag::{UsbSerialJtag, UsbSerialJtagTx};
use esp_hal::{Blocking, ram};
use esp_radio::Controller;
use esp_radio::ble::controller::BleConnector;
use esp_rtos::embassy::{Executor, InterruptExecutor};
use esp_storage::FlashStorage;
use itertools::chain;
use log::{error, info};
use log::{LevelFilter, Log, debug, error, info};
use rmk::channel::EVENT_CHANNEL;
use rmk::config::{BehaviorConfig, PositionalConfig, RmkConfig, StorageConfig, VialConfig};
use rmk::debounce::default_debouncer::DefaultDebouncer;
use rmk::embassy_futures::yield_now;
use rmk::input_device::Runnable;
use rmk::join_all;
use rmk::{join_all};
use rmk::keyboard::Keyboard;
use rmk::storage::async_flash_wrapper;
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
use slint::platform::software_renderer::{Rgb565Pixel, TargetPixel};
use slint::{ComponentHandle, PhysicalSize, WindowSize};
use slint::platform::Platform;
use static_cell::StaticCell;
use ui::AppWindow;
use {esp_alloc as _, esp_backtrace as _};
use crate::matrix::IoeMatrix;
@ -58,12 +75,18 @@ use crate::vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
const LOG_LEVEL_FILTER: LevelFilter = LevelFilter::Info;
static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
#[esp_rtos::main]
async fn main(_spawner: Spawner) {
esp_println::logger::init_logger_from_env();
#[cfg(not(feature = "alt-log"))]
let alt_uart_rx_task = {
esp_println::logger::init_logger(LOG_LEVEL_FILTER);
info!("Logger initialized!");
async {}
};
let config = esp_hal::Config::default()
.with_cpu_clock(CpuClock::max())
@ -76,6 +99,14 @@ async fn main(_spawner: Spawner) {
let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config);
info!("System initialized!");
#[cfg(feature = "alt-log")]
let alt_uart_rx_task = {
let (uart_rx, uart_tx) = Uart::new(peripherals.UART2, Default::default()).unwrap().with_tx(peripherals.GPIO12).with_rx(peripherals.GPIO5).split();
logging::setup_alternative_logging(uart_tx, LOG_LEVEL_FILTER);
info!("Logger initialized!");
console::run_console(uart_rx.into_async())
};
// Use the internal DRAM as the heap.
esp_alloc::heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 64 * 1024);
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
@ -133,7 +164,7 @@ async fn main(_spawner: Spawner) {
rmk::ble::build_ble_stack(controller, central_addr, &mut rng, &mut host_resources).await;
// Initialize USB
#[cfg(not(feature = "no_usb"))]
#[cfg(not(feature = "no-usb"))]
let usb_driver = {
use core::ptr::addr_of_mut;
use esp_hal::otg_fs::Usb;
@ -168,8 +199,8 @@ async fn main(_spawner: Spawner) {
.with_data3(peripherals.GPIO41)
.with_data4(peripherals.GPIO42)
// Green
.with_data5(peripherals.GPIO5)
.with_data6(peripherals.GPIO12)
// .with_data5(peripherals.GPIO5) TODO
// .with_data6(peripherals.GPIO12)
.with_data7(peripherals.GPIO13)
.with_data8(peripherals.GPIO14)
.with_data9(peripherals.GPIO15)
@ -230,7 +261,15 @@ async fn main(_spawner: Spawner) {
.await;
let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller
// spawner.must_spawn(run_lcd(st7701s));
static FRAMEBUFFER: StaticCell<Framebuffer> = StaticCell::new();
let framebuffer = FRAMEBUFFER.init(Framebuffer::new(
360 + /* TODO: Figure out why more bytes are needed: */ 8,
960,
));
let window_size = [framebuffer.width, framebuffer.height];
let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _);
static SECOND_CORE_STACK: StaticCell<Stack<8192>> = StaticCell::new();
let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
esp_rtos::start_second_core(
@ -244,71 +283,64 @@ async fn main(_spawner: Spawner) {
// software_interrupt.software_interrupt2,
// ));
// let spawner = exec.start(Priority::Priority3);
// spawner.must_spawn(run_lcd_task(st7701s));
// spawner.must_spawn(run_renderer_task());
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
let executor: &mut Executor = EXECUTOR.init(Executor::new());
executor.run(|spawner| {
spawner.must_spawn(run_lcd_task(st7701s));
let slint_backend = SlintBackend {
// peripherals: RefCell::new(Some(peripherals)),
window_size,
window: RefCell::new(None),
framebuffer: framebuffer_ptr,
};
spawner.must_spawn(run_renderer_task(slint_backend));
});
},
);
join_all![
alt_uart_rx_task,
// We currently send the framebuffer data using the main core, which does not seem to slow
// down the rest of the tasks too much.
run_lcd(st7701s, framebuffer),
run_devices! (
(matrix) => EVENT_CHANNEL,
),
keyboard.run(), // Keyboard is special
run_rmk(
&keymap,
#[cfg(not(feature = "no_usb"))]
#[cfg(not(feature = "no-usb"))]
usb_driver,
#[cfg(feature = "ble")]
&stack,
&mut storage,
rmk_config,
) //, run_lcd(st7701s)
)
]
.await;
}
#[embassy_executor::task]
async fn run_lcd_task(st7701s: St7701s<'static, Blocking>) {
run_lcd(st7701s).await
async fn run_renderer_task(backend: SlintBackend) {
slint::platform::set_platform(Box::new(backend))
.expect("backend already initialized");
loop {
let main = AppWindow::new().unwrap();
main.run().unwrap();
}
}
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;
struct Framebuffer {
width: u32,
height: u32,
dma_buf: Option<DmaTxBuf>,
}
const MAX_RED: u8 = (1 << 5) - 1;
const MAX_GREEN: u8 = (1 << 6) - 1;
const MAX_BLUE: u8 = (1 << 5) - 1;
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)
}
fn row(edge: u16) -> impl Iterator<Item = u16> + 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),
]
}
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 BUFFER_LEN: usize = core::mem::size_of::<u16>()
* (360 + /* TODO: Figure out why more bytes are needed: */ 8)
* 960;
impl Framebuffer {
pub fn new(width: u32, height: u32) -> Self {
let buffer_len = width as usize * height as usize * core::mem::size_of::<u16>();
// Allocate the framebuffer in the external PSRAM memory.
// Note: We just leak this buffer.
let buffer_ptr = unsafe {
@ -317,30 +349,157 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>) {
// 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(),
Layout::from_size_align(buffer_len, 32).unwrap(),
)
};
let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_ptr, BUFFER_LEN) };
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);
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 dma_buf_descs =
Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice());
let dma_buf = DmaTxBuf::new(dma_buf_descs, buffer).unwrap();
{
for (chunk, color) in dma_buf.as_mut_slice().chunks_mut(2).zip(&mut colors) {
chunk.copy_from_slice(&color.to_le_bytes());
Self {
width,
height,
dma_buf: Some(dma_buf),
}
}
info!("chunk addr: {}", dma_buf.as_slice().as_ptr() as usize);
// colors.next(); // Shift colors
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.dma_buf.as_mut().unwrap().as_mut_slice())
}
}
struct SlintBackend {
window_size: [u32; 2],
window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
framebuffer: FramebufferPtr,
// peripherals: RefCell<Option<Peripherals>>,
}
struct FramebufferPtr(*mut [Rgb565Pixel]);
unsafe impl Send for FramebufferPtr {}
impl slint::platform::Platform for SlintBackend {
fn create_window_adapter(&self) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);
window.set_size(WindowSize::Physical(PhysicalSize::new(self.window_size[0], self.window_size[1])));
self.window.replace(Some(window.clone()));
Ok(window)
}
fn duration_since_start(&self) -> Duration {
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
}
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
loop {
slint::platform::update_timers_and_animations();
if let Some(window) = self.window.borrow().clone() {
// 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[0] as usize);
});
}
}
}
}
// impl DrawTarget for Framebuffer {
// type Color = Rgb565;
// type Error = ();
// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
// where
// I: IntoIterator<Item = Pixel<Self::Color>>,
// {
// let buffer = bytemuck::try_cast_slice_mut::<_, u16>(self.dma_buf.as_mut_slice()).unwrap();
// for Pixel(coord, color) in pixels.into_iter() {
// // Check if the pixel coordinates are out of bounds.
// // `DrawTarget` implementation are required to discard any out of bounds
// // pixels without returning an error or causing a panic.
// if coord.x >= 0
// && coord.x < self.width as i32
// && coord.y >= 0
// && coord.y < self.height as i32
// {
// let index = coord.x as usize + coord.y as usize * self.width as usize;
// buffer[index] = RawU16::from(color).into_inner();
// }
// }
// Ok(())
// }
// }
#[embassy_executor::task]
async fn run_lcd_task(st7701s: St7701s<'static, Blocking>, framebuffer: &'static mut Framebuffer) {
run_lcd(st7701s, framebuffer).await
}
async fn run_lcd(mut st7701s: St7701s<'static, Blocking>, framebuffer: &'static mut Framebuffer) {
// const PADDING_LEFT: usize = 121;
// const PADDING_RIGHT: usize = 7;
// const COLORS_WIDTH: usize = 240;
// const COLORS_HEIGHT: usize = 960;
// const MAX_RED: u8 = (1 << 5) - 1;
// const MAX_GREEN: u8 = (1 << 6) - 1;
// const MAX_BLUE: u8 = (1 << 5) - 1;
// 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)
// }
// fn row(edge: u16) -> impl Iterator<Item = u16> + 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),
// ]
// }
// 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 BUFFER_LEN: usize = core::mem::size_of::<u16>()
// * (360 + /* TODO: Figure out why more bytes are needed: */ 8)
// * 960;
// {
// 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;
@ -349,19 +508,27 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>) {
// 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) {
let transfer = match st7701s.dpi.send(false, framebuffer.dma_buf.take().unwrap()) {
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;
framebuffer.dma_buf = Some(result_dma_buf);
continue;
}
Ok(transfer) => transfer,
};
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);
if let Err(error) = result {
error!(

3
firmware2/src/ui/mod.rs Normal file
View file

@ -0,0 +1,3 @@
// #![cfg_attr(not(feature = "simulator"), no_main)]
slint::include_modules!();

18
firmware2/ui/main.slint Normal file
View file

@ -0,0 +1,18 @@
import { Button, VerticalBox } from "std-widgets.slint";
export component AppWindow inherits Window {
in-out property <int> counter: 42;
callback request-increase-value();
VerticalBox {
Text {
text: "Counter: \{root.counter}";
}
Button {
text: "Increase value";
clicked => {
root.request-increase-value();
}
}
}
}