//! TODO: //! * GUI event dispatch. //! * 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. //! https://esp32.com/viewtopic.php?t=28230 //! https://esp32.com/viewtopic.php?f=12&t=26793&start=20#p95677 #![no_std] #![no_main] #![feature(allocator_api)] #![feature(macro_metavar_expr)] #![feature(c_variadic)] #![feature(c_size_t)] #![feature(debug_closure_helpers)] extern crate alloc; use core::alloc::Layout; use core::cell::RefCell; use core::fmt::Write; use core::sync::atomic::{AtomicBool, Ordering}; use alloc::alloc::Global; use alloc::boxed::Box; use alloc::collections::vec_deque::VecDeque; use alloc::format; use alloc::string::String; use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; use cfg_if::cfg_if; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::flash::partition::Partition; use embassy_executor::Spawner; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::{Duration, Timer}; use esp_alloc::{HeapRegion, MemoryCapability}; use esp_bootloader_esp_idf::partitions::PartitionTable; use esp_hal::clock::CpuClock; use esp_hal::dma::{ BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig, InternalBurstConfig, }; use esp_hal::efuse::Efuse; #[cfg(not(feature = "alt-log"))] use esp_hal::gpio::NoPin; use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull}; use esp_hal::i2c::master::{I2c, I2cAddress}; use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl}; use esp_hal::lcd_cam::LcdCam; use esp_hal::lcd_cam::lcd::dpi::{Dpi, DpiTransfer}; use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; use esp_hal::peripherals::{DMA_CH0, SPI0, SPI2}; use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock}; use esp_hal::ram; use esp_hal::rng::TrngSource; use esp_hal::sha::ShaBackend; use esp_hal::spi::master::AnySpi; use esp_hal::system::Stack; use esp_hal::timer::timg::TimerGroup; use esp_hal::uart::{Uart, UartRx}; use esp_hal::{Blocking, interrupt}; use esp_rtos::embassy::{Executor, InterruptExecutor}; use esp_storage::FlashStorage; use i_slint_core::software_renderer::TargetPixel; use indoc::writedoc; use itertools::Itertools; use log::{error, info, warn}; use rmk::channel::{CONTROLLER_CHANNEL, ControllerSub}; use rmk::config::{DeviceConfig, RmkConfig, StorageConfig, VialConfig}; use rmk::controller::{Controller, EventController}; use rmk::debounce::default_debouncer::DefaultDebouncer; use rmk::event::ControllerEvent; use rmk::hid::Report; use rmk::input_device::Runnable; use rmk::join_all; use rmk::keyboard::Keyboard; use rmk::storage::async_flash_wrapper; use rmk::types::action::{Action, KeyAction}; use rmk::{initialize_keymap_and_storage, run_devices, run_rmk}; use slint::platform::software_renderer::Rgb565Pixel; use static_cell::StaticCell; use {esp_alloc as _, esp_backtrace as _}; use crate::matrix::IoeMatrix; use crate::peripherals::st7701s::St7701s; use crate::proxy::create_hid_report_interceptor; use crate::ui::backend::{FramebufferPtr, SlintBackend}; use crate::ui::dpi::{DmaBounce, DmaTxBounceBuf, Framebuffer, allocate_dma_buffer_in}; use crate::vial::{ CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID, VIAL_VENDOR_ID, }; mutually_exclusive_features::none_or_one_of!["usb-log", "alt-log", "rtt-log"]; mod config; mod crypto; mod db; mod ffi; mod logging; mod matrix; mod peripherals; mod proxy; mod ui; mod util; mod vial; #[cfg(feature = "alt-log")] mod console; // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); // Memory allocation regions. // These can be debugged using `xtensa-esp32s3-elf-size -A `. // 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 const HEAP_SIZE: usize = 112 * 1024; /// Size of the app core's stack const STACK_SIZE_CORE_APP: usize = 80 * 1024; // const FRAME_DURATION_MIN: Duration = Duration::from_millis(40); // 25 FPS const FRAME_DURATION_MIN: Duration = Duration::from_millis(100); // 10 FPS pub static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); static KEYBOARD_REPORT_PROXY: Channel = Channel::new(); static LCD_ENABLED: AtomicBool = AtomicBool::new(false); // /// Used to signal that MCU is ready to submit the framebuffer to the LCD. // static SIGNAL_LCD_SUBMIT: Signal = Signal::new(); // /// Used to signal that the MCU is ready to render the GUI. // static SIGNAL_UI_RENDER: Signal = Signal::new(); #[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; } async fn test_bounce_buffers( channel: DMA_CH0<'static>, peripheral: SPI2<'static>, st7701s: St7701s<'static, Blocking>, ) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> { error!("TEST BOUNCE BUFFERS SECTION ENTERED"); const WIDTH: usize = 368; const HEIGHT: usize = 960; const ROWS_PER_WINDOW: usize = 8; let windows_len = HEIGHT / ROWS_PER_WINDOW; let window_size = ROWS_PER_WINDOW * WIDTH * core::mem::size_of::(); let burst_config = BurstConfig { internal_memory: InternalBurstConfig::Enabled, external_memory: ExternalBurstConfig::Size64, }; let buffer_src = Box::leak(allocate_dma_buffer_in( windows_len * window_size, burst_config, &PSRAM_ALLOCATOR, )); let buffer_src = bytemuck::cast_slice_mut::(buffer_src); let colors = (0..120_u8) .rev() .map(|val| { // Rgb565Pixel::from_rgb( // (val % 2) * (0b11111 / (2 - 1)), // (val % 8) * (0b111111 / (8 - 1)), // (val % 32) * (0b11111 / (32 - 1)), // ) // Rgb565Pixel::from_rgb( // (0b11111 as f32 * (val as f32 / 119.0)) as u8, // (0b111111 as f32 * (val as f32 / 119.0)) as u8, // (0b11111 as f32 * (val as f32 / 119.0)) as u8, // ) Rgb565Pixel::from_rgb(0xFF, val * 2, 0) }) .collect::>(); for (index, pixel) in buffer_src.iter_mut().enumerate() { let mut x = (index % WIDTH) as i16 - 120; let mut y = (index / WIDTH) as i16; if x < 240 { x = core::cmp::min(x, 240 - 1 - x); y = core::cmp::min(y, 960 - 1 - y); let min = core::cmp::min(x, y); *pixel = colors[min as usize % colors.len()].clone(); continue; } *pixel = Rgb565Pixel::default(); } let buffer_src = bytemuck::cast_slice_mut::(buffer_src); // let mut counter: u8 = 0; // buffer_src.fill_with(|| { // counter = counter.wrapping_add(1); // counter // }); let mut buf = DmaBounce::new( Global, channel, AnySpi::from(peripheral), st7701s.dpi, buffer_src, window_size, BurstConfig { internal_memory: InternalBurstConfig::Enabled, external_memory: ExternalBurstConfig::Size64, }, false, ); let _ = buf.send().await; error!("TEST BOUNCE BUFFERS SECTION DONE"); loop { Timer::after_secs(10).await; } } #[esp_rtos::main] 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::FlashFreq120m, ram_frequency: SpiRamFreq::Freq120m, }); let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config); #[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() }; #[cfg(feature = "usb-log")] logging::usb::setup_logging(); #[cfg(feature = "alt-log")] logging::uart::setup_logging(uart_tx); #[cfg(feature = "rtt-log")] logging::rtt::setup_logging(); // 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); info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats()); // 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(), )); } info!( "PSRAM allocator initialized with capacity of {} MiB!", psram_size / 1024 / 1024 ); } // let mut io = Io::new(peripherals.IO_MUX); // io.set_interrupt_handler(interrupt_handler); // info!("IO Mux initialized!"); // 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()); let mut sha_backend = ShaBackend::new(peripherals.SHA); let _sha_driver_handle = sha_backend.start(); let timg0 = TimerGroup::new(peripherals.TIMG0); let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0); // A task executor that is able to handle interrupts, and then return back to executing tasks. static EXECUTOR_CORE_0: StaticCell> = StaticCell::new(); let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2); let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0); let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority3); info!("ESP-RTOS started!"); let main_task_peripherals = MainPeripherals { 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 { 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) { // Enable the TRNG source, so `Trng` can be constructed. let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1); #[cfg(feature = "ble")] let mut host_resources = rmk::HostResources::new(); #[cfg(feature = "ble")] let stack = { use bt_hci::controller::ExternalController; use esp_radio::{Controller as RadioController, ble::controller::BleConnector}; let mut rng = esp_hal::rng::Trng::try_new().unwrap(); static RADIO: StaticCell> = StaticCell::new(); 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]; let ble_stack = rmk::ble::build_ble_stack(controller, central_addr, &mut rng, &mut host_resources) .await; info!("BLE stack for RMK built!"); ble_stack }; // Initialize USB #[cfg(not(feature = "no-usb"))] let usb_driver = { 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; _]); 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); info!("USB driver for RMK built!"); driver }; // Initialize the flash static PARTITION_TABLE_BUFFER: StaticCell> = StaticCell::new(); let partition_table_buffer = PARTITION_TABLE_BUFFER.init_with(|| { let mut buffer = Vec::::new_in(&PSRAM_ALLOCATOR); buffer.resize(1024, 0_u8); buffer }); static FLASH: StaticCell<( Mutex>, PartitionTable<'static>, )> = StaticCell::new(); let (flash, partition_table) = FLASH.init_with(|| { let mut flash = FlashStorage::new(peripherals.FLASH) // 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::::new(async_flash_wrapper(flash)), partition_table, ) }); { 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(), ); info!("Flash memory configured!"); 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()); let lcd = LcdCam::new(peripherals.LCD_CAM).lcd; let unconfigured_dpi = Dpi::new(lcd, peripherals.DMA_CH2, Default::default()) .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(peripherals.GPIO0) .with_data12(peripherals.GPIO1) .with_data13(peripherals.GPIO2) .with_data14(peripherals.GPIO3) .with_data15(peripherals.GPIO4); #[cfg(not(feature = "alt-log"))] let unconfigured_dpi = unconfigured_dpi // Green .with_data5(peripherals.GPIO5) .with_data6(peripherals.GPIO12); let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await; info!("ST7701S-based LCD display initialized!"); // interrupt_core_0_spawner.must_spawn(test_bounce_buffers_task( // peripherals.DMA_CH0, // peripherals.SPI2, // st7701s, // )); // let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s); // let _ = lcd_task.await; // return; // RMK config let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]); let storage_config = StorageConfig { start_addr: 0, num_sectors: { assert!( flash_part_info_rmk.len() % 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() / FlashStorage::SECTOR_SIZE) as u8 }, ..Default::default() }; // Retrieve the hardware-unique MAC address. let mac_address = Efuse::read_base_mac_address(); static SERIAL_NUMBER: StaticCell> = 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?}"); let rmk_config = RmkConfig { device_config, vial_config, storage_config, }; // Initialze keyboard stuffs // Initialize the storage and keymap let mut default_keymap = config::get_default_keymap(); let mut behavior_config = config::get_behavior_config(); let mut positional_config = config::get_positional_config(); let (keymap, mut storage) = initialize_keymap_and_storage( &mut default_keymap, flash_part_rmk, &storage_config, &mut behavior_config, &mut positional_config, ) .await; info!("Initialized keymap and storage for RMK!"); // 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); let matrix_interrupt_low = Input::new(peripherals.GPIO7, InputConfig::default()); let mut matrix = IoeMatrix::new( matrix_interrupt_low, i2c.into_async(), DefaultDebouncer::new(), [I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT], ) .await; let mut keyboard = Keyboard::new(&keymap); // Initialize the light controller info!("Keyboard initialized!"); static FRAMEBUFFER: StaticCell = StaticCell::new(); let framebuffer = FRAMEBUFFER.init(Framebuffer::new( peripherals.DMA_CH0, peripherals.SPI2.into(), st7701s.dpi, // The burst config (16/32/64) doesn't seem to affect the alignment of the row size. // // | | ( 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. 368, 960, 16, false, )); info!("Framebuffer created!"); // 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> = StaticCell::new(); let second_core_stack = SECOND_CORE_STACK.init(Stack::new()); esp_rtos::start_second_core( peripherals.CPU_CTRL, // peripherals.software_interrupt0, peripherals.software_interrupt1, second_core_stack, move || { // static EXECUTOR: StaticCell> = StaticCell::new(); // let exec = EXECUTOR.init(InterruptExecutor::new( // peripherals.software_interrupt2, // )); // let spawner = exec.start(Priority::Priority3); // spawner.must_spawn(run_renderer_task()); static EXECUTOR: StaticCell = 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, quit_event_loop: Default::default(), events: Arc::new(critical_section::Mutex::new(RefCell::new(VecDeque::new()))), }; spawner.must_spawn(ui::run_renderer_task(slint_backend, flash_part_acid)); }); }, ); info!("Second core started!"); let mut user_controller = UserController::new(); info!("Awaiting on all tasks..."); // TODO: Probably want to select! instead and re-try. 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. // async { // warn!("Waiting..."); // Timer::after_secs(3).await; // warn!("Waited."); // framebuffer.bounce_buffers.send().await; // }, framebuffer.bounce_buffers.send(), // ui::dpi::run_lcd(st7701s, framebuffer), // lcd_task, run_devices! ( (matrix) => rmk::channel::EVENT_CHANNEL, ), keyboard.run(), // Keyboard is special run_rmk( &keymap, #[cfg(not(feature = "no-usb"))] usb_driver, #[cfg(feature = "ble")] &stack, &mut storage, rmk_config, ), create_hid_report_interceptor(), user_controller.event_loop(), console::run_console(peripherals.uart_rx.into_async()) ] .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) { 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 { 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; } } } } async fn next_message(&mut self) -> Self::Event { self.sub.next_message_pure().await } } // // TODO: Not needed currently. If it is ever enabled, don't forget to register it in Io. // #[handler] // #[ram] // Improves performance. // fn interrupt_handler() { // // esp_println::println!( // // "GPIO Interrupt with priority {}", // // esp_hal::xtensa_lx::interrupt::get_level() // // ); // }