Abstraction Layer Architecture
이전 섹션에서 설명했듯이 TouchGFX AL은 특정한 책임을 맡고 있습니다. Responsibilities are either implemented in the hardware part of the AL (HAL) or the part of the AL that synchronizes with TouchGFX Engine, typically through an RTOS (OSAL). The following table summarizes these responsibilities which were outlined in the previous section:
책임 | 운영 체제 또는 하드웨어 |
---|---|
TouchGFX Engine 메인 루프를 디스플레이 전송과 동기화 | 운영 체제 및 하드웨어 |
터치 및 물리적 버튼 이벤트 보고 | 하드웨어 |
프레임 버퍼 액세스 동기화 | 운영 체제 |
차기 가용 프레임 버퍼 영역 보고 | 하드웨어 |
렌더링 작업 수행 | 하드웨어 |
디스플레이에 프레임 버퍼 전송 처리 | 하드웨어 |
아래의 각 하위 섹션에는 위의 책임들을 이행하기 위해 수행해야 하는 작업이 명시되어 있습니다. For custom hardware platforms the TouchGFX Generator, inside STM32CubeMX, can generate most of the AL and accompanying TouchGFX project. The remaining parts, that the AL developer must implement manually, are pointed out through code comments and notifications through the TouchGFX Generator. Read more about the TouchGFX Generator in the next section.
AL(Abstraction Layer) 클래스
The TouchGFX AL is accessed by the TouchGFX Engine through concrete sub-classes and through class implementation files (.cpp) where member functions of classes defined in the TouchGFX Engine are implemented. These sub-classes and class implementation files are generated by the TouchGFX Generator. The TouchGFX Generator can generate both the part of the HAL that reflects configurations from STM32CubeMX, as well as the OSAL for the used RTOS. Please read the section on TouchGFX Generator for further details. 일반적으로 HAL의 아키텍처는 아래 그림과 같습니다.
TouchGFX Engine 메인 루프를 디스플레이 전송과 동기화
The main idea behind this step is to block the TouchGFX Engine main loop when rendering is done, ensuring that no further frames are produced. 디스플레이가 준비되면 OSAL은 차단된 TouchGFX Engine 메인 루프를 시그널링 하여 프레임을 계속 생성합니다.
In order to fulfill this responsibility the typical way of a TouchGFX AL is to utilize the engine hook Rendering done and the interrupt Display Ready, as outlined in Responsibilities of the Abstraction Layer. The OSAL defines a function OSWrappers::signalVSync
in which developers can signal the semaphore that the engine waits upon when called.
Tip
Rendering Done
Rendering done 후크인 OSWrappers::waitForVSync
는 렌더링이 완료된 후에 TouchGFX Engine에서 호출됩니다.
When implementing this OSAL method, the Abstraction Layer must block the graphics engine until it is time to render the next frame. 이 블록을 구현하는 표준 메서드는 메시지 대기열에서 읽기 차단을 수행하는 것입니다. The HAL developer is free to use any method to implement the block if this is not feasible.
Tip
When OSWrappers::signalVSync
is signaled (or the semaphore/queue used in OSWrappers::waitForVSync
is signaled) TouchGFX will start rendering the next application frame. The following code based on CMSIS V2 causes the TouchGFX engine to block until an element is added to the queue by another part of the system, typically an interrupt synchronized with the display.
OSWrappers.cpp (CMSIS V2)
static osMessageQueueId_t vsync_queue = NULL; //Queue identifier is assigned elsewhere
void OSWrappers::waitForVSync()
{
uint32_t dummyGet;
// First make sure the queue is empty, by trying to remove an element with 0 timeout.
osMessageQueueGet(vsync_queue, &dummyGet, 0, 0);
// Then, wait for next VSYNC to occur.
osMessageQueueGet(vsync_queue, &dummyGet, 0, osWaitForever);
}
RTOS를 사용하지 않는 경우에는 TouchGFX Generator가 휘발성 변수를 사용해 waitForVSync
에 대해 다음과 같이 함수를 구현합니다.
OSWrappers.cpp (No OS)
static volatile uint32_t vsync_sem = 0;
void OSWrappers::waitForVSync()
{
if(vsync_sem)
{
vsync_sem = 0;
// Signal TouchGFX to start rendering next frame
...
}
}
Tip
Display ready
The Display ready signal to unblock the main loop should come from an interrupt from a display controller, from the display itself or even from a hardware timer. The source of the signal is dependent on the type of display.
OSWrappers
클래스는 이 신호에 대해 함수 OSWrappers::signalVsync
를 정의합니다. The implementation of the function must unblock the main loop by satisfying the wait condition used in OSWrappers::waitForVSync
.
Continuing from the above CMSIS V2 example, the following code puts a message into the message queue vsync_queue
which unblocks the TouchGFX Engine.
OSWrappers.cpp (CMSIS V2)
void OSWrappers::signalVSync()
{
osMessageQueuePut(vsync_queue, &dummy, 0, 0);
}
This OSWrappers::signalVSync
method must be called at hardware level from an interrupt for e.g. an LTDC, an external signal from the display, or a hardware timer.
RTOS를 사용하지 않는 경우에는 변수를 사용하고 0이 아닌 값을 할당해서 while 루프를 종료해야 합니다.
OSWrappers.cpp (No OS)
void OSWrappers::signalVSync()
{
vsync_sem = 1;
}
터치 및 물리적 버튼 이벤트 보고
Before rendering a new frame, the TouchGFX Engine collects external input from the TouchController
and ButtonController
interfaces.
터치 좌표
Coordinates from the touch controller are translated into click-, drag- and gesture events by the TouchGFX Engine and passed to the application.
The way touch coordinated from the touch controller is accessed by the TouchGFX Engine is by passing an implementation
The following code is generated by the TouchGFX Generator:
TouchGFXConfiguration.cpp
static STM32TouchController tc;
...
static TouchGFXHAL hal(dma, display, tc, 390, 390);
TouchGFX Engine는 렌더링 주기 동안 입력을 수집할 때 tc 객체에서 sampleTouch()
함수를 호출합니다.
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
AL 개발자가 제공하는 함수는 읽기 터치 좌표 값을 x와 y로 할당하고 터치 감지 여부(true 또는 false)를 반환해야 합니다.
Tip
이러한 함수를 구현하는 방법에는 몇 가지가 있습니다.
- sampleTouch() 에서 폴링: 요청을 전송하고 결과에 대해 폴링을 수행함으로써 하드웨어 터치 컨트롤러(보통 I2C)에서 터치 상태를 읽어옵니다. 그래픽 엔진이 차단되는 동안 I2C 왕복에 최대 1ms의 시간이 소요되는 경우가 종종 있다는 점에서 이는 애플리케이션의 전체 렌더링 시간에 영향을 줍니다.
- 인터럽트 기반: 인터럽트를 사용하는 방법도 가능합니다. I2C 읽기 명령은 타이머에 의해, 또는 터치 하드웨어에서 나온 외부 인터럽트에 대한 응답으로서 주기적으로 시작됩니다. I2C 데이터가 가용 상태가 되면(또 다른 인터럽트) 이를 메시지 대기열이나 전역 변수를 통해 STM32TouchController에서 사용할 수 있습니다.
STM32TouchController.cpp
(TouchGFX Generator에서 생성됨)에서 나온 아래 코드는sampleTouch
가 RTOS를 이용해 시스템을 검색하는 방식을 보여줍니다.
STM32TouchController.cpp
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
{
if (osMessageQueueGet(mid_MsgQueue, &msg, NULL, 0U) == osOK)
{
x = msg.x;
y = msg.y;
return true;
}
return false;
}
이 파일의 위치는 TouchGFX Generator에 대한 다음 장에 나와 있습니다.
기타 외부 이벤트
버튼 컨트롤러 인터페이스인 touchgfx::ButtonController
는 하드웨어 신호(버튼 또는 기타)를 애플리케이션에 대한 이벤트로 매핑하는 데 사용할 수 있습니다. 이러한 이벤트에 대한 반응을 TouchGFX Designer 내에 구성할 수 있습니다. 이 인터페이스의 사용 방법은 ButtonController를 갖는 것이 필수가 아니라는 점을 제외하고 위의 터치 컨트롤러와 유사합니다.
이 인터페이스의 사용 방법은 ButtonController를 갖는 것이 필수가 아니라는 점을 제외하고 위의 터치 컨트롤러와 유사합니다. 사용을 위해 클래스의 인스턴스를 생성하여 ButtonController
인터페이스를 구현하고 HAL에 인스턴스에 대한 참조를 전달합니다.
MyButtonController.cpp
class MyButtonController : public touchgfx::ButtonController
{
bool sample(uint8_t& key)
{
... //Sample IO, set key, return true/false
}
};
TouchGFXConfiguration.cpp
static MyButtonController bc;
void touchgfx_init()
{
...
hal.initialize();
hal.setButtonController(&bc);
}
ButtonController 클래스의 sample 메서드는 각 프레임에 앞서 호출됩니다. True를 반환하면 이 값이 현재 화면의 handleKeyEvent 이벤트 핸들러에 전달됩니다.
Further reading
프레임 버퍼 액세스 동기화
다수의 동작들이 프레임 버퍼 메모리를 액세스하는 데 관심을 가질 수 있습니다.
1 | CPU | 렌더링 동안 픽셀 읽기 및 쓰기 |
2 | DMA2D* | 하드웨어 지원 렌더링 동안 픽셀 읽기 및 쓰기 |
3 | LTDC | 병렬 RGB 디스플레이에 전송하는 동안 픽셀 읽기 |
4 | DMA | SPI 디스플레이에 전송하는 동안 픽셀 읽기 |
TouchGFX Engine은 OSWrappers
인터페이스를 통해 프레임 버퍼 액세스를 동기화하며, 동일한 프레임 버퍼에 액세스하고 싶은 주변 장치(예: DMA2D)는 이와 똑같이 해야 합니다. 일반적인 설계에서는 세마포어를 사용하여 프레임 버퍼에 대한 액세스를 보호하지만, 다른 동기화 메커니즘을 사용할 수도 있습니다.
아래 표에는 TouchGFX Generator를 통해서 생성하거나 사용자가 직접 생성할 수 있는 OSWrappers
클래스(OSWrappers.cpp)의 함수 목록이 나와 있습니다.
메서드 | 설명 |
---|---|
takeFrameBufferSemaphore | 프레임 버퍼에 대한 독점 액세스 권한을 얻기 위해 TouchGFX Engine에서 호출됩니다. 이 메서드는 DMA2D가 수행될 때까지(실행되는 경우) TouchGFX Engine을 차단합니다. |
tryTakeFrameBufferSemaphore | 잠금이 실행되었는지 확인합니다. 이 메서드는 차단을 직접 하지 않고 takeFrameBufferSemaphore에 대한 다음 호출이 호출자를 차단하는지 확인합니다. |
giveFrameBufferSemaphore | 프레임 버퍼 잠금을 해제합니다. |
giveFrameBufferSemaphoreFromISR | 인터럽트 컨텍스트에서 프레임 버퍼 잠금을 해제합니다. |
Tip
차기 가용 프레임 버퍼 영역 보고
렌더링 전략에 관계없이 TouchGFX Engine은 각각의 틱에서 픽셀을 렌더링해야 하는 메모리 영역을 알아야 합니다. TouchGFX Engine은 단일 또는 이중 프레임 버퍼 전략을 사용하여 프레임 버퍼의 전체 너비, 높이 및 비트 심도에 따라 메모리 영역에 픽셀 데이터를 기록합니다. 또한 이중 버퍼 설정에서 두 버퍼 간의 스왑도 관리합니다.
프레임 버퍼에 대한 액세스 권한을 프레임 버퍼의 일부로 제한하는 것도 가능합니다. HAL::getTFTCurrentLine()
메소드는 HAL 하위 클래스에서 재구현이 가능합니다. TouchGFX Engine이 그리기를 위해 저장한 메소드 위의 라인 번호를 반환합니다.
개발자는 부분 프레임 버퍼 전략을 사용해 TouchGFX Engine이 렌더링 시 사용하게 될 메모리의 블록을 1개 이상 정의합니다. 이에 대한 자세한 내용은 여기를 참조하십시오.
Tip
렌더링 작업 수행
그래픽 렌더링 및 표시가 애플리케이션의 유일한 목적인 경우는 거의 없습니다. CPU를 사용하기 위해서는 다른 작업들도 필요합니다. TouchGFX의 목표 중 하나는 가능한 적은 CPU 주기를 사용해 사용자 인터페이스를 그리는 것입니다. HAL 클래스는 많은 STM32 마이크로컨틀로러(또는 기타 하드웨어 기능)에서 나타나는 DMA2D를 추상화하여 이를 TouchGFX Engine에서 사용할 수 있게 지원합니다.
TouchGFX Engine은 비트맵 같은 자산을 프레임 버퍼에 렌더링할 때 HAL에 비트맵의 일부 또는 전체를 프레임 버퍼로 '블릿(blit, block transfer)' 할 수 있는 기능이 있는지 확인합니다. 블릿 기능이 있는 경우에는 그리기 작업이 CPU에서 처리되는 대신 HAL에 위임됩니다.
TouchGFX Engine은 HAL::getBlitCaps()
메소드를 호출하여 하드웨어의 기능에 대한 설명을 가져옵니다. HAL 하위 클래스는 기능을 추가하기 위해 이를 다시 구현할 수 있습니다.
TouchGFX Engine은 사용자 인터페이스를 그리는 동안 HAL 클래스에서 연산(예: DMA에 대한 연산을 대기열에 저장하는 HAL::blitCopy)
을 사용합니다. HAL이 필요한 기능을 보고하지 않는 경우, TouchGFX Engine은 소프트웨어 렌더링 폴백(fallback)을 사용합니다.
Tip
디스플레이에 프레임 버퍼 전송 처리
프레임 버퍼를 디스플레이에 전달하기 위해 TouchGFX AL에서는 "Rendering of area complete" 후크가 주로 사용됩니다. TouchGFX Engine은 프레임 버퍼의 일부에 대한 렌더링이 완료되면 AL을 시그널링합니다. AL은 프레임 버퍼의 이 부분을 디스플레이에 전송하는 방법을 선택할 수 있습니다.
전체 영역 렌더링
코드에서 이러한 후크는 가상 함수 HAL::flushFrameBuffer(Rect& rect)
입니다.
LTDC 컨트롤러를 장착한 STM32 마이크로컨트롤러에서는 모든 렌더링 이후에 프레임 버퍼를 전송하기 위한 작업을 수행할 필요가 없습니다. LTDC가 개시된 이후에 일정한 주기로 전송이 계속 이루어지기 때문에 이 메서드를 빈 상태로 남겨둘 수 있습니다.
SPI나 8080 같은 다른 디스플레이 유형에서는 프레임 버퍼를 수동으로 전송해야 합니다.
이 함수를 구현하면 개발자가 GRAM에서 디스플레이에 프레임 버퍼의 해당 영역을 수동 전송하는 작업을 개시할 수 있습니다:
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& r)
{
HAL::flushFrameBuffer(rect); //call superclass
//start transfer if not running already!
if (!IsTransmittingData())
{
const uint8_t* pixels = ...; // Calculate pixel address
SendFrameBufferRect((uint8_t*)pixels, r.x, r.y, r.width, r.height);
}
else
{
... // Queue rect for later or wait here
}
}