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},
|
2026-02-28 01:34:59 +01:00
|
|
|
gpio::{Flex, Level, Output, OutputConfig},
|
|
|
|
|
lcd_cam::{LcdCam, lcd::dpi::Dpi},
|
|
|
|
|
ledc::{self, LSGlobalClkSource, Ledc, LowSpeed},
|
2026-02-27 23:54:29 +01:00
|
|
|
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};
|
2026-02-28 01:34:59 +01:00
|
|
|
use log::{error, info};
|
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-28 01:34:59 +01:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
pub struct DisplayPeripherals {
|
|
|
|
|
pub DMA_CH2: esp_hal::peripherals::DMA_CH2<'static>,
|
|
|
|
|
pub LCD_CAM: esp_hal::peripherals::LCD_CAM<'static>,
|
|
|
|
|
pub LEDC: esp_hal::peripherals::LEDC<'static>,
|
|
|
|
|
pub GPIO0: Output<'static>,
|
|
|
|
|
pub GPIO1: esp_hal::peripherals::GPIO1<'static>,
|
|
|
|
|
pub GPIO2: esp_hal::peripherals::GPIO2<'static>,
|
|
|
|
|
pub GPIO3: esp_hal::peripherals::GPIO3<'static>,
|
|
|
|
|
pub GPIO4: esp_hal::peripherals::GPIO4<'static>,
|
|
|
|
|
#[cfg(not(feature = "alt-log"))]
|
|
|
|
|
pub GPIO5: esp_hal::peripherals::GPIO5<'static>,
|
|
|
|
|
pub GPIO6: esp_hal::peripherals::GPIO6<'static>,
|
|
|
|
|
#[cfg(not(feature = "alt-log"))]
|
|
|
|
|
pub GPIO12: esp_hal::peripherals::GPIO12<'static>,
|
|
|
|
|
pub GPIO13: esp_hal::peripherals::GPIO13<'static>,
|
|
|
|
|
pub GPIO14: esp_hal::peripherals::GPIO14<'static>,
|
|
|
|
|
pub GPIO15: esp_hal::peripherals::GPIO15<'static>,
|
|
|
|
|
pub GPIO16: esp_hal::peripherals::GPIO16<'static>,
|
|
|
|
|
pub GPIO21: esp_hal::peripherals::GPIO21<'static>,
|
|
|
|
|
pub GPIO34: esp_hal::peripherals::GPIO34<'static>,
|
|
|
|
|
pub GPIO35: esp_hal::peripherals::GPIO35<'static>,
|
|
|
|
|
pub GPIO36: esp_hal::peripherals::GPIO36<'static>,
|
|
|
|
|
pub GPIO37: esp_hal::peripherals::GPIO37<'static>,
|
|
|
|
|
pub GPIO38: esp_hal::peripherals::GPIO38<'static>,
|
|
|
|
|
pub GPIO39: esp_hal::peripherals::GPIO39<'static>,
|
|
|
|
|
pub GPIO40: esp_hal::peripherals::GPIO40<'static>,
|
|
|
|
|
pub GPIO41: esp_hal::peripherals::GPIO41<'static>,
|
|
|
|
|
pub GPIO42: esp_hal::peripherals::GPIO42<'static>,
|
|
|
|
|
pub GPIO43: esp_hal::peripherals::GPIO43<'static>,
|
|
|
|
|
pub GPIO44: esp_hal::peripherals::GPIO44<'static>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DisplayPeripherals {
|
|
|
|
|
pub async fn into_display(self) -> St7701s<'static, Blocking> {
|
|
|
|
|
let mut ledc = Ledc::new(self.LEDC);
|
|
|
|
|
ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);
|
|
|
|
|
let bl_timer = ledc.timer::<LowSpeed>(ledc::timer::Number::Timer0);
|
|
|
|
|
let bl_channel = ledc.channel::<LowSpeed>(ledc::channel::Number::Channel0, self.GPIO21);
|
|
|
|
|
|
|
|
|
|
let sck = Output::new(self.GPIO36, Level::High, OutputConfig::default());
|
|
|
|
|
let mosi = Flex::new(self.GPIO35);
|
|
|
|
|
let cs = Output::new(self.GPIO6, Level::High, OutputConfig::default());
|
|
|
|
|
|
|
|
|
|
let lcd = LcdCam::new(self.LCD_CAM).lcd;
|
|
|
|
|
#[allow(unused_mut)]
|
|
|
|
|
let mut unconfigured_dpi = Dpi::new(lcd, self.DMA_CH2, Default::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.with_de(self.GPIO37)
|
|
|
|
|
.with_pclk(self.GPIO34)
|
|
|
|
|
.with_hsync(self.GPIO44)
|
|
|
|
|
.with_vsync(self.GPIO43)
|
|
|
|
|
// Blue
|
|
|
|
|
.with_data0(self.GPIO38)
|
|
|
|
|
.with_data1(self.GPIO39)
|
|
|
|
|
.with_data2(self.GPIO40)
|
|
|
|
|
.with_data3(self.GPIO41)
|
|
|
|
|
.with_data4(self.GPIO42)
|
|
|
|
|
// Green
|
|
|
|
|
.with_data7(self.GPIO13)
|
|
|
|
|
.with_data8(self.GPIO14)
|
|
|
|
|
.with_data9(self.GPIO15)
|
|
|
|
|
.with_data10(self.GPIO16)
|
|
|
|
|
// Red
|
|
|
|
|
.with_data11(self.GPIO0)
|
|
|
|
|
.with_data12(self.GPIO1)
|
|
|
|
|
.with_data13(self.GPIO2)
|
|
|
|
|
.with_data14(self.GPIO3)
|
|
|
|
|
.with_data15(self.GPIO4);
|
|
|
|
|
|
|
|
|
|
#[cfg(not(feature = "alt-log"))]
|
|
|
|
|
{
|
|
|
|
|
unconfigured_dpi = unconfigured_dpi
|
|
|
|
|
// Green
|
|
|
|
|
.with_data5(peripherals.GPIO5)
|
|
|
|
|
.with_data6(peripherals.GPIO12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi, bl_timer, bl_channel).await;
|
|
|
|
|
info!("ST7701S-based LCD display initialized!");
|
|
|
|
|
st7701s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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)]
|
2026-02-28 01:34:59 +01:00
|
|
|
pub async fn test_bounce_buffers(
|
2026-02-27 23:54:29 +01:00
|
|
|
channel: esp_hal::peripherals::DMA_CH0<'static>,
|
|
|
|
|
peripheral: esp_hal::peripherals::SPI2<'static>,
|
2026-02-28 01:34:59 +01:00
|
|
|
display_peripherals: DisplayPeripherals,
|
2026-02-27 23:54:29 +01:00
|
|
|
) {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 01:34:59 +01:00
|
|
|
let mut st7701s = display_peripherals.into_display().await;
|
2026-02-27 23:54:29 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|