◎ TFT LCD 동작
지난 파트 1부터 파트 4까지의 내용을 통해 FSMC를 이용한 TFT LCD 제어에 대한 전반적인 내용을 다뤘습니다. 이번 글에서는 실제로 어떤 방식으로 동작하는지와 그 결과 값을 설명하도록 하겠습니다.
우선, 제가 다루는 TFT LCD 드라이버는 RAIO사에서 제작한 RA8875라는 드라이버가 내장된 LCD이며, 데이터시트와 애플리케이션 노트, 그리고 제조사에서 제공하는 API를 활용해 보드 시스템에 맞게 개발을 진행하였습니다.
● 포트 / FSMC 초기화 진행
위에서도 정리 했지만 8080 병렬 통신을 위해서는 물리적인 포트와 SRAM 접근을 위한 FSMC 를 초기화 합니다. 아래 내용은 초기화 관련 코드 입니다.
/*
*********************************************************************************************************
* 함수명: LCD_CtrlLinesConfig
* 기능설명: LCD
* 매개변수: 없음
* 반환값: 없음
*********************************************************************************************************
*/
static void LCD_CtrlLinesConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOI_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*일반 GPIO 초기화*/
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(TP_INT_GPIO_Port, TP_INT_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LCD_BUSY_GPIO_Port, LCD_BUSY_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LCD_PWM_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(LCD_PWM_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = TP_NCS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TP_NCS_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = TP_INT_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(TP_INT_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LCD_BUSY_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LCD_BUSY_GPIO_Port, &GPIO_InitStruct);
/* FSMC 포트 초기화 */
if (FSMC_Initialized) {
return;
}
FSMC_Initialized = 1;
/* Peripheral clock enable */
__HAL_RCC_FSMC_CLK_ENABLE();
/** FSMC GPIO Configuration
PE7 ------> FSMC_D4
PE8 ------> FSMC_D5
PE9 ------> FSMC_D6
PE10 ------> FSMC_D7
PE11 ------> FSMC_D8
PE12 ------> FSMC_D9
PE13 ------> FSMC_D10
PE14 ------> FSMC_D11
PE15 ------> FSMC_D12
PD8 ------> FSMC_D13
PD9 ------> FSMC_D14
PD10 ------> FSMC_D15
PD13 ------> FSMC_A18
PD14 ------> FSMC_D0
PD15 ------> FSMC_D1
PD0 ------> FSMC_D2
PD1 ------> FSMC_D3
PD4 ------> FSMC_NOE
PD5 ------> FSMC_NWE
PG12 ------> FSMC_NE4
*/
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_13
|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1
|GPIO_PIN_4|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}
/*
*********************************************************************************************************
* 함수명: LCD_CtrlLinesConfig
* 기능설명: LCD
* 매개변수: 없음
* 반환값: 없음
*********************************************************************************************************
*/
static void LCD_FSMCConfig(void)
{
SRAM_HandleTypeDef hsram4;
FSMC_NORSRAM_TimingTypeDef Timing = {0};
hsram4.Instance = FSMC_NORSRAM_DEVICE;
hsram4.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram4.Init */
hsram4.Init.NSBank = FSMC_NORSRAM_BANK4;
hsram4.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram4.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram4.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram4.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram4.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram4.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram4.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram4.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram4.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram4.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram4.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram4.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
hsram4.Init.PageSize = FSMC_PAGE_SIZE_NONE;
/* Timing */
Timing.AddressSetupTime = 1;
Timing.AddressHoldTime = 0;
Timing.DataSetupTime = 4;
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 0;
Timing.DataLatency = 0;
Timing.AccessMode = FSMC_ACCESS_MODE_A;
/* ExtTiming */
if (HAL_SRAM_Init(&hsram4, &Timing, NULL) != HAL_OK)
{
Error_Handler( );
}
}
● TFT LCD 드라이브 초기화
8080 병렬통신을 위한 모든 물리적인 초기화가 성공한 가운데 이제는 본격적으로 TFT LCD 드라이브와 통신을 진행 해야 합니다. 그러기 위해서는 TFT LCD 드라이브 (RA8875)의 초기화를 진행 해야 합니다. 이 과정은 RA8875 동작을 위한 스탠드바이 같은 프로세스 입니다.
어플리케이션 노트를 보면 아래 그림과 같은 내용이 있습니다.
코드상 다소 차이가 있겠지만 최대한의 동작 하는 방식은 거의 같습니다.
주소값 0x88 을 시작 으로 PLL 세팅을 진행 합니다.
void RA8875_PLL_ini(void)
{
// 480 x 272
LCD_CmdWrite(0x88);
RA8875_Delaly1us();
LCD_DataWrite(0x0a);
RA8875_Delaly1ms();
LCD_CmdWrite(0x89);
RA8875_Delaly1us();
LCD_DataWrite(0x02);
}
이 코드의 내용을 파악 하기 위해서는 아래의 내용을 참고 하면 됩니다.
이 프로세서는 RA8875에 시스템 클록 설정을 위한 과정이라고 생각하면 됩니다.
PLLDIVN[4:0] 은 0x0a 이것을 10진수로 변환 하면 10입니다 , PLLDIVM 은 0 이되고 PLLDIVK[2:0] 은 2입니다.
회로상 외부 오실레이터는 25MHz 이기 때문에 FIN은 25 입니다. 이 모든것들을 대입하면
25M * (10 + 1) / ((0 + 1) * (2 ^ 2)) = 68.75MHz
시스템 클록값은 68.75MHz 가 됩니다.
MCU에 시스템 클록 168MHz 에서 PLL 을 통해 RA8875에 맞는 클록 주파수로 세팅 되는 과정 입니다.
다음 단계는 픽셀 클록을 세팅 합니다.
LCD_CmdWrite(0x04); //set PCLK invers
LCD_DataWrite(0x82);
픽셀 클록은 주사율 계산하는 공식중에 한 요소입니다.
주사율 = 픽셀 클럭 / (전체 수평 픽셀 수 × 전체 수직 픽셀 수)
세팅 값을 확인해 보면
위의 그림과 같습니다. 위에서 시스템 클록은 68.75MHz 라고 했습니다. 픽셀 클록은 68.75 MHz ÷ 4 = 17.1875 MHz 입니다.
나머지 코드는 모두 분석 하면 좋지만 시간이 많이 걸리기 때문에 데이터 시트를 보고 분석 하는 시간이 필요 합니다.
나머지 세팅 관련 코드는 RAIO 사에서 제공 하는 Open source 를 보고 참고 했습니다.
※ 다른 TFT LCD 드라이버도 동작하는 방식과 코드의 양식이 거의 비슷합니다.
이제 마지막으로 LCD에 데이터를 RAM 에 쓰기를 진행하는 동작을 설명 하겠습니다.
void LCD_Clear(uint16_t Color)
{
uint32_t index = 0;
LCD_SetCursor(0,0);
LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
for(index = 0; index < 130560; index++)
{
LCD_DataWrite(Color);
}
// while(1);
}
첨부된 코드는 LCD 전체 화면에 색을 표현 하는 코드 입니다.
● LCD_SetCursor 함수
void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos)
{
LCD_CmdWrite(0x46);
LCD_DataWrite(Xpos);
LCD_CmdWrite(0x47);
LCD_DataWrite(Xpos>>8);
LCD_CmdWrite(0x48);
LCD_DataWrite(Ypos);
LCD_CmdWrite(0x49);
LCD_DataWrite(Ypos>>8);
}
위 코드는 디스플레이의 커서 좌표를 잡는 코드 입니다.
RAM에 0x46 주소에 명령어를 보냅니다.(수평값) Xpos 는 매개 변수로 0을 인자값을 받아서 좌표는 윈도우 좌표로 0에서 부터 시작 합니다.
Xpos 값을 설정하는 것으로 0을 오른쪽으로 8번 시프트 했기 때문에 그냥 0입니다.
위 사진은 (수직값) Ypos 를 세팅을 위해 0x48 주소를 명령어로 보내 활성화 시킵니다.
수직값을 설정 합니다 역시 0을 오른쪽으로 시프트 했기 때문에 그냥 0입니다.
● LCD_WriteRAM_Prepare 함수
void LCD_WriteRAM_Prepare(void)
{
LCD_CmdWrite(0x02); //
}
SRAM 메모리에 데이터를 읽고 쓰기 위한 주소 입니다. 0x02 를 통해 메모리에 데이터 쓰기를 활성화 합니다.
현재 4.3 인치 TFT LCD 이므로 전체 픽셀은 130560 pixel 입니다.
for 문을 돌려 전체 픽셀에 컬러값 데이터를 전송(Write) 입니다.