STM32 Advanced Peripherals : LTDC Part 2: Initialization and draw pixels

In the previous guide (here), we took a loot at what LTDC, different embedded graphics etc.

In this guide, we shall initialize the LTDC, layer and fill the screen with colors pixel by pixel.

In this guide, we shall cover the following:

  • SAI Configuration.
  • GPIO initialization.
  • LTDC Initialization.
  • Layer initialization.
  • Color header file
  • Main.c code.
  • Results.

1. SAI Configuration:

We start off by adding the following to the sys_init.c source file.

After the enabling cell compensation:

Enable over drive to reach the maximum 180MHz with stable operation.

To enable over driver, the following steps are required:

  • Enable over drive.
  • Wait for over drive to be enabled.
  • Enable over-drive switching.
  • Wait for the over-drive switching to be enabled.

	  /*Enable over drive*/

	  PWR->CR|=PWR_CR_ODEN;

	  while((PWR->CSR & PWR_CSR_ODRDY) ==PWR_CSR_ODRDY);

	  PWR->CR|=PWR_CR_ODSWEN;

	  while((PWR->CSR & PWR_CSR_ODSWRDY) ==PWR_CSR_ODSWRDY);

After the enabling the over-drive, we need to configure the SAI clock generation as following:

  • PLLSAI->N to be 50.
  • PLLSA->R to be 3.
  • PLLCDCLK over 2.

At full speed, you will get LCD frequency of 16.6MHz

	  
	  /*SAI clock configuration */
	  RCC->PLLSAICFGR|=(0x02U<<RCC_PLLSAICFGR_PLLSAIR_Pos);

	  RCC->PLLSAICFGR|=(50<<RCC_PLLSAICFGR_PLLSAIN_Pos);

	  RCC->DCKCFGR|=(0x03<<RCC_DCKCFGR_PLLSAIDIVR_Pos);

	  RCC->CR|=RCC_CR_PLLSAION;
	  while(!(RCC->CR & RCC_CR_PLLSAION));

Thats all for the clock configuration.

2. GPIO Initialization:

Before we initialize the GPIOs. we need to find which pins are connected to LCD.

We can find the pins from the schematic file of STM32F429-discovery board and they are the following:

Create new source file with name of LTDC.h and header file with name of LTDC.h.

Within the source file, include the following:

#include "stm32f4xx.h"
#include "LTDC.h"

Now declare the following function:

void LTDC_Pins_Init()

This function will initialize the pins according to the schematic.

Hence, the initialization of GPIO as following:

	#define LTDC_AF 14U
	/*Refer to bsp_lcd.h for the pins required by the LCD*/
	//enable the peripheral clock for GPIO ports involved in LTDC interface
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN;
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOBEN;
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOCEN;
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIODEN;
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOGEN;
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOFEN;

	/*
	 * Configure GPIOA pins related to LTDC
	 * PA4, PA3, PA6, PA11, PA12 */
	/*Set the GPIOs to Alternate function*/
	GPIOA->MODER|=GPIO_MODER_MODE3_1|GPIO_MODER_MODE4_1|GPIO_MODER_MODE6_1|
			GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1;

	GPIOA->MODER&=~(GPIO_MODER_MODE3_0|GPIO_MODER_MODE4_0|GPIO_MODER_MODE6_0|
				GPIO_MODER_MODE11_0|GPIO_MODER_MODE12_0);

	/*Set the output speed to max*/
	GPIOA->OSPEEDR|=GPIO_OSPEEDR_OSPEED3|GPIO_OSPEEDR_OSPEED4|GPIO_OSPEEDR_OSPEED6|
			GPIO_OSPEEDR_OSPEED11|GPIO_OSPEEDR_OSPEED12;

	/*Select which alternate function to use */
	GPIOA->AFR[0]|=(LTDC_AF<<GPIO_AFRL_AFSEL3_Pos)|(LTDC_AF<<GPIO_AFRL_AFSEL4_Pos)|
			(LTDC_AF<<GPIO_AFRL_AFSEL6_Pos);

	GPIOA->AFR[1]|=(LTDC_AF<<GPIO_AFRH_AFSEL11_Pos)|(LTDC_AF<<GPIO_AFRH_AFSEL12_Pos);

	/*
		 * Configure GPIOB pins related to LTDC
		 * PB8, PB9, PB10, PB11, PB0, PB1*/

	GPIOB->MODER|=GPIO_MODER_MODE0_1|GPIO_MODER_MODE1_1|GPIO_MODER_MODE8_1|GPIO_MODER_MODE9_1|
			GPIO_MODER_MODE10_1|GPIO_MODER_MODE11_1;

	GPIOB->MODER&=~(GPIO_MODER_MODE0_0|GPIO_MODER_MODE1_0|GPIO_MODER_MODE8_0|GPIO_MODER_MODE9_0|
				GPIO_MODER_MODE10_0|GPIO_MODER_MODE11_0);

	GPIOB->OSPEEDR|=GPIO_OSPEEDER_OSPEEDR0|GPIO_OSPEEDER_OSPEEDR1|GPIO_OSPEEDER_OSPEEDR8|
			GPIO_OSPEEDER_OSPEEDR9|GPIO_OSPEEDER_OSPEEDR10|GPIO_OSPEEDER_OSPEEDR11;

	GPIOB->AFR[0]|=(LTDC_AF<<GPIO_AFRL_AFSEL0_Pos)|(LTDC_AF<<GPIO_AFRL_AFSEL1_Pos);
	GPIOB->AFR[1]|=(LTDC_AF<<GPIO_AFRH_AFSEL8_Pos)|(LTDC_AF<<GPIO_AFRH_AFSEL9_Pos)|
			(LTDC_AF<<GPIO_AFRH_AFSEL10_Pos)|(LTDC_AF<<GPIO_AFRH_AFSEL11_Pos);


	/*
		 * Configure GPIOC pins related to LTDC
		 * PC6, PC7, PC10*/

	GPIOC->MODER|=GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1|GPIO_MODER_MODE10_1;
	GPIOC->MODER&=~(GPIO_MODER_MODE6_0|GPIO_MODER_MODE7_0|GPIO_MODER_MODE10_0);

	GPIOC->OSPEEDR|=GPIO_OSPEEDR_OSPEED6|GPIO_OSPEEDR_OSPEED7|GPIO_OSPEEDR_OSPEED10;

	GPIOC->AFR[0]|=(LTDC_AF<<GPIO_AFRL_AFSEL6_Pos)|(LTDC_AF<<GPIO_AFRL_AFSEL7_Pos);
	GPIOC->AFR[1]|=(LTDC_AF<<GPIO_AFRH_AFSEL10_Pos);

		/*
		 * Configure GPIOD pins related to LTDC
		 * PD6, PD3*/

	GPIOD->MODER|=GPIO_MODER_MODE6_1|GPIO_MODER_MODE3_1;
	GPIOD->MODER&=~(GPIO_MODER_MODE6_0|GPIO_MODER_MODE3_0);

	GPIOD->OSPEEDR|=GPIO_OSPEEDER_OSPEEDR3|GPIO_OSPEEDER_OSPEEDR6;

	GPIOD->AFR[0]|=(LTDC_AF<<GPIO_AFRL_AFSEL6_Pos)|(LTDC_AF<<GPIO_AFRL_AFSEL3_Pos);


	/*
	 * Configure GPIOG pins related to LTDC
	 * PG7, PG11, PG12, PG10, PG6*/

	GPIOG->MODER|=GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1|GPIO_MODER_MODE10_1|
			GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1;

	GPIOG->MODER&=~(GPIO_MODER_MODE6_0|GPIO_MODER_MODE7_0|GPIO_MODER_MODE10_0|
			GPIO_MODER_MODE11_0|GPIO_MODER_MODE12_0);

	GPIOG->OSPEEDR|=GPIO_OSPEEDER_OSPEEDR6|GPIO_OSPEEDER_OSPEEDR7|GPIO_OSPEEDER_OSPEEDR10|
			GPIO_OSPEEDER_OSPEEDR11|GPIO_OSPEEDER_OSPEEDR12;

	GPIOG->AFR[0]|=(LTDC_AF<<GPIO_AFRL_AFSEL6_Pos)|(LTDC_AF<<GPIO_AFRL_AFSEL7_Pos);
	GPIOG->AFR[1]|=(LTDC_AF<<GPIO_AFRH_AFSEL11_Pos)|(LTDC_AF<<GPIO_AFRH_AFSEL12_Pos)|
			(LTDC_AF<<GPIO_AFRH_AFSEL10_Pos);

	/*
	 * Configure GPIOF pin related to LTDC
	 * PF10*/

	GPIOF->MODER|=GPIO_MODER_MODE10_1;
	GPIOF->MODER&=~GPIO_MODER_MODE10_0;
	GPIOF->OSPEEDR|=GPIO_OSPEEDR_OSPEED10;

	GPIOF->AFR[1]|=(LTDC_AF<<GPIO_AFRH_AFSEL10_Pos);

}

