Abstract. STM32. CMSIS. LTDC

This abstract (guide) is intended for people who want to get acquainted with the configuration of the LTDC module of STM microcontrollers using the example of STM32F429ZIT6 connected via a 16-bit RGB565 interface to a TM043NBH02 display with a resolution of 480×272 and using one layer without external memory for the video buffer.

Documentation used:
MCU Reference manual: RM0090;
MCU Datasheet: DocID024030 Rev 10;
Display Datasheet: DS TM043NBH02-40 Ver 1.0.

There are three steps to configuring the LTDC module to work with displays:

1. Clock configuration.

The LCD-TFT controller uses 3 clock domains:
– AHB clock domain (HCLK), used to quickly move data to the Layer FIFO and framebuffer configuration register.
– APB2 clock domain (PCLK2), used for the global configuration register and interrupt registers.
– Pixel Clock domain (LCD_CLK), used to generate LCD-TFT interface signals, generate pixel data and layer configuration. The LCD_CLK output must be configured according to the requirements of the panel being used. LCD_CLK is configured via PLLSAI.

Since setting the clocking of the AHB \ APB buses is commonplace, due to their use by all peripherals, we will describe only the Pixel Clock clocking configuration.
Consider the PLLSAI clock block:

The clocking of the LCD_CLK bus is configured through the PLLSAI block, the frequency to which comes from the clocking resonator (generator), in our case, from the HSI, through the divider M, after which it can be multiplied by an arbitrary factor N and brought to the required value by the dividers R and PLL_LCD_CLK_DIV.

For the TM043NBH02 display, you need to set the Pixel Clock clock frequency in the range from 8 to 12 MHz:

Thus, let’s move on to the clocking configuration. The inputs are as follows: the frequency of the built-in resonator HSI = 16 MHz, the divider M = 16, the frequency of 1 MHz is supplied to the VCO input, we select the frequency DCLK (Pixel Clock) = 9 MHz.

Let’s start from the end, select the divisor PLL_LCD_CLK_DIV. To do this, we climb into RM and look for the register responsible for this divisor. In our case, this is RCC_DCKCFG, in which we are interested in the PLLSAIDIVR bit field:

As you can see, the minimum divisor is 2, which is set by clearing the bit field, and we select it:

RCC->DCKCFGR &= ~RCC_DCLCFGR_PLLSAIDIVR;

We get a frequency of 18 MHz at the input of the divider DIV. Next, we determine what needs to be applied to the divisor R. We look at the RCC_PLLSAICFGR register:

It can be seen from the documentation that you cannot set the divisor R less than 2, “wrong configuration”, so we choose any other value, except for “0” and “1”. However, as can be seen from the description of the factor N, it cannot be less than 50. The minimum value of the divisor R that satisfies our conditions can be 3:

RCC->PLLSAICFGR |= 0x3 << RCC_PLLSAICFGR_PLLSAIR_Pos;

The frequency value at the input of the divider R must be 18 MHz * 3 = 54 MHz, so the multiplier N must be set to a value equal to 54 in the same register:

RCC->PLLSAICFGR |= 0x54 << RCC_PLLSAICFGR_PLLSAIN_Pos;

The only thing left is to allow LCD_CLK clocking and the operation of the PLLSAI block, which we will now check, for this we need the RCC-> APB2ENR registers, where we set the LTDCEN bit and the RCC-> CR register, set the PLLSAION bit in it:

RCC->APB2ENR |= RCC_APB2ENR_LTDCEN;
RCC->CR |= RCC_CR_PLLSAION;

After setting the RCC_CR_PLLSAION bit, you must wait for the RCC_CR_PLLSAIRDY bit to be set to indicate readiness:

while(!(RCC->CR & RCC_CR_PLLSAIRDY)){ 
		__NOP();
};
Source code for the clock configuration step
__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_CLOCKS_Init(void){
    
    RCC->DCKCFGR &= ~RCC_DCLCFGR_PLLSAIDIVR;
    RCC->PLLSAICFGR |= 0x3 << RCC_PLLSAICFGR_PLLSAIR_Pos;
    RCC->PLLSAICFGR |= 0x54 << RCC_PLLSAICFGR_PLLSAIN_Pos;
    //VCOxN = HSIclk / PLLM * PLLN = 16 / 16 * 54 = 54 MHz
	//PLLLCD = VCOxN / PLLR = 54 / 3 = 18 MHz
	//LCDCLK = PLLLCD / PLLSAIDIVR = 18 / 2 = 9 MHz

	RCC->APB2ENR |= RCC_APB2ENR_LTDCEN;
    RCC->CR |= RCC_CR_PLLSAION;
    while(!(RCC->CR & RCC_CR_PLLSAIRDY)){ 
    		__NOP();
    };
}

2. Configuration of I/O ports.

The LTDC interface uses the following signal lines:

The LTDC controller outputs must be configured by the user. Unused pins can be used for any other purpose.

For LTDC outputs in up to 24-bit (RGB888) mode, if less than 8 bits per pixel is used for the output (e.g. RGB565 or RGB666 for interface with 16-bit or 18-bit displays), then the display’s RGB data signals must be connected to the MSB bits LCD controller RGB data. For example, in our case, the LTDC controller is connected to the display via a 16-bit RGB565 interface, LCD R data signals[4:0]G[5:0] and B[4:0] must be connected to the LTDC controller via LCD_R[7:3]LCD_G[7:2] and LCD_B[7:3].

In order to find out which pins need to be used to work with the LTDC module, you should carefully consider the table of alternative functions from the Datasheet:

In the context of the STM32F429 microcontroller, the LTDC module refers to the alternative functions AF9 and AF14. We will use the following conclusions:

Conclusion

Function

Selecting an alternative function

PA3

LCD_B5

AF14

PA4

LCD_VSYNC

AF14

PA6

LCD_G2

AF14

PA11

LCD_R4

AF14

PA12

LCD_R5

AF14

PB0

LCD_R3

AF9

PB1

LCD_R6

AF9

PB8

LCD_B6

AF14

PB9

LCD_B7

AF14

PB10

LCD_G4

AF14

PB11

LCD_G5

AF14

PC6

LCD_HSYNC

AF14

PC7

LCD_G6

AF14

PD3

LCD_G7

AF14

PF10

LCD_DE

AF14

PG6

LCD_R7

AF14

PG7

LCD_DTCLK

AF14

PG10

LCD_G3

AF9

PG11

LCD_B3

AF14

PG12

LCD_B4

AF9

As an example, I will give the configuration of one output of PF10, since all the others will be configured by analogy.

Configuration algorithm:
– turn on the clocking of the I / O port in the RCC_AHB1ENR register;

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN;

– set the output operation mode in the GPIOx_MODER register (Alternative function);

GPIOF->MODER |= GPIO_MODER_MODE10_1;

– set the output type to GPIOx_OTYPER (Output push-pull);

GPIOF->OTYPER &=  GPIO_OTYPER_OT10;

– select the pull-up of the GPIO_PUPDR output (No pull-up / pull-down);

GPIOF->PUPDR &= GPIO_PUPDR_PUPDR10;

– select the type of alternative function in GPIOx_AFRH (AF14);

GPIOF->AFR[1] |= GPIO_AFRH_AFSEL10_3 | GPIO_AFRH_AFSEL10_2 | GPIO_AFRH_AFSEL10_1;

– set the speed of the output in GPIOx_OSPEEDR (Very high speed).

GPIOF->OSPEEDR |= GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0;
Source code for the I/O port configuration step
__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_GPIO_Init(void){


	//Backlight
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); // PORT D CLOCKS are ENABLED
	MODIFY_REG(GPIOD->MODER, GPIO_MODER_MODER12, GPIO_MODER_MODE12_0); // PD12 output

	/*	PB0 R3
		PB1 R6
		PB10 G4
		PB11 G5
		PB8 B6
		PB9 B7*/
	//---PORT---B---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN); // PORT B CLOCKS are ENABLED

	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER0, GPIO_MODER_MODE0_1); // PB0 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER1, GPIO_MODER_MODE1_1); // PB1 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER8, GPIO_MODER_MODE8_1); // PB8 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER9, GPIO_MODER_MODE9_1); // PB9 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER10, GPIO_MODER_MODE10_1); // PB10 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER11, GPIO_MODER_MODE11_1); // PB11 Alternative func

	CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT0 | GPIO_OTYPER_OT1 | GPIO_OTYPER_OT8 | GPIO_OTYPER_OT9 | GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11);
	// PB0 PB1 PB8 PB9 PB10 PB11 Output push-pull

	MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFRL0, GPIO_AFRL_AFRL0_3 | GPIO_AFRL_AFRL0_0); // PB0 AF9
	MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFRL1, GPIO_AFRL_AFRL1_3 | GPIO_AFRL_AFRL1_0); // PB1 AF9
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8, GPIO_AFRH_AFSEL8_3 | GPIO_AFRH_AFSEL8_2 | GPIO_AFRH_AFSEL8_1); // PB8 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL9, GPIO_AFRH_AFSEL9_3 | GPIO_AFRH_AFSEL9_2 | GPIO_AFRH_AFSEL9_1); // PB9 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 | GPIO_AFRH_AFSEL10_2 | GPIO_AFRH_AFSEL10_1); // PB10 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PB11 AF14

	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED0_Msk, GPIO_OSPEEDR_OSPEED0_1 |  GPIO_OSPEEDR_OSPEED0_0); //PB0 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED1_Msk, GPIO_OSPEEDR_OSPEED1_1 | GPIO_OSPEEDR_OSPEED1_0); //PB1 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED8_Msk, GPIO_OSPEEDR_OSPEED8_1 | GPIO_OSPEEDR_OSPEED8_0); //PB8 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED9_Msk, GPIO_OSPEEDR_OSPEED9_1 | GPIO_OSPEEDR_OSPEED9_0); //PB9 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 | GPIO_OSPEEDR_OSPEED10_0); //PB10 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 | GPIO_OSPEEDR_OSPEED11_0); //PB11 Very high speed

	CLEAR_BIT(GPIOB->PUPDR, GPIO_PUPDR_PUPDR0 | GPIO_PUPDR_PUPDR1 | GPIO_PUPDR_PUPDR8 | GPIO_PUPDR_PUPDR9 | GPIO_PUPDR_PUPDR10 | GPIO_PUPDR_PUPDR11);
	// PB0 PB1 PB8 PB9 PB10 PB11 No pull-up/pull-down

	/*	PC6 HSYNC
		PC7 G6*/
	//---PORT---C---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN); // PORT B CLOCKS are ENABLED

	MODIFY_REG(GPIOC->MODER, GPIO_MODER_MODER6, GPIO_MODER_MODE6_1); // PC6 Alternative func
	MODIFY_REG(GPIOC->MODER, GPIO_MODER_MODER7, GPIO_MODER_MODE7_1); // PC7 Alternative func

	CLEAR_BIT(GPIOC->OTYPER, GPIO_OTYPER_OT6 | GPIO_OTYPER_OT7);
	// PC6 PC7 Output push-pull

	MODIFY_REG(GPIOC->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PC6 AF14
	MODIFY_REG(GPIOC->AFR[0], GPIO_AFRL_AFRL7, GPIO_AFRL_AFRL7_3 | GPIO_AFRL_AFRL7_2 | GPIO_AFRL_AFRL7_1); // PC7 AF14

	MODIFY_REG(GPIOC->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PC6 Very high speed
	MODIFY_REG(GPIOC->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, GPIO_OSPEEDR_OSPEED7_1 | GPIO_OSPEEDR_OSPEED7_0); //PC7 Very high speed

	CLEAR_BIT(GPIOC->PUPDR, GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7);
	// PC6 PC7 No pull-up/pull-down


	/*	PD3 G7*/
	//---PORT---D---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); // PORT D CLOCKS are ENABLED

	MODIFY_REG(GPIOD->MODER, GPIO_MODER_MODER3, GPIO_MODER_MODE3_1); // PD3 Alternative func

	CLEAR_BIT(GPIOD->OTYPER, GPIO_OTYPER_OT3);
	// PD3 Output push-pull

	MODIFY_REG(GPIOD->AFR[0], GPIO_AFRL_AFRL3, GPIO_AFRL_AFRL3_3 | GPIO_AFRL_AFRL3_2 | GPIO_AFRL_AFRL3_1); // PD3 AF14
	MODIFY_REG(GPIOD->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, GPIO_OSPEEDR_OSPEED3_1 |  GPIO_OSPEEDR_OSPEED3_0); //PD3 Very high speed
	CLEAR_BIT(GPIOD->PUPDR, GPIO_PUPDR_PUPDR3);  // PD3 No pull-up/pull-down


/*	PA11 R4
	PA12 R5
	PA6  G2
	PA3 B5
	PA4 VSYNC*/

	//---PORT---A---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN); // PORT A CLOCKS are ENABLED

	// PA3 PA4 PA6 PA11 PA12 Alternative func
	MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODER3 | GPIO_MODER_MODER4 | GPIO_MODER_MODER6, GPIO_MODER_MODE6_1 | GPIO_MODER_MODE4_1 | GPIO_MODER_MODE3_1);
	MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODER11 | GPIO_MODER_MODER12, GPIO_MODER_MODE11_1 | GPIO_MODER_MODE12_1);

	CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT12 | GPIO_OTYPER_OT11 | GPIO_OTYPER_OT6 | GPIO_OTYPER_OT4 | GPIO_OTYPER_OT3);
	// PCD Output push-pull

	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL3, GPIO_AFRL_AFRL3_3 | GPIO_AFRL_AFRL3_2 | GPIO_AFRL_AFRL3_1); // PA3 AF14
	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL4, GPIO_AFRL_AFRL4_3 | GPIO_AFRL_AFRL4_2 | GPIO_AFRL_AFRL4_1); // PA4 AF14
	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PA6 AF14
	MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PA11 AF14
	MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL12, GPIO_AFRH_AFSEL12_3 | GPIO_AFRH_AFSEL12_2 | GPIO_AFRH_AFSEL12_1); // PA12 AF14

	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, GPIO_OSPEEDR_OSPEED3_1 |  GPIO_OSPEEDR_OSPEED3_0); //PA3 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, GPIO_OSPEEDR_OSPEED4_1 |  GPIO_OSPEEDR_OSPEED4_0); //PA4 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PA6 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 |  GPIO_OSPEEDR_OSPEED11_0); //PA11 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, GPIO_OSPEEDR_OSPEED12_1 |  GPIO_OSPEEDR_OSPEED12_0); //PA12 Very high speed

	// PA3 PA4 PA6 PA11 PA12 No pull-up/pull-down
	CLEAR_BIT(GPIOA->PUPDR, GPIO_PUPDR_PUPDR3 | GPIO_PUPDR_PUPDR4 | GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR11 | GPIO_PUPDR_PUPDR12);

/*	PF10 DE*/
	//---PORT---F---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN); // PORT F CLOCKS are ENABLED

	MODIFY_REG(GPIOF->MODER, GPIO_MODER_MODER10, GPIO_MODER_MODE10_1); // PF10 Alternative func

	CLEAR_BIT(GPIOF->OTYPER, GPIO_OTYPER_OT10);
	// PF10 Output push-pull

	MODIFY_REG(GPIOF->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 | GPIO_AFRH_AFSEL10_2 | GPIO_AFRH_AFSEL10_1); // PF10 AF14
	MODIFY_REG(GPIOF->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0); //PF10 Very high speed
	CLEAR_BIT(GPIOF->PUPDR, GPIO_PUPDR_PUPDR10);  // PF10 No pull-up/pull-down

/*	PG7 DOTCLK
	PG6 R7
	PG10 G3
	PG11 B3
	PG12 B4*/
	//---PORT---G---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOGEN); // PORT G CLOCKS are ENABLED

	// PG6 PG7 PG10 PG11 PG12 Alternative func
	MODIFY_REG(GPIOG->MODER, GPIO_MODER_MODER6 | GPIO_MODER_MODER7 , GPIO_MODER_MODE6_1 | GPIO_MODER_MODE7_1);
	MODIFY_REG(GPIOG->MODER, GPIO_MODER_MODER10 | GPIO_MODER_MODER11 | GPIO_MODER_MODER12, GPIO_MODER_MODE10_1 | GPIO_MODER_MODE11_1 | GPIO_MODER_MODE12_1);

	CLEAR_BIT(GPIOG->OTYPER, GPIO_OTYPER_OT12 | GPIO_OTYPER_OT11 | GPIO_OTYPER_OT10 | GPIO_OTYPER_OT7 | GPIO_OTYPER_OT6);
	// PG6 PG7 PG10 PG11 PG12 Output push-pull

	MODIFY_REG(GPIOG->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PG6 AF14
	MODIFY_REG(GPIOG->AFR[0], GPIO_AFRL_AFRL7, GPIO_AFRL_AFRL7_3 | GPIO_AFRL_AFRL7_2 | GPIO_AFRL_AFRL7_1); // PG7 AF14
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 |  GPIO_AFRH_AFSEL10_0); // PG10 AF9
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PG11 AF14
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL12, GPIO_AFRH_AFSEL12_3 |  GPIO_AFRH_AFSEL12_0); // PG12 AF9

	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PG6 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, GPIO_OSPEEDR_OSPEED7_1 |  GPIO_OSPEEDR_OSPEED7_0); //PG7 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0); //PG10 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 |  GPIO_OSPEEDR_OSPEED11_0); //PG11 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, GPIO_OSPEEDR_OSPEED12_1 |  GPIO_OSPEEDR_OSPEED12_0); //PG12 Very high speed

	CLEAR_BIT(GPIOG->PUPDR, GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7 | GPIO_PUPDR_PUPDR10 | GPIO_PUPDR_PUPDR11 | GPIO_PUPDR_PUPDR12);  //  No pull-up/pull-down
}

3. Configuration of module registers.

A) Configuration of time indicators. We configure the duration of horizontal (HSYNC) and vertical synchronization (VSYNC), the width of the forward and reverse strokes (front / back porch), the active display area (active display area).

Vertical Sync (HSYNC) is used to reset the display driver pointer to the top left corner.
Horizontal sync (VSYNC) is designed to move to a new line.
The back porch width is the time between the end of the clock pulse and the start of valid data.
Forward width (fronf porch) is the time between the end of valid data and the start of the clock pulse.
The active display area is the data display area.

Consider display timing. For the display used, the table and timing diagram is shown below:

In the context of the timing diagram and timing table, we are interested in the times Tvw (Vertical Sync Width) and Thw (Horizontal Sync Width). It can be seen from the timing diagram that the minimum width of Tvw is 2 HSYNC clocks, while from the table the minimum width of Thw is 2 clocks of DCLK (Pixel Clock). You can also use the minimum timings to set up synchronization, however, we will use the recommended values ​​(TYP) from the table. It is worth noting that the number of clock cycles must be less than the number of back porch width cycles.

From the table, select the value of back and front porch for HSYNC and VSYNC. We also select from the values ​​in the TYP column. We look at the comments and make sure that the sum of the back and front porch values ​​for HSYNC and VSYNC is 20 and 51 units, respectively:

Tvbp + Tvfp = 12 + 8 = 20Thbp + Tbfp = 43 + 8 = 51

The active display area is 480×272 pixels.

Let’s determine the levels of active signals.

With vertical sync, the level drops, Active Low:

With horizontal the same situation, Active Low:

During data recording (data enable), the signal rises, Active High:

Recording data about the color of a particular pixel, in order to avoid a race of fronts, we will carry out on a falling edge (on the transition from “1” to “0”) DCLK (Pixel clocks):

Write the received data to the registers. We set the synchronization values ​​​​and subtract one from it. Vertical and horizontal synchronization correspond to their own bit fields in the LTDC_SSCR register (the same approach will be for other module settings):

Defines
#define ACTIVE_LOW 0
#define ACTIVE_HIGH 1
#define NORMAL_INPUT 0
#define INVERTED_INPUT 1

#define V_SYNC_WIDTH	4
#define V_SYNC_POL ACTIVE_LOW
#define V_BACK_PORCH	12
#define V_FRONT_PORCH	8
#define DISP_ACTIVE_HEIGHT	272

#define H_SYNC_WIDTH	4
#define H_SYNC_POL	ACTIVE_LOW
#define H_BACK_PORCH	43
#define H_FRONT_PORCH	8
#define DISP_ACTIVE_WIDTH	480

#define DE_POL ACTIVE_HIGH
#define PIXEL_CLOCK_POL INVERTED_INPUT

#define PAINTING_ZONE_H_SIZE 100
#define PAINTING_ZONE_V_SIZE 100
#define PAINTING_ZONE_H_OFFSET 190
#define PAINTING_ZONE_V_OFFSET 86

#define DISPLAY_PIXEL_FORMAT 0x02

#define MEMORY_SIZE (PAINTING_ZONE_H_SIZE * PAINTING_ZONE_V_SIZE)
LTDC->SSCR = ((H_SYNC_WIDTH - 1) << LTDC_SSCR_HSW_Pos) | (V_SYNC_WIDTH - 1);

A slightly interesting and strange approach is to write the back porch value to the LTDC_BPCR register. To do this, you need to determine the sum of the synchronization and back porch values ​​and write them to the register, while not forgetting to subtract 1:

LTDC->BPCR = ((H_BACK_PORCH - 1) << LTDC_BPCR_AHBP_Pos) | (V_BACK_PORCH - 1);

Similarly, for the designation of the active area of ​​the display. It is necessary to write down the sum of the synchronization values, back porch and add the dimensions of the active area to them in the LTDC_AWCR register:

LTDC->AWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH - 1) 
              << LTDC_AWCR_AAW_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT - 1);

The LTDC_TWCR register indicates the full width of the display dimension, taking into account all the previous parameters, where, among other things, the front porch is indicated. Again, we determine the amount of synchronization, back porch, active area and front porch, do not forget to subtract one:

LTDC->TWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH + H_FRONT_PORCH - 1) << LTDC_TWCR_TOTALW_Pos) 
             | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT + V_FRONT_PORCH - 1);

Set the polarity of the control signals HSYNC, VSYNC and DE. As we previously learned from the documentation for the display, the polarity of the signals is as follows:

HSYNC – Active Low
VSYNC – Active High
DE – Active Low;
PCP (Pixel Clock Polarity) – inverted input.

Write these values ​​to the LTDC_GCR register:

LTDC->GCR = LTDC_GCR_DEPOL | LTDC_GCR_PCPOL;

At the very end, we will also use this register to enable the module by setting the LTDCEN bit.

B) Layer configuration. In this example, we will analyze the system configuration with one (first) data display layer.

Let’s adjust the size and location of the first layer on the display. Let’s define the height and length of the rendered area as 100×100 in the middle of the display. To do this, in the LTDC_LxWHPCR register of the horizontal position, we indicate the start and stop pixels on the display:

When determining the position of the displayed area in the active area of ​​the display, you should also take into account the width of the back porch:

LTDC_Layer1->WHPCR = ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET) ) 
                      | ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET + PAINTING_ZONE_H_SIZE - 1) << LTDC_LxWHPCR_WHSPPOS_Pos);

PS It’s worth noting that there is a bug in RM, which is taken into account in HAL, but has not been fixed in the documentation. The error lies in the fact that in the example, the authors write the value of the end position into the bit field “WSHPPOS” without taking into account the “zero”/start position index, because if you simply add the width of the active area to the value of the start position, then the width of the displayed area will be 1 more. As a result, the LTDC module selects data from the video buffer incorrectly and the image will be displayed at an angle. Therefore, one should be subtracted when writing the end position.

Same approach for vertical layout:

LTDC_Layer1->WVPCR = ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET) ) 
                      | ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET + PAINTING_ZONE_V_SIZE - 1) << LTDC_LxWVPCR_WVSPPOS_Pos);

Let’s choose the input format pixel (Pixel Format), or otherwise the color palette in which we will work. The LTDC_LxPFCR register is used for this.

Since only 1 layer will be used and this is a test project, there is no need to use transparency and high color depth. Therefore, let’s agree that we will use RGB565 (which, in principle, is enough for writing most projects / yes, and such a wiring was made on the board):

LTDC_Layer1->PFCR = DISPLAY_PIXEL_FORMAT;

Next, you need to determine the location of the rendered frame in memory. It can be both internal RAM and external SDRAM, and even FLASH. First, let’s define how much memory our buffer should have:

PAINTING_ZONE_H_SIZE * PAINTING_ZONE_V_SIZE * PIXEL_FORMAT = MEMORY_SIZE

In our case:

100 pixels * 100 pixels * 2 bytes = 20000 bytes

Since our controller has enough RAM memory for such a video buffer, we specify it as a frame storage in the LTDC_LxCFBAR register:

LTDC_Layer1->CFBAR = &frameBuffer;

Further, in the LTDC_LxCFBLR register, we indicate to the module how many pixels (in bytes) of one line should be drawn (Color Frame Buffer Pitch in bytes) and what is the actual length of the frame line (in bytes):

LTDC_Layer1->CFBLR = ((PAINTING_ZONE_H_SIZE * 2) << LTDC_LxCFBLR_CFBLL_Pos) | (PAINTING_ZONE_H_SIZE * 2 + 3);

It is worth noting that a magic constant must be added to the length of the frame line, in our case 3. For other controllers, this constant may differ, for example, for the H7 series of controllers it is 7. Why this happens is not described in the documentation.

Specify, in the register LTDC_LxCFBLNR, modulo the number of lines in the frame:

LTDC_Layer1->CFBLNR = PAINTING_ZONE_V_SIZE;

C) The last point of initialization is to enable the used layer, reload the data from the shadow registers and enable the LTDC module itself.

First, let’s enable the layer by setting the LEN bit in the LTDC_LxCR register:

LTDC_Layer1->CR = LTDC_LxCR_LEN;

Let’s reload data from shadow registers to active LTDC_SRCR. Reloading can be done in two ways: during vertical synchronization and immediately. For initial initialization, an immediate reload of active registers is also suitable. But when changing the parameters of the LTDC module during operation, you should use the vertical synchronization method to avoid drawing with “artifacts”.

And the last thing in the initialization is the permission to work with the LTDC module, in the previously used LTDC_GCR register by setting the LTDCEN bit:

LTDC->GCR |= LTDC_GCR_LTDCEN;

PS do not forget to also turn on the backlight, without it nothing will be visible.

If you did everything correctly, then you will get an image similar to the following:

A chaotic set of colors in an initially uninitialized video buffer.

And so if you start writing something to the buffer:

Source code of the LTDC module initialization step
__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_MODULE_Init(void){

	/*The First step:
		configure HSYNC, VSYNC, V&H back porch
		active data area & front porch timings
		following the panel datasheet*/

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

	LTDC->BPCR = ((H_SYNC_WIDTH + H_BACK_PORCH - 1) << LTDC_BPCR_AHBP_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH - 1);
	LTDC->AWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH - 1) << LTDC_AWCR_AAW_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT - 1);
	LTDC->TWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH + H_FRONT_PORCH - 1) << LTDC_TWCR_TOTALW_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT + V_FRONT_PORCH - 1);

	LTDC->GCR = LTDC_GCR_DEPOL | LTDC_GCR_PCPOL;

	/*The Second step: 
		Layer Configuration*/
	LTDC_Layer1->WHPCR = ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET) ) | ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET + PAINTING_ZONE_H_SIZE - 1) << LTDC_LxWHPCR_WHSPPOS_Pos);
	LTDC_Layer1->WVPCR = ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET) ) | ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET + PAINTING_ZONE_V_SIZE - 1) << LTDC_LxWVPCR_WVSPPOS_Pos);
	LTDC_Layer1->PFCR = DISPLAY_PIXEL_FORMAT;
	LTDC_Layer1->CFBAR = (uint32_t)&frameBuffer;

	//LTDC_Layer1->BFCR = (0x4 << LTDC_LxBFCR_BF1_Pos) | 0x5;

	LTDC_Layer1->CFBLR = ((PAINTING_ZONE_H_SIZE * 2) << LTDC_LxCFBLR_CFBP_Pos) | (PAINTING_ZONE_H_SIZE * 2 + 3);
	LTDC_Layer1->CFBLNR = PAINTING_ZONE_V_SIZE;

	/*The Third step: 
		Clocks and module enabling*/
	BL_MCU_LTDC_LAYER_Enable();

	LTDC->SRCR = LTDC_SRCR_IMR;

	BL_MCU_LTDC_Enable();

	BL_MCU_LTDC_BACKLIGNHT_Enable();
}
All source code
/*
 *      Author: Zmitrovich Stanislau
 */


//INCLUDES
#include "../MCUs_MainFunctions.h"
#include "../../bl_can.h"
#include "../inc/drawString.h"

#if (BL_USED_MCU == BL_STM_32_F429)
#include "mcu_stm32f429.h"
//DEFINES
#define ACTIVE_LOW 0
#define ACTIVE_HIGH 1
#define NORMAL_INPUT 0
#define INVERTED_INPUT 1

#define V_SYNC_WIDTH	4
#define V_SYNC_POL ACTIVE_LOW
#define V_BACK_PORCH	12
#define V_FRONT_PORCH	8
#define DISP_ACTIVE_HEIGHT	272

#define H_SYNC_WIDTH	4
#define H_SYNC_POL	ACTIVE_LOW
#define H_BACK_PORCH	43
#define H_FRONT_PORCH	8
#define DISP_ACTIVE_WIDTH	480

#define DE_POL ACTIVE_HIGH
#define PIXEL_CLOCK_POL INVERTED_INPUT

#define PAINTING_ZONE_H_SIZE 100
#define PAINTING_ZONE_V_SIZE 100
#define PAINTING_ZONE_H_OFFSET 190
#define PAINTING_ZONE_V_OFFSET 86

#define DISPLAY_PIXEL_FORMAT 0x02

#define MEMORY_SIZE (PAINTING_ZONE_H_SIZE * PAINTING_ZONE_V_SIZE)
//VARIABLES
uint16_t frameBuffer[PAINTING_ZONE_V_SIZE][PAINTING_ZONE_H_SIZE];

//FUNCTIONS_PROTOTYPES
void BL_MCU_LTDC_GPIO_Init(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));
void BL_MCU_LTDC_CLOCKS_Init(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));
void BL_MCU_LTDC_MODULE_Init(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));

void BL_MCU_LTDC_Enable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));
void BL_MCU_LTDC_Disable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));

void BL_MCU_LTDC_LAYER_Enable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));
void BL_MCU_LTDC_LAYER_Disable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));

void BL_MCU_LTDC_BACKLIGNHT_Enable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));
void BL_MCU_LTDC_BACKLIGNHT_Disable(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));


void BL_MCU_LTDC_Init(void) __attribute__((optimize(BL_OPTIMIZATION_LVL)));




//FUNCTIONS
__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_Function(void){
	BL_MCU_LTDC_Init();


	uint8_t string1[] = "B O O T";
	uint8_t string2[] = {"L O A D"};
	drawDOS16String(22, 36, string1, 7, 0x3F << 5);
	drawDOS16String(22, 48, string2, 7, 0x3F << 5);


}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_Init(void){
	BL_MCU_LTDC_GPIO_Init();
	BL_MCU_LTDC_CLOCKS_Init();
	BL_MCU_LTDC_MODULE_Init();

	for(uint32_t i = 0; i < PAINTING_ZONE_H_SIZE; i++){
		for(uint32_t j = 0; j < PAINTING_ZONE_V_SIZE; j++){
			if(i==0 || i == (PAINTING_ZONE_H_SIZE - 1) || j == 0 || j == (PAINTING_ZONE_V_SIZE - 1)){
				frameBuffer[i][j] = 0xFFFF;
			}else{
				frameBuffer[i][j] = 0x0000;
			}
		}
	}

}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_Enable(void) {
	LTDC->GCR |= LTDC_GCR_LTDCEN;
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_Disable(void) {
	LTDC->GCR &= ~LTDC_GCR_LTDCEN;
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_LAYER_Enable(void) {
	LTDC_Layer1->CR |= LTDC_LxCR_LEN;
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_LAYER_Disable(void) {
	LTDC_Layer1->CR &= ~LTDC_LxCR_LEN;
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_BACKLIGNHT_Enable(void){
	GPIOD->ODR |= GPIO_ODR_OD12;
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_BACKLIGNHT_Disable(void){
	GPIOD->ODR &= ~GPIO_ODR_OD12;
}


__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_GPIO_Init(void){

	//Backlight
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); // PORT D CLOCKS are ENABLED
	MODIFY_REG(GPIOD->MODER, GPIO_MODER_MODER12, GPIO_MODER_MODE12_0); // PD12 output

	/*	PB0 R3
		PB1 R6
		PB10 G4
		PB11 G5
		PB8 B6
		PB9 B7*/
	//---PORT---B---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN); // PORT B CLOCKS are ENABLED

	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER0, GPIO_MODER_MODE0_1); // PB0 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER1, GPIO_MODER_MODE1_1); // PB1 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER8, GPIO_MODER_MODE8_1); // PB8 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER9, GPIO_MODER_MODE9_1); // PB9 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER10, GPIO_MODER_MODE10_1); // PB10 Alternative func
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODER11, GPIO_MODER_MODE11_1); // PB11 Alternative func

	CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT0 | GPIO_OTYPER_OT1 | GPIO_OTYPER_OT8 | GPIO_OTYPER_OT9 | GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11);
	// PB0 PB1 PB8 PB9 PB10 PB11 Output push-pull

	MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFRL0, GPIO_AFRL_AFRL0_3 | GPIO_AFRL_AFRL0_0); // PB0 AF9
	MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFRL1, GPIO_AFRL_AFRL1_3 | GPIO_AFRL_AFRL1_0); // PB1 AF9
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8, GPIO_AFRH_AFSEL8_3 | GPIO_AFRH_AFSEL8_2 | GPIO_AFRH_AFSEL8_1); // PB8 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL9, GPIO_AFRH_AFSEL9_3 | GPIO_AFRH_AFSEL9_2 | GPIO_AFRH_AFSEL9_1); // PB9 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 | GPIO_AFRH_AFSEL10_2 | GPIO_AFRH_AFSEL10_1); // PB10 AF14
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PB11 AF14

	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED0_Msk, GPIO_OSPEEDR_OSPEED0_1 |  GPIO_OSPEEDR_OSPEED0_0); //PB0 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED1_Msk, GPIO_OSPEEDR_OSPEED1_1 | GPIO_OSPEEDR_OSPEED1_0); //PB1 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED8_Msk, GPIO_OSPEEDR_OSPEED8_1 | GPIO_OSPEEDR_OSPEED8_0); //PB8 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED9_Msk, GPIO_OSPEEDR_OSPEED9_1 | GPIO_OSPEEDR_OSPEED9_0); //PB9 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 | GPIO_OSPEEDR_OSPEED10_0); //PB10 Very high speed
	MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 | GPIO_OSPEEDR_OSPEED11_0); //PB11 Very high speed

	CLEAR_BIT(GPIOB->PUPDR, GPIO_PUPDR_PUPDR0 | GPIO_PUPDR_PUPDR1 | GPIO_PUPDR_PUPDR8 | GPIO_PUPDR_PUPDR9 | GPIO_PUPDR_PUPDR10 | GPIO_PUPDR_PUPDR11);
	// PB0 PB1 PB8 PB9 PB10 PB11 No pull-up/pull-down

	/*	PC6 HSYNC
		PC7 G6*/
	//---PORT---C---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN); // PORT B CLOCKS are ENABLED

	MODIFY_REG(GPIOC->MODER, GPIO_MODER_MODER6, GPIO_MODER_MODE6_1); // PC6 Alternative func
	MODIFY_REG(GPIOC->MODER, GPIO_MODER_MODER7, GPIO_MODER_MODE7_1); // PC7 Alternative func

	CLEAR_BIT(GPIOC->OTYPER, GPIO_OTYPER_OT6 | GPIO_OTYPER_OT7);
	// PC6 PC7 Output push-pull

	MODIFY_REG(GPIOC->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PC6 AF14
	MODIFY_REG(GPIOC->AFR[0], GPIO_AFRL_AFRL7, GPIO_AFRL_AFRL7_3 | GPIO_AFRL_AFRL7_2 | GPIO_AFRL_AFRL7_1); // PC7 AF14

	MODIFY_REG(GPIOC->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PC6 Very high speed
	MODIFY_REG(GPIOC->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, GPIO_OSPEEDR_OSPEED7_1 | GPIO_OSPEEDR_OSPEED7_0); //PC7 Very high speed

	CLEAR_BIT(GPIOC->PUPDR, GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7);
	// PC6 PC7 No pull-up/pull-down


	/*	PD3 G7*/
	//---PORT---D---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); // PORT D CLOCKS are ENABLED

	MODIFY_REG(GPIOD->MODER, GPIO_MODER_MODER3, GPIO_MODER_MODE3_1); // PD3 Alternative func

	CLEAR_BIT(GPIOD->OTYPER, GPIO_OTYPER_OT3);
	// PD3 Output push-pull

	MODIFY_REG(GPIOD->AFR[0], GPIO_AFRL_AFRL3, GPIO_AFRL_AFRL3_3 | GPIO_AFRL_AFRL3_2 | GPIO_AFRL_AFRL3_1); // PD3 AF14
	MODIFY_REG(GPIOD->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, GPIO_OSPEEDR_OSPEED3_1 |  GPIO_OSPEEDR_OSPEED3_0); //PD3 Very high speed
	CLEAR_BIT(GPIOD->PUPDR, GPIO_PUPDR_PUPDR3);  // PD3 No pull-up/pull-down


/*	PA11 R4
	PA12 R5
	PA6  G2
	PA3 B5
	PA4 VSYNC*/

	//---PORT---A---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN); // PORT A CLOCKS are ENABLED

	// PA3 PA4 PA6 PA11 PA12 Alternative func
	MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODER3 | GPIO_MODER_MODER4 | GPIO_MODER_MODER6, GPIO_MODER_MODE6_1 | GPIO_MODER_MODE4_1 | GPIO_MODER_MODE3_1);
	MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODER11 | GPIO_MODER_MODER12, GPIO_MODER_MODE11_1 | GPIO_MODER_MODE12_1);

	CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT12 | GPIO_OTYPER_OT11 | GPIO_OTYPER_OT6 | GPIO_OTYPER_OT4 | GPIO_OTYPER_OT3);
	// PCD Output push-pull

	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL3, GPIO_AFRL_AFRL3_3 | GPIO_AFRL_AFRL3_2 | GPIO_AFRL_AFRL3_1); // PA3 AF14
	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL4, GPIO_AFRL_AFRL4_3 | GPIO_AFRL_AFRL4_2 | GPIO_AFRL_AFRL4_1); // PA4 AF14
	MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PA6 AF14
	MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PA11 AF14
	MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL12, GPIO_AFRH_AFSEL12_3 | GPIO_AFRH_AFSEL12_2 | GPIO_AFRH_AFSEL12_1); // PA12 AF14

	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, GPIO_OSPEEDR_OSPEED3_1 |  GPIO_OSPEEDR_OSPEED3_0); //PA3 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, GPIO_OSPEEDR_OSPEED4_1 |  GPIO_OSPEEDR_OSPEED4_0); //PA4 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PA6 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 |  GPIO_OSPEEDR_OSPEED11_0); //PA11 Very high speed
	MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, GPIO_OSPEEDR_OSPEED12_1 |  GPIO_OSPEEDR_OSPEED12_0); //PA12 Very high speed

	// PA3 PA4 PA6 PA11 PA12 No pull-up/pull-down
	CLEAR_BIT(GPIOA->PUPDR, GPIO_PUPDR_PUPDR3 | GPIO_PUPDR_PUPDR4 | GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR11 | GPIO_PUPDR_PUPDR12);

/*	PF10 DE*/
	//---PORT---F---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN); // PORT F CLOCKS are ENABLED

	MODIFY_REG(GPIOF->MODER, GPIO_MODER_MODER10, GPIO_MODER_MODE10_1); // PF10 Alternative func

	CLEAR_BIT(GPIOF->OTYPER, GPIO_OTYPER_OT10);
	// PF10 Output push-pull

	MODIFY_REG(GPIOF->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 | GPIO_AFRH_AFSEL10_2 | GPIO_AFRH_AFSEL10_1); // PF10 AF14
	MODIFY_REG(GPIOF->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0); //PF10 Very high speed
	CLEAR_BIT(GPIOF->PUPDR, GPIO_PUPDR_PUPDR10);  // PF10 No pull-up/pull-down

/*	PG7 DOTCLK
	PG6 R7
	PG10 G3
	PG11 B3
	PG12 B4*/
	//---PORT---G---
	SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOGEN); // PORT G CLOCKS are ENABLED

	// PG6 PG7 PG10 PG11 PG12 Alternative func
	MODIFY_REG(GPIOG->MODER, GPIO_MODER_MODER6 | GPIO_MODER_MODER7 , GPIO_MODER_MODE6_1 | GPIO_MODER_MODE7_1);
	MODIFY_REG(GPIOG->MODER, GPIO_MODER_MODER10 | GPIO_MODER_MODER11 | GPIO_MODER_MODER12, GPIO_MODER_MODE10_1 | GPIO_MODER_MODE11_1 | GPIO_MODER_MODE12_1);

	CLEAR_BIT(GPIOG->OTYPER, GPIO_OTYPER_OT12 | GPIO_OTYPER_OT11 | GPIO_OTYPER_OT10 | GPIO_OTYPER_OT7 | GPIO_OTYPER_OT6);
	// PG6 PG7 PG10 PG11 PG12 Output push-pull

	MODIFY_REG(GPIOG->AFR[0], GPIO_AFRL_AFRL6, GPIO_AFRL_AFRL6_3 | GPIO_AFRL_AFRL6_2 | GPIO_AFRL_AFRL6_1); // PG6 AF14
	MODIFY_REG(GPIOG->AFR[0], GPIO_AFRL_AFRL7, GPIO_AFRL_AFRL7_3 | GPIO_AFRL_AFRL7_2 | GPIO_AFRL_AFRL7_1); // PG7 AF14
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL10, GPIO_AFRH_AFSEL10_3 |  GPIO_AFRH_AFSEL10_0); // PG10 AF9
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL11, GPIO_AFRH_AFSEL11_3 | GPIO_AFRH_AFSEL11_2 | GPIO_AFRH_AFSEL11_1); // PG11 AF14
	MODIFY_REG(GPIOG->AFR[1], GPIO_AFRH_AFSEL12, GPIO_AFRH_AFSEL12_3 |  GPIO_AFRH_AFSEL12_0); // PG12 AF9

	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, GPIO_OSPEEDR_OSPEED6_1 |  GPIO_OSPEEDR_OSPEED6_0); //PG6 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, GPIO_OSPEEDR_OSPEED7_1 |  GPIO_OSPEEDR_OSPEED7_0); //PG7 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0); //PG10 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, GPIO_OSPEEDR_OSPEED11_1 |  GPIO_OSPEEDR_OSPEED11_0); //PG11 Very high speed
	MODIFY_REG(GPIOG->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, GPIO_OSPEEDR_OSPEED12_1 |  GPIO_OSPEEDR_OSPEED12_0); //PG12 Very high speed

	CLEAR_BIT(GPIOG->PUPDR, GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7 | GPIO_PUPDR_PUPDR10 | GPIO_PUPDR_PUPDR11 | GPIO_PUPDR_PUPDR12);  //  No pull-up/pull-down
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_CLOCKS_Init(void){
	SET_BIT(RCC->APB2ENR, RCC_APB2ENR_LTDCEN); // LTDC CLOCKS are ENABLED

	MODIFY_REG(RCC->PLLSAICFGR, RCC_PLLSAICFGR_PLLSAIR, RCC_PLLSAICFGR_PLLSAIR_0 | RCC_PLLSAICFGR_PLLSAIR_1); // PLLSAIR is 3
	MODIFY_REG(RCC->PLLSAICFGR, RCC_PLLSAICFGR_PLLSAIN, 54 << RCC_PLLSAICFGR_PLLSAIN_Pos); // PLLSAI MULL N is 54 (input division)
	MODIFY_REG(RCC->DCKCFGR, RCC_DCKCFGR_PLLSAIDIVR, RCC_DCKCFGR_PLLSAIDIVR_0); //PLLSAIDIVR is 4 (01b = 4d)
	//VCOxN = HSIclk / PLLM * PLLN = 16 / 16 * 54 = 54 MHz
	//PLLLCD = VCOxN / PLLR = 108 / 3 = 36 MHz
	//LCDCLK = PLLLCD / PLLSAIDIVR = 36 / 4 = 9 MHz

	SET_BIT(RCC->CR, RCC_CR_PLLSAION); //
	while(!(READ_BIT(RCC->CR, RCC_CR_PLLSAIRDY))){
		__NOP();
	};
}

__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_MODULE_Init(void){

	/*The First step:
		configure HSYNC, VSYNC, V&H back porch
		active data area & front porch timings
		following the panel datasheet*/

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

	LTDC->BPCR = ((H_SYNC_WIDTH + H_BACK_PORCH - 1) << LTDC_BPCR_AHBP_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH - 1);
	LTDC->AWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH - 1) << LTDC_AWCR_AAW_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT - 1);
	LTDC->TWCR = ((H_SYNC_WIDTH + H_BACK_PORCH + DISP_ACTIVE_WIDTH + H_FRONT_PORCH - 1) << LTDC_TWCR_TOTALW_Pos) | (V_SYNC_WIDTH + V_BACK_PORCH + DISP_ACTIVE_HEIGHT + V_FRONT_PORCH - 1);

	LTDC->GCR = LTDC_GCR_DEPOL | LTDC_GCR_PCPOL;

	/*The Second step: 
		Layer Configuration*/
	LTDC_Layer1->WHPCR = ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET) ) | ((H_SYNC_WIDTH + H_BACK_PORCH + PAINTING_ZONE_H_OFFSET + PAINTING_ZONE_H_SIZE - 1) << LTDC_LxWHPCR_WHSPPOS_Pos);
	LTDC_Layer1->WVPCR = ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET) ) | ((V_SYNC_WIDTH + V_BACK_PORCH + PAINTING_ZONE_V_OFFSET + PAINTING_ZONE_V_SIZE - 1) << LTDC_LxWVPCR_WVSPPOS_Pos);
	LTDC_Layer1->PFCR = DISPLAY_PIXEL_FORMAT;
	LTDC_Layer1->CFBAR = (uint32_t)&frameBuffer;

	//LTDC_Layer1->BFCR = (0x4 << LTDC_LxBFCR_BF1_Pos) | 0x5;

	LTDC_Layer1->CFBLR = ((PAINTING_ZONE_H_SIZE * 2) << LTDC_LxCFBLR_CFBP_Pos) | (PAINTING_ZONE_H_SIZE * 2 + 3);
	LTDC_Layer1->CFBLNR = PAINTING_ZONE_V_SIZE;

	/*The Third step: 
		Clocks and module enabling*/
	BL_MCU_LTDC_LAYER_Enable();

	LTDC->SRCR = LTDC_SRCR_IMR;

	BL_MCU_LTDC_Enable();

	BL_MCU_LTDC_BACKLIGNHT_Enable();
}

#endif

Similar Posts

Leave a Reply

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