Данный конспект (гайд) предназначен для лиц, желающих ознакомиться с конфигурацией LTDC модуля микроконтроллеров STM на примере STM32F429ZIT6 подключенному по 16-битному RGB565 интерфейсу к дисплею TM043NBH02 с разрешением 480x272 и использованием одного слоя без внешней памяти для видеобуфера.

Используемая документация:
MCU Reference manual: RM0090;
MCU Datasheet: DocID024030 Rev 10;
Display Datasheet: DS TM043NBH02-40 Ver 1.0.

Конфигурирование модуля LTDC для работы с дисплеями осуществляется в три этапа:

1. Конфигурация тактирования

Контроллер LCD-TFT использует 3 домена тактирования:

  • AHB clock domain (HCLK), используется для быстрого перемещения данных в Layer FIFO и регистр конфигурации буфера кадр

  • APB2 clock domain (PCLK2), используется для регистра глобальной конфигурации и регистров прерываний

  • Pixel Clock domain (LCD_CLK), используется для генерации сигналов интерфейса LCD-TFT, генерации данных пикселей и конфигурации слоя. Выход LCD_CLK должен быть сконфигурирован в соответствии с требованиями используемой панели. LCD_CLK конфигурируется через PLLSAI.

Так как настройка тактирования шин AHB\APB является обыденностью, из-за использования их всей периферией, то опишем только конфигурацию тактирования Pixel Clock.
Рассмотрим блок тактирования PLLSAI:

Тактирование шины LCD_CLK настраивается через блок PLLSAI, частота на который поступает от тактирующего резонатора (генератора) в нашем случае от HSI, через делитель M, после чего может быть умножена на произвольный множитель N и доведена до необходимого значения делителями R и PLL_LCD_CLK_DIV.

Для дисплея TM043NBH02 необходимо задать частоту тактирования Pixel Clock в диапазоне от 8 до 12 МГц:

Таким образом, перейдём к конфигурации тактирования. Вводные следующие: частота встроенного резонатора HSI = 16 МГц, делитель M = 16, на вход VCO поступает частота в 1 МГц, выберем частоту DCLK(Pixel Clock) = 9 МГц.

Начнём с конца, подберём делитель PLL_LCD_CLK_DIV. Для этого лезем в RM и ищем регистр ответственный за данный делитель. В нашем случае это RCC_DCKCFG, в нём нас интересует битовое поле PLLSAIDIVR:

Как можно увидеть, минимальный делитель 2, который выставляется очисткой битового поля, его и выбираем:

RCC->DCKCFGR &= ~RCC_DCLCFGR_PLLSAIDIVR;

Получаем частоту в 18 МГц на входе делителя DIV. Далее определим, что необходимо подать на делитель R. Смотрим в регистр RCC_PLLSAICFGR:

Из документации видно, что нельзя выставлять делитель R менее 2, «wrong configuration», поэтому выбираем любое другое значение, за исключением «0» и «1». Однако, как можно увидеть из описания множителя N, он не может быть меньше, чем 50. Минимальное значение делителя R, удовлетворяющее нашим условиям, может быть 3:

RCC->PLLSAICFGR |= 3 << RCC_PLLSAICFGR_PLLSAIR_Pos;

Значение частоты на входе делителя R должно быть 18 МГЦ * 3 = 54 МГц, поэтому множитель N необходимо установить в значение равное 54 в том же регистре:

RCC->PLLSAICFGR |= 54 << RCC_PLLSAICFGR_PLLSAIN_Pos;

Остаётся дело за малым, разрешить тактирование LCD_CLK и работу блока PLLSAI, что мы сейчас и провернём, для этого нам необходимы регистры RCC->APB2ENR, где выставляем бит LTDCEN и регистр RCC->CR, в нём выставляем бит PLLSAION:

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

После выставления бита RCC_CR_PLLSAION, необходимо дождаться установки бита RCC_CR_PLLSAIRDY уведомляющего о готовности:

while(!(RCC->CR & RCC_CR_PLLSAIRDY)){ 
		__NOP();
};
Исходный код этапа конфигурации тактирования
__attribute__((optimize(BL_OPTIMIZATION_LVL))) void BL_MCU_LTDC_CLOCKS_Init(void){
    
    RCC->DCKCFGR &= ~RCC_DCLCFGR_PLLSAIDIVR;
    RCC->PLLSAICFGR |= 3 << RCC_PLLSAICFGR_PLLSAIR_Pos;
    RCC->PLLSAICFGR |= 54 << 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. Конфигурация портов ввода вывода

В интерфейсе LTDC используются следующие сигнальные линии:

Выводы контроллера LTDC должны быть сконфигурированы пользователем. Незадействованные выводы можно использовать для любых других целей.

Для выходов LTDC в режиме до 24 бит (RGB888), если для вывода используется меньше чем 8 бит на пиксель (например RGB565 или RGB666 для интерфейса с 16-битными или 18-битными дисплеями), то сигналы данных RGB дисплея должны подключаться к MSB разрядам данных RGB контроллера LCDС. Например, в нашем случае, подключения контроллера LTDС к дисплею происходит по 16-битному RGB565 интерфейсу, сигналы данных LCD R[4:0], G[5:0] и B[4:0] должны быть подключены к контроллеру LTDC через LCD_R[7:3], LCD_G[7:2] и LCD_B[7:3].

Для того, чтобы узнать какие выводы необходимо задействовать для работы с модулем LTDC, следует внимательно рассмотреть таблицу альтернативных функций из Datasheet:

В контексте микроконтроллера STM32F429, к модулю LTDC относятся альтернативные функции AF9 и AF14. Будем задействовать следующие выводы:

Вывод

Функция

Выбор альтернативной функции

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

В качестве примера, приведу конфигурацию одного вывода PF10, так как все остальные будут конфигурироваться по аналогии.

Алгоритм конфигурации:
- включаем тактирование порта ввода-вывода в регистре RCC_AHB1ENR;

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN;

- устанавливаем режим работы вывода в регистре GPIOx_MODER (Alternative function);

GPIOF->MODER |= GPIO_MODER_MODE10_1;

- устанавливаем тип выхода в GPIOx_OTYPER (Output push-pull);

GPIOF->OTYPER &=  GPIO_OTYPER_OT10;

- выбираем подтяжку вывода GPIO_PUPDR (No pull-up/pull-down);

GPIOF->PUPDR &= GPIO_PUPDR_PUPDR10;

- выбираем тип альтернативной функции в GPIOx_AFRH (AF14);

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

- устанавливаем скорость работы вывода в GPIOx_OSPEEDR (Very high speed).

GPIOF->OSPEEDR |= GPIO_OSPEEDR_OSPEED10_1 |  GPIO_OSPEEDR_OSPEED10_0;
Исходный код этапа конфигурации портов ввода-вывода
__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. Конфигурация регистров модуля

А) Конфигурация временных показателей. Конфигурируем длительности горизонтальной (HSYNC) и вертикальной синхронизаций (VSYNC), ширину переднего и обратного ходов (front/back porch), активную областб дисплея (active display area).

Вертикальная синхронизация (HSYNC) используется для сброса указателя драйвера дисплея на верхний левый угол.
Горизонтальная синхронизация (VSYNC) предназначена для перехода на новую строку.
Ширина обратного хода (back porch) – время между концом синхроимпульса и началом достоверных данных.
Ширина переднего хода (fronf porch) – время между концом достоверных данных и началом синхроимпульса.
Активная область дисплея (active dispaly area) – область отображения данных.

Рассмотрим синхронизации дисплея. Для используемого дисплея таблица и диаграмма временных параметров представлена ниже:

В контексте временной диаграммы и таблицы таймингов нас интересует время Tvw (ширина вертикальной синхронизации) и Thw (ширина горизонтальной синхронизации). По временной диаграмме видно, что минимальная ширина Tvw составляет 2 такта HSYNC, в то же время из таблицы минимальная ширина Thw составляет 2 такта DCLK (Pixel Clock). Можно использовать и минимальные тайминги для настройки синхронизации, однако, воспользуемся рекомендуемыми значениями (TYP) из таблицы. Стоит отметить, что число тактов синхронизации должно быть меньше числа тактов ширины обратного хода (back porch).

Из таблицы выбираем величину back и front porch для HSYNC и VSYNC. Так же выбираем из значений в колонке TYP. Смотрим замечания и убеждаемся в том, что сумма величин back и front porch для HSYNC и VSYNC 20 и 51 единице соответственно:

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

Активная область дисплея составляет 480х272 пикселя.

Определим уровни активных сигналов.

При вертикальной синхронизации происходит спад уровня, Active Low:

При горизонтальной та же ситуация, Active Low:

Во время записи данных (data enable) сигнал нарастает, Active High:

Запись данных о цвете того или иного пикселя, во избежание гонки фронтов, будем осуществлять по спадающему фронту (по переходу с «1» в «0») DCLK (Pixel clocks):

Запишем полученные данные в регистры. Устанавливаем значения синхронизаций и отнимаем от него единицу. Вертикальной и горизонтальной синхронизациям соответствуют свои битовые поля в регистре LTDC_SSCR (тот же подход будет и для других настроек модуля):

Дефайны
#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);

Немного интересный и странный подход, чтобы записать значение back porch в регистр LTDC_BPCR. Для этого необходимо определить сумму значений синхронизации и back porch и уже их записать в регистр, при этом не забыв отнять 1:

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

Аналогично и для обозначения активной области дисплея. Необходимо записать сумму значений синхронизации, back porch и к ним уже добавить размерности активной области в регистр LTDC_AWCR:

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 указывается полная ширина размерности дисплея с учётом всех предыдущих параметров, где, в том числе, указывается и front porch. Опять определяем сумму синхронизации, back porch, активной области и front porch, не забываем отнять единицу:

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);

Установим полярность управляющих сигналов HSYNC, VSYNC и DE. Как мы ранее узнали из документации на дисплей полярность сигналов следующая:

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

Запишем эти значения в регистр LTDC_GCR:

LTDC->GCR = LTDC_GCR_DEPOL | LTDC_GCR_PCPOL;


В самом конце, данный регистр будем задействовать и для включения модуля установкой бита LTDCEN.

Б) Конфигурация слоёв. В данном примере разберём конфигурацию системы с одним(первым) слоем отображения данных.

Настроим размеры и расположение первого слоя на дисплее. Определим высоту и длину отрисовываемой области в размерах 100x100 в середине дисплея. Для этого в регистре LTDC_LxWHPCR горизонтальной позиции указываем стартовый и стоповый пиксели на дисплее:

При определении положения отображаемой области в активной области дисплея, следует учесть ещё и ширину 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);

P.S. Стоит отметить, что в RM допущена ошибка, которая учтена в HAL, но так и не исправлена в документации. Ошибка заключается в том, что в примере авторы записывают в битовое поле «WSHPPOS» значение конечной позиции без учёта индекса «нулевой»/стартовой позиции, так как, если просто добавить к значению начальной позиции ширину активной области, то ширина отображаемой области получится на 1 больше. В следствии чего, модуль LTDC, выбирает данные из видеобуфера неправильно и изображение будет отображено под наклоном. Поэтому следует вычесть единицу при записи конечной позиции.

Тот же подход и для вертикального расположения:

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);

Выберем входной формат пиксел (Pixel Format), или иначе цветовую палитру, в котором будем работать. Для этого используется регистр LTDC_LxPFCR.

Так как будет использоваться только 1 слой и это тестовый проект, то нет необходимости в использовании прозрачности и большой глубины цветов. Поэтому договоримся, что будем использовать RGB565 (чего, в принципе, достаточно для написания большинства проектов/да и на плате произведена такая разводка):

LTDC_Layer1->PFCR = DISPLAY_PIXEL_FORMAT;

Далее, необходимо определиться с расположением отрисовываемого кадра в памяти. Это может быть, как внутренняя RAM, так и внешние SDRAM, и даже FLASH.  Для начала, определим как много наш буфер должен иметь памяти:

PAINTING_ZONE_H_SIZE * PAINTING_ZONE_V_SIZE * PIXEL_FORMAT = MEMORY_SIZE

В нашем случае:

100 пикселей * 100 пикселей * 2 байта = 20000 байт

Так как в нашем контроллере достаточное количество RAM памяти для такого видеобуфера, то указываем его в качестве хранилища кадра в регистре LTDC_LxCFBAR:

LTDC_Layer1->CFBAR = &frameBuffer;

Далее, в регистре LTDC_LxCFBLR, указываем модулю сколько пикселей (в байтах) одной линии необходимо отрисовывать (Color Frame Buffer Pitch in bytes) и какая действительная длина линии кадра (в байтах):

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

Стоит обратить внимание, что к длине линии кадра необходимо прибавлять магическую константу, в нашем случае 3. Для других контроллеров данная константа может отличаться, например, для H7 серии контроллеров она равняется 7. Почему это происходит в документации не описано.

Укажем, в регистре LTDC_LxCFBLNR, модулю количество строк в кадре:

LTDC_Layer1->CFBLNR = PAINTING_ZONE_V_SIZE;

В) Последним пунктом инициализации необходимо разрешить работу используемого слоя, перезагрузить данные из теневых регистров и разрешить работу самого модуля LTDC.

Для начала разрешим работу слою выставлением бита LEN в регистре LTDC_LxCR:

LTDC_Layer1->CR = LTDC_LxCR_LEN;

Перезагрузим данные из теневых регистров в активные LTDC_SRCR. Перезагрузку возможно осуществить двумя способами: во время вертикальной синхронизации и немедленно. Для начальной инициализации подойдёт и немедленная перезагрузка активных регистров. Но при изменении параметров модуля LTDC во время работы, следует использовать способ по вертикальной синхронизации, для избегания отрисовки с «артефактами».

И последнее в инициализации – разрешение на работу модуля LTDC, в ранее использованном регистре LTDC_GCR путём установки бита LTDCEN:

LTDC->GCR |= LTDC_GCR_LTDCEN;

P.S. не забудьте ещё и подсветку включить, без неё ничего не будет видно.

Если вы всё сделали правильно, то у вас получится изображение похожее на следующее:

Хаотичный набор цветов в изначально неинициализированном видеобуфере.

А так если в буфер начать что-нибудь писать:

Исходный код этапа инициализации модуля LTDC
__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();
}

Весь исходный код
/*
 *      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){
    RCC->DCKCFGR &= ~RCC_DCLCFGR_PLLSAIDIVR;
    RCC->PLLSAICFGR |= 3 << RCC_PLLSAICFGR_PLLSAIR_Pos;
    RCC->PLLSAICFGR |= 54 << 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();
    };
}

__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

Комментарии (2)


  1. zatim
    18.06.2023 14:55
    +5

    Извиняюсь, но немного уточню терминологию.

    В отечественной терминологии принято: горизонтальный синхронизирующий импульс называют строчным (ССИ), вертикальный синхроимпульс называют кадровым (КСИ), расстояние между концом данных и началом следующих - гасящий импульс или импульс гашения, соответственно, кадровый и строчный (КГИ и СГИ). Внутри импульса гашения располагается синхроимпульс. Расстояние между началом синхроимпульса и началом данных - кадровый или строчный импульс обратного хода (КИОХ и СИОХ). Однако, оба этих импульса существуют только в оконечном устройстве (телевизоре, мониторе, дисплее) в стандарте их нет. Стандарт только выделяет максимальное время, за которое оконечное устройство должно завершить обратный ход луча. Более того, к цифровому дисплею, у которого нет кинескопа и отклоняющей системы этот термин вообще неприменим и он употребляется исключительно по инерции.

    Поэтому front и back porch правильнее перевести почти дословно как площадка перед синхроимпульсом и после него (передняя/задняя площадка [гасящего импульса]). Обе этих площадки плюс синхроимпульс = гасящий импульс.


    1. StanislavZm Автор
      18.06.2023 14:55

      Спасибо за уточнение!