주요 내용으로 건너뛰기

Abstraction Layer Architecture

이전 섹션에서 설명했듯이 TouchGFX AL은 특정한 책임을 맡고 있습니다. 책임은 AL(HAL)의 하드웨어 부분이나 RTOS(OSAL)를 통해 TouchGFX Engine과 동기화되는 AL 부분에서 구현됩니다. 아래 표에는 이전 섹션에서 설명한 이러한 책임들이 요약되어 있습니다.

책임운영 체제 또는 하드웨어
TouchGFX Engine 메인 루프를 디스플레이 전송과 동기화운영 체제 및 하드웨어
터치 및 물리적 버튼 이벤트 보고하드웨어
프레임 버퍼 액세스 동기화운영 체제
차기 가용 프레임 버퍼 영역 보고하드웨어
렌더링 작업 수행하드웨어
디스플레이에 프레임 버퍼 전송 처리 하드웨어

아래의 각 하위 섹션에는 위의 책임들을 이행하기 위해 수행해야 하는 작업이 명시되어 있습니다. 커스텀 하드웨어 플랫폼의 경우, STM32CubeMX 내부의 TouchGFX Generator는 대부분의 AL과 이에 수반되는 TouchGFX 프로젝트를 생성할 수 있습니다. AL 개발자가 수동으로 구현해야 하는 나머지 부분은 TouchGFX Generator를 통한 코드 주석 및 알림을 통해 지정됩니다. 다음 섹션에서 TouchGFX Generator에 대해 자세히 알아보십시오.

AL(Abstraction Layer) 클래스

TouchGFX AL은 구체적인 하위 클래스와 TouchGFX Engine에 정의된 클래스의 멤버 함수가 구현되는 클래스 구현 파일(.cpp)을 통해 TouchGFX Engine이 액세스합니다. 이러한 하위 클래스 및 클래스 구현 파일은TouchGFX Generator에서 생성됩니다. TouchGFX Generator는 STM32CubeMX의 구성을 반영하는 HAL 부분과 사용된 RTOS에 대한 OSAL을 모두 생성할 수 있습니다. 자세한 내용은 TouchGFX Generator 섹션을 참조하시기 바랍니다. 일반적으로 HAL의 아키텍처는 아래 그림과 같습니다.

생성된 코드의 계층 구조

TouchGFX Engine 메인 루프를 디스플레이 전송과 동기화

이는 기본적으로 렌더링이 완료될 때 TouchGFX Engine 메인 루프를 차단하여 추가 프레임이 생성되지 않도록 하기 위한 단계입니다. 디스플레이가 준비되면 OSAL은 차단된 TouchGFX Engine 메인 루프를 시그널링 하여 프레임을 계속 생성합니다.

이러한 책임을 이행하기 위해 TouchGFX AL이 일반적으로 사용하는 방법은 Responsibilities of the Abstraction Layer에 설명되어 있듯이 엔진 후크 Rendering done과 인터럽트 Display Ready를 활용하는 것입니다. OSAL은 개발자가 호출 시 TouchGFX Engine이 대기하게 세마포어에 신호를 보낼 수 있도록 OSWrappers::signalVSync 함수를 정의하합니다.

Tip
TouchGFX Generator는 RTOS를 사용하지 않을 때 CMSIS V1, CMSIS V2, ThreadX를 위한 완전한 OSAL을 생성할 수 있습니다.

Rendering Done

Rendering done 후크인 OSWrappers::waitForVSync는 렌더링이 완료된 후에 TouchGFX Engine에서 호출됩니다.

이 OSAL 메서드를 구현할 때 추상화 계층은 그 다음 프레임을 렌더링할 때까지 그래픽 엔진을 차단해야 합니다. 이 블록을 구현하는 표준 메서드는 메시지 대기열에서 읽기 차단을 수행하는 것입니다. 이것이 가능하지 않은 경우, HAL 개발자는 블록을 구현하기 위해 어떤 메서드이든 자유롭게 사용할 수 있습니다.

Tip
또한 TouchGFX Generator는 이러한 소프트웨어를 사용할 수 없는 경우에 RTOS의 기본 요소들이 아닌 스핀락(spinlock) 을 사용해 대기하도록 빈 OSAL을 생성할 수도 있습니다.

OSWrappers::signalVSync가 신호를 받으면(또는 OSWrappers::waitForVSync에서 사용되는 세마포어/대기열이 신호를 받으면) TouchGFX는 그 다음 애플리케이션 프레임을 렌더링하기 시작합니다. CMSIS V2에 기반한 다음 코드는 요소가 시스템의 다른 부분(일반적으로 디스플레이에 동기화된 인터럽트)에 의해 대기열에 추가될 때까지 TouchGFX Engine을 차단하도록 합니다.

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
  • While TouchGFX Engine은 다른 작업들을 통해 중요한 작업을 수행할 수 있는 다음 프레임을 생성할 때까지 대기합니다.
  • Display ready

    메인 루프의 차단을 해제하는 Display ready 신호가 디스플레이 컨트롤러, 디스플레이 자체, 심지어 하드웨어 타이머의 인터럽트에서 나옵니다. 신호 소스는 디스플레이 유형에 따라 다릅니다.

    OSWrappers 클래스는 이 신호에 대해 함수 OSWrappers::signalVsync를 정의합니다. 구현된 함수는 OSWrappers::waitForVSync에 사용되는 대기 조건을 충족하여 메인 루프를 차단 해제해야 합니다.

    위의 CMSIS V2 예제에서 계속하자면, 다음 코드는 TouchGFX Engine의 차단을 해제하는 메시지 대기열 vsync_queue에 메시지를 저장합니다.

    OSWrappers.cpp (CMSIS V2)
    void OSWrappers::signalVSync()
    {
    osMessageQueuePut(vsync_queue, &dummy, 0, 0);
    }

    OSWrappers::signalVSync 메서드는 예를 들어 디스플레이나 하드웨어 타이머에서 나온 외부 신호인 LTDC를 위한 인터럽트로부터 하드웨어 수준에서 호출이 되어야 합니다.

    RTOS를 사용하지 않는 경우에는 변수를 사용하고 0이 아닌 값을 할당해서 while 루프를 종료해야 합니다.

    OSWrappers.cpp (No OS)
    void OSWrappers::signalVSync()
    {
    vsync_sem = 1;
    }

    터치 및 물리적 버튼 이벤트 보고

    TouchGFX Engine은 새 프레임을 렌더링하기 전에 TouchControllerButtonController 인터페이스에서 외부 입력을 수집합니다.

    터치 좌표

    터치 컨트롤러의 좌표는 TouchGFX Engine에서 클릭, 드래그 및 제스처 이벤트로 변환되어 애플리케이션에 전달됩니다.

    TouchGFX Engine은 구현된 이벤트를 전달하는 방법으로 터치 컨트롤러에서 조정된 터치 좌표에 액세스합니다.

    다음 코드는 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
    TouchGFX Generator는 TouchController 인터페이스 함수를 빈 함수로 정의하는 클래스를 생성합니다. Hal 개발자는 함수를 채워야 합니다.

    이러한 함수를 구현하는 방법에는 몇 가지가 있습니다.

    1. sampleTouch() 에서 폴링: 요청을 전송하고 결과에 대해 폴링을 수행함으로써 하드웨어 터치 컨트롤러(보통 I2C)에서 터치 상태를 읽어옵니다. 그래픽 엔진이 차단되는 동안 I2C 왕복에 최대 1ms의 시간이 소요되는 경우가 종종 있다는 점에서 이는 애플리케이션의 전체 렌더링 시간에 영향을 줍니다.
    2. 인터럽트 기반: 인터럽트를 사용하는 방법도 가능합니다. 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
    ButtonController를 통해 샘플링된 값을 TouchGFX Designer의 상호 작용을 위한 트리거로 사용하는 자세한 방법은 상호 작용 문서를 참조하십시오.

    프레임 버퍼 액세스 동기화

    다수의 동작들이 프레임 버퍼 메모리를 액세스하는 데 관심을 가질 수 있습니다.

    1CPU렌더링 동안 픽셀 읽기 및 쓰기
    2DMA2D*하드웨어 지원 렌더링 동안 픽셀 읽기 및 쓰기
    3LTDC병렬 RGB 디스플레이에 전송하는 동안 픽셀 읽기
    4DMASPI 디스플레이에 전송하는 동안 픽셀 읽기

    TouchGFX Engine은 OSWrappers 인터페이스를 통해 프레임 버퍼 액세스를 동기화하며, 동일한 프레임 버퍼에 액세스하고 싶은 주변 장치(예: DMA2D)는 이와 똑같이 해야 합니다. 일반적인 설계에서는 세마포어를 사용하여 프레임 버퍼에 대한 액세스를 보호하지만, 다른 동기화 메커니즘을 사용할 수도 있습니다.

    아래 표에는 TouchGFX Generator를 통해서 생성하거나 사용자가 직접 생성할 수 있는 OSWrappers 클래스(OSWrappers.cpp)의 함수 목록이 나와 있습니다.

    메서드설명
    takeFrameBufferSemaphore프레임 버퍼에 대한 독점 액세스 권한을 얻기 위해 TouchGFX Engine에서 호출됩니다. 이 메서드는 DMA2D가 수행될 때까지(실행되는 경우) TouchGFX Engine을 차단합니다.
    tryTakeFrameBufferSemaphore잠금이 실행되었는지 확인합니다. 이 메서드는 차단을 직접 하지 않고 takeFrameBufferSemaphore에 대한 다음 호출이 호출자를 차단하는지 확인합니다.
    giveFrameBufferSemaphore프레임 버퍼 잠금을 해제합니다.
    giveFrameBufferSemaphoreFromISR인터럽트 컨텍스트에서 프레임 버퍼 잠금을 해제합니다.
    Tip
    TouchGFX Generator는 OSWrappers 인터페이스를 사용해 동기화하는 ChromART 드라이버를 비롯해, 선택한 RTOS에 따라 이러한 동기화를 수행하는 함수를 생성할 수 있습니다.

    차기 가용 프레임 버퍼 영역 보고

    렌더링 전략에 관계없이 TouchGFX Engine은 각각의 틱에서 픽셀을 렌더링해야 하는 메모리 영역을 알아야 합니다. TouchGFX Engine은 단일 또는 이중 프레임 버퍼 전략을 사용하여 프레임 버퍼의 전체 너비, 높이 및 비트 심도에 따라 메모리 영역에 픽셀 데이터를 기록합니다. 또한 이중 버퍼 설정에서 두 버퍼 간의 스왑도 관리합니다.

    프레임 버퍼에 대한 액세스 권한을 프레임 버퍼의 일부로 제한하는 것도 가능합니다. HAL::getTFTCurrentLine() 메소드는 HAL 하위 클래스에서 재구현이 가능합니다. TouchGFX Engine이 그리기를 위해 저장한 메소드 위의 라인 번호를 반환합니다.

    개발자는 부분 프레임 버퍼 전략을 사용해 TouchGFX Engine이 렌더링 시 사용하게 될 메모리의 블록을 1개 이상 정의합니다. 이에 대한 자세한 내용은 여기를 참조하십시오.

    Tip
    TouchGFX Generator는 지원되는 모든 프레임 버퍼 전략을 위한 구성을 제공할 수 있습니다.

    렌더링 작업 수행

    그래픽 렌더링 및 표시가 애플리케이션의 유일한 목적인 경우는 거의 없습니다. 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
    많은 STM32 MCU들이 ChromART 칩을 가지고 있어서 픽셀에 대한 알파 블렌딩을 수행하는 동안 예를 들면 외부 플래시 메모리에서 프레임 버퍼로 데이터를 옮길 수 있습니다. 많은 MCU에서 TouchGFX Generator는 ChromART 칩을 사용해 몇몇 "블릿(blit)" 연산 기능을 추가하는 ChromART 드라이버를 생성할 수 있습니다.

    디스플레이에 프레임 버퍼 전송 처리

    프레임 버퍼를 디스플레이에 전달하기 위해 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
    }
    }
    Further reading
    다양한 디스플레이 인터페이스를 지원하는 방법의 구체적인 예는 시나리오를 참조하십시오.