WIP: Higher prio
This commit is contained in:
parent
376416c32e
commit
dde67e46fe
|
|
@ -86,6 +86,7 @@ tinyvec = { version = "1.10.0", default-features = false, features = ["alloc"] }
|
||||||
esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] }
|
esp-metadata-generated = { version = "0.3.0", features = ["esp32s3"] }
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
|
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
|
||||||
indoc = "2.0.7"
|
indoc = "2.0.7"
|
||||||
|
ouroboros = "0.18.5"
|
||||||
|
|
||||||
# A fork of slint with patches for `allocator_api` support.
|
# A fork of slint with patches for `allocator_api` support.
|
||||||
# Don't forget to change `slint-build` in build dependencies, if this is changed.
|
# Don't forget to change `slint-build` in build dependencies, if this is changed.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use log::LevelFilter;
|
||||||
|
|
||||||
pub const LOG_LEVEL_FILTER: LevelFilter = {
|
pub const LOG_LEVEL_FILTER: LevelFilter = {
|
||||||
if let Some(string) = option_env!("ESP_LOG") {
|
if let Some(string) = option_env!("ESP_LOG") {
|
||||||
if string.eq_ignore_ascii_case("ERROR") {
|
if string.eq_ignore_ascii_case("OFF") {
|
||||||
|
LevelFilter::Off
|
||||||
|
} else if string.eq_ignore_ascii_case("ERROR") {
|
||||||
LevelFilter::Error
|
LevelFilter::Error
|
||||||
} else if string.eq_ignore_ascii_case("WARN") {
|
} else if string.eq_ignore_ascii_case("WARN") {
|
||||||
LevelFilter::Warn
|
LevelFilter::Warn
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ use alloc::vec::Vec;
|
||||||
use cfg_if::cfg_if;
|
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::{SendSpawner, Spawner, SpawnerTraceExt};
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::channel::Channel;
|
use embassy_sync::channel::Channel;
|
||||||
use embassy_sync::mutex::Mutex;
|
use embassy_sync::mutex::Mutex;
|
||||||
|
|
@ -224,7 +224,7 @@ async fn test_bounce_buffers(
|
||||||
burst_config,
|
burst_config,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
buf.send().await;
|
buf.launch_interrupt_driven_task().await;
|
||||||
error!("TEST BOUNCE BUFFERS SECTION DONE");
|
error!("TEST BOUNCE BUFFERS SECTION DONE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,11 +321,17 @@ async fn main(_spawner: Spawner) {
|
||||||
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);
|
||||||
let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority3);
|
let interrupt_core_0_spawner = executor_core_0.start(interrupt::Priority::Priority1);
|
||||||
|
|
||||||
|
// static EXECUTOR_CORE_1: StaticCell<InterruptExecutor<3>> = StaticCell::new();
|
||||||
|
// let executor_core_1 = InterruptExecutor::new(software_interrupt.software_interrupt3);
|
||||||
|
// let executor_core_1 = EXECUTOR_CORE_1.init(executor_core_1);
|
||||||
|
// let interrupt_core_1_spawner = executor_core_1.start(interrupt::Priority::Priority2);
|
||||||
|
|
||||||
info!("ESP-RTOS started!");
|
info!("ESP-RTOS started!");
|
||||||
|
|
||||||
let main_task_peripherals = MainPeripherals {
|
let main_task_peripherals = MainPeripherals {
|
||||||
|
// high_priority_task_spawner: interrupt_core_1_spawner,
|
||||||
uart_rx,
|
uart_rx,
|
||||||
software_interrupt1: software_interrupt.software_interrupt1,
|
software_interrupt1: software_interrupt.software_interrupt1,
|
||||||
RNG: peripherals.RNG,
|
RNG: peripherals.RNG,
|
||||||
|
|
@ -376,6 +382,7 @@ async fn main(_spawner: Spawner) {
|
||||||
/// Peripherals passed to the main task.
|
/// Peripherals passed to the main task.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
struct MainPeripherals {
|
struct MainPeripherals {
|
||||||
|
// high_priority_task_spawner: SendSpawner,
|
||||||
uart_rx: UartRx<'static, Blocking>,
|
uart_rx: UartRx<'static, Blocking>,
|
||||||
software_interrupt1: SoftwareInterrupt<'static, 1>,
|
software_interrupt1: SoftwareInterrupt<'static, 1>,
|
||||||
RNG: esp_hal::peripherals::RNG<'static>,
|
RNG: esp_hal::peripherals::RNG<'static>,
|
||||||
|
|
@ -706,8 +713,8 @@ async fn main_task(peripherals: MainPeripherals) {
|
||||||
112,
|
112,
|
||||||
368 - 112,
|
368 - 112,
|
||||||
960,
|
960,
|
||||||
16,
|
8,
|
||||||
false,
|
true,
|
||||||
));
|
));
|
||||||
|
|
||||||
info!("Framebuffer created!");
|
info!("Framebuffer created!");
|
||||||
|
|
@ -751,6 +758,19 @@ async fn main_task(peripherals: MainPeripherals) {
|
||||||
|
|
||||||
info!("Awaiting on all tasks...");
|
info!("Awaiting on all tasks...");
|
||||||
|
|
||||||
|
// let spawner = unsafe { Spawner::for_current_executor() }.await;
|
||||||
|
|
||||||
|
// peripherals
|
||||||
|
// .high_priority_task_spawner
|
||||||
|
// .must_spawn(run_bounce_buffers(framebuffer));
|
||||||
|
|
||||||
|
framebuffer
|
||||||
|
.bounce_buffers
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.launch_interrupt_driven_task()
|
||||||
|
.await;
|
||||||
|
|
||||||
// TODO: Probably want to select! instead and re-try.
|
// TODO: Probably want to select! instead and re-try.
|
||||||
join_all![
|
join_all![
|
||||||
run_alloc_stats_reporter(),
|
run_alloc_stats_reporter(),
|
||||||
|
|
@ -762,7 +782,7 @@ async fn main_task(peripherals: MainPeripherals) {
|
||||||
// warn!("Waited.");
|
// warn!("Waited.");
|
||||||
// framebuffer.bounce_buffers.send().await;
|
// framebuffer.bounce_buffers.send().await;
|
||||||
// },
|
// },
|
||||||
framebuffer.bounce_buffers.send(),
|
// framebuffer.bounce_buffers.send(),
|
||||||
// ui::dpi::run_lcd(st7701s, framebuffer),
|
// ui::dpi::run_lcd(st7701s, framebuffer),
|
||||||
// lcd_task,
|
// lcd_task,
|
||||||
run_devices! (
|
run_devices! (
|
||||||
|
|
@ -785,6 +805,11 @@ async fn main_task(peripherals: MainPeripherals) {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn run_bounce_buffers(framebuffer: &'static mut Framebuffer) {
|
||||||
|
framebuffer.bounce_buffers.as_mut().unwrap().send().await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_alloc_stats_reporter() {
|
async fn run_alloc_stats_reporter() {
|
||||||
let mut psram_used_prev = 0;
|
let mut psram_used_prev = 0;
|
||||||
let mut heap_used_prev = 0;
|
let mut heap_used_prev = 0;
|
||||||
|
|
|
||||||
|
|
@ -1276,7 +1276,7 @@ where
|
||||||
//
|
//
|
||||||
// Adafruit would use 11 MHz.
|
// Adafruit would use 11 MHz.
|
||||||
// I had lowered the frequency, so that `DmaBounce` could keep up.
|
// I had lowered the frequency, so that `DmaBounce` could keep up.
|
||||||
.with_frequency(Rate::from_mhz(7)) // From Adafruit
|
.with_frequency(Rate::from_mhz(5)) // From Adafruit
|
||||||
.with_clock_mode(ClockMode {
|
.with_clock_mode(ClockMode {
|
||||||
polarity: Polarity::IdleLow, // From Adafruit
|
polarity: Polarity::IdleLow, // From Adafruit
|
||||||
phase: Phase::ShiftHigh, // From Adafruit
|
phase: Phase::ShiftHigh, // From Adafruit
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use core::{
|
use core::{
|
||||||
alloc::Layout,
|
alloc::Layout,
|
||||||
|
cell::UnsafeCell,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize},
|
sync::atomic::{self, AtomicBool, AtomicU32, AtomicUsize},
|
||||||
};
|
};
|
||||||
|
|
@ -20,7 +21,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, InternalBurstConfig, Mem2Mem, SimpleMem2MemTransfer,
|
ExternalBurstConfig, InternalBurstConfig, Mem2Mem, SimpleMem2Mem, SimpleMem2MemTransfer,
|
||||||
},
|
},
|
||||||
dma_descriptors, handler,
|
dma_descriptors, handler,
|
||||||
interrupt::{self, Priority},
|
interrupt::{self, Priority},
|
||||||
|
|
@ -33,8 +34,9 @@ use esp_sync::RawMutex;
|
||||||
use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel};
|
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 ouroboros::self_referencing;
|
||||||
use rmk::{
|
use rmk::{
|
||||||
futures::{FutureExt, pin_mut},
|
futures::{self, FutureExt, pin_mut},
|
||||||
join_all,
|
join_all,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -76,6 +78,14 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) {
|
||||||
const DMA_CHANNEL_OUTBOUND: usize = 2;
|
const DMA_CHANNEL_OUTBOUND: usize = 2;
|
||||||
const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2;
|
const INTERRUPT_OUTBOUND: Interrupt = Interrupt::DMA_OUT_CH2;
|
||||||
|
|
||||||
|
#[self_referencing]
|
||||||
|
struct ReceivingTransfer {
|
||||||
|
mem2mem: SimpleMem2Mem<'static, Blocking>,
|
||||||
|
#[borrows(mut mem2mem)]
|
||||||
|
#[covariant]
|
||||||
|
transfer: Option<SimpleMem2MemTransfer<'this, 'static, Blocking>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DmaBounce {
|
pub struct DmaBounce {
|
||||||
// TODO: Make these generic.
|
// TODO: Make these generic.
|
||||||
// They currently cannot be generic, because they lacks a `reborrow` method.
|
// They currently cannot be generic, because they lacks a `reborrow` method.
|
||||||
|
|
@ -84,6 +94,8 @@ pub struct DmaBounce {
|
||||||
peripheral_src: AnySpi<'static>,
|
peripheral_src: AnySpi<'static>,
|
||||||
// This can also be more generic, see `DmaEligible` in `Mem2Mem::new`.
|
// This can also be more generic, see `DmaEligible` in `Mem2Mem::new`.
|
||||||
peripheral_dst: Option<Dpi<'static, Blocking>>,
|
peripheral_dst: Option<Dpi<'static, Blocking>>,
|
||||||
|
// TODO: Combine with peripheral_dst using an enum?
|
||||||
|
transfer_dst: Option<DpiTransfer<'static, DmaTxBounceBuf, Blocking>>,
|
||||||
|
|
||||||
// TODO: Consider having a separate burst config for the two transfers.
|
// TODO: Consider having a separate burst config for the two transfers.
|
||||||
burst_config: BurstConfig,
|
burst_config: BurstConfig,
|
||||||
|
|
@ -105,9 +117,11 @@ pub struct DmaBounce {
|
||||||
bounce_dst_descs: &'static mut [DmaDescriptor],
|
bounce_dst_descs: &'static mut [DmaDescriptor],
|
||||||
// A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`.
|
// A cyclic descriptor list that spans the buffers `bounce_buffer_dst` and `bounce_buffer_src`.
|
||||||
bounce_src_descs: &'static mut [DmaDescriptor],
|
bounce_src_descs: &'static mut [DmaDescriptor],
|
||||||
|
descriptors_per_window: usize,
|
||||||
// The index of the next window about to be received into the destination bounce buffer.
|
// The index of the next window about to be received into the destination bounce buffer.
|
||||||
window_index_next: usize,
|
window_index_next: usize,
|
||||||
frame_index_next: usize,
|
frame_index_next: usize,
|
||||||
|
receiving_transfer: Option<ReceivingTransfer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaBounce {
|
impl DmaBounce {
|
||||||
|
|
@ -183,21 +197,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, descriptors_per_window) = Self::bounce_descriptors_for_buffer(
|
||||||
Self::bounce_descriptors_for_buffer_cyclic(
|
|
||||||
row_front_porch_bytes,
|
|
||||||
row_width_bytes,
|
|
||||||
window_size_rows,
|
|
||||||
unsafe {
|
|
||||||
(
|
|
||||||
&mut *(bounce_buffer_dst as *mut _),
|
|
||||||
&mut *(bounce_buffer_src as *mut _),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
burst_config,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Self::bounce_descriptors_for_buffer_single(
|
|
||||||
windows_len,
|
windows_len,
|
||||||
row_front_porch_bytes,
|
row_front_porch_bytes,
|
||||||
row_width_bytes,
|
row_width_bytes,
|
||||||
|
|
@ -209,13 +209,14 @@ impl DmaBounce {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
burst_config,
|
burst_config,
|
||||||
)
|
cyclic,
|
||||||
};
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
channel,
|
channel,
|
||||||
peripheral_src,
|
peripheral_src,
|
||||||
peripheral_dst: Some(peripheral_dst),
|
peripheral_dst: Some(peripheral_dst),
|
||||||
|
transfer_dst: None,
|
||||||
burst_config,
|
burst_config,
|
||||||
cyclic,
|
cyclic,
|
||||||
window_size,
|
window_size,
|
||||||
|
|
@ -226,8 +227,10 @@ impl DmaBounce {
|
||||||
src_descs,
|
src_descs,
|
||||||
bounce_dst_descs,
|
bounce_dst_descs,
|
||||||
bounce_src_descs,
|
bounce_src_descs,
|
||||||
|
descriptors_per_window,
|
||||||
window_index_next: 0,
|
window_index_next: 0,
|
||||||
frame_index_next: 0,
|
frame_index_next: 0,
|
||||||
|
receiving_transfer: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,81 +361,15 @@ impl DmaBounce {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounce_descriptors_for_buffer_cyclic(
|
fn bounce_descriptors_for_buffer(
|
||||||
row_front_porch_bytes: usize,
|
|
||||||
row_width_bytes: usize,
|
|
||||||
window_size_rows: usize,
|
|
||||||
bounce_buffers: (&'static mut [u8], &'static mut [u8]),
|
|
||||||
burst_config: BurstConfig,
|
|
||||||
) -> &'static mut [DmaDescriptor] {
|
|
||||||
assert_eq!(
|
|
||||||
bounce_buffers.0.len(),
|
|
||||||
bounce_buffers.1.len(),
|
|
||||||
"bounce buffers must be equal in size"
|
|
||||||
);
|
|
||||||
|
|
||||||
let buffer_len = bounce_buffers.0.len();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
buffer_len,
|
|
||||||
row_width_bytes * window_size_rows,
|
|
||||||
"the provided bounce buffers have an invalid size"
|
|
||||||
);
|
|
||||||
|
|
||||||
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_combined =
|
|
||||||
Box::leak(vec![DmaDescriptor::EMPTY; 2 * descriptors_per_window].into_boxed_slice());
|
|
||||||
let descriptors_pair = descriptors_combined.split_at_mut(descriptors_per_window);
|
|
||||||
|
|
||||||
// Link up the descriptors.
|
|
||||||
fn link_up_descriptors(
|
|
||||||
descriptors: &mut [DmaDescriptor],
|
|
||||||
descriptors_other: &mut [DmaDescriptor],
|
|
||||||
) {
|
|
||||||
let mut next = descriptors_other.first_mut().unwrap();
|
|
||||||
for desc in descriptors.iter_mut().rev() {
|
|
||||||
desc.next = next;
|
|
||||||
next = desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
link_up_descriptors(descriptors_pair.0, descriptors_pair.1);
|
|
||||||
link_up_descriptors(descriptors_pair.1, descriptors_pair.0);
|
|
||||||
|
|
||||||
// Prepare each descriptor's buffer size.
|
|
||||||
for (bounce_buffer, descriptors) in [
|
|
||||||
(bounce_buffers.0, descriptors_pair.0),
|
|
||||||
(bounce_buffers.1, descriptors_pair.1),
|
|
||||||
] {
|
|
||||||
Self::prepare_descriptors_window(
|
|
||||||
bounce_buffer,
|
|
||||||
descriptors,
|
|
||||||
row_front_porch_bytes,
|
|
||||||
row_width_bytes,
|
|
||||||
window_size_rows,
|
|
||||||
max_chunk_size,
|
|
||||||
descriptors_per_row,
|
|
||||||
descriptors_per_row_front_porch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptors_combined
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bounce_descriptors_for_buffer_single(
|
|
||||||
windows_len: usize,
|
windows_len: usize,
|
||||||
row_front_porch_bytes: usize,
|
row_front_porch_bytes: usize,
|
||||||
row_width_bytes: usize,
|
row_width_bytes: usize,
|
||||||
window_size_rows: 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,
|
||||||
) -> &'static mut [DmaDescriptor] {
|
cyclic: bool,
|
||||||
|
) -> (&'static mut [DmaDescriptor], usize) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bounce_buffers.0.len(),
|
bounce_buffers.0.len(),
|
||||||
bounce_buffers.1.len(),
|
bounce_buffers.1.len(),
|
||||||
|
|
@ -449,6 +386,15 @@ impl DmaBounce {
|
||||||
"the provided bounce buffers have an invalid size"
|
"the provided bounce buffers have an invalid size"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Implementation note:
|
||||||
|
// A cyclic descriptor could consist of just a set of descriptors per window,
|
||||||
|
// so two sets in total, because there are two bounce buffers.
|
||||||
|
// However, we can also access the pointer of the EOF descriptor within the
|
||||||
|
// EOF interrupt handler, which lets us compute which descriptor generated that
|
||||||
|
// interrupt.
|
||||||
|
// This is useful in the case when an interrupt is missed. Then the number of interrupts
|
||||||
|
// handled doesn't correspond to the number of windows sent to the destination peripheral.
|
||||||
|
// In that case, the number of windows sent can be computed from the address of the descriptor.
|
||||||
let max_chunk_size = burst_config.max_compatible_chunk_size();
|
let max_chunk_size = burst_config.max_compatible_chunk_size();
|
||||||
let descriptors_per_row_front_porch =
|
let descriptors_per_row_front_porch =
|
||||||
dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false);
|
dma::descriptor_count(row_front_porch_bytes, max_chunk_size, false);
|
||||||
|
|
@ -459,9 +405,15 @@ impl DmaBounce {
|
||||||
let descriptors_per_frame = descriptors_per_window * windows_len;
|
let descriptors_per_frame = descriptors_per_window * windows_len;
|
||||||
let descriptors_frame =
|
let descriptors_frame =
|
||||||
Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice());
|
Box::leak(vec![DmaDescriptor::EMPTY; descriptors_per_frame].into_boxed_slice());
|
||||||
|
let descriptors_frame_ptr = descriptors_frame.as_ptr();
|
||||||
|
|
||||||
// Link up the descriptors.
|
// Link up the descriptors.
|
||||||
let mut next = core::ptr::null_mut();
|
let mut next = if cyclic {
|
||||||
|
descriptors_frame.first_mut().unwrap() as *mut _
|
||||||
|
} else {
|
||||||
|
core::ptr::null_mut()
|
||||||
|
};
|
||||||
|
|
||||||
for desc in descriptors_frame.iter_mut().rev() {
|
for desc in descriptors_frame.iter_mut().rev() {
|
||||||
desc.next = next;
|
desc.next = next;
|
||||||
next = desc;
|
next = desc;
|
||||||
|
|
@ -497,7 +449,7 @@ impl DmaBounce {
|
||||||
windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes)
|
windows_len * window_size_rows * (row_front_porch_bytes + row_width_bytes)
|
||||||
);
|
);
|
||||||
|
|
||||||
descriptors_frame
|
(descriptors_frame, descriptors_per_window)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linear_descriptors_prepare(
|
fn linear_descriptors_prepare(
|
||||||
|
|
@ -549,6 +501,61 @@ impl DmaBounce {
|
||||||
.modify(|_, w| w.out_eof().bit(true));
|
.modify(|_, w| w.out_eof().bit(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive a window of bytes into the current dst bounce buffer.
|
||||||
|
/// Finally, swaps the bounce buffers.
|
||||||
|
///
|
||||||
|
/// # Safety:
|
||||||
|
/// TODO
|
||||||
|
unsafe fn receive_window_start(&mut self) -> ReceivingTransfer {
|
||||||
|
// Descriptors are initialized by `DmaTxBuf::new`.
|
||||||
|
let buffer_src_window =
|
||||||
|
&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| {
|
||||||
|
// 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)`.
|
||||||
|
Self::linear_descriptors_prepare(
|
||||||
|
self.bounce_dst_descs,
|
||||||
|
Some(&mut *self.bounce_buffer_dst),
|
||||||
|
|desc| {
|
||||||
|
desc.reset_for_rx();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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: &'static mut [DmaDescriptor] =
|
||||||
|
unsafe { &mut *(self.bounce_dst_descs as *mut _) };
|
||||||
|
let src_descs: &'static mut [DmaDescriptor] = unsafe { &mut *(self.src_descs as *mut _) };
|
||||||
|
|
||||||
|
let mem2mem = unsafe {
|
||||||
|
Mem2Mem::new(
|
||||||
|
self.channel.clone_unchecked(),
|
||||||
|
self.peripheral_src.clone_unchecked(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.with_descriptors(bounce_dst_descs, src_descs, self.burst_config)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ReceivingTransferBuilder {
|
||||||
|
mem2mem,
|
||||||
|
transfer_builder: |mem2mem| {
|
||||||
|
Some(
|
||||||
|
mem2mem
|
||||||
|
.start_transfer(&mut self.bounce_buffer_dst, buffer_src_window)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
/// Receive a window of bytes into the current dst bounce buffer.
|
/// Receive a window of bytes into the current dst bounce buffer.
|
||||||
/// Finally, swaps the bounce buffers.
|
/// Finally, swaps the bounce buffers.
|
||||||
async fn receive_window(&mut self) {
|
async fn receive_window(&mut self) {
|
||||||
|
|
@ -595,14 +602,39 @@ impl DmaBounce {
|
||||||
self.increase_window_counter(1);
|
self.increase_window_counter(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increase_window_counter(&mut self, windows: usize) {
|
fn increase_window_counter(&mut self, windows: isize) {
|
||||||
if windows % 2 == 1 {
|
if windows.rem_euclid(2) == 1 {
|
||||||
core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src);
|
core::mem::swap(&mut self.bounce_buffer_dst, &mut self.bounce_buffer_src);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window_index_next += windows;
|
let window_index_next = self.window_index_next as isize + windows;
|
||||||
self.frame_index_next += self.window_index_next / self.windows_len;
|
self.frame_index_next = (self.frame_index_next as isize
|
||||||
self.window_index_next = self.window_index_next % self.windows_len;
|
+ window_index_next / self.windows_len as isize)
|
||||||
|
as usize;
|
||||||
|
self.window_index_next = window_index_next.rem_euclid(self.windows_len as isize) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn launch_interrupt_driven_task(mut self) {
|
||||||
|
Self::enable_interrupts();
|
||||||
|
|
||||||
|
// Receive the first 2 windows, so that the outbound transfer can read valid data.
|
||||||
|
self.receive_window().await;
|
||||||
|
|
||||||
|
let dma_tx_buffer = self.get_dma_tx_buffer();
|
||||||
|
let transfer = self
|
||||||
|
.peripheral_dst
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.send(self.cyclic /* Send perpetually */, dma_tx_buffer)
|
||||||
|
.unwrap_or_else(|(error, _, _)| {
|
||||||
|
panic!("failed to begin the transmission of the first frame: {error:?}");
|
||||||
|
});
|
||||||
|
self.transfer_dst = Some(transfer);
|
||||||
|
self.receiving_transfer = Some(unsafe { self.receive_window_start() });
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*DMA_STATE.0.get() = Some(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&mut self) {
|
pub async fn send(&mut self) {
|
||||||
|
|
@ -633,31 +665,17 @@ impl DmaBounce {
|
||||||
// "Window received: {} {}",
|
// "Window received: {} {}",
|
||||||
// self.window_index_next, self.frame_index_next
|
// self.window_index_next, self.frame_index_next
|
||||||
// );
|
// );
|
||||||
let windows_skipped = WINDOWS_SKIPPED
|
let windows_sent = WINDOWS_SENT
|
||||||
.wait()
|
.wait()
|
||||||
.with_timeout(Duration::from_millis(100))
|
.with_timeout(Duration::from_millis(100))
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
error!("Timed out when waiting for skipped windows.");
|
error!("Timed out when waiting for skipped windows.");
|
||||||
0 // TODO: This should be -1 to repeat the same window.
|
0
|
||||||
});
|
});
|
||||||
|
let windows_skipped = windows_sent as isize - 1;
|
||||||
|
|
||||||
// let windows_skipped = match windows_skipped {
|
if windows_skipped != 0 {
|
||||||
// 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 {
|
|
||||||
self.increase_window_counter(windows_skipped);
|
self.increase_window_counter(windows_skipped);
|
||||||
windows_skipped_total += windows_skipped;
|
windows_skipped_total += windows_skipped;
|
||||||
// error!(
|
// error!(
|
||||||
|
|
@ -674,7 +692,9 @@ impl DmaBounce {
|
||||||
|
|
||||||
if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) {
|
if !self.cyclic && (self.window_index_next == 1 || transfer.is_done()) {
|
||||||
if self.window_index_next > 1 {
|
if self.window_index_next > 1 {
|
||||||
self.increase_window_counter(self.windows_len - self.window_index_next + 1);
|
self.increase_window_counter(
|
||||||
|
self.windows_len as isize - self.window_index_next as isize + 1,
|
||||||
|
);
|
||||||
} else if self.window_index_next == 0 {
|
} else if self.window_index_next == 0 {
|
||||||
self.increase_window_counter(1);
|
self.increase_window_counter(1);
|
||||||
}
|
}
|
||||||
|
|
@ -763,25 +783,94 @@ unsafe impl DmaTxBuffer for DmaTxBounceBuf {
|
||||||
|
|
||||||
/// Intended to be listened on by the renderer, to synchronize the refresh frequency with.
|
/// Intended to be listened on by the renderer, to synchronize the refresh frequency with.
|
||||||
pub static FRAMES_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
pub static FRAMES_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
||||||
static WINDOWS_SKIPPED: Signal<RawMutex, usize> = Signal::new();
|
static WINDOWS_SENT: Signal<RawMutex, usize> = Signal::new();
|
||||||
// static INBOUND_TRANSFER_FINISHED: Signal<RawMutex, ()> = Signal::new();
|
static DMA_STATE: SyncUnsafeCell<Option<DmaBounce>> = SyncUnsafeCell(UnsafeCell::new(None));
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct SyncUnsafeCell<T>(UnsafeCell<T>);
|
||||||
|
|
||||||
|
unsafe impl<T> Sync for SyncUnsafeCell<T> {}
|
||||||
|
|
||||||
|
// #[derive(Clone, Copy)]
|
||||||
|
// struct DmaState {
|
||||||
|
// descriptors_ptr: *const DmaDescriptor,
|
||||||
|
// descriptors_per_window: usize,
|
||||||
|
// windows_per_frame: usize,
|
||||||
|
// last_window_index: usize,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// unsafe impl Sync for DmaState {}
|
||||||
|
|
||||||
#[handler(priority = Priority::Priority3)]
|
#[handler(priority = Priority::Priority3)]
|
||||||
#[ram] // Improves performance.
|
#[ram] // Improves performance.
|
||||||
fn dma_outbound_interrupt_handler() {
|
fn dma_outbound_interrupt_handler() {
|
||||||
let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int();
|
let interrupt = DMA::regs().ch(DMA_CHANNEL_OUTBOUND).out_int();
|
||||||
let bounce_buffer_processed = interrupt.st().read().out_eof().bit_is_set();
|
let bounce_buffer_sent = interrupt.st().read().out_eof().bit_is_set();
|
||||||
if bounce_buffer_processed {
|
|
||||||
|
if !bounce_buffer_sent {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 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));
|
||||||
|
|
||||||
WINDOWS_SKIPPED.signal(
|
// SAFETY: This value is only ever read in our interrupt handler,
|
||||||
WINDOWS_SKIPPED
|
// and interrupts are disabled, and we only use this in one thread.
|
||||||
.try_take()
|
let Some(dma_state) = unsafe { &mut *DMA_STATE.0.get() }.as_mut() else {
|
||||||
.map(|windows_skipped| windows_skipped + 1)
|
error!("no DMA state available when executing DMA interrupt handler");
|
||||||
.unwrap_or_default(),
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The descriptor of the buffer with an EOF flag that just finished being sent.
|
||||||
|
let descriptor_ptr = DMA::regs()
|
||||||
|
.ch(DMA_CHANNEL_OUTBOUND)
|
||||||
|
.out_eof_des_addr()
|
||||||
|
.read()
|
||||||
|
.out_eof_des_addr()
|
||||||
|
.bits() as *const DmaDescriptor;
|
||||||
|
// This is the index of the window that just finished being transmitted to the destination peripheral.
|
||||||
|
let window_sent_index =
|
||||||
|
unsafe { descriptor_ptr.offset_from_unsigned(dma_state.bounce_src_descs.as_ptr()) }
|
||||||
|
/ dma_state.descriptors_per_window;
|
||||||
|
// warn!("{window_sent_index}");
|
||||||
|
// The next window to be sent is `(window_sent_index + 1) % dma_state.windows_len`.
|
||||||
|
// That is not the window we want to buffer, because the transmissions would race.
|
||||||
|
// We instead want to buffer the next window:
|
||||||
|
let window_index_next = (window_sent_index + 2) % dma_state.windows_len;
|
||||||
|
|
||||||
|
// Swap bounce buffers.
|
||||||
|
if (dma_state.windows_len + window_index_next - dma_state.window_index_next) % 2 == 1 {
|
||||||
|
core::mem::swap(
|
||||||
|
&mut dma_state.bounce_buffer_dst,
|
||||||
|
&mut dma_state.bounce_buffer_src,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dma_state.window_index_next = window_index_next;
|
||||||
|
|
||||||
|
// warn!("{window_sent_index} {window_index_next}");
|
||||||
|
|
||||||
|
let mut receiving_transfer = dma_state
|
||||||
|
.receiving_transfer
|
||||||
|
.take()
|
||||||
|
.expect("no ongoing transfer to a bounce buffer present");
|
||||||
|
let receiving_transfer = receiving_transfer
|
||||||
|
.with_mut(|x| x.transfer.take())
|
||||||
|
.expect("no ongoing inner transfer to a bounce buffer present");
|
||||||
|
|
||||||
|
// if !receiving_transfer.is_done() {
|
||||||
|
// error!("{window_sent_index}");
|
||||||
|
// error!("the transfer to a bounce buffer has not finished yet, aborting");
|
||||||
|
// }
|
||||||
|
|
||||||
|
if receiving_transfer.is_done() {
|
||||||
|
drop(receiving_transfer);
|
||||||
|
} else {
|
||||||
|
receiving_transfer.wait().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is any ongoing transfer, cancel it and start a new one.
|
||||||
|
dma_state.receiving_transfer = Some(unsafe { dma_state.receive_window_start() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[handler(priority = Priority::Priority3)]
|
// #[handler(priority = Priority::Priority3)]
|
||||||
|
|
@ -882,7 +971,7 @@ pub fn allocate_dma_buffer_in<A: Allocator>(
|
||||||
pub struct Framebuffer {
|
pub struct Framebuffer {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub bounce_buffers: DmaBounce,
|
pub bounce_buffers: Option<DmaBounce>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Framebuffer {
|
impl Framebuffer {
|
||||||
|
|
@ -920,11 +1009,11 @@ impl Framebuffer {
|
||||||
Self {
|
Self {
|
||||||
width: width_pixels,
|
width: width_pixels,
|
||||||
height: height_pixels,
|
height: height_pixels,
|
||||||
bounce_buffers,
|
bounce_buffers: Some(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.bounce_buffers.buffer_src)
|
bytemuck::cast_slice_mut::<_, Rgb565Pixel>(self.bounce_buffers.as_mut().unwrap().buffer_src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,11 +377,12 @@ impl State {
|
||||||
// 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;
|
||||||
let frames_skipped = FRAMES_SKIPPED.wait().await;
|
// let frames_skipped = FRAMES_SKIPPED.wait().await;
|
||||||
|
|
||||||
|
// if frames_skipped > 0 {
|
||||||
|
// error!("Renderer missed {frames_skipped} frames.");
|
||||||
|
// }
|
||||||
|
|
||||||
if frames_skipped > 0 {
|
|
||||||
error!("Renderer missed {frames_skipped} frames.");
|
|
||||||
}
|
|
||||||
// SIGNAL_UI_RENDER.wait().await;
|
// SIGNAL_UI_RENDER.wait().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue