WIP
This commit is contained in:
parent
ea58ef0c8e
commit
74d7b6df5f
|
|
@ -40,7 +40,9 @@ use esp_alloc::{HeapRegion, MemoryCapability};
|
||||||
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||||
use esp_hal::Blocking;
|
use esp_hal::Blocking;
|
||||||
use esp_hal::clock::CpuClock;
|
use esp_hal::clock::CpuClock;
|
||||||
use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig};
|
use esp_hal::dma::{
|
||||||
|
BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig, InternalBurstConfig,
|
||||||
|
};
|
||||||
use esp_hal::efuse::Efuse;
|
use esp_hal::efuse::Efuse;
|
||||||
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
|
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
|
||||||
use esp_hal::i2c::master::{I2c, I2cAddress};
|
use esp_hal::i2c::master::{I2c, I2cAddress};
|
||||||
|
|
@ -48,10 +50,12 @@ use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||||
use esp_hal::lcd_cam::LcdCam;
|
use esp_hal::lcd_cam::LcdCam;
|
||||||
use esp_hal::lcd_cam::lcd::dpi::Dpi;
|
use esp_hal::lcd_cam::lcd::dpi::Dpi;
|
||||||
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
|
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::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
|
||||||
use esp_hal::ram;
|
use esp_hal::ram;
|
||||||
use esp_hal::rng::TrngSource;
|
use esp_hal::rng::TrngSource;
|
||||||
use esp_hal::sha::ShaBackend;
|
use esp_hal::sha::ShaBackend;
|
||||||
|
use esp_hal::spi::master::AnySpi;
|
||||||
use esp_hal::system::Stack;
|
use esp_hal::system::Stack;
|
||||||
use esp_hal::timer::timg::TimerGroup;
|
use esp_hal::timer::timg::TimerGroup;
|
||||||
use esp_rtos::embassy::Executor;
|
use esp_rtos::embassy::Executor;
|
||||||
|
|
@ -60,19 +64,16 @@ use indoc::writedoc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use rmk::channel::{CONTROLLER_CHANNEL, ControllerSub};
|
use rmk::channel::{CONTROLLER_CHANNEL, ControllerSub};
|
||||||
use rmk::config::{
|
use rmk::config::{DeviceConfig, RmkConfig, StorageConfig, VialConfig};
|
||||||
DeviceConfig, RmkConfig, StorageConfig,
|
|
||||||
VialConfig,
|
|
||||||
};
|
|
||||||
use rmk::controller::{Controller, EventController};
|
use rmk::controller::{Controller, EventController};
|
||||||
use rmk::debounce::default_debouncer::DefaultDebouncer;
|
use rmk::debounce::default_debouncer::DefaultDebouncer;
|
||||||
use rmk::event::ControllerEvent;
|
use rmk::event::ControllerEvent;
|
||||||
use rmk::hid::Report;
|
use rmk::hid::Report;
|
||||||
use rmk::input_device::Runnable;
|
use rmk::input_device::Runnable;
|
||||||
|
use rmk::join_all;
|
||||||
use rmk::keyboard::Keyboard;
|
use rmk::keyboard::Keyboard;
|
||||||
use rmk::storage::async_flash_wrapper;
|
use rmk::storage::async_flash_wrapper;
|
||||||
use rmk::types::action::{Action, KeyAction};
|
use rmk::types::action::{Action, KeyAction};
|
||||||
use rmk::join_all;
|
|
||||||
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
|
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
|
||||||
use slint::platform::software_renderer::Rgb565Pixel;
|
use slint::platform::software_renderer::Rgb565Pixel;
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
|
|
@ -82,6 +83,7 @@ use crate::matrix::IoeMatrix;
|
||||||
use crate::peripherals::st7701s::St7701s;
|
use crate::peripherals::st7701s::St7701s;
|
||||||
use crate::proxy::create_hid_report_interceptor;
|
use crate::proxy::create_hid_report_interceptor;
|
||||||
use crate::ui::backend::{FramebufferPtr, SlintBackend};
|
use crate::ui::backend::{FramebufferPtr, SlintBackend};
|
||||||
|
use crate::ui::dpi::{DmaTxBounceBuf, Framebuffer};
|
||||||
use crate::vial::{
|
use crate::vial::{
|
||||||
CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID,
|
CustomKeycodes, VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID, VIAL_KEYBOARD_NAME, VIAL_PRODUCT_ID,
|
||||||
VIAL_VENDOR_ID,
|
VIAL_VENDOR_ID,
|
||||||
|
|
@ -131,6 +133,26 @@ static SIGNAL_LCD_SUBMIT: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||||
/// Used to signal that the MCU is ready to render the GUI.
|
/// Used to signal that the MCU is ready to render the GUI.
|
||||||
static SIGNAL_UI_RENDER: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
static SIGNAL_UI_RENDER: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
||||||
|
|
||||||
|
async fn test_bounce_buffers(channel: DMA_CH0<'static>, peripheral: SPI2<'static>) -> ! {
|
||||||
|
error!("TEST BOUNCE BUFFERS SECTION ENTERED");
|
||||||
|
let windows_len = 2;
|
||||||
|
let window_size = 368 * core::mem::size_of::<u16>();
|
||||||
|
let buffer_src = Box::leak(vec![0_u8; windows_len * window_size].into_boxed_slice());
|
||||||
|
let mut buf = DmaTxBounceBuf::new(
|
||||||
|
channel,
|
||||||
|
AnySpi::from(peripheral),
|
||||||
|
buffer_src,
|
||||||
|
window_size,
|
||||||
|
BurstConfig {
|
||||||
|
internal_memory: InternalBurstConfig::Enabled,
|
||||||
|
external_memory: ExternalBurstConfig::Size32,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
buf.send().await;
|
||||||
|
error!("TEST BOUNCE BUFFERS SECTION DONE");
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
#[esp_rtos::main]
|
#[esp_rtos::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let config = esp_hal::Config::default()
|
let config = esp_hal::Config::default()
|
||||||
|
|
@ -382,6 +404,9 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
info!("ST7701S-based LCD display initialized!");
|
info!("ST7701S-based LCD display initialized!");
|
||||||
|
|
||||||
|
test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2).await;
|
||||||
|
return;
|
||||||
|
|
||||||
// RMK config
|
// RMK config
|
||||||
let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]);
|
let vial_config = VialConfig::new(VIAL_KEYBOARD_ID, VIAL_KEYBOARD_DEF, &[(0, 0), (1, 1)]);
|
||||||
let storage_config = StorageConfig {
|
let storage_config = StorageConfig {
|
||||||
|
|
@ -463,7 +488,22 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
static FRAMEBUFFER: StaticCell<Framebuffer> = StaticCell::new();
|
static FRAMEBUFFER: StaticCell<Framebuffer> = StaticCell::new();
|
||||||
let framebuffer = FRAMEBUFFER.init(Framebuffer::new(
|
let framebuffer = FRAMEBUFFER.init(Framebuffer::new(
|
||||||
360 + /* TODO: Figure out why more bytes are needed: */ 8,
|
368,
|
||||||
|
// It is likely due to DMA transfer requirements.
|
||||||
|
// The size of each chunk transferred via DMA must be a multiple of 32 bytes.
|
||||||
|
// (360 * size_of<u16>()) % 32 == 16
|
||||||
|
// Make it so that the renderer renders into the smallest transmissible region of memory.
|
||||||
|
//
|
||||||
|
// | | ( 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.
|
||||||
960,
|
960,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -514,7 +554,7 @@ async fn main(_spawner: Spawner) {
|
||||||
run_alloc_stats_reporter(),
|
run_alloc_stats_reporter(),
|
||||||
// We currently send the framebuffer data using the main core, which does not seem to slow
|
// We currently send the framebuffer data using the main core, which does not seem to slow
|
||||||
// down the rest of the tasks too much.
|
// down the rest of the tasks too much.
|
||||||
run_lcd(st7701s, framebuffer),
|
ui::dpi::run_lcd(st7701s, framebuffer),
|
||||||
run_devices! (
|
run_devices! (
|
||||||
(matrix) => rmk::channel::EVENT_CHANNEL,
|
(matrix) => rmk::channel::EVENT_CHANNEL,
|
||||||
),
|
),
|
||||||
|
|
@ -605,109 +645,9 @@ impl Controller for UserController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Framebuffer {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
dma_buf: Option<DmaTxBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
|
|
||||||
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.dma_buf.as_mut().unwrap().as_mut_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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) {
|
|
||||||
loop {
|
|
||||||
// Timer::after(Duration::from_millis(100)).await;
|
|
||||||
// yield_now().await;
|
|
||||||
SIGNAL_LCD_SUBMIT.wait().await;
|
|
||||||
|
|
||||||
// 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()) {
|
|
||||||
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);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Ok(transfer) => transfer,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
#[cfg(not(feature = "limit-fps"))]
|
|
||||||
while !transfer.is_done() {
|
|
||||||
// Timer::after_millis(1).await;
|
|
||||||
rmk::embassy_futures::yield_now().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result;
|
|
||||||
let dma_buf;
|
|
||||||
(result, st7701s.dpi, dma_buf) = transfer.wait();
|
|
||||||
framebuffer.dma_buf = Some(dma_buf);
|
|
||||||
|
|
||||||
SIGNAL_UI_RENDER.signal(());
|
|
||||||
|
|
||||||
if let Err(error) = result {
|
|
||||||
error!(
|
|
||||||
"An error occurred while transferring framebuffer to the LCD display: {error:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: Not needed currently. If it is ever enabled, don't forget to register it in Io.
|
// // TODO: Not needed currently. If it is ever enabled, don't forget to register it in Io.
|
||||||
// #[handler]
|
// #[handler]
|
||||||
// #[ram] // TODO: Is this necessary?
|
// #[ram] // Improves performance.
|
||||||
// fn interrupt_handler() {
|
// fn interrupt_handler() {
|
||||||
// // esp_println::println!(
|
// // esp_println::println!(
|
||||||
// // "GPIO Interrupt with priority {}",
|
// // "GPIO Interrupt with priority {}",
|
||||||
|
|
|
||||||
427
firmware/acid-firmware/src/ui/dpi.rs
Normal file
427
firmware/acid-firmware/src/ui/dpi.rs
Normal file
|
|
@ -0,0 +1,427 @@
|
||||||
|
use core::{alloc::Layout, pin::Pin};
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
|
alloc::{Allocator, Global},
|
||||||
|
boxed::Box,
|
||||||
|
vec,
|
||||||
|
};
|
||||||
|
use embassy_sync::channel::Channel;
|
||||||
|
use esp_alloc::MemoryCapability;
|
||||||
|
use esp_hal::{
|
||||||
|
Blocking,
|
||||||
|
dma::{
|
||||||
|
self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor,
|
||||||
|
DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt,
|
||||||
|
ExternalBurstConfig, Mem2Mem,
|
||||||
|
},
|
||||||
|
dma_descriptors, handler, interrupt,
|
||||||
|
peripherals::{DMA, DMA_CH0, Peripherals, SPI2},
|
||||||
|
ram,
|
||||||
|
spi::master::AnySpi,
|
||||||
|
};
|
||||||
|
use esp_sync::RawMutex;
|
||||||
|
use i_slint_core::software_renderer::Rgb565Pixel;
|
||||||
|
use indoc::{formatdoc, indoc};
|
||||||
|
use log::{error, info};
|
||||||
|
|
||||||
|
use crate::{PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s};
|
||||||
|
|
||||||
|
pub struct DmaTxBounceBuf {
|
||||||
|
// TODO: Make these generic.
|
||||||
|
// They currently cannot be generic, because they lacks a `reborrow` method.
|
||||||
|
channel: DMA_CH0<'static>,
|
||||||
|
// This can also be more generic, see `DmaEligible` in `Mem2Mem::new`.
|
||||||
|
peripheral: AnySpi<'static>,
|
||||||
|
|
||||||
|
burst_config: BurstConfig,
|
||||||
|
// rx: DmaRxStreamBuf,
|
||||||
|
/// The size of each window.
|
||||||
|
window_size: usize,
|
||||||
|
/// The number of windows.
|
||||||
|
windows_len: usize,
|
||||||
|
buffer_src: &'static mut [u8],
|
||||||
|
// Two buffers of size `window_size`,
|
||||||
|
// one of which is being written to, while the other is being read from.
|
||||||
|
bounce_buffer_dst: Box<[u8]>,
|
||||||
|
bounce_buffer_src: Box<[u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaTxBounceBuf {
|
||||||
|
pub fn new(
|
||||||
|
channel: DMA_CH0<'static>,
|
||||||
|
peripheral: AnySpi<'static>,
|
||||||
|
buffer_src: &'static mut [u8],
|
||||||
|
window_size: usize,
|
||||||
|
burst_config: BurstConfig,
|
||||||
|
) -> Self {
|
||||||
|
assert_eq!(
|
||||||
|
buffer_src.len() % window_size,
|
||||||
|
0,
|
||||||
|
"the size of a source buffer must be a multiple of the window size ({window_size} bytes), but it is {len} bytes large",
|
||||||
|
len = buffer_src.len()
|
||||||
|
);
|
||||||
|
// let dma_buf_descs_len = esp_hal::dma::descriptor_count(
|
||||||
|
// window_size,
|
||||||
|
// burst_config.max_compatible_chunk_size(),
|
||||||
|
// false,
|
||||||
|
// );
|
||||||
|
// let dma_buf_descs =
|
||||||
|
// Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice());
|
||||||
|
// let rx = DmaRxStreamBuf::new(dma_buf_descs, buffer_src).unwrap();
|
||||||
|
Self {
|
||||||
|
channel,
|
||||||
|
peripheral,
|
||||||
|
burst_config,
|
||||||
|
// rx,
|
||||||
|
window_size,
|
||||||
|
windows_len: buffer_src.len() / window_size,
|
||||||
|
buffer_src,
|
||||||
|
bounce_buffer_dst: allocate_dma_buffer_in(window_size, Global),
|
||||||
|
bounce_buffer_src: allocate_dma_buffer_in(window_size, Global),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn linear_descriptors_for_buffer(
|
||||||
|
buffer_len: usize,
|
||||||
|
burst_config: &BurstConfig,
|
||||||
|
mut setup_desc: impl FnMut(&mut DmaDescriptor),
|
||||||
|
) -> &'static mut [DmaDescriptor] {
|
||||||
|
let max_chunk_size = burst_config.max_compatible_chunk_size();
|
||||||
|
let descriptors_len = esp_hal::dma::descriptor_count(buffer_len, max_chunk_size, false);
|
||||||
|
// TODO: This leaks memory. Ensure it's only called during setup.
|
||||||
|
let descriptors = Box::leak(vec![DmaDescriptor::EMPTY; descriptors_len].into_boxed_slice());
|
||||||
|
|
||||||
|
// Link up the descriptors.
|
||||||
|
let mut next = core::ptr::null_mut();
|
||||||
|
for desc in descriptors.iter_mut().rev() {
|
||||||
|
desc.next = next;
|
||||||
|
next = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare each descriptor's buffer size.
|
||||||
|
let mut descriptors_it = descriptors.iter_mut();
|
||||||
|
let mut remaining_len = buffer_len;
|
||||||
|
while remaining_len > 0 {
|
||||||
|
let chunk_size = core::cmp::min(max_chunk_size, remaining_len);
|
||||||
|
let desc = descriptors_it.next().unwrap();
|
||||||
|
desc.set_size(chunk_size);
|
||||||
|
(setup_desc)(desc);
|
||||||
|
remaining_len -= chunk_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn linear_descriptors_set_buffer(
|
||||||
|
descriptors: &mut [DmaDescriptor],
|
||||||
|
mut buffer: &mut [u8],
|
||||||
|
mut setup_desc: impl FnMut(&mut DmaDescriptor),
|
||||||
|
) {
|
||||||
|
for descriptor in descriptors.iter_mut() {
|
||||||
|
descriptor.buffer = buffer.as_mut_ptr();
|
||||||
|
(setup_desc)(descriptor);
|
||||||
|
buffer = &mut buffer[descriptor.size()..];
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
buffer.is_empty(),
|
||||||
|
"a buffer of an incompatible length was asssigned to a descriptor set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&mut self) {
|
||||||
|
// TODO: Precompute as much as possible by moving to `Self::new()`.
|
||||||
|
let src_descs =
|
||||||
|
Self::linear_descriptors_for_buffer(self.window_size, &self.burst_config, |desc| {
|
||||||
|
desc.reset_for_tx(desc.next.is_null());
|
||||||
|
// Length for TX buffers must be set in software.
|
||||||
|
// In RX buffers, it is set by hardware.
|
||||||
|
desc.set_length(desc.size());
|
||||||
|
});
|
||||||
|
let bounce_dst_descs =
|
||||||
|
Self::linear_descriptors_for_buffer(self.window_size, &self.burst_config, |_| {});
|
||||||
|
|
||||||
|
// Enable interrupts for the peripheral
|
||||||
|
interrupt::enable(
|
||||||
|
esp_hal::peripherals::Interrupt::DMA_OUT_CH0,
|
||||||
|
dma_interrupt_handler.priority(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
interrupt::enable(
|
||||||
|
esp_hal::peripherals::Interrupt::SPI2_DMA,
|
||||||
|
dma_interrupt_handler.priority(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Bind the handler
|
||||||
|
unsafe {
|
||||||
|
interrupt::bind_interrupt(
|
||||||
|
esp_hal::peripherals::Interrupt::DMA_OUT_CH0,
|
||||||
|
dma_interrupt_handler.handler(),
|
||||||
|
);
|
||||||
|
interrupt::bind_interrupt(
|
||||||
|
esp_hal::peripherals::Interrupt::SPI2_DMA,
|
||||||
|
dma_interrupt_handler.handler(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable interrupts in the peripheral.
|
||||||
|
let channel_number = 0; // TODO: Get from self.channel
|
||||||
|
DMA::regs()
|
||||||
|
.ch(channel_number)
|
||||||
|
.out_int()
|
||||||
|
.ena()
|
||||||
|
.modify(|_, w| {
|
||||||
|
w.out_total_eof().bit(true);
|
||||||
|
w.out_dscr_err().bit(true);
|
||||||
|
w.out_eof().bit(true);
|
||||||
|
w.out_done().bit(true);
|
||||||
|
w
|
||||||
|
});
|
||||||
|
SPI2::regs().dma_int_ena().modify(|_, w| {
|
||||||
|
w.slv_rd_dma_done().bit(true);
|
||||||
|
w.slv_wr_dma_done().bit(true);
|
||||||
|
w.dma_seg_trans_done().bit(true);
|
||||||
|
w.trans_done().bit(true);
|
||||||
|
w
|
||||||
|
});
|
||||||
|
|
||||||
|
for window_index in 0..self.windows_len {
|
||||||
|
// Descriptors are initialized by `DmaTxBuf::new`.
|
||||||
|
let buffer_src_window =
|
||||||
|
&mut self.buffer_src[window_index * self.window_size..][..self.window_size];
|
||||||
|
|
||||||
|
Self::linear_descriptors_set_buffer(src_descs, buffer_src_window, |_| {});
|
||||||
|
Self::linear_descriptors_set_buffer(bounce_dst_descs, buffer_src_window, |desc| {
|
||||||
|
desc.reset_for_rx();
|
||||||
|
});
|
||||||
|
|
||||||
|
// let (channel_rx, channel_tx) = self.channel.split();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Extend the lifetime to 'static because it is required by Mem2Mem.
|
||||||
|
//
|
||||||
|
// Safety:
|
||||||
|
// Pointees are done being used by the driver before this scope ends,
|
||||||
|
// this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish.
|
||||||
|
let bounce_dst_descs = unsafe { &mut *(bounce_dst_descs as *mut _) };
|
||||||
|
let src_descs = unsafe { &mut *(src_descs as *mut _) };
|
||||||
|
let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral.reborrow())
|
||||||
|
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
|
||||||
|
.unwrap();
|
||||||
|
let transfer = mem2mem
|
||||||
|
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let int_raw = DMA::regs()
|
||||||
|
.ch(channel_number as usize)
|
||||||
|
.out_int()
|
||||||
|
.raw()
|
||||||
|
.read();
|
||||||
|
log::error!(
|
||||||
|
indoc! {"
|
||||||
|
int_raw:
|
||||||
|
total_eof: {total_eof}
|
||||||
|
eof: {eof}
|
||||||
|
done: {done}
|
||||||
|
dscr_err: {dscr_err}
|
||||||
|
"},
|
||||||
|
total_eof = int_raw.out_total_eof().bit_is_set(),
|
||||||
|
eof = int_raw.out_eof().bit_is_set(),
|
||||||
|
done = int_raw.out_done().bit_is_set(),
|
||||||
|
dscr_err = int_raw.out_dscr_err().bit_is_set(),
|
||||||
|
);
|
||||||
|
error!("int_raw_msg: 0x{:08x?}", INT_CHANNEL.try_receive());
|
||||||
|
|
||||||
|
transfer.wait().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let int_raw = DMA::regs()
|
||||||
|
.ch(channel_number as usize)
|
||||||
|
.out_int()
|
||||||
|
.raw()
|
||||||
|
.read();
|
||||||
|
log::error!(
|
||||||
|
indoc! {"
|
||||||
|
int_raw:
|
||||||
|
total_eof: {total_eof}
|
||||||
|
eof: {eof}
|
||||||
|
done: {done}
|
||||||
|
dscr_err: {dscr_err}
|
||||||
|
"},
|
||||||
|
total_eof = int_raw.out_total_eof().bit_is_set(),
|
||||||
|
eof = int_raw.out_eof().bit_is_set(),
|
||||||
|
done = int_raw.out_done().bit_is_set(),
|
||||||
|
dscr_err = int_raw.out_dscr_err().bit_is_set(),
|
||||||
|
);
|
||||||
|
error!("int_raw_msg: 0x{:08x?}", INT_CHANNEL.try_receive());
|
||||||
|
|
||||||
|
// TODO: Get rid of this!
|
||||||
|
assert_eq!(&*self.bounce_buffer_dst, buffer_src_window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT_CHANNEL: Channel<RawMutex, u32, 128> = Channel::new();
|
||||||
|
|
||||||
|
#[handler]
|
||||||
|
#[ram] // Improves performance.
|
||||||
|
fn dma_interrupt_handler() {
|
||||||
|
let int_raw = DMA::regs().ch(0).out_int().raw().read();
|
||||||
|
|
||||||
|
INT_CHANNEL.try_send(int_raw.bits()).unwrap();
|
||||||
|
|
||||||
|
// let lcd_cam = unsafe { &*esp_hal::peripherals::LCD_CAM::PTR };
|
||||||
|
|
||||||
|
// // Check and clear VSYNC interrupt
|
||||||
|
// if lcd_cam
|
||||||
|
// .lc_dma_int_raw()
|
||||||
|
// .read()
|
||||||
|
// .lcd_vsync_int_raw()
|
||||||
|
// .bit_is_set()
|
||||||
|
// {
|
||||||
|
// lcd_cam
|
||||||
|
// .lc_dma_int_clr()
|
||||||
|
// .write(|w| w.lcd_vsync_int_clr().set_bit());
|
||||||
|
|
||||||
|
// INT_CHANNEL.send();
|
||||||
|
// // VSYNC_SIGNAL.signal(());
|
||||||
|
|
||||||
|
// // Signal the event
|
||||||
|
// // critical_section::with(|cs| {
|
||||||
|
// // *VSYNC_FLAG.borrow_ref_mut(cs) = true;
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafe impl DmaTxBuffer for DmaTxStreamBuf {
|
||||||
|
// type View = Self;
|
||||||
|
// type Final = Self;
|
||||||
|
|
||||||
|
// fn prepare(&mut self) -> dma::Preparation {
|
||||||
|
// dma::Preparation {
|
||||||
|
// start: (),
|
||||||
|
// direction: (),
|
||||||
|
// accesses_psram: false,
|
||||||
|
// burst_transfer: (),
|
||||||
|
// check_owner: (),
|
||||||
|
// auto_write_back: (),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn into_view(self) -> Self::View {
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn from_view(view: Self::View) -> Self::Final {
|
||||||
|
// view
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub async fn run_lcd(
|
||||||
|
mut st7701s: St7701s<'static, Blocking>,
|
||||||
|
framebuffer: &'static mut Framebuffer,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
// Timer::after(Duration::from_millis(100)).await;
|
||||||
|
// yield_now().await;
|
||||||
|
SIGNAL_LCD_SUBMIT.wait().await;
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
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);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(transfer) => transfer,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
#[cfg(not(feature = "limit-fps"))]
|
||||||
|
while !transfer.is_done() {
|
||||||
|
// Timer::after_millis(1).await;
|
||||||
|
rmk::embassy_futures::yield_now().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
let dma_buf;
|
||||||
|
(result, st7701s.dpi, dma_buf) = transfer.wait();
|
||||||
|
framebuffer.dma_buf = Some(dma_buf);
|
||||||
|
|
||||||
|
SIGNAL_UI_RENDER.signal(());
|
||||||
|
|
||||||
|
if let Err(error) = result {
|
||||||
|
error!(
|
||||||
|
"An error occurred while transferring framebuffer to the LCD display: {error:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Framebuffer {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub dma_buf: Option<DmaTxBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates a buffer appropriately aligned for use with DMA.
|
||||||
|
pub fn allocate_dma_buffer_in<A: Allocator>(len: usize, alloc: A) -> Box<[u8], A> {
|
||||||
|
const DMA_ALIGNMENT: usize = 32;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
len % DMA_ALIGNMENT,
|
||||||
|
0,
|
||||||
|
"the size of a DMA buffer must be a multiple of {DMA_ALIGNMENT} bytes, but it is {len} bytes large"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ⚠️ 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.
|
||||||
|
// That is ensured by the `assert_eq` preceding this block.
|
||||||
|
unsafe {
|
||||||
|
let raw = alloc
|
||||||
|
.allocate_zeroed(Layout::from_size_align(len, DMA_ALIGNMENT).unwrap())
|
||||||
|
.expect("failed to allocate a DMA buffer");
|
||||||
|
Box::from_raw_in(raw.as_ptr(), alloc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Framebuffer {
|
||||||
|
pub fn new(width: u32, height: u32) -> Self {
|
||||||
|
let buffer_len = width as usize * height as usize * core::mem::size_of::<u16>();
|
||||||
|
let buffer = allocate_dma_buffer_in(buffer_len, &PSRAM_ALLOCATOR);
|
||||||
|
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 = vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice();
|
||||||
|
// We just leak the buffers.
|
||||||
|
let dma_buf = DmaTxBuf::new(Box::leak(dma_buf_descs), Box::leak(buffer)).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
dma_buf: Some(dma_buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
|
||||||
|
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.dma_buf.as_mut().unwrap().as_mut_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
pub mod dpi;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod window_adapter;
|
pub mod window_adapter;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue