Skip to main content

FMC and SPI Display Interface

아래 시나리오들은 일반적으로 TouchGFX Generator에서 FMC에 연결된 LCD를 사용하거나 SPI를 통해 사용자 지정 디스플레이 인터페이스를 선택할 때 TouchGFX 드라이버를 작성하는 데 관련된 단계들을 보여줍니다.

Further reading
TouchGFX Designer에서 지원되는 STM32L496-DISCO 애플리케이션 템플릿은 FMC를 사용하는데, 이를 조사하여 TouchGFX 디스플레이 드라이버를 구현하는 방법에 대한 힌트를 얻을 수 있습니다.

FMC 또는 SPI에 대한 임베디드 디스플레이 컨트롤러가 장착되지 않은 MCU용으로 TouchGFX 디스플레이 드라이버를 작성하는 프로세스도 동일합니다. 이 섹션에 기술된 시나리오에서는 예증을 위해 ST7789H2 LCD 컨트롤러를 사용합니다.

CubeMX 의 보드 사양에 따라 FMC 또는 SPI가 구성이 되면 Custom 디스플레이 인터페이스를 선택하여 TouchGFX Generator를 HAL을 생성하는 데 사용할 수 있습니다.

아래 그림에는 Custom Display Interface가 선택된 TouchGFX Generator 구성이 나와 있습니다. 이 구성은 개발자가 원하는 대로 픽셀을 구성해서 프레임 버퍼 메모리에서 디스플레이로 직접 전송하도록 TouchGFX Generator에게 명령하고, 이를 수행하기 위한 핸들을 생성합니다.

Note
이 섹션에 나와 있는 ST7789H2를 위한 드라이버 코드는 보드 브링업 단계에서 개발되었고, 일단 작동이 되면 대개는 TouchGFX Generator에서 생성된 HAL 클래스에 복사가 가능합니다.

드라이버는 픽셀을 디스플레이에 전송하고 디스플레이의 메모리 작성 위치를 제어할 수 있어야 합니다. 해당하는 명령(아래에 간략하게 설명) 과 추가적인 세부 사항은 데이터 시트를 참조하십시오.

TouchGFX Generator 구성

일반적으로 8080 또는 SPI 디스플레이 같이 임베디드 GRAM을 탑재한 디스플레이의 경우, 드라이버는 다음과 같이 작동합니다.

  1. 다시 그리게 될 프레임 버퍼의 영역에 따라 "display cursor"와 "active window"를 이 영역과 일치하는 GRAM의 위치로 옮깁니다.
  2. GRAM에 입력되는 픽셀 데이터를 기록할 준비를 합니다.
  3. 픽셀 데이터를 전송합니다.

프레임 버퍼 전송

프레임 버퍼의 영역이 업데이트되면 TouchGFX Engine에서 HAL::flushFrameBuffer(Rect r)를 호출합니다. 이 함수는 개발자가 '사용자 지정' 디스플레이 인터페이스에 대한 드라이버를 구현해야 할 때 재지정이 가능합니다.

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 */
this->copyFrameBufferBlockToLCD(rect);
}

아래의 __ST7789H2_SetDisplayWindow 함수는 GRAM을 사용하는 디스플레이에서 보통 그러하듯이 특정 레지스터에 기록하여 GRAM의 가상 "cursor"에 대한 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) * BSP_LCD_GetXSize();
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;
}

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

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

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 메인 루프의 차단을 해제합니다. 입력에 대한 GPIO를 구성하고 CubeMX의 핀에 대해 외부 인터럽트를 활성화해야 한다는 점에 유의하십시오.

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 등에 전송하는 데 이 함수를 사용할 수 있습니다.

또한 custom 디스플레이 인터페이스를 선택하기 위해서는 개발자가 TouchGFX Engine 메인 루프의 차단을 해제하도록 OSWrappers::signalVSync()를 시그널링 하는 사용자 지정 TouchGFX Application Tick 드라이버를 구현해야 합니다. 일반적으로 TFT 컨트롤러가 장착되지 않은 MCU에서 사용되는 디스플레이는 MCU에 연결된 TE 신호를 제공할 수 있습니다.