Thats all for the GPIO Initialization.

3. LTDC initialization:

Now, we shall initialize the LTDC.

We start with the header.

Within the header file, include the header guard:

#ifndef LTDC_H_
#define LTDC_H_

#endif /* LTDC_H_ */

Within the guard, include the following:

#include "stdint.h"

declare the dimensions of the LCD:

#define BSP_LCD_WIDTH  					240
#define BSP_LCD_HEIGHT 					320

Declare active height and width:

#define  LCD_ACTIVE_WIDTH 			LCD_WIDTH
#define  LCD_ACTIVE_HEIGHT  		LCD_HEIGHT

Layer width and hight to take full screen:

#define LTDC_LAYER_WIDTH			LCD_WIDTH
#define LTDC_LAYER_HEIGHT			LCD_HEIGHT
#define LTDC_LAYER_H_START			0
#define LTDC_LAYER_H_STOP			LCD_ACTIVE_WIDTH
#define LTDC_LAYER_V_START			0
#define LTDC_LAYER_V_STOP			LCD_ACTIVE_HEIGHT

Since the LCD is RGB565, it will take 2byte (16-bit) for each pixel for color values.

#define LCD_PIXEL_FMT 				2

Declare the LCD specification:

#define LCD_HSW 					10
#define LCD_HBP						20
#define LCD_HFP						10
#define LCD_VSW						2
#define LCD_VBP						2
#define LCD_VFP						4

#define FB_WIDTH					LTDC_LAYER_WIDTH
#define FB_HEIGHT					LTDC_LAYER_HEIGHT

Declare the following functions:

void LTDC_Pins_Init();
void LTDC_Init();
void Layer_Init(LTDC_Layer_TypeDef *Layer);
void LCD_DrawPixel(uint16_t Xpos, uint16_t Ypos, uint16_t RGB_Code);

uint32_t lcd_get_fb_address(void);

Hence, the header file as following:

#ifndef LTDC_H_
#define LTDC_H_
#include "stdint.h"


#define LCD_WIDTH  					240
#define LCD_HEIGHT 					320




#define  LCD_ACTIVE_WIDTH 			LCD_WIDTH
#define  LCD_ACTIVE_HEIGHT  		LCD_HEIGHT

/*Set layer width and height */
#define LTDC_LAYER_WIDTH			LCD_WIDTH
#define LTDC_LAYER_HEIGHT			LCD_HEIGHT
#define LTDC_LAYER_H_START			0
#define LTDC_LAYER_H_STOP			LCD_ACTIVE_WIDTH
#define LTDC_LAYER_V_START			0
#define LTDC_LAYER_V_STOP			LCD_ACTIVE_HEIGHT


/*Select pixel format */

#define LCD_PIXEL_FMT 				2


#define LCD_HSW 					10
#define LCD_HBP						20
#define LCD_HFP						10
#define LCD_VSW						2
#define LCD_VBP						2
#define LCD_VFP						4

#define FB_WIDTH					LTDC_LAYER_WIDTH
#define FB_HEIGHT					LTDC_LAYER_HEIGHT

/*Functions*/

void LTDC_Pins_Init();
void LTDC_Init();
void Layer_Init(LTDC_Layer_TypeDef *Layer);
void LCD_DrawPixel(uint16_t Xpos, uint16_t Ypos, uint16_t RGB_Code);

uint32_t lcd_get_fb_address(void);

#endif /* LTDC_H_ */

Now, move the source file.

Declare the following function:

void LTDC_Init()

This will configure the LTDC and start sending clocks RGB data to the LCD automatically using the hardware level.

First, enable clock access to LTDC:

RCC->APB2ENR|=RCC_APB2ENR_LTDCEN;

Configure the horizontal width to be LCD_HSW-1:

LTDC->SSCR|=((LCD_HSW-1)<<LTDC_SSCR_HSW_Pos);

Configure the back porch to be LCD_HSW+LCD_HBP-1 :

LTDC->BPCR|=((LCD_HSW+LCD_HBP-1)<<LTDC_BPCR_AHBP_Pos);

Configure active width to be LCD_HSW+LCD_HBP+LCD_ACTIVE_WIDTH-1

LTDC->AWCR|=((LCD_HSW+LCD_HBP+LCD_ACTIVE_WIDTH-1)<<LTDC_AWCR_AAW_Pos);

Configure total width to be total_width = LCD_HSW+LCD_HBP+LCD_ACTIVE_WIDTH+LCD_HFP-1:

	uint32_t total_width = LCD_HSW+LCD_HBP+LCD_ACTIVE_WIDTH+LCD_HFP-1;

	LTDC->TWCR|=(total_width<<LTDC_TWCR_TOTALW_Pos);

Configure the VSYNC is similar to HSYNC as following:

	LTDC->SSCR|=((LCD_VSW-1)<<LTDC_SSCR_VSH_Pos);

	LTDC->BPCR|=((LCD_VSW+LCD_VBP-1)<<LTDC_BPCR_AVBP_Pos);

	LTDC->AWCR|=((LCD_VSW+LCD_VBP+LCD_ACTIVE_HEIGHT-1)<<LTDC_AWCR_AAH_Pos);

	uint32_t total_height = LCD_VSW+LCD_VBP+LCD_ACTIVE_HEIGHT+LCD_VFP-1;

	LTDC->TWCR|=(total_height<<LTDC_TWCR_TOTALH_Pos);

Now, configure the shadow register with enabling the vertical banking reload:

	/*Configure the shadow register*/
	/*Enable vertical banking reload*/
	LTDC->SRCR|=LTDC_SRCR_VBR;

Finally enable the LTDC:

LTDC->GCR|=LTDC_GCR_LTDCEN;

4. Layer Initialization:

We start off by the function:

void Layer_Init(LTDC_Layer_TypeDef *Layer)

It takes pointer to LTDC_Layer_TypeDef since we have two layers.

Disable the layer:

Layer->CR&=~LTDC_LxCR_LEN;

declare local variable:

uint32_t tmp;

Set color formate to be RGB565:

Layer->PFCR|=(0x02<<LTDC_LxPFCR_PF_Pos);

Configure alpha to be constant:

	//2. configure the constant alpha and blending factors
	Layer->CACR&=~(0xFFU<<LTDC_LxCACR_CONSTA_Pos);
	Layer->CACR=(0xFFU<<LTDC_LxCACR_CONSTA_Pos);

Set blending factor 1 to be 1-alpha and blending factor 2 to constant alpha:

	tmp = 0;
	tmp|=(0x4U<<LTDC_LxBFCR_BF1_Pos)|(0x5U<<LTDC_LxBFCR_BF2_Pos);

	Layer->BFCR=tmp;

Configure the layer position:

	uint32_t AHBP=(LTDC->BPCR >>LTDC_BPCR_AHBP_Pos)&0xFFFU;

	uint32_t WHSTART = AHBP+LTDC_LAYER_H_START +1;
	tmp=0;
	tmp|=WHSTART<<LTDC_LxWHPCR_WHSTPOS_Pos;

	uint32_t WHSTOP = AHBP+LTDC_LAYER_H_START+LTDC_LAYER_WIDTH+1;
	uint32_t AAW =   (LTDC->AWCR>>LTDC_AWCR_AAW_Pos)&0xFFFU;
	WHSTOP = (WHSTOP > AAW)?AAW:WHSTOP;
	tmp&=~(0xFFFU<<LTDC_LxWHPCR_WHSPPOS_Pos);
	tmp|=WHSTOP<<LTDC_LxWHPCR_WHSPPOS_Pos;

	Layer->WHPCR=tmp;

	tmp = 0;
	uint32_t AVBP = (LTDC->BPCR>>LTDC_BPCR_AVBP_Pos)&0x7FFU;
	uint32_t WVSTART = AVBP+LTDC_LAYER_V_START+1;
	tmp|=WVSTART<<LTDC_LxWVPCR_WVSTPOS_Pos;

	uint32_t AAH = (LTDC->AWCR>>LTDC_AWCR_AAH_Pos)&0x7FFU;
	uint32_t WVSTOP = AVBP+LTDC_LAYER_V_START+LTDC_LAYER_HEIGHT+1;
	WVSTOP = (WVSTOP > AAH)?AAH:WVSTOP;
	tmp&=~(0x7FFU<<LTDC_LxWVPCR_WVSPPOS_Pos);
	tmp|=WVSTOP<<LTDC_LxWVPCR_WVSPPOS_Pos;

	Layer->WVPCR=tmp;

Configure the frame buffer address:

Set a global variable as following:

#define FB_SIZE (FB_WIDTH * FB_HEIGHT)
uint16_t  Frame_buffer[FB_SIZE];
	//4. Configure Frame buffer address
	Layer->CFBAR=(uint32_t)lcd_get_fb_address();

Configure the pitch, line length and total buffer length:

	tmp = 0;

	uint32_t pitch =  LTDC_LAYER_WIDTH * 2;

	uint32_t line_len = pitch + 3;

	tmp|=pitch<<LTDC_LxCFBLR_CFBP_Pos;

	tmp&=~(0x1FFFU<<LTDC_LxCFBLR_CFBLL_Pos);

	tmp|=line_len<<LTDC_LxCFBLR_CFBLL_Pos;

	Layer->CFBLR|=tmp;

	Layer->CFBLNR&=~(LTDC_LxCFBLNR_CFBLNBR_Msk<<LTDC_LxCFBLNR_CFBLNBR_Pos);

	Layer->CFBLNR|=LTDC_LAYER_HEIGHT<<LTDC_LxCFBLNR_CFBLNBR_Pos;

Enable the layer:

	//8. Enable the layer
	Layer->CR|=LTDC_LxCR_LEN;

Finally the draw pixel function:

void LCD_DrawPixel(uint16_t Xpos, uint16_t Ypos, uint16_t RGB_Code)
{

	*(__IO uint16_t*) ( lcd_get_fb_address()+ 2*((Ypos*LTDC_LAYER_WIDTH + Xpos))) = (uint16_t)RGB_Code;

}

That all for the layer.

5. Color header file:

Create new header file with name of colors.h.

The content of the header file:

#ifndef COLORS_H_
#define COLORS_H_
#include "stdint.h"


#define VIOLET   	0x901A
#define INDIGO   	0x4810
#define BLUE   		0x001F
#define GREEN   	0x07E0
#define YELLOW   	0xFFE0
#define ORANGE   	0xFBE0
#define RED   		0xF800
#define WHITE   	0xFFFF
#define BLACK		0xFFFF


#endif /* COLORS_H_ */

6. Main.c:

#include "LCD_Pins.h"
#include "ILI9341.h"
#include "LTDC.h"
#include "colors.h"

uint16_t color_table[3]={VIOLET,GREEN,ORANGE};

int main()
{
	delay_init(180000000);
  	LCD_Pin_Init();
	LCD_SPI_Init();
	LTDC_Pins_Init();

    ILI9341_Init();
    LTDC_Init();
    Layer_Init(LTDC_Layer1);
  while(1)
	{

		for (int k=0;k<3;k++)
		{
			for (int i=0;i<240;i++)
			{
				for (int j=0;j<320;j++)
				{
					LCD_DrawPixel(i,j,color_table[k]);
				}
			}
			delay(1000);
		}
  
}

7. Results:

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *