2026-05-28 7:12 AM
Hello everybody,
I am currently trying to get the LTDC running on the STM32N6570-DK without using the HAL drivers.
The LTDC itself seems to work partially, because the background color configured via LTDC->BCCR is displayed correctly and can even be changed.
However, as soon as I enable Layer1 with a framebuffer, I only get a solid black rectangle in the top left corner instead of the framebuffer contents.
Current configuration:
What i observe:
At this point i suspect that the LTDC master cannot properly access/read the selected AXI SRAM region, even though the CPU can. I also tried other memory regions
My Questions:
Any hint would be greatly appreciated.
Thanks!
Framebuffer Code sample:
#ifndef SIMPLE_LCD_FRAMEBUFFER_H
#define SIMPLE_LCD_FRAMEBUFFER_H
#include <stdint.h>
#define LCD_WIDTH 200
#define LCD_HEIGHT 200
#define LCD_COLOR_BLACK 0x0000
#define LCD_COLOR_WHITE 0xFFFF
#define LCD_COLOR_RED 0xF800
#define LCD_COLOR_GREEN 0x07E0
#define LCD_COLOR_BLUE 0x001F
#define lcd_framebuffer ((volatile uint16_t*)0x24064000UL)
void LCD_Fill(uint16_t color);
//void LCD_DrawPixel(uint32_t x, uint32_t y, uint16_t color);
#endif /* SIMPLE_LCD_FRAMEBUFFER_H */
#include "simple_lcd_framebuffer.h"
void LCD_Fill(uint16_t color){
volatile uint16_t* ptr = lcd_framebuffer;
for (uint32_t i = 0; i < LCD_WIDTH * LCD_HEIGHT; i++) {
ptr[i] = color;
}
}
RCC LTDC Code sample:
void RCC_enable_LTDC_memory(void){
RCC->MEMENR |= RCC_MEMENR_AXISRAM1EN
| RCC_MEMENR_AXISRAM2EN
| RCC_MEMENR_AXISRAM3EN
| RCC_MEMENR_AXISRAM4EN
| RCC_MEMENR_AXISRAM5EN
| RCC_MEMENR_AXISRAM6EN;
(void)RCC->MEMENR;
}
void RCC_enable_LTDC(void){
RCC->APB5ENSR |= RCC_APB5ENSR_LTDCENS;
(void)RCC->APB5ENSR;
}
void RCC_reset_LTDC(void){
RCC->APB5RSTSR |= RCC_APB5RSTSR_LTDCRSTS;
(void)RCC->APB5RSTSR;
delay_ms(10);
RCC->APB5RSTCR |= RCC_APB5RSTCR_LTDCRSTC;
(void)RCC->APB5RSTCR;
}
void RCC_setLTDC_clock_source(uint32_t source){
source &= 0x3U; // 2 bits width
RCC->CCIPR4 &= ~RCC_CCIPR4_LTDCSEL;
RCC->CCIPR4 |= (source << RCC_CCIPR4_LTDCSEL_Pos);
(void)RCC->CCIPR4;
}
void RCC_config_LTDC_clock(void){
/* PLL4 configuration for 25 MHz pixel clock
* PLL4 source = HSI (64 MHz), M=8, N=225, P1=6, P2=6
* VCO = (64/8) * 225 = 1800 MHz
* PLL4_out = 1800 / (6*6) = 50 MHz
* IC16 divider = 2 -> LTDC clock = 25 MHz
*/
if (!(RCC->SR & RCC_SR_PLL4RDY)) {
RCC->PLL4CFGR1 &= ~RCC_PLL4CFGR1_PLL4SEL;
RCC->PLL4CFGR1 = (RCC->PLL4CFGR1 & ~(RCC_PLL4CFGR1_PLL4DIVM | RCC_PLL4CFGR1_PLL4DIVN))
| (8UL << RCC_PLL4CFGR1_PLL4DIVM_Pos)
| (225UL << RCC_PLL4CFGR1_PLL4DIVN_Pos);
RCC->PLL4CFGR2 &= ~RCC_PLL4CFGR2_PLL4DIVNFRAC;
RCC->PLL4CFGR3 = (RCC->PLL4CFGR3 & ~(RCC_PLL4CFGR3_PLL4PDIV1 | RCC_PLL4CFGR3_PLL4PDIV2))
| (6UL << RCC_PLL4CFGR3_PLL4PDIV1_Pos)
| (6UL << RCC_PLL4CFGR3_PLL4PDIV2_Pos);
RCC->PLL4CFGR3 |= RCC_PLL4CFGR3_PLL4PDIVEN;
(void)RCC->PLL4CFGR3;
RCC->CR |= RCC_CR_PLL4ON;
while (!(RCC->SR & RCC_SR_PLL4RDY));
}
uint32_t ic16sel = RCC_IC16CFGR_IC16SEL_0 | RCC_IC16CFGR_IC16SEL_1;
uint32_t ic16int = (2UL - 1UL) << RCC_IC16CFGR_IC16INT_Pos;
RCC->IC16CFGR = (RCC->IC16CFGR & ~(RCC_IC16CFGR_IC16SEL | RCC_IC16CFGR_IC16INT))
| ic16sel | ic16int;
(void)RCC->IC16CFGR;
RCC->DIVENR |= RCC_DIVENR_IC16EN;
(void)RCC->DIVENR;
RCC->CCIPR4 = (RCC->CCIPR4 & ~RCC_CCIPR4_LTDCSEL) | RCC_CCIPR4_LTDCSEL_1;
(void)RCC->CCIPR4;
}
LTDC Code sample:
static void LCD_ConfigGPIO(void){
uint32_t pa_pins[] = {0, 1, 2, 7, 8, 15};
for (int i = 0; i < 6; i++)
GPIO_Config(GPIOA, pa_pins[i], GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
uint32_t pb_pins[] = {2, 4, 11, 12, 13, 14, 15};
for (int i = 0; i < 7; i++)
GPIO_Config(GPIOB, pb_pins[i], GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOD, 8, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOD, 9, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOD, 15, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOE, 11, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
uint32_t pg_pins[] = {0, 1, 6, 8, 11, 12};
for (int i = 0; i < 6; i++)
GPIO_Config(GPIOG, pg_pins[i], GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOH, 3, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOH, 4, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOH, 6, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_PUPD_NONE, LTDC_AF);
GPIO_Config(GPIOE, 1, GPIO_MODE_OUTPUT, GPIO_OTYPE_PP, GPIO_PUPD_NONE, GPIO_AF_NONE);
GPIO_Config(GPIOG, 13, GPIO_MODE_OUTPUT, GPIO_OTYPE_PP, GPIO_PUPD_NONE, GPIO_AF_NONE);
GPIO_Config(GPIOQ, 3, GPIO_MODE_OUTPUT, GPIO_OTYPE_PP, GPIO_PUPD_NONE, GPIO_AF_NONE);
GPIO_Config(GPIOQ, 6, GPIO_MODE_OUTPUT, GPIO_OTYPE_PP, GPIO_PUPD_NONE, GPIO_AF_NONE);
}
static void LCD_PowerOn(void){
GPIO_BSRR_reset(GPIOE, 1);
delay_ms(10);
GPIO_BSRR_set(GPIOE, 1);
delay_ms(10);
GPIO_BSRR_set(GPIOQ, 3);
GPIO_BSRR_set(GPIOQ, 6);
GPIO_BSRR_set(GPIOG, 13);
}
static void LCD_ConfigTiming(void){
uint32_t hsync = 4U, hbp = 4U, hfp = 4U, width = 800U;
uint32_t vsync = 4U, vbp = 4U, vfp = 4U, height = 480U;
LTDC->SSCR = ((hsync - 1U) << LTDC_SSCR_HSW_Pos) |
((vsync - 1U) << LTDC_SSCR_VSH_Pos);
LTDC->BPCR = ((hsync + hbp - 1U) << LTDC_BPCR_AHBP_Pos) |
((vsync + vbp - 1U) << LTDC_BPCR_AVBP_Pos);
LTDC->AWCR = ((hsync + hbp + width - 1U) << LTDC_AWCR_AAW_Pos) |
((vsync + vbp + height - 1U) << LTDC_AWCR_AAH_Pos);
LTDC->TWCR = ((hsync + hbp + width + hfp - 1U) << LTDC_TWCR_TOTALW_Pos) |
((vsync + vbp + height + vfp - 1U) << LTDC_TWCR_TOTALH_Pos);
}
void LCD_Init(void){
RCC_enable_LTDC_memory();
RCC_config_LTDC_clock();
LCD_ConfigGPIO();
LCD_PowerOn();
RCC_enable_LTDC();
RCC_reset_LTDC();
LTDC->GCR &= ~LTDC_GCR_LTDCEN;
LCD_ConfigTiming();
LTDC->GCR &= ~(LTDC_GCR_HSPOL | LTDC_GCR_VSPOL | LTDC_GCR_DEPOL | LTDC_GCR_PCPOL);
LTDC->GCR |= (LTDC_GCR_DEPOL | LTDC_GCR_PCPOL);
LTDC->BCCR = 0xFF00FF00UL;
LTDC->GCR |= LTDC_GCR_LTDCEN;
}
void LCD_SetBackgroundColor(uint8_t r, uint8_t g, uint8_t b){
while (!(LTDC->CDSR & LTDC_CDSR_VDES));
while (LTDC->CDSR & LTDC_CDSR_VDES);
LTDC->BCCR = ((uint32_t)r << 16U) | ((uint32_t)g << 8U) | (uint32_t)b;
}
void LCD_ConfigLayer1(void){
uint32_t hsync = 4U;
uint32_t hbp = 4U;
uint32_t vsync = 4U;
uint32_t vbp = 4U;
uint32_t pitch = LCD_WIDTH * 2U; // RGB565 = 2 Byte per pixel
LTDC_Layer1->CR = 0U;
LTDC_Layer1->RCR = LTDC_LxRCR_GRMSK;
LTDC_Layer1->WHPCR =
((hsync + hbp) << LTDC_LxWHPCR_WHSTPOS_Pos) |
((hsync + hbp + LCD_WIDTH - 1U) << LTDC_LxWHPCR_WHSPPOS_Pos);
LTDC_Layer1->WVPCR =
((vsync + vbp) << LTDC_LxWVPCR_WVSTPOS_Pos) |
((vsync + vbp + LCD_HEIGHT - 1U) << LTDC_LxWVPCR_WVSPPOS_Pos);
LTDC_Layer1->PFCR = 2U; // RGB565
LTDC_Layer1->CACR = 0xFFU; // Alpha 255
LTDC_Layer1->BFCR =
(4U << LTDC_LxBFCR_BF1_Pos) |
(5U << LTDC_LxBFCR_BF2_Pos);
LTDC_Layer1->CFBAR = (uint32_t)lcd_framebuffer;
LTDC_Layer1->CFBLR =
(pitch << LTDC_LxCFBLR_CFBP_Pos) |
((pitch + 3U) << LTDC_LxCFBLR_CFBLL_Pos);
LTDC_Layer1->CFBLNR = LCD_HEIGHT;
LTDC_Layer1->PCR &= ~LTDC_LxPCR_YCEN;
LTDC_Layer1->CR = LTDC_LxCR_LEN;
LTDC_Layer1->RCR = LTDC_LxRCR_IMR | LTDC_LxRCR_GRMSK;
while (LTDC_Layer1->RCR & LTDC_LxRCR_IMR) {
}
}
Main Code sample:
int main(void){
...
GPIO_Config(GPIOG, LED2_PIN, GPIO_MODE_OUTPUT, GPIO_OTYPE_PP, GPIO_PUPD_NONE, GPIO_AF_NONE);
LCD_Init();
LCD_Fill(0xF800);
uintptr_t addr = (uintptr_t)lcd_framebuffer;
uintptr_t start = addr & ~31U;
uintptr_t size = (LCD_WIDTH * LCD_HEIGHT * 2U + 31U) & ~31U;
SCB_CleanDCache_by_Addr((uint32_t*)start, size);
delay_ms(10);
LCD_ConfigLayer1();
...
}