Skip to main content
Logo
Overview
STM32N6 + LTDC : Basic Peripheral Tutorial

STM32N6 + LTDC : Basic Peripheral Tutorial

July 25, 2026
11 min read

The objective

There are plenty of full application examples and tutorials for the STM32N6570-DK. However most of them are step-by-step tutorials, without a proper explanation for the selected peripheral configuration. Therefore I wanted to work on working with the LCD-TFT Display Controller the LTDC.

My main objective is to basically: learn to interface an LCD display (the included in the STM32N6570-DK), configure and use the LTDC peripheral. I want the final software to have the framebuffer stored in ram (a simple array) and get the screen to update the displayed information based on the array contents, automatically without software intervention.

The STM32N6-DK

For this test I am using the STM32N6570 Discovery Kit (ST Page) which includes a five inch LCD with capacitive touch panel, and a camera board (MB1854B IMX335). I will work with a basic FSBL and do the actual work in the Secure Application code, if you want to work with Non-Secure Application is up to you, but you will need to adapt the RIF.

STM32N6570-DK Picture with the Supervisor.
Figure: STM32N6570-DK Picture with the Supervisor.

Reference documents

RefNameLinkDesc.
[1]STM32N657 Discovery Kit - User ManualUM3300For Discovery kit pinout, connectors and internal interfaces.
[2]STM32N6 - Reference ManualFor the detailed information about the peripherals, registers, and programming required data.
[3]STM32N6 - DatasheetFor technical related description: clock speeds, voltages, and so on.
[4]RK050HR18-CTG_DS_001DatasheetLCD Screen of the discovery kit screen

Debug configuration: UART

In order to debug the application, I am configuring the UART to send string information back to the Host computer. The board has the UART1 (PE5/PE6) directly available as a virtual COM port using the STLINK, so we are using it for basic debugging. You can also disable all the unused peripherals.

Important

Remember to assign the GPIOs (PE5/PE6) to the Application in the GPIO section.º

The UART is configuring according to the User Manual[1] (7.19):

  • 115200 bit/s
  • 8-bit data
  • Even parity
  • One-stop bit
  • No flow control
UART Configuration for STM32N6570-DK
Figure: UART Configuration for STM32N6570-DK

LCD-TFT first steps

The STM32N6570-DK comes with a five inch LCD screen, the screen is a RK050HR18H-CTG with a resolution of 800x480.

The LCD is connected through the CN3 connector of the discovery kit which connects directly to the LTDC (LCD-TFT Display Controller) peripheral of the STM32N6. The LTDC can be used to increase the system performance since the LTDC can read the buffer directly from the RAM and update the screen without CPU intervention.

Note

There is a more detailed explanation of the LTDC working modes in LTDC Introduction document

We are using the LTDC to draw a framebuffer, located in internal SRAM (AXISRAM1/2) to the LCD screen. Since we are not using any UI library, we will focus on just understanding the frame updating with simple colors.

CubeMX: LTDC Peripheral configuration

The LTDC peripheral is located for the STM32N6 configuration under the Multimedia section. Once activated, the first box allows us to select the display type and, since we have the 24 lines connected to the LCD connector (R0-7, B0-7 and G0-7), we have to select the RGB888 interface. Also assign the LTDC to the Application environment.

LCD Interface definition in [4]
Figure: LCD Interface definition in [4]

Now , let go though the configuration tabs: Features, NVIC Settings, GPIO Settings, Parameters Settings and Layer settings.

Features

CubeMX LTDC Features tab configuration
Figure: CubeMX LTDC Features tab configuration

The Features tab list two items, the LTDC_L1 and LTDC_L2, which correspond to the layers of the LTDC. These layers contain the framebuffers that can be shown or blended together, using an alpha channel (transparency).

This can be use, for example, to have one frame buffer (L1) with the constant UI display information with a transparent area, and then the second layer, with the framebuffer that is fed from a camera. Having a final image of the two framebuffers mixed. This blend, or combination is performed automatically by the LTDC peripheral.

Note

You can find more information in section 4.3 of the LTDC Introduction document

Since I want the simplest example, I will only use Layer 1 to draw something, so I assign he LTDC_L1 layer to the Application.

NVIC Settings

CubeMX LTDC NVIC tab configuration
Figure: CubeMX LTDC NVIC tab configuration

