FMC 및 SPI 디스플레이 인터페이스
다음 시나리오들은 일반적으로 FMC 또는 SPI를 통해 연결된 LCD에 픽셀을 전송하는 단계들을 보여줍니다. 두 방법은 몇 가지 요소들을 공유하고 있습니다. 이 섹션에 기술된 시나리오에서는 예증을 위해 ST7789H2 LCD 컨트롤러를 사용합니다.
STM32CubeMX의 보드 사양에 따라 FMC 또는 SPI가 구성되고 나면, 개발자가 애플리케이션 프레임 버퍼의 업데이트된 부분을 연결된 디스플레이로 전송하는 코드를 작성할 수 있도록 TouchGFX Generator를 사용해 TouchGFX HAL을 생성할 수 있습니다.
아래 그림에는 Custom Display Interface가 선택된 TouchGFX Generator 구성이 나와 있습니다. 이것은 개발자가 픽셀을 구성하여 프레임 버퍼 메모리에서 디스플레이로 수동으로 전송하기를 원한다는 것을 TouchGFX Generator에게 알려 주고 이를 수행하기 위한 핸들이 생성됩니다.
Tip
일반적으로 임베디드 GRAM이 장착된 디스플레이의 경우에는 생성된 TouchGFX HAL에서 사용자가 작성한 코드가 다음 단계를 수행하게 됩니다.
- 다시 그리게 될 프레임 버퍼의 영역에 따라 "display cursor"와 "active window"를 이 영역과 일치하는 GRAM의 위치로 옮깁니다.
- GRAM에 입력되는 픽셀 데이터를 기록할 준비를 합니다.
- 픽셀 데이터를 전송합니다.
프레임 버퍼 전송
프레임 버퍼의 영역이 업데이트되면 TouchGFX Engine에서 HAL::flushFrameBuffer(Rect r)
를 호출합니다. 개발자가 SPI 및 FMC의 경우와 마찬가지로 픽셀을 디스플레이에 수동으로 전송하는 코드를 구현해야 하는 경우에는 이 함수를 재정의할 수 있습니다. 앞으로 살펴보겠지만, FMC 뱅크를 통해 픽셀을 전송하는 함수는 TouchGFX Generator에서 생성됩니다.
Note
드라이버는 픽셀을 디스플레이에 전송하고 디스플레이의 메모리 작성 위치를 제어할 수 있어야 합니다. 자세한 내용은 디스플레이의 데이터시트를 확인하십시오.
void TouchGFXHAL::flushFrameBuffer(const Rect& rect)
{
/* Set Cursor */
ST7789H2_SetDisplayWindow(rect.x, rect.y, rect.width, rect.height);
/* Prepare to write to LCD RAM */
__ST7789H2_WriteReg(ST7789H2_WRITE_RAM, (uint8_t*)NULL, 0);
/* Send Pixels - User defined function */
this->copyFrameBufferBlockToLCD(rect);
}
아래의 ST7789H2_SetDisplayWindow
함수는 GRAM을 사용해 디스플레이에 일반적으로 사용되는 특정 레지스터에 기록하는 방법으로 GRAM의 가상 "커서"에 대한 x
및 y
좌표를 설정합니다.
extern "C"
void ST7789H2_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
{
uint8_t parameter[4];
/* CASET: Column Address Set */
parameter[0] = 0x00;
parameter[1] = Xpos;
parameter[2] = 0x00;
parameter[3] = Xpos + Width - 1;
ST7789H2_WriteReg(ST7789H2_CASET, parameter, 4);
/* RASET: Row Address Set */
parameter[0] = 0x00;
parameter[1] = Ypos;
parameter[2] = 0x00;
parameter[3] = Ypos + Height - 1;
ST7789H2_WriteReg(ST7789H2_RASET, parameter, 4);
}
아래의 TouchGFXHAL::copyFrameBufferBlockToLCD
함수는 이 예시에서 한 번에 한 줄의 업데이트된 영역(Rect
)을 전송하고 이에 따라 프레임 버퍼 포인터가 진행되도록 사용자가 정의한 함수입니다.
void TouchGFXHAL::copyFrameBufferBlockToLCD(const Rect& rect)
{
__IO uint16_t* ptr;
uint32_t height;
// This can be accelerated using regular DMA hardware
for (height = 0; height < rect.height ; height++)
{
ptr = getClientFrameBuffer() + rect.x + (height + rect.y) * HAL::DISPLAY_WIDTH;
LCD_IO_WriteMultipleData((uint16_t*)ptr, rect.width);
}
}
TouchGFX Generator는 ptr
을 수동으로 전진시키는 대신, 프레임 버퍼에서 Rect
의 위치에 따라 ptr
을 전진시키도록 advanceFrameBufferToRect
함수를 생성합니다.
inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const
{
// Advance vertically Advance horizontally
fbPtr += rect.y * lcd().framebufferStride() + rect.x * 2;
return fbPtr;
}
FMC
또한, TouchGFX Generator는 FMC 뱅크가 하나 이상 올바르게 구성된 경우에 FMC 디스플레이 인터페이스도 지원합니다. 이 경우, TouchGFX Generator에서 생성한 코드는 디스플레이에 연결된 FMC 뱅크를 통해 상호 작용을 하도록 LCD_IO_WriteMultipleData
함수가 생성된다는 점을 제외하고 맞춤형 디스플레이 인터페이스의 코드와 비슷합니다. copyFrameBufferBlockToLCD
함수에 대해 앞서 보여드린 코드를 다시 방문하면 생성된 함수를 사용한다는 것을 확인하게 될 것입니다.
Tip
__weak void LCD_IO_WriteMultipleData(uint16_t* pData, uint32_t Size)
{
uint32_t i;
for (i = 0; i < Size; i++)
{
FMC_BANK1_WriteData(pData[i]);
}
}
다음 그림은 FMC 뱅크 2(둘 중 하나 사용 가능)의 유효한 16비트(필수) 구성을 보여줍니다.
유효한 구성이 충족되면 TouchGFX Generator에서 이 뱅크를 선택할 수 있습니다. MCU에 대한 FMC 뱅크 레지스터의 시작 주소를 확인하십시오.
TouchGFX Generator는 FMC 뱅크의 구성을 확인하고 확인한 문제를 보고합니다.
HAL::flushFrameBuffer()에서 반환
함수가 값을 반환하면 TouchGFX Engine은 계속해서 프레임의 나머지 부분을 그립니다. DMA를 사용해 픽셀을 디스플레이로 전송하고 싶은 개발자는 예를 들어 DMA Completed 인터럽트에 의해 시그널링된 세마포어에서 대기를 하는 방식으로 HAL::flushFrameBuffer(Rect& rect)
가 값을 즉시 반환하지 않도록 해야 합니다.
아래의 의사 코드는 DMA가 사용되는 경우에 HAL::flushFrameBuffer()
를 구성하는 방법에 대한 예를 보여줍니다. 이 코드는 FreeRTOS 세마포어 screen_frame_buffer_sem
을 사용합니다.
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
uint16_t* fb = HAL::lockFrameBuffer();
//Prepare display: Set cursor, write to display gram as described previously in this scenario
//Try to take a display semaphore - Always free at this point
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Set up DMA
screenDMAEnable();
// Wait for the DMA transfer to complete
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Unlock framebuffer and give semaphore back
HAL::unlockFrameBuffer();
xSemaphoreGive(screen_frame_buffer_sem);
}
Caution
TouchGFX Driver/Te(Tearing Effect) 신호
위의 TouchGFX Generator 구성에서 알 수 있듯이 임베디드 TFT 컨트롤러가 장착되지 않은 MCU에서 보통 그러하듯 "Application Tick Source" 역시 "Custom"으로 설정되어 있습니다.
AL(Abstraction Layer) 아키텍처 섹션에서 설명했듯이 TouchGFX Engine 메인 루프는 보통 디스플레이에서 시그널링이 수행될 때 OSWrappers::signalVSync()
를 호출하는 방식으로 차단이 해제됩니다.
시리얼 또는 8080 디스플레이 인터페이스를 갖춘 디스플레이의 경우, 보통 임베디드 디스플레이 컨트롤러가 MCU의 GPIO에 연결할 수 있는 TE(Tearing Effect) 신호를 발생시킵니다. 이 경우, 일반적으로 GPIO가 시그널링될 때 인터럽트를 생성하도록 MCU가 구성되어 있습니다. 이러한 "Tearing Effect" 인터럽트는 다음 프레임을 렌더링하도록 TouchGFX Engine 메인 루프의 차단을 해제합니다. 반드시 STM32CubeMX의 핀에 대한 외부 인터럽트를 입력하고 활성화하도록 GPIO를 구성하십시오.
extern "C"
void TE_Handler(void)
{
...
/* Unblock TouchGFX Engine Main Loop to render next frame */
OSWrappers::signalVSync();
...
}
결론
TouchGFX Generator를 통해 Custom Display Interface를 선택한다는 것은 개발자가 직접 애플리케이션 프레임 버퍼에서 디스플레이로 픽셀을 전송하도록 코드를 작성하겠다는 의사를 표현하는 것입니다.
TouchGFX Generator는 프레임 버퍼의 영역을 렌더링한 이후에 TouchGFX에서 자동으로 호출되는 TouchGFXHAL::flushFrameBuffer(Rect& rect)
함수를 생성합니다. 개발자는 영향을 받는 픽셀을 디스플레이, SPI, FMC 등에 전송하는 데 이 함수를 사용할 수 있습니다. 그럼에도 불구하고, 두 경우 모두 아래의 단계들을 완료해야 합니다.
- 다시 그리게 될 프레임 버퍼의 영역에 따라 "display cursor"와 "active window"를 이 영역과 일치하는 GRAM의 위치로 옮깁니다.
- GRAM에 입력되는 픽셀 데이터를 기록할 준비를 합니다.
- 픽셀 데이터를 전송합니다. FMC 디스플레이 인터페이스의 경우, 개발자를 위해 이 함수가 자동 생성되어
flushFrameBuffer(Rect& rect)
내에서 사용할 수 있습니다(이 문서의 앞부분 참조).
맞춤형 또는 FMC 디스플레이 인터페이스를 선택하려면 개발자가 OSWrappers::signalVSync()
에 신호를 전송하도록 맞춤형 TouchGFX Application Tick 드라이버를 구현하여 TouchGFX Engine 메인 루프에 대한 차단을 해제해야 합니다. 일반적으로 TFT 컨트롤러가 장착되지 않은 MCU에서 사용되는 디스플레이는 MCU에 연결된 TE 신호를 제공할 수 있습니다.