In this new guide series, we shall see the advanced peripheral that being offered by ST. The first part is LTDC which stands for LCD-TFT Display Controller (LTDC).
In this guide, we shall cover the following:
- What is LTDC.
- Basic of Embedded Display.
- Environment Setup.
1. What is LTDC:
The evolution of the mobile, industrial, and consumer applications leads to a stronger need of graphical user interfaces (GUIs) and to an increase in the required hardware resources. These applications require higher quality graphics, more hardware and software resources (like memory for graphical primitives or framebuffer) and higher processing performances.
To respond to this increasing demand, microprocessor units are often used, which leads to a higher costs and to more complex designs with longer time to market. To face these requirements, the STM32 microcontrollers (MCUs) offer a large graphical portfolio.
Thanks to their embedded LCD-TFT display controller (LTDC), the STM32 MCUs allow high-resolution display panels to be directly driven, without any CPU intervention. In addition, the LTDC can access autonomously to internal memories or external memories to fetch pixel data.
Functional description
On every pixel clock-raising edge or clock-falling edge and within the screen active area, the
LTDC layer retrieves one pixel data from its FIFO, converts it to the internal ARGB8888 pixel
format and blends it with the background and/or with the other layer pixel color. The
resulting pixel, coded in the RGB888 format, goes through the dithering unit and is driven
into the RGB interface. The pixel is then displayed on the screen.
2. Basic of Embedded Display:
A basic embedded graphic system schematic is described in the figure below.
A basic embedded graphic system is composed of a microcontroller, a framebuffer, a display controller and a display glass.
• The MCU computes the image to be displayed in the framebuffer, assembling graphical primitives such as icons or images. The CPU performs this operation by running a graphical library software. This process can be accelerated by a dedicated hardware like the DMA2D Chrom-Art Accelerator, used by the graphical library. The more often the framebuffer is updated, the more fluent the animations are (animation fps).
• The framebuffer is a volatile memory used to store pixel data of the image to be displayed. This memory is usually called the graphic RAM (GRAM). The required size of the framebuffer depends on the resolution and color depth of the display.
• The display controller is continuously “refreshing” the display, transferring the framebuffer content to the display glass 60 times per second (60 Hz). The display controller can be embedded either in the display module or in the MCU.
• The display glass is driven by the display controller and is responsible to display the image that is composed of a matrix of pixels.
A display is characterized by:
– Display size (resolution): is defined by the number of pixels of the display that is represented by horizontal (pixels number) x vertical (lines number).
– Color depth: defines the number of colors in which a pixel can be drawn. It is represented in bits per pixel (bpp). For a color depth of 24 bpp (that can also be represented by RGB888) a pixel can be represented in 16777216 colors.
– Refresh rate (in Hz): is the number of times per second that the display panel is refreshed. A display must be refreshed 60 times per seconds (60 Hz) since lower refresh rate creates bad visual effects.
Display module categories
The display modules are classified in two main categories, depending on whether they embed or not an internal controller and a GRAM:
• The first category corresponds to the displays with an on-glass display controller and a GRAM (see the figure below).
• The second category corresponds to the displays with an on-glass display with no main controller and that have only a low-level timing controller.
For more information about LTDC and technology behind the embedded displays, please refer to this application note from ST.
3. Environment Setup:
Before we start adding the required header and source files, we need to setup the environment to work in bare metal by the following this guide here.
The modification is in defining type of STM32 which is:
STM32F429xx
Thats all for the project setup.
Now, create new source file with name of sys_init.c which will initialize the clock during the startup of the MCU.
For more details, please refer to this guide.
In the source file, include the main STM32F4 header file:
#include "stm32f4xx.h"
Declare a function named SystemInit and it contents as following:
void SystemInit (void) { #define PLL_M 4 #define PLL_N 180 #define PLL_P 2 #define PLL_Q 9 __IO uint32_t StartUpCounter = 0, HSEStatus = 0; RCC->CR |= ((uint32_t)RCC_CR_HSEON); do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != 3000)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } if (HSEStatus == (uint32_t)0x01) { RCC->APB1ENR |= RCC_APB1ENR_PWREN; PWR->CR &= (uint32_t)~(PWR_CR_VOS); RCC->CFGR |= RCC_CFGR_HPRE_DIV1; RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24); RCC->CR |= RCC_CR_PLLON; while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Configure Flash prefetch, Instruction cache, Data cache and wait state */ FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS; /* Select the main PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= RCC_CFGR_SW_PLL; /* Wait till the main PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL) {;} } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ } /*Enable FPU*/ SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /*Enable cell compensation*/ RCC->APB2ENR|=RCC_APB2ENR_SYSCFGEN ; SYSCFG->CMPCR|=(1<<0); while(!((SYSCFG->CMPCR)&(1<<8))){;} }
We shall add extra clock configurations later to deal with LTDC.
Now create pair delay.c and delay.h file.
In delay.h:
#ifndef DELAY_H_ #define DELAY_H_ #include "stdint.h" void delay_init(uint32_t freq); uint64_t millis(); void delay(uint32_t time); #endif /* DELAY_H_ */
delay.c:
#include "delay.h" #include "stm32f4xx.h" #define CTRL_ENABLE (1U<<0) #define CTRL_CLKSRC (1U<<2) #define CTRL_COUNTFLAG (1U<<16) #define CTRL_TICKINT (1U<<1) volatile uint64_t mil; void delay_init(uint32_t freq) { SysTick->LOAD = (freq/1000) - 1; /*Clear systick current value register */ SysTick->VAL = 0; /*Enable systick and select internal clk src*/ SysTick->CTRL = CTRL_ENABLE | CTRL_CLKSRC ; /*Enable systick interrupt*/ SysTick->CTRL |= CTRL_TICKINT; } uint64_t millis() { __disable_irq(); uint64_t ml=mil; __enable_irq(); return ml; } void delay(uint32_t time) { uint64_t start=millis(); while((millis() - start) < (time+1)); } void SysTick_Handler(void) { mil++; }
Since we are dealing with the embedded display on STM32F429 discovery, we need to initialize the LCD to work in parallel mode.
First, we need to the LCD itself using SPI command.
First, refer to these two guides:
- Working with STM32F429-discovery Display Part 1: Environment Setup
- Working with STM32F429-discovery Display Part 2: LCD Initializing
Create new source pair of source and header file with name of LCD_Pins.c and LCD_Pins.h.
Within the header file:
#ifndef LCD_PINS_H_ #define LCD_PINS_H_ #include "delay.h" #include "stm32f4xx.h" #define LCD_RES_HIGH(void) GPIOA->BSRR=GPIO_BSRR_BS7 #define LCD_RES_LOW(void) GPIOA->BSRR=GPIO_BSRR_BR7 #define LCD_CS_HIGH(void) GPIOC->BSRR=GPIO_BSRR_BS2 #define LCD_CS_LOW(void) GPIOC->BSRR=GPIO_BSRR_BR2 #define LCD_DC_HIGH(void) GPIOD->BSRR=GPIO_BSRR_BS13 #define LCD_DC_LOW(void) GPIOD->BSRR=GPIO_BSRR_BR13 /*SPI */ void LCD_Pin_Init(void); void LCD_SPI_Init(void); /*LCD*/ void LCD_RST(void); void LCD_Write_Cmd(uint8_t cmd); void LCD_Write_Data (uint8_t data); #endif /* LCD_PINS_H_ */
Within the source file:
#include "stm32f4xx.h" #include "LCD_Pins.h" #define AF05 0x05 void LCD_Pin_Init(void) { RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN|RCC_AHB1ENR_GPIOCEN|RCC_AHB1ENR_GPIODEN|RCC_AHB1ENR_GPIOFEN; /*PA7*/ GPIOA->MODER|=GPIO_MODER_MODE7_0; GPIOA->MODER&=~GPIO_MODER_MODE7_1; GPIOA->OSPEEDR|=GPIO_OSPEEDR_OSPEED7; GPIOA->ODR|=GPIO_ODR_OD7; /*PC2*/ GPIOC->MODER|=GPIO_MODER_MODE2_0; GPIOC->MODER&=~GPIO_MODER_MODE2_1; GPIOC->OSPEEDR|=GPIO_OSPEEDR_OSPEED2; GPIOC->ODR|=GPIO_ODR_OD2; /*PD13*/ GPIOD->MODER|=GPIO_MODER_MODE13_0; GPIOD->MODER&=~GPIO_MODER_MODE13_1; GPIOD->OSPEEDR|=GPIO_OSPEEDR_OSPEED13; GPIOD->ODR|=GPIO_ODR_OD13; /*PF7 and PF9 for SPI5*/ GPIOF->MODER|=GPIO_MODER_MODE7_1|GPIO_MODER_MODE9_1; GPIOF->MODER&=~(GPIO_MODER_MODE7_0|GPIO_MODER_MODE9_0); GPIOF->OSPEEDR|=GPIO_OSPEEDR_OSPEED7|GPIO_OSPEEDR_OSPEED9; GPIOF->AFR[0]|=(AF05<<GPIO_AFRL_AFSEL7_Pos); GPIOF->AFR[1]|=(AF05<<GPIO_AFRH_AFSEL9_Pos); } void LCD_SPI_Init(void) { RCC->APB2ENR|=RCC_APB2ENR_SPI5EN; SPI5->CR1|=SPI_CR1_MSTR|SPI_CR1_SSM|SPI_CR1_SSI|SPI_CR1_BR_2|SPI_CR1_BR_1|SPI_CR1_BR_0; SPI5->CR1|=SPI_CR1_SPE; } static void spi5_transmit(uint8_t data) { /*Wait until TXE is set*/ while(!(SPI5->SR & (SPI_SR_TXE))){} /*Write the data to the data register*/ SPI5->DR = data; /*Wait until TXE is set*/ while(!(SPI5->SR & (SPI_SR_TXE))){} /*Wait for BUSY flag to reset*/ while((SPI5->SR & (SPI_SR_BSY))){} /*Clear OVR flag*/ (void) SPI5->DR; (void) SPI5->SR; } void LCD_RST(void) { LCD_RES_LOW(); delay(50); LCD_RES_HIGH(); delay(20); } void LCD_Write_Cmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_LOW(); spi5_transmit(cmd); LCD_CS_HIGH(); } void LCD_Write_Data (uint8_t data) { LCD_CS_LOW(); LCD_DC_HIGH(); spi5_transmit (data); LCD_CS_HIGH(); }
Now, we create new pair of source and header file with name of ILI9341.c and ILI9341.h
Within the header file:
#ifndef ILI9341_H_ #define ILI9341_H_ //List of includes #include "delay.h" //LCD dimensions defines #define ILI9341_WIDTH 320 #define ILI9341_HEIGHT 240 #define ILI9341_PIXEL_COUNT ILI9341_WIDTH * ILI9341_HEIGHT #define ILI9341_HSYNC ((uint32_t)9) /* Horizontal synchronization */ #define ILI9341_HBP ((uint32_t)29) /* Horizontal back porch */ #define ILI9341_HFP ((uint32_t)2) /* Horizontal front porch */ #define ILI9341_VSYNC ((uint32_t)1) /* Vertical synchronization */ #define ILI9341_VBP ((uint32_t)3) /* Vertical back porch */ #define ILI9341_VFP ((uint32_t)2) /* Vertical front porch */ /** * @brief ILI9341 Registers */ /* Level 1 Commands */ /* Level 1 Commands */ #define ILI9341_SWRESET 0x01U /* Software Reset */ #define ILI9341_READ_DISPLAY_ID 0x04U /* Read display identification information */ #define ILI9341_RDDST 0x09U /* Read Display Status */ #define ILI9341_RDDPM 0x0AU /* Read Display Power Mode */ #define ILI9341_RDDMADCTL 0x0BU /* Read Display MADCTL */ #define ILI9341_RDDCOLMOD 0x0CU /* Read Display Pixel Format */ #define ILI9341_RDDIM 0x0DU /* Read Display Image Format */ #define ILI9341_RDDSM 0x0EU /* Read Display Signal Mode */ #define ILI9341_RDDSDR 0x0FU /* Read Display Self-Diagnostic Result */ #define ILI9341_SPLIN 0x10U /* Enter Sleep Mode */ #define ILI9341_SLEEP_OUT 0x11U /* Sleep out register */ #define ILI9341_PTLON 0x12U /* Partial Mode ON */ #define ILI9341_NORMAL_MODE_ON 0x13U /* Normal Display Mode ON */ #define ILI9341_DINVOFF 0x20U /* Display Inversion OFF */ #define ILI9341_DINVON 0x21U /* Display Inversion ON */ #define ILI9341_GAMMA 0x26U /* Gamma register */ #define ILI9341_DISPLAY_OFF 0x28U /* Display off register */ #define ILI9341_DISPLAY_ON 0x29U /* Display on register */ #define ILI9341_CASET 0x2AU /* Column address register */ #define ILI9341_RASET 0x2BU /* Page address register */ #define ILI9341_GRAM 0x2CU /* GRAM register */ #define ILI9341_RGBSET 0x2DU /* Color SET */ #define ILI9341_RAMRD 0x2EU /* Memory Read */ #define ILI9341_PLTAR 0x30U /* Partial Area */ #define ILI9341_VSCRDEF 0x33U /* Vertical Scrolling Definition */ #define ILI9341_TEOFF 0x34U /* Tearing Effect Line OFF */ #define ILI9341_TEON 0x35U /* Tearing Effect Line ON */ #define ILI9341_MAC 0x36U /* Memory Access Control register*/ #define ILI9341_VSCRSADD 0x37U /* Vertical Scrolling Start Address */ #define ILI9341_IDMOFF 0x38U /* Idle Mode OFF */ #define ILI9341_IDMON 0x39U /* Idle Mode ON */ #define ILI9341_PIXEL_FORMAT 0x3AU /* Pixel Format register */ #define ILI9341_WRITE_MEM_CONTINUE 0x3CU /* Write Memory Continue */ #define ILI9341_READ_MEM_CONTINUE 0x3EU /* Read Memory Continue */ #define ILI9341_SET_TEAR_SCANLINE 0x44U /* Set Tear Scanline */ #define ILI9341_GET_SCANLINE 0x45U /* Get Scanline */ #define ILI9341_WDB 0x51U /* Write Brightness Display register */ #define ILI9341_RDDISBV 0x52U /* Read Display Brightness */ #define ILI9341_WCD 0x53U /* Write Control Display register*/ #define ILI9341_RDCTRLD 0x54U /* Read CTRL Display */ #define ILI9341_WRCABC 0x55U /* Write Content Adaptive Brightness Control */ #define ILI9341_RDCABC 0x56U /* Read Content Adaptive Brightness Control */ #define ILI9341_WRITE_CABC 0x5EU /* Write CABC Minimum Brightness */ #define ILI9341_READ_CABC 0x5FU /* Read CABC Minimum Brightness */ #define ILI9341_READ_ID1 0xDAU /* Read ID1 */ #define ILI9341_READ_ID2 0xDBU /* Read ID2 */ #define ILI9341_READ_ID3 0xDCU /* Read ID3 */ /* Level 2 Commands */ #define ILI9341_RGB_INTERFACE 0xB0U /* RGB Interface Signal Control */ #define ILI9341_FRMCTR1 0xB1U /* Frame Rate Control (In Normal Mode) */ #define ILI9341_FRMCTR2 0xB2U /* Frame Rate Control (In Idle Mode) */ #define ILI9341_FRMCTR3 0xB3U /* Frame Rate Control (In Partial Mode) */ #define ILI9341_INVTR 0xB4U /* Display Inversion Control */ #define ILI9341_BPC 0xB5U /* Blanking Porch Control register */ #define ILI9341_DFC 0xB6U /* Display Function Control register */ #define ILI9341_ETMOD 0xB7U /* Entry Mode Set */ #define ILI9341_BACKLIGHT1 0xB8U /* Backlight Control 1 */ #define ILI9341_BACKLIGHT2 0xB9U /* Backlight Control 2 */ #define ILI9341_BACKLIGHT3 0xBAU /* Backlight Control 3 */ #define ILI9341_BACKLIGHT4 0xBBU /* Backlight Control 4 */ #define ILI9341_BACKLIGHT5 0xBCU /* Backlight Control 5 */ #define ILI9341_BACKLIGHT7 0xBEU /* Backlight Control 7 */ #define ILI9341_BACKLIGHT8 0xBFU /* Backlight Control 8 */ #define ILI9341_POWER1 0xC0U /* Power Control 1 register */ #define ILI9341_POWER2 0xC1U /* Power Control 2 register */ #define ILI9341_VCOM1 0xC5U /* VCOM Control 1 register */ #define ILI9341_VCOM2 0xC7U /* VCOM Control 2 register */ #define ILI9341_NVMWR 0xD0U /* NV Memory Write */ #define ILI9341_NVMPKEY 0xD1U /* NV Memory Protection Key */ #define ILI9341_RDNVM 0xD2U /* NV Memory Status Read */ #define ILI9341_READ_ID4 0xD3U /* Read ID4 */ #define ILI9341_PGAMMA 0xE0U /* Positive Gamma Correction register */ #define ILI9341_NGAMMA 0xE1U /* Negative Gamma Correction register */ #define ILI9341_DGAMCTRL1 0xE2U /* Digital Gamma Control 1 */ #define ILI9341_DGAMCTRL2 0xE3U /* Digital Gamma Control 2 */ #define ILI9341_INTERFACE 0xF6U /* Interface control register */ /* Extend register commands */ #define ILI9341_POWERA 0xCBU /* Power control A register */ #define ILI9341_POWERB 0xCFU /* Power control B register */ #define ILI9341_DTCA 0xE8U /* Driver timing control A */ #define ILI9341_DTCB 0xEAU /* Driver timing control B */ #define ILI9341_POWER_SEQ 0xEDU /* Power on sequence register */ #define ILI9341_3GAMMA_EN 0xF2U /* 3 Gamma enable register */ #define ILI9341_PRC 0xF7U /* Pump ratio control register */ #define MADCTL_MY 0x80 ///< Bottom to top #define MADCTL_MX 0x40 ///< Right to left #define MADCTL_MV 0x20 ///< Reverse Mode #define MADCTL_ML 0x10 ///< LCD refresh Bottom to top #define MADCTL_RGB 0x00 ///< Red-Green-Blue pixel order #define MADCTL_BGR 0x08 ///< Blue-Green-Red pixel order #define MADCTL_MH 0x04 ///< LCD refresh right to left /* Size of read registers */ #define LCD_READ_ID4_SIZE 3 /* Size of Read ID4 */ void ILI9341_Init(void); #endif
In the source file:
#include "ILI9341.h" #include "LCD_Pins.h" #include "stdlib.h" //initialize the tft void ILI9341_Init(void) { LCD_RST(); LCD_Write_Cmd(ILI9341_SWRESET); delay(10); LCD_Write_Cmd(ILI9341_POWERB); delay(10); LCD_Write_Data(0x00);; LCD_Write_Data(0xD9); LCD_Write_Data(0x30); LCD_Write_Cmd(ILI9341_POWER_SEQ); LCD_Write_Data(0x64); LCD_Write_Data(0x03); LCD_Write_Data(0X12); LCD_Write_Data(0X81); LCD_Write_Cmd(ILI9341_DTCA); LCD_Write_Data(0x85); LCD_Write_Data(0x10); LCD_Write_Data(0x7A); LCD_Write_Cmd(ILI9341_POWERA); LCD_Write_Data(0x39); LCD_Write_Data(0x2C); LCD_Write_Data(0x00); LCD_Write_Data(0x34); LCD_Write_Data(0x02); LCD_Write_Cmd(ILI9341_PRC); LCD_Write_Data(0x20); LCD_Write_Cmd(ILI9341_DTCB); LCD_Write_Data(0x00); LCD_Write_Data(0x00); LCD_Write_Cmd(ILI9341_POWER1); LCD_Write_Data(0x1B); LCD_Write_Cmd(ILI9341_POWER2); LCD_Write_Data(0x12); LCD_Write_Cmd(ILI9341_VCOM1); LCD_Write_Data(0x08); LCD_Write_Data(0x26); LCD_Write_Cmd(ILI9341_VCOM2); LCD_Write_Data(0XB7); LCD_Write_Cmd(ILI9341_PIXEL_FORMAT); LCD_Write_Data(0x55); //select RGB565 LCD_Write_Cmd(ILI9341_FRMCTR1); LCD_Write_Data(0x00); LCD_Write_Data(0x1B);//frame rate = 70 LCD_Write_Cmd(ILI9341_DFC); // Display Function Control LCD_Write_Data(0x0A); LCD_Write_Data(0xA2); LCD_Write_Cmd(ILI9341_3GAMMA_EN); // 3Gamma Function Disable LCD_Write_Data(0x02); LCD_Write_Cmd(ILI9341_GAMMA); LCD_Write_Data(0x01); LCD_Write_Cmd(ILI9341_PGAMMA); //Set Gamma LCD_Write_Data(0x0F); LCD_Write_Data(0x1D); LCD_Write_Data(0x1A); LCD_Write_Data(0x0A); LCD_Write_Data(0x0D); LCD_Write_Data(0x07); LCD_Write_Data(0x49); LCD_Write_Data(0X66); LCD_Write_Data(0x3B); LCD_Write_Data(0x07); LCD_Write_Data(0x11); LCD_Write_Data(0x01); LCD_Write_Data(0x09); LCD_Write_Data(0x05); LCD_Write_Data(0x04); LCD_Write_Cmd(ILI9341_NGAMMA); LCD_Write_Data(0x00); LCD_Write_Data(0x18); LCD_Write_Data(0x1D); LCD_Write_Data(0x02); LCD_Write_Data(0x0F); LCD_Write_Data(0x04); LCD_Write_Data(0x36); LCD_Write_Data(0x13); LCD_Write_Data(0x4C); LCD_Write_Data(0x07); LCD_Write_Data(0x13); LCD_Write_Data(0x0F); LCD_Write_Data(0x2E); LCD_Write_Data(0x2F); LCD_Write_Data(0x05); LCD_Write_Cmd(ILI9341_RGB_INTERFACE); LCD_Write_Data(0xC2); //Data is fetched during falling edge of DOTCLK LCD_Write_Cmd(ILI9341_INTERFACE); LCD_Write_Data(0x01); LCD_Write_Data(0x00); LCD_Write_Data(0x06); LCD_Write_Cmd(ILI9341_MAC); // Memory Access Control command LCD_Write_Data(MADCTL_BGR); LCD_Write_Cmd(ILI9341_SLEEP_OUT); //Exit Sleep delay(100); LCD_Write_Cmd(ILI9341_DISPLAY_ON); //display on delay(100); }
In part two, we shall initialize the LTDC and fill the screen with colors pixel by pix.
Stay tuned.
Happy coding 🙂
Add Comment