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 🙂
2 Comments
Hi ,
where you have defined
uint32_t lcd_get_fb_address(void) ?
Hi,
it seems I forgot to add this function:
uint32_t lcd_get_fb_address(void)
{
return (uint32_t)&Frame_buffer;
}
This is the function.
Add Comment