use core::future::poll_fn;
use core::marker::PhantomData;
use core::task::Poll;
use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use stm32_metapac::ltdc::regs::Dccr;
use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr};
use crate::gpio::{AfType, OutputType, Speed};
use crate::interrupt::typelevel::Interrupt;
use crate::interrupt::{self};
use crate::{peripherals, rcc, Peripheral};
static LTDC_WAKER: AtomicWaker = AtomicWaker::new();
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
FifoUnderrun,
TransferError,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LtdcConfiguration {
pub active_width: u16,
pub active_height: u16,
pub h_back_porch: u16,
pub h_front_porch: u16,
pub v_back_porch: u16,
pub v_front_porch: u16,
pub h_sync: u16,
pub v_sync: u16,
pub h_sync_polarity: PolarityActive,
pub v_sync_polarity: PolarityActive,
pub data_enable_polarity: PolarityActive,
pub pixel_clock_polarity: PolarityEdge,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PolarityEdge {
FallingEdge,
RisingEdge,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PolarityActive {
ActiveLow,
ActiveHigh,
}
pub struct Ltdc<'d, T: Instance> {
_peri: PeripheralRef<'d, T>,
}
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RgbColor {
pub red: u8,
pub green: u8,
pub blue: u8,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LtdcLayerConfig {
pub layer: LtdcLayer,
pub pixel_format: PixelFormat,
pub window_x0: u16,
pub window_x1: u16,
pub window_y0: u16,
pub window_y1: u16,
}
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PixelFormat {
ARGB8888 = Pf::ARGB8888 as u8,
RGB888 = Pf::RGB888 as u8,
RGB565 = Pf::RGB565 as u8,
ARGB1555 = Pf::ARGB1555 as u8,
ARGB4444 = Pf::ARGB4444 as u8,
L8 = Pf::L8 as u8,
AL44 = Pf::AL44 as u8,
AL88 = Pf::AL88 as u8,
}
impl PixelFormat {
pub fn bytes_per_pixel(&self) -> usize {
match self {
PixelFormat::ARGB8888 => 4,
PixelFormat::RGB888 => 3,
PixelFormat::RGB565 | PixelFormat::ARGB4444 | PixelFormat::ARGB1555 | PixelFormat::AL88 => 2,
PixelFormat::AL44 | PixelFormat::L8 => 1,
}
}
}
#[repr(usize)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LtdcLayer {
Layer1 = 0,
Layer2 = 1,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
cortex_m::asm::dsb();
Ltdc::<T>::enable_interrupts(false);
LTDC_WAKER.wake();
}
}
impl<'d, T: Instance> Ltdc<'d, T> {
pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self {
Self::setup_clocks();
into_ref!(peri);
Self { _peri: peri }
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_pins(
peri: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
clk: impl Peripheral<P = impl ClkPin<T>> + 'd,
hsync: impl Peripheral<P = impl HsyncPin<T>> + 'd,
vsync: impl Peripheral<P = impl VsyncPin<T>> + 'd,
b0: impl Peripheral<P = impl B0Pin<T>> + 'd,
b1: impl Peripheral<P = impl B1Pin<T>> + 'd,
b2: impl Peripheral<P = impl B2Pin<T>> + 'd,
b3: impl Peripheral<P = impl B3Pin<T>> + 'd,
b4: impl Peripheral<P = impl B4Pin<T>> + 'd,
b5: impl Peripheral<P = impl B5Pin<T>> + 'd,
b6: impl Peripheral<P = impl B6Pin<T>> + 'd,
b7: impl Peripheral<P = impl B7Pin<T>> + 'd,
g0: impl Peripheral<P = impl G0Pin<T>> + 'd,
g1: impl Peripheral<P = impl G1Pin<T>> + 'd,
g2: impl Peripheral<P = impl G2Pin<T>> + 'd,
g3: impl Peripheral<P = impl G3Pin<T>> + 'd,
g4: impl Peripheral<P = impl G4Pin<T>> + 'd,
g5: impl Peripheral<P = impl G5Pin<T>> + 'd,
g6: impl Peripheral<P = impl G6Pin<T>> + 'd,
g7: impl Peripheral<P = impl G7Pin<T>> + 'd,
r0: impl Peripheral<P = impl R0Pin<T>> + 'd,
r1: impl Peripheral<P = impl R1Pin<T>> + 'd,
r2: impl Peripheral<P = impl R2Pin<T>> + 'd,
r3: impl Peripheral<P = impl R3Pin<T>> + 'd,
r4: impl Peripheral<P = impl R4Pin<T>> + 'd,
r5: impl Peripheral<P = impl R5Pin<T>> + 'd,
r6: impl Peripheral<P = impl R6Pin<T>> + 'd,
r7: impl Peripheral<P = impl R7Pin<T>> + 'd,
) -> Self {
Self::setup_clocks();
into_ref!(peri);
new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(b7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(g7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r0, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r1, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r2, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r3, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r4, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r5, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r6, AfType::output(OutputType::PushPull, Speed::VeryHigh));
new_pin!(r7, AfType::output(OutputType::PushPull, Speed::VeryHigh));
Self { _peri: peri }
}
pub fn init(&mut self, config: &LtdcConfiguration) {
use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
let ltdc = T::regs();
assert!(ltdc.gcr().read().0 == 0x2220); ltdc.gcr().modify(|w| {
w.set_hspol(match config.h_sync_polarity {
PolarityActive::ActiveHigh => Hspol::ACTIVE_HIGH,
PolarityActive::ActiveLow => Hspol::ACTIVE_LOW,
});
w.set_vspol(match config.v_sync_polarity {
PolarityActive::ActiveHigh => Vspol::ACTIVE_HIGH,
PolarityActive::ActiveLow => Vspol::ACTIVE_LOW,
});
w.set_depol(match config.data_enable_polarity {
PolarityActive::ActiveHigh => Depol::ACTIVE_HIGH,
PolarityActive::ActiveLow => Depol::ACTIVE_LOW,
});
w.set_pcpol(match config.pixel_clock_polarity {
PolarityEdge::RisingEdge => Pcpol::RISING_EDGE,
PolarityEdge::FallingEdge => Pcpol::FALLING_EDGE,
});
});
ltdc.sscr().modify(|w| {
w.set_vsh(config.v_sync - 1);
w.set_hsw(config.h_sync - 1);
});
ltdc.bpcr().modify(|w| {
w.set_avbp(config.v_sync + config.v_back_porch - 1);
w.set_ahbp(config.h_sync + config.h_back_porch - 1);
});
let aa_height = config.v_sync + config.v_back_porch + config.active_height - 1;
let aa_width = config.h_sync + config.h_back_porch + config.active_width - 1;
ltdc.awcr().modify(|w| {
w.set_aah(aa_height);
w.set_aaw(aa_width);
});
let total_height: u16 = config.v_sync + config.v_back_porch + config.active_height + config.v_front_porch - 1;
let total_width: u16 = config.h_sync + config.h_back_porch + config.active_width + config.h_front_porch - 1;
ltdc.twcr().modify(|w| {
w.set_totalh(total_height);
w.set_totalw(total_width)
});
ltdc.bccr().modify(|w| {
w.set_bcred(0);
w.set_bcgreen(0);
w.set_bcblue(0);
});
self.enable();
}
pub fn enable(&mut self) {
T::regs().gcr().modify(|w| w.set_ltdcen(true));
assert!(T::regs().gcr().read().ltdcen())
}
pub fn disable(&mut self) {
T::regs().gcr().modify(|w| w.set_ltdcen(false));
assert!(!T::regs().gcr().read().ltdcen())
}
pub fn init_layer(&mut self, layer_config: &LtdcLayerConfig, clut: Option<&[RgbColor]>) {
let ltdc = T::regs();
let layer = ltdc.layer(layer_config.layer as usize);
if let Some(clut) = clut {
assert_eq!(clut.len(), 256, "Color lookup table must be exactly 256 in length");
for (index, color) in clut.iter().enumerate() {
layer.clutwr().write(|w| {
w.set_clutadd(index as u8);
w.set_red(color.red);
w.set_green(color.green);
w.set_blue(color.blue);
});
}
}
let h_win_start = layer_config.window_x0 + ltdc.bpcr().read().ahbp() + 1;
let h_win_stop = layer_config.window_x1 + ltdc.bpcr().read().ahbp();
layer.whpcr().write(|w| {
w.set_whstpos(h_win_start);
w.set_whsppos(h_win_stop);
});
let v_win_start = layer_config.window_y0 + ltdc.bpcr().read().avbp() + 1;
let v_win_stop = layer_config.window_y1 + ltdc.bpcr().read().avbp();
layer.wvpcr().write(|w| {
w.set_wvstpos(v_win_start);
w.set_wvsppos(v_win_stop)
});
layer
.pfcr()
.write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8)));
layer.dccr().write_value(Dccr::default());
let alpha = 0xFF;
layer.cacr().write(|w| w.set_consta(alpha));
layer.bfcr().modify(|w| {
w.set_bf1(Bf1::PIXEL);
w.set_bf2(Bf2::PIXEL);
});
let bytes_per_pixel = layer_config.pixel_format.bytes_per_pixel() as u16;
let width = layer_config.window_x1 - layer_config.window_x0;
let height = layer_config.window_y1 - layer_config.window_y0;
layer.cfblr().modify(|w| {
w.set_cfbp(width * bytes_per_pixel);
#[cfg(not(stm32u5))]
w.set_cfbll(width * bytes_per_pixel + 7);
#[cfg(stm32u5)]
w.set_cfbll(width * bytes_per_pixel + 3);
});
layer.cfblnr().modify(|w| w.set_cfblnbr(height));
layer.cr().modify(|w| {
if clut.is_some() {
w.set_cluten(true);
}
w.set_len(true);
});
}
pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
let mut bits = T::regs().isr().read();
if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
poll_fn(|cx| {
let bits = T::regs().isr().read();
if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
return Poll::Ready(());
}
LTDC_WAKER.register(cx.waker());
Self::clear_interrupt_flags(); Self::enable_interrupts(true);
let layer = T::regs().layer(layer as usize);
layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
T::regs().srcr().write(|w| {
w.set_vbr(Vbr::RELOAD);
});
let bits = T::regs().isr().read();
if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
bits = T::regs().isr().read();
}
let result = if bits.fuif() {
Err(Error::FifoUnderrun)
} else if bits.terrif() {
Err(Error::TransferError)
} else if bits.lif() {
panic!("line interrupt event is disabled")
} else if bits.rrif() {
Ok(())
} else {
unreachable!("all interrupt status values checked")
};
Self::clear_interrupt_flags();
result
}
fn setup_clocks() {
critical_section::with(|_cs| {
#[cfg(stm32f7)]
crate::pac::RCC
.dckcfgr1()
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
#[cfg(stm32f4)]
crate::pac::RCC
.dckcfgr()
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
});
rcc::enable_and_reset::<T>();
}
fn clear_interrupt_flags() {
T::regs().icr().write(|w| {
w.set_cfuif(Cfuif::CLEAR);
w.set_clif(Clif::CLEAR);
w.set_crrif(Crrif::CLEAR);
w.set_cterrif(Cterrif::CLEAR);
});
}
fn enable_interrupts(enable: bool) {
T::regs().ier().write(|w| {
w.set_fuie(enable);
w.set_lie(false); w.set_rrie(enable);
w.set_terrie(enable)
});
T::Interrupt::unpend();
if enable {
unsafe { T::Interrupt::enable() };
} else {
T::Interrupt::disable()
}
}
}
impl<'d, T: Instance> Drop for Ltdc<'d, T> {
fn drop(&mut self) {}
}
trait SealedInstance: crate::rcc::SealedRccPeripheral {
fn regs() -> crate::pac::ltdc::Ltdc;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send {
type Interrupt: interrupt::typelevel::Interrupt;
}
pin_trait!(ClkPin, Instance);
pin_trait!(HsyncPin, Instance);
pin_trait!(VsyncPin, Instance);
pin_trait!(DePin, Instance);
pin_trait!(R0Pin, Instance);
pin_trait!(R1Pin, Instance);
pin_trait!(R2Pin, Instance);
pin_trait!(R3Pin, Instance);
pin_trait!(R4Pin, Instance);
pin_trait!(R5Pin, Instance);
pin_trait!(R6Pin, Instance);
pin_trait!(R7Pin, Instance);
pin_trait!(G0Pin, Instance);
pin_trait!(G1Pin, Instance);
pin_trait!(G2Pin, Instance);
pin_trait!(G3Pin, Instance);
pin_trait!(G4Pin, Instance);
pin_trait!(G5Pin, Instance);
pin_trait!(G6Pin, Instance);
pin_trait!(G7Pin, Instance);
pin_trait!(B0Pin, Instance);
pin_trait!(B1Pin, Instance);
pin_trait!(B2Pin, Instance);
pin_trait!(B3Pin, Instance);
pin_trait!(B4Pin, Instance);
pin_trait!(B5Pin, Instance);
pin_trait!(B6Pin, Instance);
pin_trait!(B7Pin, Instance);
foreach_interrupt!(
($inst:ident, ltdc, LTDC, GLOBAL, $irq:ident) => {
impl Instance for peripherals::$inst {
type Interrupt = crate::interrupt::typelevel::$irq;
}
impl SealedInstance for peripherals::$inst {
fn regs() -> crate::pac::ltdc::Ltdc {
crate::pac::$inst
}
}
};
);