Compare commits
2 commits
ebf8205f2d
...
c012d5d11d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c012d5d11d | ||
|
|
f20f4e7993 |
|
|
@ -40,12 +40,12 @@ ACID_COMPOSE_LOCALE = "cs_CZ.UTF-8"
|
||||||
build-std = ["alloc", "core"]
|
build-std = ["alloc", "core"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
esp-backtrace = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-backtrace = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-hal = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-hal = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-storage = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-storage = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-alloc = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-alloc = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-println = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-println = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-radio = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-radio = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-rtos = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-rtos = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-bootloader-esp-idf = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-bootloader-esp-idf = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
esp-sync = { git = "https://github.com/Limeth/esp-hal.git", rev = "72e22e2de678297da65a185023309a76aef8a5ca" }
|
# esp-sync = { git = "https://github.com/Limeth/esp-hal.git", rev = "95d8c8b046e945e41294d5577528d0a1c4b03247" }
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,9 @@ fn with_formatted_log_record<R>(
|
||||||
pub mod usb {
|
pub mod usb {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn setup_logging() -> impl Future<Output = ()> {
|
pub fn setup_logging() {
|
||||||
esp_println::logger::init_logger(LOG_LEVEL_FILTER);
|
esp_println::logger::init_logger(LOG_LEVEL_FILTER);
|
||||||
log::info!("Logger initialized!");
|
log::info!("Logger initialized!");
|
||||||
async {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,17 +152,7 @@ pub mod uart {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_logging(
|
pub fn setup_logging(uart_tx: UartTx<'static, Blocking>) {
|
||||||
uart: impl esp_hal::uart::Instance + 'static,
|
|
||||||
tx: impl PeripheralOutput<'static>,
|
|
||||||
rx: impl PeripheralInput<'static>,
|
|
||||||
) -> impl Future<Output = ()> {
|
|
||||||
let (uart_rx, uart_tx) = Uart::new(uart, Default::default())
|
|
||||||
.unwrap()
|
|
||||||
.with_tx(tx)
|
|
||||||
.with_rx(rx)
|
|
||||||
.split();
|
|
||||||
|
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
*ALT_LOGGER_UART.borrow(cs).borrow_mut() = Some(uart_tx);
|
*ALT_LOGGER_UART.borrow(cs).borrow_mut() = Some(uart_tx);
|
||||||
});
|
});
|
||||||
|
|
@ -174,7 +163,6 @@ pub mod uart {
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Logger initialized!");
|
info!("Logger initialized!");
|
||||||
console::run_console(uart_rx.into_async())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,10 +175,9 @@ pub mod rtt {
|
||||||
use panic_rtt_target as _; // Use the RTT panic handler.
|
use panic_rtt_target as _; // Use the RTT panic handler.
|
||||||
use rtt_target::ChannelMode;
|
use rtt_target::ChannelMode;
|
||||||
|
|
||||||
pub fn setup_logging() -> impl Future<Output = ()> {
|
pub fn setup_logging() {
|
||||||
rtt_target::rtt_init_log!(LOG_LEVEL_FILTER, ChannelMode::BlockIfFull);
|
rtt_target::rtt_init_log!(LOG_LEVEL_FILTER, ChannelMode::BlockIfFull);
|
||||||
log::info!("Logger initialized!");
|
log::info!("Logger initialized!");
|
||||||
async {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use core::cell::RefCell;
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
use alloc::alloc::Global;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::collections::vec_deque::VecDeque;
|
use alloc::collections::vec_deque::VecDeque;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
|
|
@ -28,6 +29,7 @@ use alloc::string::String;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
use embassy_embedded_hal::adapter::BlockingAsync;
|
use embassy_embedded_hal::adapter::BlockingAsync;
|
||||||
use embassy_embedded_hal::flash::partition::Partition;
|
use embassy_embedded_hal::flash::partition::Partition;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
|
@ -43,6 +45,8 @@ use esp_hal::dma::{
|
||||||
BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig, InternalBurstConfig,
|
BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig, InternalBurstConfig,
|
||||||
};
|
};
|
||||||
use esp_hal::efuse::Efuse;
|
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::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
|
||||||
use esp_hal::i2c::master::{I2c, I2cAddress};
|
use esp_hal::i2c::master::{I2c, I2cAddress};
|
||||||
use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl};
|
use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl};
|
||||||
|
|
@ -57,6 +61,7 @@ use esp_hal::sha::ShaBackend;
|
||||||
use esp_hal::spi::master::AnySpi;
|
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_hal::uart::{Uart, UartRx};
|
||||||
use esp_hal::{Blocking, interrupt};
|
use esp_hal::{Blocking, interrupt};
|
||||||
use esp_rtos::embassy::{Executor, InterruptExecutor};
|
use esp_rtos::embassy::{Executor, InterruptExecutor};
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
|
|
@ -128,11 +133,11 @@ pub static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();
|
||||||
static KEYBOARD_REPORT_PROXY: Channel<CriticalSectionRawMutex, Report, 16> = Channel::new();
|
static KEYBOARD_REPORT_PROXY: Channel<CriticalSectionRawMutex, Report, 16> = Channel::new();
|
||||||
static LCD_ENABLED: AtomicBool = AtomicBool::new(false);
|
static LCD_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
/// Used to signal that MCU is ready to submit the framebuffer to the LCD.
|
// /// Used to signal that MCU is ready to submit the framebuffer to the LCD.
|
||||||
static SIGNAL_LCD_SUBMIT: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
// 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();
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn test_bounce_buffers_task(
|
async fn test_bounce_buffers_task(
|
||||||
|
|
@ -147,72 +152,80 @@ async fn test_bounce_buffers(
|
||||||
channel: DMA_CH0<'static>,
|
channel: DMA_CH0<'static>,
|
||||||
peripheral: SPI2<'static>,
|
peripheral: SPI2<'static>,
|
||||||
st7701s: St7701s<'static, Blocking>,
|
st7701s: St7701s<'static, Blocking>,
|
||||||
) -> DpiTransfer<'static, DmaTxBounceBuf, Blocking> {
|
) {
|
||||||
error!("TEST BOUNCE BUFFERS SECTION ENTERED");
|
error!("TEST BOUNCE BUFFERS SECTION ENTERED");
|
||||||
const WIDTH: usize = 368;
|
const BYTES_PER_PIXEL: usize = core::mem::size_of::<u16>();
|
||||||
const HEIGHT: usize = 960;
|
// Assume highest burst config setting.
|
||||||
const ROWS_PER_WINDOW: usize = 8;
|
const EXTERNAL_BURST_CONFIG: ExternalBurstConfig = ExternalBurstConfig::Size32;
|
||||||
let windows_len = HEIGHT / ROWS_PER_WINDOW;
|
const ALIGNMENT_PIXELS: usize = EXTERNAL_BURST_CONFIG as usize / BYTES_PER_PIXEL;
|
||||||
let window_size = ROWS_PER_WINDOW * WIDTH * core::mem::size_of::<u16>();
|
// 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;
|
||||||
|
let burst_config = BurstConfig {
|
||||||
|
internal_memory: InternalBurstConfig::Enabled,
|
||||||
|
external_memory: EXTERNAL_BURST_CONFIG,
|
||||||
|
};
|
||||||
let buffer_src = Box::leak(allocate_dma_buffer_in(
|
let buffer_src = Box::leak(allocate_dma_buffer_in(
|
||||||
windows_len * window_size,
|
HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL,
|
||||||
|
burst_config,
|
||||||
&PSRAM_ALLOCATOR,
|
&PSRAM_ALLOCATOR,
|
||||||
));
|
));
|
||||||
let buffer_src = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(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::<Vec<_>>();
|
|
||||||
for (index, pixel) in buffer_src.iter_mut().enumerate() {
|
|
||||||
let mut x = (index % 368) as i16 - 120;
|
|
||||||
let mut y = (index / 368) as i16;
|
|
||||||
|
|
||||||
if x < 240 {
|
{
|
||||||
x = core::cmp::min(x, 240 - 1 - x);
|
let buffer_src = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(buffer_src);
|
||||||
y = core::cmp::min(y, 960 - 1 - y);
|
let colors = (0..WIDTH_VISIBLE_PIXELS as u8 / 2)
|
||||||
let min = core::cmp::min(x, y);
|
.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;
|
||||||
|
|
||||||
*pixel = colors[min as usize % colors.len()].clone();
|
if x < WIDTH_VISIBLE_PIXELS as i16 {
|
||||||
continue;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
*pixel = Rgb565Pixel::default();
|
|
||||||
}
|
}
|
||||||
let buffer_src = bytemuck::cast_slice_mut::<Rgb565Pixel, u8>(buffer_src);
|
|
||||||
// let mut counter: u8 = 0;
|
warn!("FRONT_PORCH_SKIPPED_PIXELS: {FRONT_PORCH_SKIPPED_PIXELS}");
|
||||||
// buffer_src.fill_with(|| {
|
warn!("WIDTH_STORED_PIXELS: {WIDTH_STORED_PIXELS}");
|
||||||
// counter = counter.wrapping_add(1);
|
warn!("ROWS_PER_WINDOW: {ROWS_PER_WINDOW}");
|
||||||
// counter
|
|
||||||
// });
|
|
||||||
let mut buf = DmaBounce::new(
|
let mut buf = DmaBounce::new(
|
||||||
|
Global,
|
||||||
channel,
|
channel,
|
||||||
AnySpi::from(peripheral),
|
AnySpi::from(peripheral),
|
||||||
st7701s.dpi,
|
st7701s.dpi,
|
||||||
buffer_src,
|
buffer_src,
|
||||||
window_size,
|
FRONT_PORCH_SKIPPED_PIXELS * BYTES_PER_PIXEL,
|
||||||
BurstConfig {
|
WIDTH_STORED_PIXELS * BYTES_PER_PIXEL,
|
||||||
internal_memory: InternalBurstConfig::Enabled,
|
ROWS_PER_WINDOW,
|
||||||
external_memory: ExternalBurstConfig::Size32,
|
burst_config,
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
let _ = buf.send().await;
|
buf.send().await;
|
||||||
error!("TEST BOUNCE BUFFERS SECTION DONE");
|
error!("TEST BOUNCE BUFFERS SECTION DONE");
|
||||||
loop {
|
|
||||||
Timer::after_secs(10).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[esp_rtos::main]
|
#[esp_rtos::main]
|
||||||
|
|
@ -222,18 +235,31 @@ async fn main(_spawner: Spawner) {
|
||||||
.with_psram(PsramConfig {
|
.with_psram(PsramConfig {
|
||||||
size: PsramSize::AutoDetect,
|
size: PsramSize::AutoDetect,
|
||||||
core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m),
|
core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m),
|
||||||
flash_frequency: FlashFreq::default(),
|
flash_frequency: FlashFreq::FlashFreq120m,
|
||||||
ram_frequency: SpiRamFreq::Freq80m,
|
ram_frequency: SpiRamFreq::Freq120m,
|
||||||
});
|
});
|
||||||
let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config);
|
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")]
|
#[cfg(feature = "usb-log")]
|
||||||
let console_task = logging::usb::setup_logging();
|
logging::usb::setup_logging();
|
||||||
#[cfg(feature = "alt-log")]
|
#[cfg(feature = "alt-log")]
|
||||||
let console_task =
|
logging::uart::setup_logging(uart_tx);
|
||||||
logging::uart::setup_logging(peripherals.UART2, peripherals.GPIO12, peripherals.GPIO5);
|
|
||||||
#[cfg(feature = "rtt-log")]
|
#[cfg(feature = "rtt-log")]
|
||||||
let console_task = logging::rtt::setup_logging();
|
logging::rtt::setup_logging();
|
||||||
|
|
||||||
// Use the internal DRAM as the heap.
|
// Use the internal DRAM as the heap.
|
||||||
// Memory reclaimed from the esp-idf bootloader.
|
// Memory reclaimed from the esp-idf bootloader.
|
||||||
|
|
@ -291,6 +317,7 @@ async fn main(_spawner: Spawner) {
|
||||||
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
||||||
esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);
|
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<InterruptExecutor<2>> = StaticCell::new();
|
static EXECUTOR_CORE_0: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
||||||
let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2);
|
let executor_core_0 = InterruptExecutor::new(software_interrupt.software_interrupt2);
|
||||||
let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0);
|
let executor_core_0 = EXECUTOR_CORE_0.init(executor_core_0);
|
||||||
|
|
@ -298,6 +325,110 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
info!("ESP-RTOS started!");
|
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.
|
// Enable the TRNG source, so `Trng` can be constructed.
|
||||||
let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
|
let _trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1);
|
||||||
|
|
||||||
|
|
@ -453,7 +584,7 @@ async fn main(_spawner: Spawner) {
|
||||||
.with_data9(peripherals.GPIO15)
|
.with_data9(peripherals.GPIO15)
|
||||||
.with_data10(peripherals.GPIO16)
|
.with_data10(peripherals.GPIO16)
|
||||||
// Red
|
// Red
|
||||||
.with_data11(gpio0)
|
.with_data11(peripherals.GPIO0)
|
||||||
.with_data12(peripherals.GPIO1)
|
.with_data12(peripherals.GPIO1)
|
||||||
.with_data13(peripherals.GPIO2)
|
.with_data13(peripherals.GPIO2)
|
||||||
.with_data14(peripherals.GPIO3)
|
.with_data14(peripherals.GPIO3)
|
||||||
|
|
@ -469,15 +600,7 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
info!("ST7701S-based LCD display initialized!");
|
info!("ST7701S-based LCD display initialized!");
|
||||||
|
|
||||||
interrupt_core_0_spawner.must_spawn(test_bounce_buffers_task(
|
test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s).await;
|
||||||
peripherals.DMA_CH0,
|
|
||||||
peripherals.SPI2,
|
|
||||||
st7701s,
|
|
||||||
));
|
|
||||||
|
|
||||||
// let lcd_task = test_bounce_buffers(peripherals.DMA_CH0, peripherals.SPI2, st7701s);
|
|
||||||
|
|
||||||
// let _ = lcd_task.await;
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// RMK config
|
// RMK config
|
||||||
|
|
@ -561,11 +684,10 @@ 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(
|
||||||
368,
|
peripherals.DMA_CH0,
|
||||||
// It is likely due to DMA transfer requirements.
|
peripherals.SPI2.into(),
|
||||||
// The size of each chunk transferred via DMA must be a multiple of 32 bytes.
|
st7701s.dpi,
|
||||||
// (360 * size_of<u16>()) % 32 == 16
|
// The burst config (16/32/64) doesn't seem to affect the alignment of the row size.
|
||||||
// Make it so that the renderer renders into the smallest transmissible region of memory.
|
|
||||||
//
|
//
|
||||||
// | | ( displayed range ) |
|
// | | ( displayed range ) |
|
||||||
// | [ pad ] [ pad ]
|
// | [ pad ] [ pad ]
|
||||||
|
|
@ -577,7 +699,11 @@ async fn main(_spawner: Spawner) {
|
||||||
// TODO: Compute the appropriate ranges to pass to the renderer and DPI peripheral.
|
// 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,
|
// The renderer should pass the size of the `pad`ding to the GUI is parameters,
|
||||||
// to align the content to the displayed range.
|
// to align the content to the displayed range.
|
||||||
|
112,
|
||||||
|
368 - 112,
|
||||||
960,
|
960,
|
||||||
|
16,
|
||||||
|
false,
|
||||||
));
|
));
|
||||||
|
|
||||||
info!("Framebuffer created!");
|
info!("Framebuffer created!");
|
||||||
|
|
@ -590,13 +716,13 @@ async fn main(_spawner: Spawner) {
|
||||||
let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
|
let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
|
||||||
esp_rtos::start_second_core(
|
esp_rtos::start_second_core(
|
||||||
peripherals.CPU_CTRL,
|
peripherals.CPU_CTRL,
|
||||||
// software_interrupt.software_interrupt0,
|
// peripherals.software_interrupt0,
|
||||||
software_interrupt.software_interrupt1,
|
peripherals.software_interrupt1,
|
||||||
second_core_stack,
|
second_core_stack,
|
||||||
move || {
|
move || {
|
||||||
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
// static EXECUTOR: StaticCell<InterruptExecutor<2>> = StaticCell::new();
|
||||||
// let exec = EXECUTOR.init(InterruptExecutor::new(
|
// let exec = EXECUTOR.init(InterruptExecutor::new(
|
||||||
// software_interrupt.software_interrupt2,
|
// peripherals.software_interrupt2,
|
||||||
// ));
|
// ));
|
||||||
// let spawner = exec.start(Priority::Priority3);
|
// let spawner = exec.start(Priority::Priority3);
|
||||||
// spawner.must_spawn(run_renderer_task());
|
// spawner.must_spawn(run_renderer_task());
|
||||||
|
|
@ -627,6 +753,13 @@ 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.
|
||||||
|
// 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),
|
// ui::dpi::run_lcd(st7701s, framebuffer),
|
||||||
// lcd_task,
|
// lcd_task,
|
||||||
run_devices! (
|
run_devices! (
|
||||||
|
|
@ -644,7 +777,7 @@ async fn main(_spawner: Spawner) {
|
||||||
),
|
),
|
||||||
create_hid_report_interceptor(),
|
create_hid_report_interceptor(),
|
||||||
user_controller.event_loop(),
|
user_controller.event_loop(),
|
||||||
console_task
|
console::run_console(peripherals.uart_rx.into_async())
|
||||||
]
|
]
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use esp_hal::{
|
||||||
dma::{
|
dma::{
|
||||||
self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor,
|
self, AnyGdmaChannel, BufView, BurstConfig, DmaChannel, DmaChannelConvert, DmaDescriptor,
|
||||||
DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt,
|
DmaDescriptorFlags, DmaEligible, DmaRxStreamBuf, DmaTxBuf, DmaTxBuffer, DmaTxInterrupt,
|
||||||
ExternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer,
|
ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer,
|
||||||
},
|
},
|
||||||
dma_descriptors, handler,
|
dma_descriptors, handler,
|
||||||
interrupt::{self, Priority},
|
interrupt::{self, Priority},
|
||||||
|
|
@ -30,7 +30,7 @@ use esp_hal::{
|
||||||
spi::master::AnySpi,
|
spi::master::AnySpi,
|
||||||
};
|
};
|
||||||
use esp_sync::RawMutex;
|
use esp_sync::RawMutex;
|
||||||
use i_slint_core::software_renderer::Rgb565Pixel;
|
use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel};
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::{formatdoc, indoc};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use rmk::{
|
use rmk::{
|
||||||
|
|
@ -38,10 +38,7 @@ use rmk::{
|
||||||
join_all,
|
join_all,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{PSRAM_ALLOCATOR, peripherals::st7701s::St7701s, util::DurationExt};
|
||||||
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, peripherals::st7701s::St7701s,
|
|
||||||
util::DurationExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// THIS IS TAKEN FROM https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/soc/esp32s3/mod.rs
|
/// THIS IS TAKEN FROM https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/soc/esp32s3/mod.rs
|
||||||
/// Write back a specific range of data in the cache.
|
/// Write back a specific range of data in the cache.
|
||||||
|
|
@ -114,24 +111,70 @@ pub struct DmaBounce {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaBounce {
|
impl DmaBounce {
|
||||||
|
/// * `allocator` - The allocator used to allocate the bounce buffers.
|
||||||
|
/// * `channel` - The DMA channel used to transfer data from the source buffer to the bounce buffers.
|
||||||
|
/// * `peripheral_src` - The peripheral to transfer data from the source buffer to the bounce buffers.
|
||||||
|
/// * `peripheral_dst` - The peripheral to transfer data to, from the bounce buffers.
|
||||||
|
/// * `buffer_src` - The source buffer, typically allocated in external memory.
|
||||||
|
/// * `row_front_porch_bytes` - The number of arbitrary-valued bytes to be sent in front of each row to the destination peripheral.
|
||||||
|
/// * `row_width_bytes` - The width of a row, in bytes.
|
||||||
|
/// * `window_size_rows` - The size of a single bounce buffer, in rows.
|
||||||
|
/// * `burst_config` - The burst config to use for memory transfers (both in and out). TODO: This could be split.
|
||||||
|
/// * `cyclic` - Experimental! Whether to use a cyclic descriptor list for transfer from the bounce buffers to the destination peripheral.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
allocator: impl Allocator + Copy + 'static,
|
||||||
channel: DMA_CH0<'static>,
|
channel: DMA_CH0<'static>,
|
||||||
peripheral_src: AnySpi<'static>,
|
peripheral_src: AnySpi<'static>,
|
||||||
peripheral_dst: Dpi<'static, Blocking>,
|
peripheral_dst: Dpi<'static, Blocking>,
|
||||||
buffer_src: &'static mut [u8],
|
buffer_src: &'static mut [u8],
|
||||||
window_size: usize,
|
row_front_porch_bytes: usize,
|
||||||
|
row_width_bytes: usize,
|
||||||
|
window_size_rows: usize,
|
||||||
burst_config: BurstConfig,
|
burst_config: BurstConfig,
|
||||||
cyclic: bool,
|
cyclic: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let window_size = row_width_bytes * window_size_rows;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer_src.len() % window_size,
|
buffer_src.len() % window_size,
|
||||||
0,
|
0,
|
||||||
"the size of a source buffer must be a multiple of the window size ({window_size} bytes), but it is {len} bytes large",
|
"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()
|
len = buffer_src.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Conservative alignment. Maxiumum of the cartesian product of [tx, rx] × [internal, external].
|
||||||
|
let alignment = burst_config.min_compatible_alignment();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buffer_src.as_ptr() as usize % alignment,
|
||||||
|
0,
|
||||||
|
"the source buffer must be sufficiently aligned to {alignment} bytes for the burst config",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
row_width_bytes % alignment,
|
||||||
|
0,
|
||||||
|
"the size of a row in bytes must be sufficiently aligned to {alignment} bytes for the burst config",
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
row_front_porch_bytes % alignment,
|
||||||
|
0,
|
||||||
|
"the size of a row's front porch in bytes must be sufficiently aligned to {alignment} bytes for the burst config",
|
||||||
|
);
|
||||||
|
// We need to make the destination peripheral read the front porch data from somewhere,
|
||||||
|
// and that somewhere is currently the bounce buffer.
|
||||||
|
// Therefore the front porch must be in bounds.
|
||||||
|
assert!(
|
||||||
|
row_front_porch_bytes <= window_size,
|
||||||
|
"front porch too large"
|
||||||
|
);
|
||||||
|
|
||||||
let windows_len = buffer_src.len() / window_size;
|
let windows_len = buffer_src.len() / window_size;
|
||||||
let bounce_buffer_dst = Box::leak(allocate_dma_buffer_in(window_size, Global));
|
// TODO: Figure out a way to avoid `leak`ing memory.
|
||||||
let bounce_buffer_src = Box::leak(allocate_dma_buffer_in(window_size, Global));
|
// We probably want to store the `Box`es and then unsafely extend the lifetime at sites of usage.
|
||||||
|
let bounce_buffer_dst =
|
||||||
|
Box::leak(allocate_dma_buffer_in(window_size, burst_config, allocator));
|
||||||
|
let bounce_buffer_src =
|
||||||
|
Box::leak(allocate_dma_buffer_in(window_size, burst_config, allocator));
|
||||||
let src_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |desc| {
|
let src_descs = Self::linear_descriptors_for_buffer(window_size, burst_config, |desc| {
|
||||||
desc.reset_for_tx(desc.next.is_null());
|
desc.reset_for_tx(desc.next.is_null());
|
||||||
// Length for TX buffers must be set in software.
|
// Length for TX buffers must be set in software.
|
||||||
|
|
@ -141,8 +184,7 @@ impl DmaBounce {
|
||||||
let bounce_dst_descs =
|
let bounce_dst_descs =
|
||||||
Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {});
|
Self::linear_descriptors_for_buffer(window_size, burst_config, |_| {});
|
||||||
let bounce_src_descs = if cyclic {
|
let bounce_src_descs = if cyclic {
|
||||||
Self::bounce_descriptors_for_buffer_single(
|
Self::bounce_descriptors_for_buffer_cyclic(
|
||||||
windows_len,
|
|
||||||
unsafe {
|
unsafe {
|
||||||
(
|
(
|
||||||
&mut *(bounce_buffer_dst as *mut _),
|
&mut *(bounce_buffer_dst as *mut _),
|
||||||
|
|
@ -152,7 +194,17 @@ impl DmaBounce {
|
||||||
burst_config,
|
burst_config,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Self::bounce_descriptors_for_buffer_cyclic(
|
let REMOVE = Box::leak(allocate_dma_buffer_in(
|
||||||
|
row_front_porch_bytes,
|
||||||
|
burst_config,
|
||||||
|
allocator,
|
||||||
|
));
|
||||||
|
bytemuck::cast_slice_mut(REMOVE).fill(Rgb565Pixel::from_rgb(0, 0, 0xFF));
|
||||||
|
Self::bounce_descriptors_for_buffer_single(
|
||||||
|
windows_len,
|
||||||
|
row_front_porch_bytes,
|
||||||
|
row_width_bytes,
|
||||||
|
window_size_rows,
|
||||||
unsafe {
|
unsafe {
|
||||||
(
|
(
|
||||||
&mut *(bounce_buffer_dst as *mut _),
|
&mut *(bounce_buffer_dst as *mut _),
|
||||||
|
|
@ -160,6 +212,7 @@ impl DmaBounce {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
burst_config,
|
burst_config,
|
||||||
|
REMOVE,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -277,8 +330,12 @@ impl DmaBounce {
|
||||||
|
|
||||||
fn bounce_descriptors_for_buffer_single(
|
fn bounce_descriptors_for_buffer_single(
|
||||||
windows_len: usize,
|
windows_len: usize,
|
||||||
|
row_front_porch_bytes: usize,
|
||||||
|
row_width_bytes: usize,
|
||||||
|
window_size_rows: usize,
|
||||||
bounce_buffers: (&'static mut [u8], &'static mut [u8]),
|
bounce_buffers: (&'static mut [u8], &'static mut [u8]),
|
||||||
burst_config: BurstConfig,
|
burst_config: BurstConfig,
|
||||||
|
REMOVE: &'static mut [u8],
|
||||||
) -> &'static mut [DmaDescriptor] {
|
) -> &'static mut [DmaDescriptor] {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bounce_buffers.0.len(),
|
bounce_buffers.0.len(),
|
||||||
|
|
@ -289,12 +346,28 @@ impl DmaBounce {
|
||||||
assert_eq!(windows_len % 2, 0, "the number of windows must be even");
|
assert_eq!(windows_len % 2, 0, "the number of windows must be even");
|
||||||
|
|
||||||
let buffer_len = bounce_buffers.0.len();
|
let buffer_len = bounce_buffers.0.len();
|
||||||
let max_chunk_size = burst_config.max_compatible_chunk_size();
|
|
||||||
let descriptors_per_window = dma::descriptor_count(buffer_len, max_chunk_size, false);
|
assert_eq!(
|
||||||
let descriptors_frame = Box::leak(
|
buffer_len,
|
||||||
vec![DmaDescriptor::EMPTY; descriptors_per_window * windows_len].into_boxed_slice(),
|
row_width_bytes * window_size_rows,
|
||||||
|
"the provided bounce buffers have an invalid size"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"windows_len: {windows_len}\nrow_front_porch_bytes: {row_front_porch_bytes}\nrow_width_bytes: {row_width_bytes}\nwindow_size_rows: {window_size_rows}\nbuffer_len: {buffer_len}",
|
||||||
|
);
|
||||||
|
|
||||||
|
let max_chunk_size = burst_config.max_compatible_chunk_size();
|
||||||
|
let descriptors_per_row_front_porch =
|
||||||
|
dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false);
|
||||||
|
let descriptors_per_row_stored =
|
||||||
|
dma::descriptor_count(row_width_bytes, max_chunk_size, false);
|
||||||
|
let descriptors_per_row = descriptors_per_row_stored + descriptors_per_row_front_porch;
|
||||||
|
let descriptors_per_window = window_size_rows * descriptors_per_row;
|
||||||
|
let descriptors_per_frame = descriptors_per_window * windows_len;
|
||||||
|
let descriptors_frame =
|
||||||
|
Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice());
|
||||||
|
|
||||||
// Link up the descriptors.
|
// Link up the descriptors.
|
||||||
let mut next = core::ptr::null_mut();
|
let mut next = core::ptr::null_mut();
|
||||||
for desc in descriptors_frame.iter_mut().rev() {
|
for desc in descriptors_frame.iter_mut().rev() {
|
||||||
|
|
@ -305,25 +378,111 @@ impl DmaBounce {
|
||||||
// Prepare each descriptor's buffer size.
|
// Prepare each descriptor's buffer size.
|
||||||
let bounce_buffers = [bounce_buffers.0, bounce_buffers.1];
|
let bounce_buffers = [bounce_buffers.0, bounce_buffers.1];
|
||||||
|
|
||||||
for (window_index, descriptors) in descriptors_frame
|
for (window_index, descriptors_window) in descriptors_frame
|
||||||
.chunks_mut(descriptors_per_window)
|
.chunks_mut(descriptors_per_window)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let mut descriptors_it = descriptors.iter_mut();
|
let bounce_buffer_index = window_index % 2;
|
||||||
let mut remaining_bounce_buffer = &mut *bounce_buffers[window_index % 2];
|
let bounce_buffer = &mut *bounce_buffers[bounce_buffer_index];
|
||||||
|
// let bounce_buffer_ptr = bounce_buffers[bounce_buffer_index].as_mut_ptr();
|
||||||
|
// let mut remaining_bounce_buffer = &mut *bounce_buffers[bounce_buffer_index];
|
||||||
|
|
||||||
while !remaining_bounce_buffer.is_empty() {
|
for (row_index_in_window, descriptors_row) in descriptors_window
|
||||||
let chunk_size = core::cmp::min(max_chunk_size, remaining_bounce_buffer.len());
|
.chunks_mut(descriptors_per_row)
|
||||||
let desc = descriptors_it.next().unwrap();
|
.enumerate()
|
||||||
desc.buffer = remaining_bounce_buffer.as_mut_ptr();
|
{
|
||||||
remaining_bounce_buffer = &mut remaining_bounce_buffer[chunk_size..];
|
// let row_index = row_index_in_window + window_index * window_size_rows;
|
||||||
let is_last = remaining_bounce_buffer.is_empty();
|
let (descriptors_row_front_porch, descriptors_row_stored) =
|
||||||
desc.set_size(chunk_size);
|
descriptors_row.split_at_mut(descriptors_per_row_front_porch);
|
||||||
desc.set_length(chunk_size);
|
|
||||||
desc.reset_for_tx(is_last);
|
// Prepare front porch descriptors.
|
||||||
|
{
|
||||||
|
let mut descriptors_it = descriptors_row_front_porch.iter_mut();
|
||||||
|
let mut remaining_front_porch = row_front_porch_bytes;
|
||||||
|
|
||||||
|
while remaining_front_porch > 0 {
|
||||||
|
let desc = descriptors_it.next().unwrap();
|
||||||
|
let chunk_size = core::cmp::min(max_chunk_size, remaining_front_porch);
|
||||||
|
remaining_front_porch -= chunk_size;
|
||||||
|
// Just make it point at a bounce buffer.
|
||||||
|
// It is guaranteed to have enough bytes by `DmaBounce::new`.
|
||||||
|
desc.buffer = REMOVE.as_mut_ptr();
|
||||||
|
// desc.buffer = unsafe { bounce_buffer_ptr.offset(0x10000) };
|
||||||
|
// desc.buffer = bounce_buffer_ptr;
|
||||||
|
desc.set_size(chunk_size);
|
||||||
|
desc.set_length(chunk_size);
|
||||||
|
desc.reset_for_tx(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
descriptors_it.next().is_none(),
|
||||||
|
"front porch descriptors must be used up"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
descriptors_row_front_porch
|
||||||
|
.iter()
|
||||||
|
.map(|desc| desc.size())
|
||||||
|
.sum::<usize>(),
|
||||||
|
row_front_porch_bytes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare window descriptors.
|
||||||
|
{
|
||||||
|
let mut remaining_bounce_buffer = &mut bounce_buffer
|
||||||
|
[row_index_in_window * row_width_bytes..][..row_width_bytes];
|
||||||
|
|
||||||
|
// if remaining_bounce_buffer.len() > row_width_bytes {
|
||||||
|
// remaining_bounce_buffer = &mut remaining_bounce_buffer[..row_width_bytes];
|
||||||
|
// }
|
||||||
|
|
||||||
|
for desc in &mut *descriptors_row_stored {
|
||||||
|
let chunk_size =
|
||||||
|
core::cmp::min(max_chunk_size, remaining_bounce_buffer.len());
|
||||||
|
desc.buffer = remaining_bounce_buffer.as_mut_ptr();
|
||||||
|
remaining_bounce_buffer = &mut remaining_bounce_buffer[chunk_size..];
|
||||||
|
desc.set_size(chunk_size);
|
||||||
|
desc.set_length(chunk_size);
|
||||||
|
desc.reset_for_tx(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
remaining_bounce_buffer.is_empty(),
|
||||||
|
"bounce buffer must be used up"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
descriptors_row_stored
|
||||||
|
.iter()
|
||||||
|
.map(|desc| desc.size())
|
||||||
|
.sum::<usize>(),
|
||||||
|
row_width_bytes
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set EOF bit on the last descriptor of the window, to signal
|
||||||
|
// that the bounce buffer is done being read from.
|
||||||
|
if let Some(last_desc) = descriptors_window.last_mut() {
|
||||||
|
last_desc.reset_for_tx(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
descriptors_window
|
||||||
|
.iter()
|
||||||
|
.map(|desc| desc.size())
|
||||||
|
.sum::<usize>(),
|
||||||
|
window_size_rows * (row_front_porch_bytes + row_width_bytes)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
descriptors_frame
|
||||||
|
.iter()
|
||||||
|
.map(|desc| desc.size())
|
||||||
|
.sum::<usize>(),
|
||||||
|
windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes)
|
||||||
|
);
|
||||||
|
|
||||||
descriptors_frame
|
descriptors_frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,7 +502,7 @@ impl DmaBounce {
|
||||||
if let Some(buffer) = buffer {
|
if let Some(buffer) = buffer {
|
||||||
assert!(
|
assert!(
|
||||||
buffer.is_empty(),
|
buffer.is_empty(),
|
||||||
"a buffer of an incompatible length was asssigned to a descriptor set"
|
"a buffer of an incompatible length was assigned to a descriptor set"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,8 +542,10 @@ impl DmaBounce {
|
||||||
let buffer_src_window =
|
let buffer_src_window =
|
||||||
&mut self.buffer_src[self.window_index_next * self.window_size..][..self.window_size];
|
&mut self.buffer_src[self.window_index_next * self.window_size..][..self.window_size];
|
||||||
|
|
||||||
Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |desc| {
|
Self::linear_descriptors_prepare(self.src_descs, Some(buffer_src_window), |_desc| {
|
||||||
// desc.reset_for_tx(desc.next.is_null());
|
// No need to call `DmaDescriptor::reset_for_tx`, because
|
||||||
|
// 1. we don't rely on the ownership flag;
|
||||||
|
// 2. the EOF flag is already set during the construction of this buffer.
|
||||||
});
|
});
|
||||||
// TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`.
|
// TODO: Precompute a descriptor list for each buffer, then use `None` instead of `Some(&mut *self.bounce_buffer_dst)`.
|
||||||
Self::linear_descriptors_prepare(
|
Self::linear_descriptors_prepare(
|
||||||
|
|
@ -401,8 +562,11 @@ impl DmaBounce {
|
||||||
// Safety:
|
// Safety:
|
||||||
// Pointees are done being used by the driver before this scope ends,
|
// Pointees are done being used by the driver before this scope ends,
|
||||||
// this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish.
|
// this is because we `SimpleMem2MemTransfer::wait()` on the transfer to finish.
|
||||||
let bounce_dst_descs = unsafe { &mut *(self.bounce_dst_descs as *mut _) };
|
let bounce_dst_descs: &'static mut [DmaDescriptor] =
|
||||||
let src_descs = unsafe { &mut *(self.src_descs as *mut _) };
|
unsafe { &mut *(self.bounce_dst_descs as *mut _) };
|
||||||
|
let src_descs: &'static mut [DmaDescriptor] =
|
||||||
|
unsafe { &mut *(self.src_descs as *mut _) };
|
||||||
|
|
||||||
let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow())
|
let mut mem2mem = Mem2Mem::new(self.channel.reborrow(), self.peripheral_src.reborrow())
|
||||||
.into_async()
|
.into_async()
|
||||||
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
|
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
|
||||||
|
|
@ -411,7 +575,6 @@ impl DmaBounce {
|
||||||
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
|
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// INBOUND_TRANSFER_FINISHED.wait().await;
|
|
||||||
transfer.wait_async().await.unwrap();
|
transfer.wait_async().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -428,8 +591,7 @@ impl DmaBounce {
|
||||||
self.window_index_next = self.window_index_next % self.windows_len;
|
self.window_index_next = self.window_index_next % self.windows_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&mut self) // -> DpiTransfer<'static, DmaTxBounceBuf, Blocking>
|
pub async fn send(&mut self) {
|
||||||
{
|
|
||||||
Self::enable_interrupts();
|
Self::enable_interrupts();
|
||||||
|
|
||||||
// Receive the first window, so that the outbound transfer can read valid data.
|
// Receive the first window, so that the outbound transfer can read valid data.
|
||||||
|
|
@ -448,26 +610,61 @@ impl DmaBounce {
|
||||||
let mut windows_skipped_total = 0;
|
let mut windows_skipped_total = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
// warn!(
|
||||||
|
// "Receiving window: {} {}",
|
||||||
|
// self.window_index_next, self.frame_index_next
|
||||||
|
// );
|
||||||
self.receive_window().await;
|
self.receive_window().await;
|
||||||
|
// warn!(
|
||||||
|
// "Window received: {} {}",
|
||||||
|
// self.window_index_next, self.frame_index_next
|
||||||
|
// );
|
||||||
let windows_skipped = WINDOWS_SKIPPED
|
let windows_skipped = WINDOWS_SKIPPED
|
||||||
.wait()
|
.wait()
|
||||||
// .with_timeout(Duration::from_secs(1))
|
.with_timeout(Duration::from_millis(100))
|
||||||
.await;
|
.await
|
||||||
// .unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
// error!("Timed out while waiting for skipped windows");
|
error!("Timed out when waiting for skipped windows.");
|
||||||
// 0
|
0 // TODO: This should be -1 to repeat the same window.
|
||||||
// });
|
});
|
||||||
|
|
||||||
|
// let windows_skipped = match windows_skipped {
|
||||||
|
// Ok(windows_skipped) => windows_skipped,
|
||||||
|
// Err(_) => {
|
||||||
|
// warn!(
|
||||||
|
// "Waiting for skipped windows timed out. Transfer done: {}",
|
||||||
|
// transfer.is_done()
|
||||||
|
// );
|
||||||
|
// if transfer.is_done() {
|
||||||
|
// let (result, _, _) = transfer.wait();
|
||||||
|
// panic!("Transfer result: {result:?}");
|
||||||
|
// }
|
||||||
|
// 0
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
if windows_skipped > 0 {
|
if windows_skipped > 0 {
|
||||||
self.increase_window_counter(windows_skipped);
|
self.increase_window_counter(windows_skipped);
|
||||||
windows_skipped_total += windows_skipped;
|
windows_skipped_total += windows_skipped;
|
||||||
error!(
|
// error!(
|
||||||
"Skipped {windows_skipped} windows. Windows skipped per frame: {:.2}%",
|
// "Skipped {windows_skipped} windows. Windows skipped per frame: {:.2}%",
|
||||||
100.0 * windows_skipped_total as f32 / (self.frame_index_next + 1) as f32
|
// 100.0 * windows_skipped_total as f32
|
||||||
);
|
// / (self.windows_len * (self.frame_index_next + 1)) as f32
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.cyclic && self.window_index_next == 1 {
|
// warn!(
|
||||||
|
// "X: {} {} {}",
|
||||||
|
// windows_skipped, self.window_index_next, self.frame_index_next
|
||||||
|
// );
|
||||||
|
|
||||||
|
if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) {
|
||||||
|
if self.window_index_next > 1 {
|
||||||
|
self.increase_window_counter(self.windows_len - self.window_index_next + 1);
|
||||||
|
} else if self.window_index_next == 0 {
|
||||||
|
self.increase_window_counter(1);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Investigate why the DPI transfer isn't done at this point.
|
// TODO: Investigate why the DPI transfer isn't done at this point.
|
||||||
// The `DpiTransfer::wait()` below takes 0.001039 s.
|
// The `DpiTransfer::wait()` below takes 0.001039 s.
|
||||||
// Perhaps it's the minimum screen refresh period?
|
// Perhaps it's the minimum screen refresh period?
|
||||||
|
|
@ -497,33 +694,15 @@ impl DmaBounce {
|
||||||
.unwrap_or_else(|(error, _, _)| {
|
.unwrap_or_else(|(error, _, _)| {
|
||||||
panic!("failed to begin the transmission of a frame: {error:?}");
|
panic!("failed to begin the transmission of a frame: {error:?}");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FRAMES_SKIPPED.signal(
|
||||||
|
FRAMES_SKIPPED
|
||||||
|
.try_take()
|
||||||
|
.map(|frames_skipped| frames_skipped + 1)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop {
|
|
||||||
// // BOUNCE_BUFFER_SENT.receive().await;
|
|
||||||
// warn!("Iteration. Done = {}", transfer.is_done());
|
|
||||||
// let receive_window = self.receive_window().fuse();
|
|
||||||
// pin_mut!(receive_window);
|
|
||||||
// // let mut send_buffer = BOUNCE_BUFFER_SENT.wait().fuse();
|
|
||||||
// let mut send_buffer = BOUNCE_BUFFER_SENT.receive().fuse();
|
|
||||||
// let window_received_first = rmk::futures::select_biased! {
|
|
||||||
// () = receive_window => Ok(()),
|
|
||||||
// windows_sent = send_buffer => Err(windows_sent),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// match window_received_first {
|
|
||||||
// Ok(()) => {
|
|
||||||
// send_buffer.await;
|
|
||||||
// }
|
|
||||||
// Err(windows_sent) => {
|
|
||||||
// error!("Sent {windows_sent} windows before a window could be received.");
|
|
||||||
// receive_window.await;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// transfer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_dma_tx_buffer(&mut self) -> DmaTxBounceBuf {
|
fn get_dma_tx_buffer(&mut self) -> DmaTxBounceBuf {
|
||||||
|
|
@ -533,8 +712,8 @@ impl DmaBounce {
|
||||||
direction: dma::TransferDirection::Out,
|
direction: dma::TransferDirection::Out,
|
||||||
accesses_psram: false,
|
accesses_psram: false,
|
||||||
burst_transfer: self.burst_config,
|
burst_transfer: self.burst_config,
|
||||||
check_owner: Some(true), // Possibly want to set this to false
|
check_owner: Some(false), // Possibly want to set this to false
|
||||||
auto_write_back: false, // Possibly true
|
auto_write_back: false, // Possibly true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -568,6 +747,8 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Intended to be listened on by the renderer, to synchronize the refresh frequency with.
|
||||||
|
pub static FRAMES_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
||||||
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
||||||
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new();
|
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new();
|
||||||
|
|
||||||
|
|
@ -580,11 +761,12 @@ fn dma_outbound_interrupt_handler() {
|
||||||
// Clear the bit by writing 1 to the clear bits.
|
// Clear the bit by writing 1 to the clear bits.
|
||||||
interrupt.clr().write(|w| w.out_eof().bit(true));
|
interrupt.clr().write(|w| w.out_eof().bit(true));
|
||||||
|
|
||||||
let windows_skipped = WINDOWS_SKIPPED
|
WINDOWS_SKIPPED.signal(
|
||||||
.try_take()
|
WINDOWS_SKIPPED
|
||||||
.map(|windows_skipped| windows_skipped + 1)
|
.try_take()
|
||||||
.unwrap_or_default();
|
.map(|windows_skipped| windows_skipped + 1)
|
||||||
WINDOWS_SKIPPED.signal(windows_skipped);
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -607,69 +789,68 @@ fn dma_outbound_interrupt_handler() {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub async fn run_lcd(
|
// pub async fn run_lcd(
|
||||||
mut st7701s: St7701s<'static, Blocking>,
|
// mut st7701s: St7701s<'static, Blocking>,
|
||||||
framebuffer: &'static mut Framebuffer,
|
// framebuffer: &'static mut Framebuffer,
|
||||||
) {
|
// ) {
|
||||||
loop {
|
// loop {
|
||||||
// Timer::after(Duration::from_millis(100)).await;
|
// // Timer::after(Duration::from_millis(100)).await;
|
||||||
// yield_now().await;
|
// // yield_now().await;
|
||||||
SIGNAL_LCD_SUBMIT.wait().await;
|
// SIGNAL_LCD_SUBMIT.wait().await;
|
||||||
|
|
||||||
// TODO: Use bounce buffers:
|
// // 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
|
// // 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`.
|
// // This can be implemented as a `DmaTxBuffer`.
|
||||||
let transfer = match st7701s.dpi.send(false, framebuffer.dma_buf.take().unwrap()) {
|
// let transfer = match st7701s.dpi.send(false, framebuffer.dma_buf.take().unwrap()) {
|
||||||
Err((error, result_dpi, result_dma_buf)) => {
|
// Err((error, result_dpi, result_dma_buf)) => {
|
||||||
error!(
|
// error!(
|
||||||
"An error occurred while initiating transfer of the framebuffer to the LCD display: {error:?}"
|
// "An error occurred while initiating transfer of the framebuffer to the LCD display: {error:?}"
|
||||||
);
|
// );
|
||||||
st7701s.dpi = result_dpi;
|
// st7701s.dpi = result_dpi;
|
||||||
framebuffer.dma_buf = Some(result_dma_buf);
|
// framebuffer.dma_buf = Some(result_dma_buf);
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
Ok(transfer) => transfer,
|
// Ok(transfer) => transfer,
|
||||||
};
|
// };
|
||||||
|
|
||||||
// This could be used to allow other tasks to be executed on the first core, but that causes
|
// // 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.
|
// // 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
|
// // For that reason, it is disabled, and this task blocks the first core, until the transfer
|
||||||
// is complete.
|
// // is complete.
|
||||||
#[cfg(not(feature = "limit-fps"))]
|
// #[cfg(not(feature = "limit-fps"))]
|
||||||
while !transfer.is_done() {
|
// while !transfer.is_done() {
|
||||||
// Timer::after_millis(1).await;
|
// // Timer::after_millis(1).await;
|
||||||
rmk::embassy_futures::yield_now().await;
|
// rmk::embassy_futures::yield_now().await;
|
||||||
}
|
// }
|
||||||
|
|
||||||
let result;
|
// let result;
|
||||||
let dma_buf;
|
// let dma_buf;
|
||||||
(result, st7701s.dpi, dma_buf) = transfer.wait();
|
// (result, st7701s.dpi, dma_buf) = transfer.wait();
|
||||||
framebuffer.dma_buf = Some(dma_buf);
|
// framebuffer.dma_buf = Some(dma_buf);
|
||||||
|
|
||||||
SIGNAL_UI_RENDER.signal(());
|
// SIGNAL_UI_RENDER.signal(());
|
||||||
|
|
||||||
if let Err(error) = result {
|
// if let Err(error) = result {
|
||||||
error!(
|
// error!(
|
||||||
"An error occurred while transferring framebuffer to the LCD display: {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.
|
/// Allocates a buffer appropriately aligned for use with DMA.
|
||||||
pub fn allocate_dma_buffer_in<A: Allocator>(len: usize, alloc: A) -> Box<[u8], A> {
|
pub fn allocate_dma_buffer_in<A: Allocator>(
|
||||||
const DMA_ALIGNMENT: usize = 32;
|
len: usize,
|
||||||
|
burst_config: BurstConfig,
|
||||||
|
alloc: A,
|
||||||
|
) -> Box<[u8], A> {
|
||||||
|
// Conservative alignment. Maxiumum of the cartesian product of [tx, rx] × [internal, external].
|
||||||
|
let alignment = burst_config.min_compatible_alignment();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
len % DMA_ALIGNMENT,
|
len % alignment,
|
||||||
0,
|
0,
|
||||||
"the size of a DMA buffer must be a multiple of {DMA_ALIGNMENT} bytes, but it is {len} bytes large"
|
"the size of a DMA buffer must be a multiple of {alignment} bytes, but it is {len} bytes large"
|
||||||
);
|
);
|
||||||
|
|
||||||
// ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM
|
// ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM
|
||||||
|
|
@ -678,40 +859,61 @@ pub fn allocate_dma_buffer_in<A: Allocator>(len: usize, alloc: A) -> Box<[u8], A
|
||||||
// That is ensured by the `assert_eq` preceding this block.
|
// That is ensured by the `assert_eq` preceding this block.
|
||||||
unsafe {
|
unsafe {
|
||||||
let raw = alloc
|
let raw = alloc
|
||||||
.allocate_zeroed(Layout::from_size_align(len, DMA_ALIGNMENT).unwrap())
|
.allocate_zeroed(Layout::from_size_align(len, alignment).unwrap())
|
||||||
.expect("failed to allocate a DMA buffer");
|
.expect("failed to allocate a DMA buffer");
|
||||||
Box::from_raw_in(raw.as_ptr(), alloc)
|
Box::from_raw_in(raw.as_ptr(), alloc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Framebuffer {
|
pub struct Framebuffer {
|
||||||
pub fn new(width: u32, height: u32) -> Self {
|
pub width: u32,
|
||||||
let buffer_len = width as usize * height as usize * core::mem::size_of::<u16>();
|
pub height: u32,
|
||||||
let buffer = allocate_dma_buffer_in(buffer_len, &PSRAM_ALLOCATOR);
|
pub bounce_buffers: DmaBounce,
|
||||||
let burst_config: BurstConfig = ExternalBurstConfig::Size16.into();
|
}
|
||||||
|
|
||||||
info!(
|
impl Framebuffer {
|
||||||
"PSRAM SPI burst config: max_compatible_chunk_size={}",
|
pub fn new(
|
||||||
burst_config.max_compatible_chunk_size()
|
channel: DMA_CH0<'static>,
|
||||||
|
peripheral_src: AnySpi<'static>,
|
||||||
|
peripheral_dst: Dpi<'static, Blocking>,
|
||||||
|
front_porch_pixels: u32,
|
||||||
|
width_pixels: u32,
|
||||||
|
height_pixels: u32,
|
||||||
|
rows_per_window: usize,
|
||||||
|
cyclic: bool,
|
||||||
|
) -> Self {
|
||||||
|
const BYTES_PER_PIXEL: usize = core::mem::size_of::<u16>();
|
||||||
|
let buffer_size = width_pixels as usize * height_pixels as usize * BYTES_PER_PIXEL;
|
||||||
|
let burst_config = BurstConfig {
|
||||||
|
internal_memory: InternalBurstConfig::Enabled,
|
||||||
|
external_memory: ExternalBurstConfig::Size64,
|
||||||
|
};
|
||||||
|
let psram_buffer = Box::leak(allocate_dma_buffer_in(
|
||||||
|
buffer_size,
|
||||||
|
burst_config,
|
||||||
|
&PSRAM_ALLOCATOR,
|
||||||
|
));
|
||||||
|
let bounce_buffers = DmaBounce::new(
|
||||||
|
Global,
|
||||||
|
channel,
|
||||||
|
peripheral_src,
|
||||||
|
peripheral_dst,
|
||||||
|
psram_buffer,
|
||||||
|
front_porch_pixels as usize * BYTES_PER_PIXEL,
|
||||||
|
width_pixels as usize * BYTES_PER_PIXEL,
|
||||||
|
rows_per_window,
|
||||||
|
burst_config,
|
||||||
|
cyclic,
|
||||||
);
|
);
|
||||||
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 {
|
Self {
|
||||||
width,
|
width: width_pixels,
|
||||||
height,
|
height: height_pixels,
|
||||||
dma_buf: Some(dma_buf),
|
bounce_buffers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
|
pub fn as_target_pixels(&mut self) -> &mut [Rgb565Pixel] {
|
||||||
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.dma_buf.as_mut().unwrap().as_mut_slice())
|
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.bounce_buffers.buffer_src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ use spectre_api_sys::{
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
use crate::FRAME_DURATION_MIN;
|
use crate::FRAME_DURATION_MIN;
|
||||||
use crate::{
|
use crate::{
|
||||||
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
PSRAM_ALLOCATOR,
|
||||||
db::{
|
db::{
|
||||||
AcidDatabase, DbKey, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers,
|
AcidDatabase, DbKey, DbPathSpectreUserSite, DbPathSpectreUserSites, DbPathSpectreUsers,
|
||||||
PartitionAcid, ReadTransactionExt,
|
PartitionAcid, ReadTransactionExt,
|
||||||
|
|
@ -38,6 +38,7 @@ use crate::{
|
||||||
proxy::OUTPUT_STRING_CHANNEL,
|
proxy::OUTPUT_STRING_CHANNEL,
|
||||||
ui::{
|
ui::{
|
||||||
backend::SlintBackend,
|
backend::SlintBackend,
|
||||||
|
dpi::FRAMES_SKIPPED,
|
||||||
messages::{
|
messages::{
|
||||||
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit,
|
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit,
|
||||||
CallbackMessageUserSites, CallbackMessageUsers, LoginResult,
|
CallbackMessageUserSites, CallbackMessageUsers, LoginResult,
|
||||||
|
|
@ -373,10 +374,15 @@ impl State {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
slint::run_event_loop().unwrap();
|
slint::run_event_loop().unwrap();
|
||||||
SIGNAL_LCD_SUBMIT.signal(());
|
// SIGNAL_LCD_SUBMIT.signal(());
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
embassy_time::Timer::after(FRAME_DURATION_MIN).await;
|
embassy_time::Timer::after(FRAME_DURATION_MIN).await;
|
||||||
SIGNAL_UI_RENDER.wait().await;
|
let frames_skipped = FRAMES_SKIPPED.wait().await;
|
||||||
|
|
||||||
|
if frames_skipped > 0 {
|
||||||
|
error!("Renderer missed {frames_skipped} frames.");
|
||||||
|
}
|
||||||
|
// SIGNAL_UI_RENDER.wait().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(unreachable_code)]
|
#[expect(unreachable_code)]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue