2026-01-01 03:59:24 +01:00
//! TODO:
2026-01-02 02:44:00 +01:00
//! * 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) ]
2026-01-11 00:06:35 +01:00
#![ 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 ;
2026-02-05 02:54:40 +01:00
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-02-22 00:59:01 +01:00
use alloc ::alloc ::Global ;
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 ;
2026-02-05 02:54:40 +01:00
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 ;
2026-02-05 02:54:40 +01:00
use alloc ::vec ::Vec ;
2026-02-22 00:59:01 +01:00
use cfg_if ::cfg_if ;
2026-01-24 00:42:16 +01:00
use embassy_embedded_hal ::adapter ::BlockingAsync ;
use embassy_embedded_hal ::flash ::partition ::Partition ;
2026-02-23 23:08:27 +01:00
use embassy_executor ::{ SendSpawner , Spawner , SpawnerTraceExt } ;
2026-01-06 22:52:41 +01:00
use embassy_sync ::blocking_mutex ::raw ::CriticalSectionRawMutex ;
2026-01-02 02:44:00 +01:00
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 ;
2026-01-28 22:46:14 +01:00
use embassy_time ::{ Duration , Timer } ;
2026-01-31 15:36:36 +01:00
use esp_alloc ::{ HeapRegion , MemoryCapability } ;
2026-02-05 02:54:40 +01:00
use esp_bootloader_esp_idf ::partitions ::PartitionTable ;
2025-12-27 21:03:52 +01:00
use esp_hal ::clock ::CpuClock ;
2026-02-14 20:03:32 +01:00
use esp_hal ::dma ::{
BurstConfig , DmaDescriptor , DmaTxBuf , ExternalBurstConfig , InternalBurstConfig ,
} ;
2026-02-12 22:44:13 +01:00
use esp_hal ::efuse ::Efuse ;
2026-02-22 00:59:01 +01:00
#[ cfg(not(feature = " alt-log " )) ]
use esp_hal ::gpio ::NoPin ;
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 } ;
2026-02-15 02:33:42 +01:00
use esp_hal ::interrupt ::software ::{ SoftwareInterrupt , SoftwareInterruptControl } ;
2025-12-24 02:07:21 +01:00
use esp_hal ::lcd_cam ::LcdCam ;
2026-02-15 02:33:42 +01:00
use esp_hal ::lcd_cam ::lcd ::dpi ::{ Dpi , DpiTransfer } ;
2025-12-24 02:07:21 +01:00
use esp_hal ::mcpwm ::{ McPwm , PeripheralClockConfig } ;
2026-02-14 20:03:32 +01:00
use esp_hal ::peripherals ::{ DMA_CH0 , SPI0 , SPI2 } ;
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-02-14 20:03:32 +01:00
use esp_hal ::spi ::master ::AnySpi ;
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 ;
2026-02-22 00:59:01 +01:00
use esp_hal ::uart ::{ Uart , UartRx } ;
2026-02-15 02:33:42 +01:00
use esp_hal ::{ Blocking , interrupt } ;
use esp_rtos ::embassy ::{ Executor , InterruptExecutor } ;
2025-12-24 02:07:21 +01:00
use esp_storage ::FlashStorage ;
2026-02-15 18:17:16 +01:00
use i_slint_core ::software_renderer ::TargetPixel ;
2026-02-05 02:54:40 +01:00
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 } ;
2026-01-02 02:44:00 +01:00
use rmk ::channel ::{ CONTROLLER_CHANNEL , ControllerSub } ;
2026-02-14 20:03:32 +01:00
use rmk ::config ::{ DeviceConfig , RmkConfig , StorageConfig , VialConfig } ;
2026-01-02 02:44:00 +01:00
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 ;
2026-01-02 02:44:00 +01:00
use rmk ::hid ::Report ;
2025-12-24 02:07:21 +01:00
use rmk ::input_device ::Runnable ;
2026-02-14 20:03:32 +01:00
use rmk ::join_all ;
2025-12-24 02:07:21 +01:00
use rmk ::keyboard ::Keyboard ;
use rmk ::storage ::async_flash_wrapper ;
2026-01-02 02:44:00 +01:00
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 _ } ;
use crate ::matrix ::IoeMatrix ;
2025-12-27 21:03:52 +01:00
use crate ::peripherals ::st7701s ::St7701s ;
2026-02-13 02:50:32 +01:00
use crate ::proxy ::create_hid_report_interceptor ;
2026-02-23 23:08:27 +01:00
use crate ::ui ::backend ::SlintBackend ;
use crate ::ui ::dpi ::{ DmaBounce , DmaTxBounceBuf , Framebuffer , Swapchain , allocate_dma_buffer_in } ;
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-02-13 02:50:32 +01:00
mod config ;
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 ;
mod logging ;
2025-12-31 00:54:48 +01:00
mod matrix ;
mod peripherals ;
2026-02-13 02:50:32 +01:00
mod proxy ;
2025-12-31 00:54:48 +01:00
mod ui ;
2026-01-20 22:01:12 +01:00
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! ( ) ;
2026-01-26 19:21:49 +01:00
// 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.
2026-01-26 19:21:49 +01:00
/// Total heap size
2026-02-04 03:14:21 +01:00
const HEAP_SIZE : usize = 112 * 1024 ;
2026-01-26 19:21:49 +01:00
/// 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-26 19:21:49 +01:00
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
2025-12-29 19:36:00 +01:00
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-02-22 00:59:01 +01:00
// /// Used to signal that MCU is ready to submit the framebuffer to the LCD.
// static SIGNAL_LCD_SUBMIT: Signal<CriticalSectionRawMutex, ()> = Signal::new();
2026-01-01 01:24:52 +01:00
2026-02-22 00:59:01 +01:00
// /// Used to signal that the MCU is ready to render the GUI.
// static SIGNAL_UI_RENDER: Signal<CriticalSectionRawMutex, ()> = Signal::new();
2026-01-01 01:24:52 +01:00
2026-02-15 18:17:16 +01:00
#[ embassy_executor::task ]
async fn test_bounce_buffers_task (
channel : DMA_CH0 < 'static > ,
peripheral : SPI2 < 'static > ,
st7701s : St7701s < 'static , Blocking > ,
) {
test_bounce_buffers ( channel , peripheral , st7701s ) . await ;
}
2026-02-15 02:33:42 +01:00
async fn test_bounce_buffers (
channel : DMA_CH0 < 'static > ,
peripheral : SPI2 < 'static > ,
st7701s : St7701s < 'static , Blocking > ,
2026-02-22 16:09:20 +01:00
) {
2026-02-14 20:03:32 +01:00
error! ( " TEST BOUNCE BUFFERS SECTION ENTERED " ) ;
2026-02-22 16:09:20 +01:00
const BYTES_PER_PIXEL : usize = core ::mem ::size_of ::< u16 > ( ) ;
// Assume highest burst config setting.
const EXTERNAL_BURST_CONFIG : ExternalBurstConfig = ExternalBurstConfig ::Size32 ;
const ALIGNMENT_PIXELS : usize = EXTERNAL_BURST_CONFIG as usize / BYTES_PER_PIXEL ;
// The total number of pixels demanded by the DPI, per row.
const WIDTH_TOTAL_PIXELS : usize = 368 ;
// The total number of rows demanded by the DPI, per frame.
const HEIGHT_PIXELS : usize = 960 ;
// The number of unused pixels at the start of the row.
const FRONT_PORCH_ACTUAL_PIXELS : usize = 120 ;
// The number of actually visible pixels, per row.
const WIDTH_VISIBLE_PIXELS : usize = 240 ;
// The number of pixels not stored in a bounce buffer, per row.
// This many arbitrary pixels are sent to the DPI.
const FRONT_PORCH_SKIPPED_PIXELS : usize =
( FRONT_PORCH_ACTUAL_PIXELS / ALIGNMENT_PIXELS ) * ALIGNMENT_PIXELS ;
const WIDTH_STORED_PIXELS : usize = WIDTH_TOTAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS ;
const VISIBLE_OFFSET_IN_BUFFER_PIXELS : usize =
FRONT_PORCH_ACTUAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS ;
const ROWS_PER_WINDOW : usize = 16 ;
2026-02-22 00:59:01 +01:00
let burst_config = BurstConfig {
internal_memory : InternalBurstConfig ::Enabled ,
2026-02-22 16:09:20 +01:00
external_memory : EXTERNAL_BURST_CONFIG ,
2026-02-22 00:59:01 +01:00
} ;
2026-02-23 23:08:27 +01:00
let ( swapchain_reader , mut swapchain_writer ) = Swapchain {
framebuffers : [
Box ::leak ( allocate_dma_buffer_in (
HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL ,
burst_config ,
& PSRAM_ALLOCATOR ,
) ) ,
Box ::leak ( allocate_dma_buffer_in (
HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL ,
burst_config ,
& PSRAM_ALLOCATOR ,
) ) ,
] ,
}
. into_reader_writer ( ) ;
2026-02-15 18:17:16 +01:00
2026-02-22 16:09:20 +01:00
{
2026-02-23 23:08:27 +01:00
let write_guard = & mut swapchain_writer . write ( ) ;
let buffer_src = write_guard . cast ::< Rgb565Pixel > ( ) ;
2026-02-22 16:09:20 +01:00
let colors = ( 0 .. WIDTH_VISIBLE_PIXELS as u8 / 2 )
. rev ( )
. map ( | val | Rgb565Pixel ::from_rgb ( 0xFF , val * 2 , 0 ) )
. collect ::< Vec < _ > > ( ) ;
for ( index , pixel ) in buffer_src . iter_mut ( ) . enumerate ( ) {
let mut x =
( index % WIDTH_STORED_PIXELS ) as i16 - VISIBLE_OFFSET_IN_BUFFER_PIXELS as i16 ;
let mut y = ( index / WIDTH_STORED_PIXELS ) as i16 ;
if x < WIDTH_VISIBLE_PIXELS as i16 {
x = core ::cmp ::min ( x , WIDTH_VISIBLE_PIXELS as i16 - 1 - x ) ;
y = core ::cmp ::min ( y , HEIGHT_PIXELS as i16 - 1 - y ) ;
let min = core ::cmp ::min ( x , y ) ;
* pixel = colors [ min as usize % colors . len ( ) ] . clone ( ) ;
continue ;
}
* pixel = Rgb565Pixel ::default ( ) ;
}
2026-02-15 18:17:16 +01:00
}
2026-02-22 16:09:20 +01:00
warn! ( " FRONT_PORCH_SKIPPED_PIXELS: {FRONT_PORCH_SKIPPED_PIXELS} " ) ;
warn! ( " WIDTH_STORED_PIXELS: {WIDTH_STORED_PIXELS} " ) ;
warn! ( " ROWS_PER_WINDOW: {ROWS_PER_WINDOW} " ) ;
2026-02-23 23:08:27 +01:00
let buf = DmaBounce ::new (
2026-02-22 00:59:01 +01:00
Global ,
2026-02-14 20:03:32 +01:00
channel ,
AnySpi ::from ( peripheral ) ,
2026-02-15 02:33:42 +01:00
st7701s . dpi ,
2026-02-23 23:08:27 +01:00
swapchain_reader ,
2026-02-22 16:09:20 +01:00
FRONT_PORCH_SKIPPED_PIXELS * BYTES_PER_PIXEL ,
WIDTH_STORED_PIXELS * BYTES_PER_PIXEL ,
ROWS_PER_WINDOW ,
burst_config ,
2026-02-22 17:19:02 +01:00
true ,
2026-02-14 20:03:32 +01:00
) ;
2026-02-23 23:08:27 +01:00
buf . launch_interrupt_driven_task ( ) . await ;
2026-02-14 20:03:32 +01:00
error! ( " TEST BOUNCE BUFFERS SECTION DONE " ) ;
}
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 ) ,
2026-02-22 00:59:01 +01:00
flash_frequency : FlashFreq ::FlashFreq120m ,
ram_frequency : SpiRamFreq ::Freq120m ,
2025-12-27 21:03:52 +01:00
} ) ;
2025-12-24 02:07:21 +01:00
let peripherals : esp_hal ::peripherals ::Peripherals = esp_hal ::init ( config ) ;
2026-02-22 00:59:01 +01:00
#[ allow(unused) ]
let ( uart_rx , uart_tx ) = {
#[ cfg(feature = " alt-log " ) ]
let ( tx , rx ) = ( peripherals . GPIO12 , peripherals . GPIO5 ) ;
#[ cfg(not(feature = " alt-log " )) ]
let ( tx , rx ) = ( NoPin , NoPin ) ;
Uart ::new ( peripherals . UART2 , Default ::default ( ) )
. unwrap ( )
. with_tx ( tx )
. with_rx ( rx )
. split ( )
} ;
2026-01-31 20:21:40 +01:00
#[ cfg(feature = " usb-log " ) ]
2026-02-22 00:59:01 +01:00
logging ::usb ::setup_logging ( ) ;
2025-12-29 19:36:00 +01:00
#[ cfg(feature = " alt-log " ) ]
2026-02-22 00:59:01 +01:00
logging ::uart ::setup_logging ( uart_tx ) ;
2026-01-31 20:21:40 +01:00
#[ cfg(feature = " rtt-log " ) ]
2026-02-22 00:59:01 +01:00
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.
2026-01-26 19:21:49 +01:00
// Memory reclaimed from the esp-idf bootloader.
2026-01-28 22:46:14 +01:00
const HEAP_SIZE_RECLAIMED : usize = const {
let range = esp_metadata_generated ::memory_range! ( " DRAM2_UNINIT " ) ;
range . end - range . start
} ;
2026-01-26 19:21:49 +01:00
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 ( ) ;
2026-01-20 22:01:12 +01:00
2025-12-24 02:07:21 +01:00
let timg0 = TimerGroup ::new ( peripherals . TIMG0 ) ;
let software_interrupt = SoftwareInterruptControl ::new ( peripherals . SW_INTERRUPT ) ;
2026-02-18 05:03:05 +01:00
esp_rtos ::start ( timg0 . timer0 , software_interrupt . software_interrupt0 ) ;
2025-12-31 01:08:12 +01:00
2026-02-22 00:59:01 +01:00
// A task executor that is able to handle interrupts, and then return back to executing tasks.
2026-02-15 02:33:42 +01:00
static EXECUTOR_CORE_0 : StaticCell < InterruptExecutor < 2 > > = StaticCell ::new ( ) ;
let executor_core_0 = InterruptExecutor ::new ( software_interrupt . software_interrupt2 ) ;
let executor_core_0 = EXECUTOR_CORE_0 . init ( executor_core_0 ) ;
2026-02-23 23:08:27 +01:00
let interrupt_core_0_spawner = executor_core_0 . start ( interrupt ::Priority ::Priority1 ) ;
// static EXECUTOR_CORE_1: StaticCell<InterruptExecutor<3>> = StaticCell::new();
// let executor_core_1 = InterruptExecutor::new(software_interrupt.software_interrupt3);
// let executor_core_1 = EXECUTOR_CORE_1.init(executor_core_1);
// let interrupt_core_1_spawner = executor_core_1.start(interrupt::Priority::Priority2);
2026-02-15 02:33:42 +01:00
2026-01-05 04:16:05 +01:00
info! ( " ESP-RTOS started! " ) ;
2026-02-22 00:59:01 +01:00
let main_task_peripherals = MainPeripherals {
2026-02-23 23:08:27 +01:00
// high_priority_task_spawner: interrupt_core_1_spawner,
2026-02-22 00:59:01 +01:00
uart_rx ,
software_interrupt1 : software_interrupt . software_interrupt1 ,
RNG : peripherals . RNG ,
ADC1 : peripherals . ADC1 ,
USB0 : peripherals . USB0 ,
FLASH : peripherals . FLASH ,
LCD_CAM : peripherals . LCD_CAM ,
DMA_CH0 : peripherals . DMA_CH0 ,
DMA_CH2 : peripherals . DMA_CH2 ,
I2C0 : peripherals . I2C0 ,
SPI2 : peripherals . SPI2 ,
CPU_CTRL : peripherals . CPU_CTRL ,
GPIO0 : gpio0 ,
GPIO1 : peripherals . GPIO1 ,
GPIO2 : peripherals . GPIO2 ,
GPIO3 : peripherals . GPIO3 ,
GPIO4 : peripherals . GPIO4 ,
#[ cfg(not(feature = " alt-log " )) ]
GPIO5 : peripherals . GPIO5 ,
GPIO6 : peripherals . GPIO6 ,
GPIO7 : peripherals . GPIO7 ,
GPIO8 : peripherals . GPIO8 ,
GPIO9 : peripherals . GPIO9 ,
#[ cfg(not(feature = " alt-log " )) ]
GPIO12 : peripherals . GPIO12 ,
GPIO13 : peripherals . GPIO13 ,
GPIO14 : peripherals . GPIO14 ,
GPIO15 : peripherals . GPIO15 ,
GPIO16 : peripherals . GPIO16 ,
GPIO19 : peripherals . GPIO19 ,
GPIO20 : peripherals . GPIO20 ,
GPIO34 : peripherals . GPIO34 ,
GPIO35 : peripherals . GPIO35 ,
GPIO36 : peripherals . GPIO36 ,
GPIO37 : peripherals . GPIO37 ,
GPIO38 : peripherals . GPIO38 ,
GPIO39 : peripherals . GPIO39 ,
GPIO40 : peripherals . GPIO40 ,
GPIO41 : peripherals . GPIO41 ,
GPIO42 : peripherals . GPIO42 ,
GPIO43 : peripherals . GPIO43 ,
GPIO44 : peripherals . GPIO44 ,
} ;
interrupt_core_0_spawner . must_spawn ( main_task ( main_task_peripherals ) ) ;
}
/// Peripherals passed to the main task.
#[ allow(non_snake_case) ]
struct MainPeripherals {
2026-02-23 23:08:27 +01:00
// high_priority_task_spawner: SendSpawner,
2026-02-22 00:59:01 +01:00
uart_rx : UartRx < 'static , Blocking > ,
software_interrupt1 : SoftwareInterrupt < 'static , 1 > ,
RNG : esp_hal ::peripherals ::RNG < 'static > ,
ADC1 : esp_hal ::peripherals ::ADC1 < 'static > ,
USB0 : esp_hal ::peripherals ::USB0 < 'static > ,
FLASH : esp_hal ::peripherals ::FLASH < 'static > ,
LCD_CAM : esp_hal ::peripherals ::LCD_CAM < 'static > ,
DMA_CH0 : esp_hal ::peripherals ::DMA_CH0 < 'static > ,
DMA_CH2 : esp_hal ::peripherals ::DMA_CH2 < 'static > ,
I2C0 : esp_hal ::peripherals ::I2C0 < 'static > ,
SPI2 : esp_hal ::peripherals ::SPI2 < 'static > ,
CPU_CTRL : esp_hal ::peripherals ::CPU_CTRL < 'static > ,
GPIO0 : Output < 'static > ,
GPIO1 : esp_hal ::peripherals ::GPIO1 < 'static > ,
GPIO2 : esp_hal ::peripherals ::GPIO2 < 'static > ,
GPIO3 : esp_hal ::peripherals ::GPIO3 < 'static > ,
GPIO4 : esp_hal ::peripherals ::GPIO4 < 'static > ,
#[ cfg(not(feature = " alt-log " )) ]
GPIO5 : esp_hal ::peripherals ::GPIO5 < 'static > ,
GPIO6 : esp_hal ::peripherals ::GPIO6 < 'static > ,
GPIO7 : esp_hal ::peripherals ::GPIO7 < 'static > ,
GPIO8 : esp_hal ::peripherals ::GPIO8 < 'static > ,
GPIO9 : esp_hal ::peripherals ::GPIO9 < 'static > ,
// GPIO10: esp_hal::peripherals::GPIO10<'static>,
#[ cfg(not(feature = " alt-log " )) ]
GPIO12 : esp_hal ::peripherals ::GPIO12 < 'static > ,
GPIO13 : esp_hal ::peripherals ::GPIO13 < 'static > ,
GPIO14 : esp_hal ::peripherals ::GPIO14 < 'static > ,
GPIO15 : esp_hal ::peripherals ::GPIO15 < 'static > ,
GPIO16 : esp_hal ::peripherals ::GPIO16 < 'static > ,
// GPIO18: esp_hal::peripherals::GPIO18<'static>,
GPIO19 : esp_hal ::peripherals ::GPIO19 < 'static > ,
GPIO20 : esp_hal ::peripherals ::GPIO20 < 'static > ,
// GPIO33: esp_hal::peripherals::GPIO33<'static>,
GPIO34 : esp_hal ::peripherals ::GPIO34 < 'static > ,
GPIO35 : esp_hal ::peripherals ::GPIO35 < 'static > ,
GPIO36 : esp_hal ::peripherals ::GPIO36 < 'static > ,
GPIO37 : esp_hal ::peripherals ::GPIO37 < 'static > ,
GPIO38 : esp_hal ::peripherals ::GPIO38 < 'static > ,
GPIO39 : esp_hal ::peripherals ::GPIO39 < 'static > ,
GPIO40 : esp_hal ::peripherals ::GPIO40 < 'static > ,
GPIO41 : esp_hal ::peripherals ::GPIO41 < 'static > ,
GPIO42 : esp_hal ::peripherals ::GPIO42 < 'static > ,
GPIO43 : esp_hal ::peripherals ::GPIO43 < 'static > ,
GPIO44 : esp_hal ::peripherals ::GPIO44 < 'static > ,
// GPIO45: esp_hal::peripherals::GPIO45<'static>,
// GPIO46: esp_hal::peripherals::GPIO46<'static>,
// GPIO47: esp_hal::peripherals::GPIO47<'static>,
// GPIO48: esp_hal::peripherals::GPIO48<'static>,
}
#[ embassy_executor::task ]
async fn main_task ( peripherals : MainPeripherals ) {
2026-02-24 23:07:51 +01:00
// let _spawner = unsafe { Spawner::for_current_executor() }.await;
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 ( ) ;
2026-01-02 02:44:00 +01:00
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
2025-12-29 19:36:00 +01:00
#[ 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 } ;
2026-01-26 19:21:49 +01:00
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 ( ) ;
2026-01-26 19:21:49 +01:00
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
2026-02-05 02:54:40 +01:00
static PARTITION_TABLE_BUFFER : StaticCell < Vec < u8 , & 'static esp_alloc ::EspHeap > > =
2026-01-24 00:42:16 +01:00
StaticCell ::new ( ) ;
2026-02-05 02:54:40 +01:00
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 ( ) ;
2026-02-05 02:54:40 +01:00
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
} ) ;
2026-02-05 02:54:40 +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 : 0 x { offset :x }
length : 0 x { len :x }
type : 0 x { 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
2026-02-22 00:59:01 +01:00
. with_data11 ( peripherals . GPIO0 )
2025-12-24 02:07:21 +01:00
. 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! " ) ;
2026-02-22 17:19:02 +01:00
// test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s).await;
// return;
2026-02-14 20:03:32 +01:00
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 ,
2026-02-05 02:54:40 +01:00
num_sectors : {
assert! (
2026-02-13 03:00:46 +01:00
flash_part_info_rmk . len ( ) % FlashStorage ::SECTOR_SIZE = = 0 ,
2026-02-05 02:54:40 +01:00
" The size of the RMK partition must be a multiple of {} bytes. Current size: {} " ,
FlashStorage ::SECTOR_SIZE ,
flash_part_info_rmk . len ( )
) ;
2026-02-13 03:00:46 +01:00
( flash_part_info_rmk . len ( ) / FlashStorage ::SECTOR_SIZE ) as u8
2026-02-05 02:54:40 +01:00
} ,
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 ,
} ;
// Initialze keyboard stuffs
// Initialize the storage and keymap
2026-02-13 02:50:32 +01:00
let mut default_keymap = config ::get_default_keymap ( ) ;
let mut behavior_config = config ::get_behavior_config ( ) ;
let mut positional_config = config ::get_positional_config ( ) ;
2025-12-24 02:07:21 +01:00
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 ,
2026-02-13 02:50:32 +01:00
& mut positional_config ,
2025-12-24 02:07:21 +01:00
)
. 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! " ) ;
2025-12-29 19:36:00 +01:00
static FRAMEBUFFER : StaticCell < Framebuffer > = StaticCell ::new ( ) ;
let framebuffer = FRAMEBUFFER . init ( Framebuffer ::new (
2026-02-22 00:59:01 +01:00
peripherals . DMA_CH0 ,
peripherals . SPI2 . into ( ) ,
st7701s . dpi ,
2026-02-22 17:19:02 +01:00
BurstConfig {
internal_memory : InternalBurstConfig ::Enabled ,
external_memory : ExternalBurstConfig ::Size32 ,
} ,
2026-02-22 00:59:01 +01:00
// The burst config (16/32/64) doesn't seem to affect the alignment of the row size.
2026-02-14 20:03:32 +01:00
//
// | | ( displayed range ) |
// | [ pad ] [ pad ]
// | [ DMA-transmissible range ]
// [ DMA-t. left overscan ] | | |
// 0 112 120 360 368 (index of u16 pixel)
// ^ aligned ^ aligned ^ aligned
//
// TODO: Compute the appropriate ranges to pass to the renderer and DPI peripheral.
// The renderer should pass the size of the `pad`ding to the GUI is parameters,
// to align the content to the displayed range.
2026-02-22 16:09:20 +01:00
112 ,
368 - 112 ,
2025-12-29 19:36:00 +01:00
960 ,
2026-02-23 23:08:27 +01:00
8 ,
true ,
2025-12-29 19:36:00 +01:00
) ) ;
2026-01-05 04:16:05 +01:00
info! ( " Framebuffer created! " ) ;
2025-12-31 22:24:26 +01:00
let window_size = [ framebuffer . height , framebuffer . width ] ;
2026-02-23 23:08:27 +01:00
let swapchain_writer = framebuffer . swapchain . take ( ) . unwrap ( ) ;
2025-12-29 19:36:00 +01:00
2026-01-26 19:21:49 +01:00
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 ,
2026-02-22 00:59:01 +01:00
// peripherals.software_interrupt0,
peripherals . software_interrupt1 ,
2025-12-27 21:03:52 +01:00
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(
2026-02-22 00:59:01 +01:00
// peripherals.software_interrupt2,
2025-12-27 23:51:46 +01:00
// ));
// let spawner = exec.start(Priority::Priority3);
2025-12-29 19:36:00 +01:00
// 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 | {
2025-12-29 19:36:00 +01:00
let slint_backend = SlintBackend {
// peripherals: RefCell::new(Some(peripherals)),
window_size ,
window : RefCell ::new ( None ) ,
2026-02-23 23:08:27 +01:00
swapchain : RefCell ::new ( swapchain_writer ) ,
2026-02-04 03:14:21 +01:00
quit_event_loop : Default ::default ( ) ,
events : Arc ::new ( critical_section ::Mutex ::new ( RefCell ::new ( VecDeque ::new ( ) ) ) ) ,
2025-12-29 19:36:00 +01:00
} ;
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! " ) ;
2026-01-02 02:44:00 +01:00
let mut user_controller = UserController ::new ( ) ;
2026-01-05 04:16:05 +01:00
info! ( " Awaiting on all tasks... " ) ;
2026-02-23 23:08:27 +01:00
framebuffer
. bounce_buffers
. take ( )
. unwrap ( )
. launch_interrupt_driven_task ( )
. await ;
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! [
2026-01-28 22:46:14 +01:00
run_alloc_stats_reporter ( ) ,
2025-12-29 19:36:00 +01:00
// We currently send the framebuffer data using the main core, which does not seem to slow
// down the rest of the tasks too much.
2026-02-22 00:59:01 +01:00
// async {
// warn!("Waiting...");
// Timer::after_secs(3).await;
// warn!("Waited.");
// framebuffer.bounce_buffers.send().await;
// },
2026-02-23 23:08:27 +01:00
// framebuffer.bounce_buffers.send(),
2026-02-15 02:33:42 +01:00
// ui::dpi::run_lcd(st7701s, framebuffer),
// lcd_task,
2025-12-24 02:07:21 +01:00
run_devices! (
2026-01-02 02:44:00 +01:00
( matrix ) = > rmk ::channel ::EVENT_CHANNEL ,
2025-12-24 02:07:21 +01:00
) ,
keyboard . run ( ) , // Keyboard is special
run_rmk (
& keymap ,
2025-12-29 19:36:00 +01:00
#[ 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
) ,
2026-01-11 00:06:35 +01:00
create_hid_report_interceptor ( ) ,
2026-01-02 02:44:00 +01:00
user_controller . event_loop ( ) ,
2026-02-22 00:59:01 +01:00
console ::run_console ( peripherals . uart_rx . into_async ( ) )
2025-12-27 21:03:52 +01:00
]
2025-12-24 02:07:21 +01:00
. await ;
}
2026-01-28 22:46:14 +01:00
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 ;
}
}
2026-01-02 02:44:00 +01:00
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
}
2026-01-02 02:44:00 +01:00
}
}
}
async fn next_message ( & mut self ) -> Self ::Event {
self . sub . next_message_pure ( ) . await
}
}
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]
2026-02-14 20:03:32 +01:00
// #[ram] // Improves performance.
2026-01-05 04:16:05 +01:00
// fn interrupt_handler() {
// // esp_println::println!(
// // "GPIO Interrupt with priority {}",
// // esp_hal::xtensa_lx::interrupt::get_level()
// // );
// }