2026-02-27 23:54:29 +01:00
|
|
|
use alloc::{alloc::Global, boxed::Box, vec::Vec};
|
|
|
|
|
use embassy_time::Timer;
|
2026-02-14 20:03:32 +01:00
|
|
|
use esp_hal::{
|
2026-02-27 23:54:29 +01:00
|
|
|
Blocking,
|
|
|
|
|
dma::{BurstConfig, ExternalBurstConfig, InternalBurstConfig},
|
|
|
|
|
lcd_cam::lcd::dpi::Dpi,
|
|
|
|
|
spi::master::AnySpi,
|
2026-02-14 20:03:32 +01:00
|
|
|
};
|
2026-02-26 02:02:25 +01:00
|
|
|
use esp_hal_bounce_buffers::{DmaBounce, Swapchain, SwapchainWriter, allocate_dma_buffer_in};
|
2026-02-27 23:54:29 +01:00
|
|
|
use i_slint_core::software_renderer::{Rgb565Pixel, TargetPixel};
|
|
|
|
|
use log::error;
|
2026-02-14 20:03:32 +01:00
|
|
|
|
2026-02-27 23:54:29 +01:00
|
|
|
use crate::{DmaBounceController, PSRAM_ALLOCATOR, peripherals::st7701s::St7701s};
|
2026-02-14 20:03:32 +01:00
|
|
|
|
2026-02-23 23:08:27 +01:00
|
|
|
// TODO: Rename or get rid of.
|
2026-02-22 00:59:01 +01:00
|
|
|
pub struct Framebuffer {
|
|
|
|
|
pub width: u32,
|
|
|
|
|
pub height: u32,
|
2026-02-23 23:08:27 +01:00
|
|
|
pub swapchain: Option<SwapchainWriter>,
|
|
|
|
|
pub bounce_buffers: Option<DmaBounce>,
|
2026-02-22 00:59:01 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 20:03:32 +01:00
|
|
|
impl Framebuffer {
|
2026-02-22 00:59:01 +01:00
|
|
|
pub fn new(
|
2026-02-27 23:54:29 +01:00
|
|
|
channel: esp_hal::peripherals::DMA_CH0<'static>,
|
2026-02-22 00:59:01 +01:00
|
|
|
peripheral_src: AnySpi<'static>,
|
|
|
|
|
peripheral_dst: Dpi<'static, Blocking>,
|
2026-02-22 17:19:02 +01:00
|
|
|
burst_config: BurstConfig,
|
2026-02-22 16:09:20 +01:00
|
|
|
front_porch_pixels: u32,
|
|
|
|
|
width_pixels: u32,
|
|
|
|
|
height_pixels: u32,
|
2026-02-22 00:59:01 +01:00
|
|
|
rows_per_window: usize,
|
|
|
|
|
cyclic: bool,
|
|
|
|
|
) -> Self {
|
2026-02-22 16:09:20 +01:00
|
|
|
const BYTES_PER_PIXEL: usize = core::mem::size_of::<u16>();
|
|
|
|
|
let buffer_size = width_pixels as usize * height_pixels as usize * BYTES_PER_PIXEL;
|
2026-02-23 23:08:27 +01:00
|
|
|
let framebuffers = [
|
|
|
|
|
Box::leak(allocate_dma_buffer_in(
|
|
|
|
|
buffer_size,
|
|
|
|
|
burst_config,
|
|
|
|
|
&PSRAM_ALLOCATOR,
|
|
|
|
|
)),
|
|
|
|
|
Box::leak(allocate_dma_buffer_in(
|
|
|
|
|
buffer_size,
|
|
|
|
|
burst_config,
|
|
|
|
|
&PSRAM_ALLOCATOR,
|
|
|
|
|
)),
|
|
|
|
|
];
|
|
|
|
|
let (swapchain_reader, swapchain_writer) = Swapchain { framebuffers }.into_reader_writer();
|
2026-02-22 00:59:01 +01:00
|
|
|
let bounce_buffers = DmaBounce::new(
|
|
|
|
|
Global,
|
|
|
|
|
channel,
|
|
|
|
|
peripheral_src,
|
|
|
|
|
peripheral_dst,
|
2026-02-23 23:08:27 +01:00
|
|
|
swapchain_reader,
|
2026-02-22 16:09:20 +01:00
|
|
|
front_porch_pixels as usize * BYTES_PER_PIXEL,
|
|
|
|
|
width_pixels as usize * BYTES_PER_PIXEL,
|
|
|
|
|
rows_per_window,
|
2026-02-22 00:59:01 +01:00
|
|
|
burst_config,
|
|
|
|
|
cyclic,
|
2026-02-14 20:03:32 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Self {
|
2026-02-22 16:09:20 +01:00
|
|
|
width: width_pixels,
|
|
|
|
|
height: height_pixels,
|
2026-02-23 23:08:27 +01:00
|
|
|
swapchain: Some(swapchain_writer),
|
|
|
|
|
bounce_buffers: Some(bounce_buffers),
|
2026-02-14 20:03:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 23:54:29 +01:00
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
async fn test_bounce_buffers(
|
|
|
|
|
channel: esp_hal::peripherals::DMA_CH0<'static>,
|
|
|
|
|
peripheral: esp_hal::peripherals::SPI2<'static>,
|
|
|
|
|
mut st7701s: St7701s<'static, Blocking>,
|
|
|
|
|
) {
|
|
|
|
|
error!("TEST BOUNCE BUFFERS SECTION ENTERED");
|
|
|
|
|
|
|
|
|
|
const BYTES_PER_PIXEL: usize = core::mem::size_of::<u16>();
|
|
|
|
|
// Assume highest burst config setting.
|
|
|
|
|
const EXTERNAL_BURST_CONFIG: ExternalBurstConfig = ExternalBurstConfig::Size32;
|
|
|
|
|
const ALIGNMENT_PIXELS: usize = EXTERNAL_BURST_CONFIG as usize / BYTES_PER_PIXEL;
|
|
|
|
|
// The total number of pixels demanded by the DPI, per row.
|
|
|
|
|
const WIDTH_TOTAL_PIXELS: usize = 368;
|
|
|
|
|
// The total number of rows demanded by the DPI, per frame.
|
|
|
|
|
const HEIGHT_PIXELS: usize = 960;
|
|
|
|
|
// The number of unused pixels at the start of the row.
|
|
|
|
|
const FRONT_PORCH_ACTUAL_PIXELS: usize = 120;
|
|
|
|
|
// The number of actually visible pixels, per row.
|
|
|
|
|
const WIDTH_VISIBLE_PIXELS: usize = 240;
|
|
|
|
|
// The number of pixels not stored in a bounce buffer, per row.
|
|
|
|
|
// This many arbitrary pixels are sent to the DPI.
|
|
|
|
|
const FRONT_PORCH_SKIPPED_PIXELS: usize =
|
|
|
|
|
(FRONT_PORCH_ACTUAL_PIXELS / ALIGNMENT_PIXELS) * ALIGNMENT_PIXELS;
|
|
|
|
|
const WIDTH_STORED_PIXELS: usize = WIDTH_TOTAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS;
|
|
|
|
|
const VISIBLE_OFFSET_IN_BUFFER_PIXELS: usize =
|
|
|
|
|
FRONT_PORCH_ACTUAL_PIXELS - FRONT_PORCH_SKIPPED_PIXELS;
|
|
|
|
|
const ROWS_PER_WINDOW: usize = 16;
|
|
|
|
|
let burst_config = BurstConfig {
|
|
|
|
|
internal_memory: InternalBurstConfig::Enabled,
|
|
|
|
|
external_memory: EXTERNAL_BURST_CONFIG,
|
|
|
|
|
};
|
|
|
|
|
let (swapchain_reader, mut swapchain_writer) = Swapchain {
|
|
|
|
|
framebuffers: [
|
|
|
|
|
Box::leak(allocate_dma_buffer_in(
|
|
|
|
|
HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL,
|
|
|
|
|
burst_config,
|
|
|
|
|
&PSRAM_ALLOCATOR,
|
|
|
|
|
)),
|
|
|
|
|
Box::leak(allocate_dma_buffer_in(
|
|
|
|
|
HEIGHT_PIXELS * WIDTH_STORED_PIXELS * BYTES_PER_PIXEL,
|
|
|
|
|
burst_config,
|
|
|
|
|
&PSRAM_ALLOCATOR,
|
|
|
|
|
)),
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
.into_reader_writer();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let write_guard = &mut swapchain_writer.write();
|
|
|
|
|
let buffer_src = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(write_guard);
|
|
|
|
|
let colors = (0..WIDTH_VISIBLE_PIXELS as u8 / 2)
|
|
|
|
|
.rev()
|
|
|
|
|
.map(|val| Rgb565Pixel::from_rgb(0xFF, val * 2, 0))
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
for (index, pixel) in buffer_src.iter_mut().enumerate() {
|
|
|
|
|
let mut x =
|
|
|
|
|
(index % WIDTH_STORED_PIXELS) as i16 - VISIBLE_OFFSET_IN_BUFFER_PIXELS as i16;
|
|
|
|
|
let mut y = (index / WIDTH_STORED_PIXELS) as i16;
|
|
|
|
|
|
|
|
|
|
if x < WIDTH_VISIBLE_PIXELS as i16 {
|
|
|
|
|
x = core::cmp::min(x, WIDTH_VISIBLE_PIXELS as i16 - 1 - x);
|
|
|
|
|
y = core::cmp::min(y, HEIGHT_PIXELS as i16 - 1 - y);
|
|
|
|
|
let min = core::cmp::min(x, y);
|
|
|
|
|
|
|
|
|
|
*pixel = colors[min as usize % colors.len()];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*pixel = Rgb565Pixel::default();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut dma_bounce = DmaBounce::new(
|
|
|
|
|
Global,
|
|
|
|
|
channel,
|
|
|
|
|
AnySpi::from(peripheral),
|
|
|
|
|
st7701s.dpi,
|
|
|
|
|
swapchain_reader,
|
|
|
|
|
FRONT_PORCH_SKIPPED_PIXELS * BYTES_PER_PIXEL,
|
|
|
|
|
WIDTH_STORED_PIXELS * BYTES_PER_PIXEL,
|
|
|
|
|
ROWS_PER_WINDOW,
|
|
|
|
|
burst_config,
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
let mut bb_controller = DmaBounceController::new(dma_bounce);
|
|
|
|
|
|
|
|
|
|
error!("TEST BOUNCE BUFFERS TASK LAUNCHED");
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
bb_controller.start().await.unwrap();
|
|
|
|
|
st7701s.controller.sleep_off().await;
|
|
|
|
|
Timer::after_secs(1).await;
|
|
|
|
|
st7701s.controller.sleep_on().await;
|
|
|
|
|
bb_controller.stop().await.unwrap();
|
|
|
|
|
Timer::after_secs(1).await;
|
|
|
|
|
}
|
|
|
|
|
}
|