acid/firmware/acid-firmware/src/main.rs

717 lines
26 KiB
Rust
Raw Normal View History

2026-01-01 03:59:24 +01:00
//! TODO:
//! * GUI event dispatch.
2026-01-01 03:59:24 +01:00
//! * Async interrupt handling of keyboard input. Reduces LCD glitching.
//! * Attempt to reduce the size of the framebuffer to 240x960 by changing the front/back porch of
//! the LCD driver. Reduces LCD glitching.
//! * Bounce buffers to get rid of glitching completely.
2026-01-02 17:31:28 +01:00
//! https://esp32.com/viewtopic.php?t=28230
//! https://esp32.com/viewtopic.php?f=12&t=26793&start=20#p95677
2025-12-24 02:07:21 +01:00
#![no_std]
#![no_main]
2026-01-05 04:16:08 +01:00
#![feature(allocator_api)]
2025-12-24 02:07:21 +01:00
#![feature(macro_metavar_expr)]
2026-01-05 04:16:05 +01:00
#![feature(c_variadic)]
#![feature(c_size_t)]
#![feature(debug_closure_helpers)]
2025-12-24 02:07:21 +01:00
extern crate alloc;
use core::alloc::Layout;
2025-12-31 01:08:12 +01:00
use core::cell::RefCell;
use core::fmt::Write;
2026-01-10 19:21:13 +01:00
use core::sync::atomic::{AtomicBool, Ordering};
2025-12-24 02:07:21 +01:00
2026-01-06 22:52:41 +01:00
use alloc::boxed::Box;
2026-02-04 03:14:21 +01:00
use alloc::collections::vec_deque::VecDeque;
2026-02-12 22:44:13 +01:00
use alloc::format;
use alloc::string::String;
2026-02-04 03:14:21 +01:00
use alloc::sync::Arc;
2025-12-24 02:07:21 +01:00
use alloc::vec;
use alloc::vec::Vec;
2026-01-24 00:42:16 +01:00
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_embedded_hal::flash::partition::Partition;
2025-12-24 02:07:21 +01:00
use embassy_executor::Spawner;
2026-01-06 22:52:41 +01:00
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel;
2026-01-24 00:42:16 +01:00
use embassy_sync::mutex::Mutex;
2026-01-01 01:24:52 +01:00
use embassy_sync::signal::Signal;
use embassy_time::{Duration, Timer};
2026-01-31 15:36:36 +01:00
use esp_alloc::{HeapRegion, MemoryCapability};
use esp_bootloader_esp_idf::partitions::PartitionTable;
2026-01-06 22:52:41 +01:00
use esp_hal::Blocking;
2025-12-27 21:03:52 +01:00
use esp_hal::clock::CpuClock;
2025-12-24 02:07:21 +01:00
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
2026-02-12 22:44:13 +01:00
use esp_hal::efuse::Efuse;
2026-01-06 22:52:41 +01:00
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
2025-12-24 02:07:21 +01:00
use esp_hal::i2c::master::{I2c, I2cAddress};
2025-12-27 21:03:52 +01:00
use esp_hal::interrupt::software::SoftwareInterruptControl;
2025-12-24 02:07:21 +01:00
use esp_hal::lcd_cam::LcdCam;
2025-12-27 21:03:52 +01:00
use esp_hal::lcd_cam::lcd::dpi::Dpi;
2025-12-24 02:07:21 +01:00
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
2025-12-27 21:03:52 +01:00
use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
2026-01-25 18:43:07 +01:00
use esp_hal::ram;
2026-01-24 00:42:16 +01:00
use esp_hal::rng::TrngSource;
2026-01-31 15:36:36 +01:00
use esp_hal::sha::ShaBackend;
2026-01-01 03:59:24 +01:00
use esp_hal::system::Stack;
2025-12-24 02:07:21 +01:00
use esp_hal::timer::timg::TimerGroup;
2025-12-31 01:08:12 +01:00
use esp_rtos::embassy::Executor;
2025-12-24 02:07:21 +01:00
use esp_storage::FlashStorage;
use indoc::writedoc;
2026-02-12 22:44:13 +01:00
use itertools::Itertools;
2026-01-19 20:13:25 +01:00
use log::{error, info, warn};
use rmk::channel::{CONTROLLER_CHANNEL, ControllerSub};
2026-02-12 22:44:13 +01:00
use rmk::config::{
BehaviorConfig, DeviceConfig, PositionalConfig, RmkConfig, StorageConfig, VialConfig,
};
use rmk::controller::{Controller, EventController};
2025-12-24 02:07:21 +01:00
use rmk::debounce::default_debouncer::DefaultDebouncer;
2026-01-06 22:52:41 +01:00
use rmk::event::ControllerEvent;
use rmk::hid::Report;
2025-12-24 02:07:21 +01:00
use rmk::input_device::Runnable;
use rmk::join_all;
2025-12-24 02:07:21 +01:00
use rmk::keyboard::Keyboard;
use rmk::storage::async_flash_wrapper;
use rmk::types::action::{Action, KeyAction};
2025-12-27 21:03:52 +01:00
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
2026-01-06 22:52:41 +01:00
use slint::platform::software_renderer::Rgb565Pixel;
2025-12-24 02:07:21 +01:00
use static_cell::StaticCell;
use {esp_alloc as _, esp_backtrace as _};
2026-01-12 01:03:27 +01:00
use crate::keymap::create_hid_report_interceptor;
use crate::logging::LOG_LEVEL_FILTER;
2025-12-24 02:07:21 +01:00
use crate::matrix::IoeMatrix;
2025-12-27 21:03:52 +01:00
use crate::peripherals::st7701s::St7701s;
2025-12-31 22:24:26 +01:00
use crate::ui::backend::{FramebufferPtr, SlintBackend};
2026-02-12 22:44:13 +01:00
use crate::vial::{
CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID,
VIAL_VENDOR_ID,
};
2025-12-24 02:07:21 +01:00
2026-01-05 04:16:05 +01:00
mutually_exclusive_features::none_or_one_of!["usb-log", "alt-log", "rtt-log"];
2026-01-19 20:13:25 +01:00
mod crypto;
2026-01-24 21:12:25 +01:00
mod db;
2026-01-06 22:52:41 +01:00
mod ffi;
2025-12-31 00:54:48 +01:00
mod keymap;
2026-01-06 22:52:41 +01:00
mod logging;
2025-12-31 00:54:48 +01:00
mod matrix;
mod peripherals;
mod ui;
mod util;
2026-01-06 22:52:41 +01:00
mod vial;
2025-12-31 01:08:12 +01:00
#[cfg(feature = "alt-log")]
2025-12-31 00:54:48 +01:00
mod console;
2025-12-24 02:07:21 +01:00
// This creates a default app-descriptor required by the esp-idf bootloader.
// 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!();
// Memory allocation regions.
// These can be debugged using `xtensa-esp32s3-elf-size -A <path-to-binary>`.
2026-01-27 02:46:53 +01:00
// A panic such as `memory allocation of 3740121773 bytes failed` is caused by a heap overflow. The size is `DEEDBAAD` in hex.
/// Total heap size
2026-02-04 03:14:21 +01:00
const HEAP_SIZE: usize = 112 * 1024;
/// Size of the app core's stack
2026-02-04 03:14:21 +01:00
const STACK_SIZE_CORE_APP: usize = 80 * 1024;
2026-01-06 03:45:30 +01:00
// const FRAME_DURATION_MIN: Duration = Duration::from_millis(40); // 25 FPS
const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS
2026-01-05 04:16:08 +01:00
pub static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
2025-12-24 02:07:21 +01:00
2026-01-10 19:21:13 +01:00
static KEYBOARD_REPORT_PROXY: Channel<CriticalSectionRawMutex, Report, 16> = Channel::new();
static LCD_ENABLED: AtomicBool = AtomicBool::new(false);
2026-01-01 01:24:52 +01:00
/// 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();
2025-12-24 02:07:21 +01:00
#[esp_rtos::main]
2025-12-27 21:03:52 +01:00
async fn main(_spawner: Spawner) {
let config = esp_hal::Config::default()
.with_cpu_clock(CpuClock::max())
.with_psram(PsramConfig {
size: PsramSize::AutoDetect,
core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m),
flash_frequency: FlashFreq::default(),
ram_frequency: SpiRamFreq::Freq80m,
});
2025-12-24 02:07:21 +01:00
let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config);
2026-01-31 20:21:40 +01:00
#[cfg(feature = "usb-log")]
let console_task = logging::usb::setup_logging();
#[cfg(feature = "alt-log")]
2026-01-31 20:21:40 +01:00
let console_task =
logging::uart::setup_logging(peripherals.UART2, peripherals.GPIO12, peripherals.GPIO5);
#[cfg(feature = "rtt-log")]
let console_task = logging::rtt::setup_logging();
2026-01-05 04:16:05 +01:00
2025-12-24 02:07:21 +01:00
// Use the internal DRAM as the heap.
// Memory reclaimed from the esp-idf bootloader.
const HEAP_SIZE_RECLAIMED: usize = const {
let range = esp_metadata_generated::memory_range!("DRAM2_UNINIT");
range.end - range.start
};
esp_alloc::heap_allocator!(#[ram(reclaimed)] size: HEAP_SIZE_RECLAIMED);
esp_alloc::heap_allocator!(size: HEAP_SIZE - HEAP_SIZE_RECLAIMED);
2025-12-24 02:07:21 +01:00
info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());
2025-12-27 21:03:52 +01:00
// Initialize the PSRAM allocator.
{
let (psram_offset, psram_size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM);
unsafe {
PSRAM_ALLOCATOR.add_region(HeapRegion::new(
psram_offset,
psram_size,
MemoryCapability::External.into(),
));
}
2026-01-25 18:43:07 +01:00
info!(
"PSRAM allocator initialized with capacity of {} MiB!",
psram_size / 1024 / 1024
);
2025-12-27 21:03:52 +01:00
}
2026-01-05 04:16:05 +01:00
// let mut io = Io::new(peripherals.IO_MUX);
// io.set_interrupt_handler(interrupt_handler);
// info!("IO Mux initialized!");
2025-12-24 02:07:21 +01:00
// Enable pull-up on GPIO0 to prevent booting into download mode.
let gpio0 = Output::new(
peripherals.GPIO0,
Level::High,
OutputConfig::default().with_pull(Pull::Up),
);
// Enable LDO2
let _ = Output::new(peripherals.GPIO17, Level::High, OutputConfig::default());
// 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());
2026-01-21 02:21:52 +01:00
let mut sha_backend = ShaBackend::new(peripherals.SHA);
let _sha_driver_handle = sha_backend.start();
2025-12-24 02:07:21 +01:00
let timg0 = TimerGroup::new(peripherals.TIMG0);
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(
2025-12-27 21:03:52 +01:00
timg0.timer0, /*, software_interrupt.software_interrupt0 */
2025-12-24 02:07:21 +01:00
);
2025-12-31 01:08:12 +01:00
2026-01-05 04:16:05 +01:00
info!("ESP-RTOS started!");
2026-01-24 00:42:16 +01:00
// Enable the TRNG source, so `Trng` can be constructed.
let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
2025-12-27 21:03:52 +01:00
#[cfg(feature = "ble")]
let mut host_resources = rmk::HostResources::new();
#[cfg(feature = "ble")]
2025-12-31 01:08:12 +01:00
let stack = {
2026-01-06 22:52:41 +01:00
use bt_hci::controller::ExternalController;
use esp_radio::{Controller as RadioController, ble::controller::BleConnector};
2025-12-31 01:08:12 +01:00
let mut rng = esp_hal::rng::Trng::try_new().unwrap();
static RADIO: StaticCell<RadioController<'static>> = StaticCell::new();
2025-12-31 01:08:12 +01:00
let radio = RADIO.init(esp_radio::init().unwrap());
let bluetooth = peripherals.BT;
let connector = BleConnector::new(radio, bluetooth, Default::default()).unwrap();
let controller: ExternalController<_, 20> = ExternalController::new(connector);
let central_addr = [0x18, 0xe2, 0x21, 0x80, 0xc0, 0xc7];
2026-01-06 22:52:41 +01:00
let ble_stack =
rmk::ble::build_ble_stack(controller, central_addr, &mut rng, &mut host_resources)
.await;
2026-01-05 04:16:05 +01:00
info!("BLE stack for RMK built!");
2025-12-31 01:08:12 +01:00
2026-01-05 04:16:05 +01:00
ble_stack
2025-12-31 01:08:12 +01:00
};
2025-12-24 02:07:21 +01:00
2025-12-27 21:03:52 +01:00
// Initialize USB
#[cfg(not(feature = "no-usb"))]
2025-12-24 02:07:21 +01:00
let usb_driver = {
2025-12-27 21:03:52 +01:00
use esp_hal::otg_fs::Usb;
use esp_hal::otg_fs::asynch::{Config, Driver};
static EP_MEMORY: StaticCell<[u8; 1024]> = StaticCell::new();
let ep_memory = EP_MEMORY.init_with(|| [0_u8; _]);
2025-12-24 02:07:21 +01:00
let usb = Usb::new(peripherals.USB0, peripherals.GPIO20, peripherals.GPIO19);
// Create the driver, from the HAL.
let config = Config::default();
let driver = Driver::new(usb, ep_memory, config);
2026-01-05 04:16:05 +01:00
info!("USB driver for RMK built!");
driver
2025-12-24 02:07:21 +01:00
};
// Initialize the flash
static PARTITION_TABLE_BUFFER: StaticCell<Vec<u8, &'static esp_alloc::EspHeap>> =
2026-01-24 00:42:16 +01:00
StaticCell::new();
let partition_table_buffer = PARTITION_TABLE_BUFFER.init_with(|| {
let mut buffer = Vec::<u8, _>::new_in(&PSRAM_ALLOCATOR);
buffer.resize(1024, 0_u8);
buffer
});
static FLASH: StaticCell<(
Mutex<CriticalSectionRawMutex, BlockingAsync<FlashStorage>>,
PartitionTable<'static>,
)> = StaticCell::new();
let (flash, partition_table) = FLASH.init_with(|| {
let mut flash = FlashStorage::new(peripherals.FLASH)
2026-01-24 00:42:16 +01:00
// Flash memory may not be written to while another core is executing from it.
// By default, `FlashStorage` is configured to abort the operation and log an error message.
// However, it can also be configured to auto-park the other core, such that writing to
// flash succeeds.
// Alternatively, XiP from PSRAM could be used along with the `multicore_ignore` strategy,
// to avoid having to park the other core, which could result in better performance.
// Invalid configuration would then present itself as freezing/UB.
.multicore_auto_park();
let partition_table = {
esp_bootloader_esp_idf::partitions::read_partition_table(
&mut flash,
partition_table_buffer,
)
.expect("Failed to read the partition table.")
};
(
Mutex::<CriticalSectionRawMutex, _>::new(async_flash_wrapper(flash)),
partition_table,
)
2026-01-24 00:42:16 +01:00
});
{
let mut buffer = String::new();
writeln!(buffer, "Partition table:").unwrap();
for (index, partition) in partition_table.iter().enumerate() {
writedoc!(
buffer,
"
Partition #{index} {label:?}:
offset: 0x{offset:x}
length: 0x{len:x}
type: 0x{type:?}
read only: {read_only}
encrypted: {encrypted}
magic: {magic}
",
label = partition.label_as_str(),
offset = partition.offset(),
len = partition.len(),
type = partition.partition_type(),
read_only = partition.is_read_only(),
encrypted = partition.is_encrypted(),
magic = partition.magic(),
)
.unwrap();
}
info!("{}", buffer);
}
let flash_part_info_rmk = partition_table
.iter()
.find(|partition| partition.label_as_str() == "rmk")
.expect("No \"rmk\" partition found. Make sure to use the custom partition-table.csv when flashing.");
let flash_part_info_acid = partition_table
.iter()
.find(|partition| partition.label_as_str() == "acid")
.expect("No \"acid\" partition found. Make sure to use the custom partition-table.csv when flashing.");
let flash_part_rmk = Partition::new(
flash,
flash_part_info_rmk.offset(),
flash_part_info_rmk.len(),
);
let flash_part_acid = Partition::new(
flash,
flash_part_info_acid.offset(),
flash_part_info_acid.len(),
);
2025-12-24 02:07:21 +01:00
2026-01-05 04:16:05 +01:00
info!("Flash memory configured!");
2025-12-27 21:03:52 +01:00
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());
2025-12-24 02:07:21 +01:00
2025-12-27 21:03:52 +01:00
let lcd = LcdCam::new(peripherals.LCD_CAM).lcd;
let unconfigured_dpi = Dpi::new(lcd, peripherals.DMA_CH2, Default::default())
2025-12-24 02:07:21 +01:00
.unwrap()
.with_de(peripherals.GPIO37)
.with_pclk(peripherals.GPIO34)
.with_hsync(peripherals.GPIO44)
.with_vsync(peripherals.GPIO43)
// Blue
.with_data0(peripherals.GPIO38)
.with_data1(peripherals.GPIO39)
.with_data2(peripherals.GPIO40)
.with_data3(peripherals.GPIO41)
.with_data4(peripherals.GPIO42)
// Green
.with_data7(peripherals.GPIO13)
.with_data8(peripherals.GPIO14)
.with_data9(peripherals.GPIO15)
.with_data10(peripherals.GPIO16)
// Red
.with_data11(gpio0)
.with_data12(peripherals.GPIO1)
.with_data13(peripherals.GPIO2)
.with_data14(peripherals.GPIO3)
.with_data15(peripherals.GPIO4);
2026-01-01 01:24:52 +01:00
#[cfg(not(feature = "alt-log"))]
let unconfigured_dpi = unconfigured_dpi
// Green
.with_data5(peripherals.GPIO5)
.with_data6(peripherals.GPIO12);
2025-12-27 21:03:52 +01:00
let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await;
2026-01-05 04:16:05 +01:00
info!("ST7701S-based LCD display initialized!");
2025-12-24 02:07:21 +01:00
// RMK config
let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]);
let storage_config = StorageConfig {
2026-01-24 00:42:16 +01:00
start_addr: 0,
num_sectors: {
assert!(
flash_part_info_rmk.len() as u32 % FlashStorage::SECTOR_SIZE == 0,
"The size of the RMK partition must be a multiple of {} bytes. Current size: {}",
FlashStorage::SECTOR_SIZE,
flash_part_info_rmk.len()
);
(flash_part_info_rmk.len() as u32 / FlashStorage::SECTOR_SIZE) as u8
},
2025-12-24 02:07:21 +01:00
..Default::default()
};
2026-02-12 22:44:13 +01:00
// Retrieve the hardware-unique MAC address.
let mac_address = Efuse::read_base_mac_address();
static SERIAL_NUMBER: StaticCell<Box<str>> = StaticCell::new();
let serial_number = SERIAL_NUMBER.init_with(|| {
/// A magic prefix string that is required for the device to be recognized by the Vial GUI.
const VIAL_SERIAL_PREFIX: &str = "vial:f64c2b3c";
format!(
"{VIAL_SERIAL_PREFIX}:acid:{:02x}",
mac_address.iter().format("")
)
.into_boxed_str()
});
let device_config = DeviceConfig {
vid: VIAL_VENDOR_ID,
pid: VIAL_PRODUCT_ID,
manufacturer: "",
product_name: VIAL_KEYBOARD_NAME,
serial_number,
};
info!("RMK Device Config: {device_config:#04x?}");
2025-12-24 02:07:21 +01:00
let rmk_config = RmkConfig {
2026-02-12 22:44:13 +01:00
device_config,
2025-12-24 02:07:21 +01:00
vial_config,
storage_config,
..Default::default()
};
// Initialze keyboard stuffs
// Initialize the storage and keymap
let mut default_keymap = keymap::get_default_keymap();
let mut behavior_config = BehaviorConfig::default();
let mut per_key_config = PositionalConfig::default();
let (keymap, mut storage) = initialize_keymap_and_storage(
&mut default_keymap,
2026-01-24 00:42:16 +01:00
flash_part_rmk,
2025-12-24 02:07:21 +01:00
&storage_config,
&mut behavior_config,
&mut per_key_config,
)
.await;
2026-01-05 04:16:05 +01:00
info!("Initialized keymap and storage for RMK!");
2025-12-24 02:07:21 +01:00
// Initialize the matrix and keyboard
const I2C_ADDR_MATRIX_LEFT: I2cAddress = I2cAddress::SevenBit(0b0100000);
const I2C_ADDR_MATRIX_RIGHT: I2cAddress = I2cAddress::SevenBit(0b0100001);
let i2c = I2c::new(peripherals.I2C0, Default::default())
.unwrap()
.with_sda(peripherals.GPIO8)
.with_scl(peripherals.GPIO9);
2025-12-27 21:03:52 +01:00
let matrix_interrupt_low = Input::new(peripherals.GPIO7, InputConfig::default());
2025-12-24 02:07:21 +01:00
let mut matrix = IoeMatrix::new(
2025-12-27 21:03:52 +01:00
matrix_interrupt_low,
2025-12-24 02:07:21 +01:00
i2c.into_async(),
DefaultDebouncer::new(),
[I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT],
)
.await;
let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller
2026-01-05 04:16:05 +01:00
info!("Keyboard initialized!");
static FRAMEBUFFER: StaticCell<Framebuffer> = StaticCell::new();
let framebuffer = FRAMEBUFFER.init(Framebuffer::new(
360 + /* TODO: Figure out why more bytes are needed: */ 8,
960,
));
2026-01-05 04:16:05 +01:00
info!("Framebuffer created!");
2025-12-31 22:24:26 +01:00
// let window_size = [framebuffer.width, framebuffer.height];
let window_size = [framebuffer.height, framebuffer.width];
let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _);
static SECOND_CORE_STACK: StaticCell<Stack<STACK_SIZE_CORE_APP>> = StaticCell::new();
2025-12-27 21:03:52 +01:00
let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
esp_rtos::start_second_core(
peripherals.CPU_CTRL,
software_interrupt.software_interrupt0,
software_interrupt.software_interrupt1,
second_core_stack,
move || {
2025-12-27 23:51:46 +01:00
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
// let exec = EXECUTOR.init(InterruptExecutor::new(
// software_interrupt.software_interrupt2,
// ));
// let spawner = exec.start(Priority::Priority3);
// spawner.must_spawn(run_renderer_task());
2025-12-27 23:51:46 +01:00
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
let executor: &mut Executor = EXECUTOR.init(Executor::new());
executor.run(|spawner| {
let slint_backend = SlintBackend {
// peripherals: RefCell::new(Some(peripherals)),
window_size,
window: RefCell::new(None),
framebuffer: framebuffer_ptr,
2026-02-04 03:14:21 +01:00
quit_event_loop: Default::default(),
events: Arc::new(critical_section::Mutex::new(RefCell::new(VecDeque::new()))),
};
2026-01-24 00:42:16 +01:00
spawner.must_spawn(ui::run_renderer_task(slint_backend, flash_part_acid));
2025-12-27 23:51:46 +01:00
});
2025-12-27 21:03:52 +01:00
},
);
2026-01-05 04:16:05 +01:00
info!("Second core started!");
let mut user_controller = UserController::new();
2026-01-05 04:16:05 +01:00
info!("Awaiting on all tasks...");
2026-01-02 17:31:28 +01:00
// TODO: Probably want to select! instead and re-try.
2025-12-27 21:03:52 +01:00
join_all![
run_alloc_stats_reporter(),
// 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),
2025-12-24 02:07:21 +01:00
run_devices! (
(matrix) => rmk::channel::EVENT_CHANNEL,
2025-12-24 02:07:21 +01:00
),
keyboard.run(), // Keyboard is special
run_rmk(
&keymap,
#[cfg(not(feature = "no-usb"))]
2025-12-24 02:07:21 +01:00
usb_driver,
2025-12-27 21:03:52 +01:00
#[cfg(feature = "ble")]
2025-12-24 02:07:21 +01:00
&stack,
&mut storage,
rmk_config,
2025-12-31 00:54:48 +01:00
),
create_hid_report_interceptor(),
user_controller.event_loop(),
2026-01-31 20:21:40 +01:00
console_task
2025-12-27 21:03:52 +01:00
]
2025-12-24 02:07:21 +01:00
.await;
}
async fn run_alloc_stats_reporter() {
let mut psram_used_prev = 0;
let mut heap_used_prev = 0;
loop {
let psram_stats = PSRAM_ALLOCATOR.stats();
let heap_stats = esp_alloc::HEAP.stats();
if psram_stats.current_usage != psram_used_prev {
let difference = psram_stats.current_usage as isize - psram_used_prev as isize;
psram_used_prev = psram_stats.current_usage;
warn!(
"PSRAM usage changed: {}{}\n{psram_stats}",
if difference < 0 { '-' } else { '+' },
difference.abs()
);
}
if heap_stats.current_usage != heap_used_prev {
let difference = heap_stats.current_usage as isize - heap_used_prev as isize;
heap_used_prev = heap_stats.current_usage;
warn!(
"HEAP usage changed: {}{}\n{heap_stats}",
if difference < 0 { '-' } else { '+' },
difference.abs()
);
}
Timer::after_secs(1).await;
}
}
struct UserController {
sub: ControllerSub,
}
impl UserController {
fn new() -> Self {
Self {
sub: CONTROLLER_CHANNEL.subscriber().unwrap(),
}
}
}
impl Controller for UserController {
type Event = ControllerEvent;
async fn process_event(&mut self, event: Self::Event) {
2026-01-12 01:03:27 +01:00
if let ControllerEvent::Key(keyboard_event, KeyAction::Single(Action::User(user_key_index))) =
event
&& user_key_index == CustomKeycodes::FOCUS_LCD as u8
&& keyboard_event.pressed
2026-01-06 22:52:41 +01:00
{
2026-01-12 01:03:27 +01:00
let enabled = !LCD_ENABLED.fetch_xor(true, Ordering::SeqCst);
match enabled {
false => {
info!("Disabling LCD.");
*rmk::channel::KEYBOARD_REPORT_SENDER.write().await =
&rmk::channel::KEYBOARD_REPORT_RECEIVER;
}
true => {
info!("Enabling LCD.");
*rmk::channel::KEYBOARD_REPORT_SENDER.write().await = &KEYBOARD_REPORT_PROXY;
2026-01-10 19:21:13 +01:00
}
}
}
}
async fn next_message(&mut self) -> Self::Event {
self.sub.next_message_pure().await
}
}
struct Framebuffer {
width: u32,
height: u32,
dma_buf: Option<DmaTxBuf>,
}
2025-12-27 21:03:52 +01:00
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 {
// ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM
// have extra alignment requirements. The address and size of the buffer pointed to by each
// 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(),
)
};
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,
);
// Descriptors are initialized by `DmaTxBuf::new`.
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();
Self {
width,
height,
dma_buf: Some(dma_buf),
}
2025-12-27 21:03:52 +01:00
}
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.dma_buf.as_mut().unwrap().as_mut_slice())
2025-12-27 21:03:52 +01:00
}
}
2025-12-27 21:03:52 +01:00
#[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) {
2025-12-27 21:03:52 +01:00
loop {
// Timer::after(Duration::from_millis(100)).await;
2026-01-01 01:24:52 +01:00
// yield_now().await;
SIGNAL_LCD_SUBMIT.wait().await;
2025-12-27 21:03:52 +01:00
// 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
// This can be implemented as a `DmaTxBuffer`.
let transfer = match st7701s.dpi.send(false, framebuffer.dma_buf.take().unwrap()) {
2025-12-27 21:03:52 +01:00
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;
framebuffer.dma_buf = Some(result_dma_buf);
2025-12-27 21:03:52 +01:00
continue;
}
Ok(transfer) => transfer,
};
2026-01-01 01:24:52 +01:00
// 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.
2026-01-01 04:13:18 +01:00
#[cfg(not(feature = "limit-fps"))]
while !transfer.is_done() {
// Timer::after_millis(1).await;
rmk::embassy_futures::yield_now().await;
}
2025-12-27 21:03:52 +01:00
let result;
let dma_buf;
2025-12-27 21:03:52 +01:00
(result, st7701s.dpi, dma_buf) = transfer.wait();
framebuffer.dma_buf = Some(dma_buf);
2025-12-27 21:03:52 +01:00
2026-01-01 01:24:52 +01:00
SIGNAL_UI_RENDER.signal(());
2025-12-27 21:03:52 +01:00
if let Err(error) = result {
error!(
"An error occurred while transferring framebuffer to the LCD display: {error:?}"
);
}
}
}
2026-01-05 04:16:05 +01:00
// // TODO: Not needed currently. If it is ever enabled, don't forget to register it in Io.
// #[handler]
// #[ram] // TODO: Is this necessary?
// fn interrupt_handler() {
// // esp_println::println!(
// // "GPIO Interrupt with priority {}",
// // esp_hal::xtensa_lx::interrupt::get_level()
// // );
// }