This tab is also pretty self-explanatory with the two sources of interrupt of the LTDC controller. In our case we want both the IRQs enable.

GPIO Settings

CubeMX LTDC GPIO tab configuration
Figure: CubeMX LTDC GPIO tab configuration

The GPIO setting shall be automatically filled. There are some changes that I made to make this works. The first critical step is to assign all the GPIOs to the Application context, otherwise, even they are configured wont be used by the Application.

Also, since we are dealing with fast and critical signals, I changed all the GPIOs’ speed to Very High.

Parameters Settings

CubeMX LTDC Parameters tab configuration
Figure: CubeMX LTDC Parameters tab configuration

The parameters settings tab is probably the most confusing one, at least for me, when starting to work with LCD screens. I wont go much into details, but I will point to to you where to find the correct information in the screen datasheet for each LCD.

We need to fill the synchronization data for the Width and the Height. Let’s start with the easy ones, Active Width and Active Height which are the display width and height: 800 and 480, which correspond to the Thdisp and Tvdisp in the datasheet.

LCD Timing characteristics
Figure: LCD Timing characteristics

Then the porches, nothing fancy:

  • Horizontal Back/Front Porch = Thbp and Thfp which are 8 and 8.
  • Vertical Back/Froint Poch = Tvbp and Tvfp which are also 8 and 8.

The synchronization width and Height correspond to the Pulse Width for the HSYNC and the VSYNC fields in the datasheet.

The next steps are the clock polarities which can be obtained from the signal diagram of the LCD datasheet [4]. From the diagram below, the VSYNC and HSYNC signals depend on the value for VDPOL and HDPOL respectively.

LCD Driver default polarity
Figure: LCD Driver default polarity

And these two values, VDPOL and HDPOL, depend on the LCD driver. The LCD driver of the RK050HR18 screen is a ST7262-G4 (from the LCD datasheet) and from the driver datasheet we can see that by default VDPOL and HDPOL are set to ‘1’, since the register default value is 0xD7.

LCD Driver default polarity
Figure: LCD Driver default polarity

Therefore the values for the clocks are all Active Low and Normal Input for the clock.

Layer settings.

This tab contain the information about the layer. Since we are using a screen with 800x480 resolution, and we want a layer that cover all the screen, we would need to cover the whole window and therefore we want the Window position from 0 to 800 in the horizontal direction, and 0 to 480 in the vertical direction. The same goes for the Frame Buffer option at the bottom.

For the pixel format, we will use ARGB8888 which has 8 bits for red, green and blue, plus the first byte assigned to the transparency. I used this format for convenience since a pixel is now 32-bit wide, which can be stored perfectly in an uint32_t.

For the blending, I am actually not using it, so leave as seen in the image.

The default color for the layer can be used to check that the screen is actually working. By selecting a color different from the black, if the LTDC is working, a color will be displayed, even with a misconfiguration in the RIF or the framebuffer address.

CubeMX LTDC Layer Settings tab configuration
Figure: CubeMX LTDC Layer Settings tab configuration

Extra GPIO Configuration

On top of the already configured GPIOs in the LTDC screen, we have to add some extra pins required by the interface. The TFT LCD Connector (CN3) of the STM32N657 has the LCD_ON/OFF (PQ3) and LCD_BL_CTRL (PQ6) signals that are not handled by the LTDC. These two pins control the ON/OFF of the screen and the backlight.

These two GPIO have to be configured in the GPIO section, and will be managed by usual HAL_GPIO_Write functions.

CubeMX: LTDC Clock configuration

LTDC Clock. Image from STM32N6 Datasheet [2].
Figure: LTDC Clock. Image from STM32N6 Datasheet [2].

We have finally completed the LTDC configuration. However, as any other peripheral, the LTDC requires a clock to work. The main clock is obtained from the pclk5 which is the APB5 Clock, but the interesting part is the pixel_clk signal.

That signal is the clock used by the LTDC to drive the actual LCD screen clock and from the timing table from the LCD dataheet (image below), the clock signal (DCLK Frequency in the table), the clock shall be set to 25 MHz.

LCD Timing characteristics
Figure: LCD Timing characteristics

In order to achieve the 25 MHz, we will generate the clock from ic16_ck since all the “normal” clocks in the N6 have values in the hundreds of MHz, but the ic16_ck has a divider to slow down the associated clock.

I wont go into much detail about clock configuration, I just leave the configuration required to work with the LTDC and generate a 25 MHz signal for the pixel_clk.

PLL Configuration

CubeMX PLL Configuration
Figure: CubeMX PLL Configuration

IC16 and LTDC Clocks

CubeMX IC16 Source Configuration
Figure: CubeMX IC16 Source Configuration
CubeMX LTDC Source Configuration
Figure: CubeMX LTDC Source Configuration

CPU, AHB and APB clock

CubeMX CPU, AHB and APB Source Configuration
Figure: CubeMX CPU, AHB and APB Source Configuration

CubeMX: RIF

We have finished configuring the LTDC peripheral and the required clocks. However, if you try to write something to the framebuffer of the LTDC you will find that only the background image is seen, and no data is updated. This is due to the RIMU box in the diagram below.

Display Subsystem diagram from the datasheet [2]
Figure: Display Subsystem diagram from the datasheet [2]

The RIMU(Resource Isolation Master Unit) box is part of the RIF (Resource Isolation Framework), a system that enforces and manage the isolation of STM32 resources and peripherals.

Since the LTDC has direct access to the framebuffer through the AXI bus, the LTDC has to read directly from the AXISRAM (in our example, you can change the location of the framebuffer), and there is RIMU box between the LTDC and the AXI bus, we need to configure the RIMU to allow the communication.

This is done by first enabling the RIF in CubeMX -> Security -> RIF and assign it to the Application.

CubeMX: RIF tab

Then move into the RIF Tab at the top of CubeMX. Here we have a Peripheral (RISUP) and Domain (RIMU) section. In the Peripherals, we have to add privilege to the LTDC_CMN and LTDC_L1 items and then in the Domain tab, check the Secure and Privilege boxes for the LTDC_L1.

The Source code

With all these steps, we are finally there and can generate our source code. There will be probably a warning with the “The following pins are free and must be assigned to a conetext: …”.

This message appear if CubeMX pre-configured all the pins used in the STM32N657-DK. Since we are focusing only in the LTDC, most of them are just left without assigning to any context (FSBL or Application).

Unassigned pin warning
Figure: Unassigned pin warning

The final change is adding our framebuffer to the source code and connect it to the LTDC Layer. The framebuffer is just an array of 480x800 pixels, since each pixel is 4 bytes-wide we can use the uint32_t type to store all the information of a pixel.

Keep in mind this is a demostration only, and only this buffer will fill 1560000 bytes of RAM, almost the whole AXISRAM! In future projects this framebuffer shall be moved to PSRAM or external RAM.

main.cpp
// ...
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// Create the image buffer and fill it with zeroes
uint32_t image[480 * 800] = {0};
/* USER CODE END 0 */
// ...

The next step is to assign the image buffer to our LTDC Layer. The LTDC register are shadowed, that means, that even you change the values, the actual used register values in the peripheral are not updated. In order to force the LTDC to get the register values and use them is by forcing a layer/global reload.

main.cpp
// ...
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOQ, GPIO_PIN_3, GPIO_PIN_SET); /* LCD On */
HAL_GPIO_WritePin(GPIOQ, GPIO_PIN_6, GPIO_PIN_SET); /* 100% Brightness */
HAL_LTDC_SetAddress(&hltdc, (uint32_t)image, LTDC_LAYER_1);
HAL_LTDC_ReloadLayer(&hltdc, LTDC_RELOAD_IMMEDIATE, LTDC_LAYER_1);
/* USER CODE END 2 */
// ...

And finally, modify the buffer in the main loop to see the screen changing.

main.cpp
// ...
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint32_t cnt = 0;
while (1) {
for (uint32_t i = cnt; i < cnt + 400; i++) {
uint8_t val = (uint8_t)(((i - cnt) * 255) / 399);
imagen[i] = 0xFFFF0000 | (val << 16) | (val << 8) | val;
}
cnt += 400;
cnt %= 480 * 800;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
// ...

And here you have the final result! the display is working and displaying a red gradient!

Working display with framebuffer from AXISRAM1/2
Figure: Working display with framebuffer from AXISRAM1/2

If you make it here, thank you very much! I hope you learn something new today! If you feel to contribute you can also buy me a coffee!

Dont forget to follow in my other social networks!

Loading comments...
Zoom overlay