UI rendering beginnings; Add debugging serial interface on GPIO5 & 12
This commit is contained in:
parent
b9789b89ac
commit
47340a63e5
4316
firmware2/Cargo.lock
generated
4316
firmware2/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
37
firmware2/src/console.rs
Normal 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
154
firmware2/src/logging.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
3
firmware2/src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
||||
|
||||
slint::include_modules!();
|
||||
18
firmware2/ui/main.slint
Normal file
18
firmware2/ui/main.slint
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue