Конспект. STM32. CMSIS. LTDC
Данный конспект (гайд) предназначен для лиц, желающих ознакомиться с конфигурацией LTDC модуля микроконтроллеров STM на примере STM32F429ZIT6 подключенному по 16-битному RGB565 интерфейсу к дисплею TM043NBH02 с разрешением 480×272 и использованием одного слоя без внешней памяти для видеобуфера.
Используемая документация:
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 |= 0x3 << RCC_PLLSAICFGR_PLLSAIR_Pos;
Значение частоты на входе делителя R должно быть 18 МГЦ * 3 = 54 МГц, поэтому множитель N необходимо установить в значение равное 54 в том же регистре:
RCC->PLLSAICFGR |= 0x54 << 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 |= 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. Конфигурация портов ввода вывода.
В интерфейсе 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 единице соответственно:
Активная область дисплея составляет 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.
Б) Конфигурация слоёв. В данном примере разберём конфигурацию системы с одним (первым) слоем отображения данных.
Настроим размеры и расположение первого слоя на дисплее. Определим высоту и длину отрисовываемой области в размерах 100×100 в середине дисплея. Для этого в регистре 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
В нашем случае:
Так как в нашем контроллере достаточное количество 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){
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