2024-06-28 18:11:34 +01:00

575 lines
20 KiB
Rust

//! LTDC - LCD-TFT Display Controller
//! See ST application note AN4861: Introduction to LCD-TFT display controller (LTDC) on STM32 MCUs for high level details
//! This module was tested against the stm32h735g-dk using the RM0468 ST reference manual for detailed register information
use crate::{
gpio::{AfType, OutputType, Speed},
interrupt::{self, typelevel::Interrupt},
peripherals, rcc, Peripheral,
};
use core::{future::poll_fn, marker::PhantomData, task::Poll};
use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use stm32_metapac::ltdc::{
regs::Dccr,
vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr},
};
static LTDC_WAKER: AtomicWaker = AtomicWaker::new();
/// LTDC error
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// FIFO underrun. Generated when a pixel is requested while the FIFO is empty
FifoUnderrun,
/// Transfer error. Generated when a bus error occurs
TransferError,
}
/// Display configuration parameters
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LtdcConfiguration {
/// Active width in pixels
pub active_width: u16,
/// Active height in pixels
pub active_height: u16,
/// Horizontal back porch (in units of pixel clock period)
pub h_back_porch: u16,
/// Horizontal front porch (in units of pixel clock period)
pub h_front_porch: u16,
/// Vertical back porch (in units of horizontal scan line)
pub v_back_porch: u16,
/// Vertical front porch (in units of horizontal scan line)
pub v_front_porch: u16,
/// Horizontal synchronization width (in units of pixel clock period)
pub h_sync: u16,
/// Vertical synchronization height (in units of horizontal scan line)
pub v_sync: u16,
/// Horizontal synchronization polarity: `false`: active low, `true`: active high
pub h_sync_polarity: PolarityActive,
/// Vertical synchronization polarity: `false`: active low, `true`: active high
pub v_sync_polarity: PolarityActive,
/// Data enable polarity: `false`: active low, `true`: active high
pub data_enable_polarity: PolarityActive,
/// Pixel clock polarity: `false`: falling edge, `true`: rising edge
pub pixel_clock_polarity: PolarityEdge,
}
/// Edge polarity
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PolarityEdge {
/// Falling edge
FallingEdge,
/// Rising edge
RisingEdge,
}
/// Active polarity
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PolarityActive {
/// Active low
ActiveLow,
/// Active high
ActiveHigh,
}
/// LTDC driver.
pub struct Ltdc<'d, T: Instance> {
_peri: PeripheralRef<'d, T>,
}
/// LTDC interrupt handler.
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
/// 24 bit color
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RgbColor {
/// Red
pub red: u8,
/// Green
pub green: u8,
/// Blue
pub blue: u8,
}
/// Layer
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LtdcLayerConfig {
/// Layer number
pub layer: LtdcLayer,
/// Pixel format
pub pixel_format: PixelFormat,
/// Window left x in pixels
pub window_x0: u16,
/// Window right x in pixels
pub window_x1: u16,
/// Window top y in pixels
pub window_y0: u16,
/// Window bottom y in pixels
pub window_y1: u16,
}
/// Pixel format
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PixelFormat {
/// ARGB8888
ARGB8888 = Pf::ARGB8888 as u8,
/// RGB888
RGB888 = Pf::RGB888 as u8,
/// RGB565
RGB565 = Pf::RGB565 as u8,
/// ARGB1555
ARGB1555 = Pf::ARGB1555 as u8,
/// ARGB4444
ARGB4444 = Pf::ARGB4444 as u8,
/// L8 (8-bit luminance)
L8 = Pf::L8 as u8,
/// AL44 (4-bit alpha, 4-bit luminance
AL44 = Pf::AL44 as u8,
/// AL88 (8-bit alpha, 8-bit luminance)
AL88 = Pf::AL88 as u8,
}
impl PixelFormat {
/// Number of bytes per pixel
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,
}
}
}
/// Ltdc Blending Layer
#[repr(usize)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LtdcLayer {
/// Bottom layer
Layer1 = 0,
/// Top layer
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> {
// Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost
/// Note: Full-Duplex modes are not supported at this time
pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self {
Self::setup_clocks();
into_ref!(peri);
Self { _peri: peri }
}
/// Create a new LTDC driver. 8 pins per color channel for blue, green and red
#[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 }
}
/// Initialise and enable the display
pub fn init(&mut self, config: &LtdcConfiguration) {
use stm32_metapac::ltdc::vals::{Depol, Hspol, Pcpol, Vspol};
let ltdc = T::regs();
// check bus access
assert!(ltdc.gcr().read().0 == 0x2220); // reset value
// configure the HS, VS, DE and PC polarity
ltdc.gcr().modify(|w| {
w.set_hspol(match config.h_sync_polarity {
PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH,
PolarityActive::ActiveLow => Hspol::ACTIVELOW,
});
w.set_vspol(match config.v_sync_polarity {
PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH,
PolarityActive::ActiveLow => Vspol::ACTIVELOW,
});
w.set_depol(match config.data_enable_polarity {
PolarityActive::ActiveHigh => Depol::ACTIVEHIGH,
PolarityActive::ActiveLow => Depol::ACTIVELOW,
});
w.set_pcpol(match config.pixel_clock_polarity {
PolarityEdge::RisingEdge => Pcpol::RISINGEDGE,
PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE,
});
});
// set synchronization pulse width
ltdc.sscr().modify(|w| {
w.set_vsh(config.v_sync - 1);
w.set_hsw(config.h_sync - 1);
});
// set accumulated back porch
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);
});
// set accumulated active width
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);
});
// set total width and height
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)
});
// set the background color value to black
ltdc.bccr().modify(|w| {
w.set_bcred(0);
w.set_bcgreen(0);
w.set_bcblue(0);
});
self.enable();
}
/// Set the enable bit in the control register and assert that it has been enabled
///
/// This does need to be called if init has already been called
pub fn enable(&mut self) {
T::regs().gcr().modify(|w| w.set_ltdcen(true));
assert!(T::regs().gcr().read().ltdcen())
}
/// Unset the enable bit in the control register and assert that it has been disabled
pub fn disable(&mut self) {
T::regs().gcr().modify(|w| w.set_ltdcen(false));
assert!(!T::regs().gcr().read().ltdcen())
}
/// Initialise and enable the layer
///
/// clut - a 256 length color look-up table applies to L8, AL44 and AL88 pixel format and will default to greyscale if `None` supplied and these pixel formats are used
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);
// 256 color look-up table for L8, AL88 and AL88 pixel formats
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);
});
}
}
// configure the horizontal start and stop position
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);
});
// configure the vertical start and stop position
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)
});
// set the pixel format
layer
.pfcr()
.write(|w| w.set_pf(Pf::from_bits(layer_config.pixel_format as u8)));
// set the default color value to transparent black
layer.dccr().write_value(Dccr::default());
// set the global constant alpha value
let alpha = 0xFF;
layer.cacr().write(|w| w.set_consta(alpha));
// set the blending factors.
layer.bfcr().modify(|w| {
w.set_bf1(Bf1::PIXEL);
w.set_bf2(Bf2::PIXEL);
});
// calculate framebuffer pixel size in bytes
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;
// framebuffer pitch and line length
layer.cfblr().modify(|w| {
w.set_cfbp(width * bytes_per_pixel);
w.set_cfbll(width * bytes_per_pixel + 7);
});
// framebuffer line number
layer.cfblnr().modify(|w| w.set_cfblnbr(height));
// enable LTDC_Layer by setting LEN bit
layer.cr().modify(|w| {
if clut.is_some() {
w.set_cluten(true);
}
w.set_len(true);
});
}
/// Set the current buffer. The async function will return when buffer has been completely copied to the LCD screen
/// frame_buffer_addr is a pointer to memory that should not move (best to make it static)
pub async fn set_buffer(&mut self, layer: LtdcLayer, frame_buffer_addr: *const ()) -> Result<(), Error> {
let mut bits = T::regs().isr().read();
// if all clear
if !bits.fuif() && !bits.lif() && !bits.rrif() && !bits.terrif() {
// wait for interrupt
poll_fn(|cx| {
// quick check to avoid registration if already done.
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(); // don't poison the request with old flags
Self::enable_interrupts(true);
// set the new frame buffer address
let layer = T::regs().layer(layer as usize);
layer.cfbar().modify(|w| w.set_cfbadd(frame_buffer_addr as u32));
// configure a shadow reload for the next blanking period
T::regs().srcr().write(|w| {
w.set_vbr(Vbr::RELOAD);
});
// need to check condition after register to avoid a race
// condition that would result in lost notifications.
let bits = T::regs().isr().read();
if bits.fuif() || bits.lif() || bits.rrif() || bits.terrif() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
// re-read the status register after wait.
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() {
// register reload flag is expected
Ok(())
} else {
unreachable!("all interrupt status values checked")
};
Self::clear_interrupt_flags();
result
}
fn setup_clocks() {
critical_section::with(|_cs| {
// RM says the pllsaidivr should only be changed when pllsai is off. But this could have other unintended side effects. So let's just give it a try like this.
// According to the debugger, this bit gets set, anyway.
#[cfg(stm32f7)]
crate::pac::RCC
.dckcfgr1()
.modify(|w| w.set_pllsaidivr(stm32_metapac::rcc::vals::Pllsaidivr::DIV2));
// It is set to RCC_PLLSAIDIVR_2 in ST's BSP example for the STM32469I-DISCO.
#[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); // we are not interested in the line interrupt enable event
w.set_rrie(enable);
w.set_terrie(enable)
});
// enable interrupts for LTDC peripheral
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;
}
/// LTDC instance trait.
#[allow(private_bounds)]
pub trait Instance: SealedInstance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send {
/// Interrupt for this LTDC instance.
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
}
}
};
);