주요 내용으로 건너뛰기

저가 하드웨어 기반의 TouchGFX

이 섹션에서는 RAM 및 플래시 용량이 제한되어 있고, 가속 기능이 없으며, 외부 플래시와 디스플레이에 대한 연결이 느린 저가 하드웨어에서 TouchGFX를 사용하는 방법에 대해 설명합니다.

해당 하드웨어에 대해 최고의 애플리케이션을 작성하는 방법에 대한 몇 가지 조언을 제공하고자 합니다.

이 섹션에서는 X-Nucleo-GFX01M1 확장 보드가 포함된 STM32G071 nucleo 보드에서 TouchGFX 보드 설정을 하드웨어 예제로 사용해보겠습니다.

X-Nucleo-GFX01M1 확장 보드가 포함된 Nucleo-G071RB

하드웨어 개요

이 키트의 하드웨어 설정은 STM32G071 MCU, SPI NOR 플래시, SPI 디스플레이 및 조이스틱 버튼으로 구성되어 있습니다.

부품 
MCUSTM32G071RB
MCU RAM32KB
MCU 플래시128KB
디스플레이Displaytech DT022CTFT
디스플레이 해상도240 x 320
디스플레이 컨트롤러ILI9341V
디스플레이 연결SPI
디스플레이 연결 속도32MHz
NOR 플래시Macronix MX25L6433F
NOR 플래시 크기64MB
NOR 플래시 연결 속도32MHz

디스플레이는 SPI1 주변 장치에 연결되고, 플래시는 SPI2 주변 장치에 연결되어 있습니다. 따라서 MCU는 데이터를 디스플레이로 전송하는 동안 플래시에서 데이터를 읽을 수 있습니다.

X-Nucleo-GFX01M1 아키텍처를 갖춘 Nucleo-G071RB

GPIO 할당

신호GPIO 핀
디스플레이 CSPB5
디스플레이 DCXPB3
디스플레이 SCKPA5
디스플레이 MOSIPA7
디스플레이 TEPA0
플래시 CSPB9
플래시 SCKPB13
플래시 MOSIPC3
플래시 MISOPC2

위의 표에는 플래시와 디스플레이에 대한 GPIO 할당 신호 목록이 나와 있습니다. 이러한 신호는 오실로스코프나 로직 분석기를 통해 모니터링할 수 있습니다. 이러한 모니터링 기능은 예를 들어 성능 문제를 디버깅할 때 매우 유용합니다.

프로젝트 시작

TouchGFX Desinger에서 STM32G071RB Nucleo 평가 키트를 위한 프로젝트를 손쉽게 시작할 수 있습니다. "Create New" 버튼을 클릭하고 STM32G071 Nucleo를 검색합니다. 이러한 보드 설정은 X-Nucleo-GFX01M1 디스플레이 실드가 있는 Nucleo-G071RB 키트용으로 특별히 개발되었습니다.

Nucleo-G071RB를 위한 새 프로젝트

TouchGFX 보드 설정은 NOR 플래시, 디스플레이 및 버튼을 지원합니다. 디스플레이는 세로 및 가로 모드에서 모두 사용이 가능합니다.

디스플레이 방향은 Config -> Display 섹션의 TouchGFX Designer에서 변경할 수 있습니다.

디스플레이 방향으로 세로 또는 가로 선택

X-Nucleo-GFX01M1 실드의 디스플레이는 기본적으로 세로 방향(넒기 보다는 높은)이지만, 가로 방향에서도 손쉽게 사용할 수 있습니다.

디스플레이 업데이트

위에서 언급했듯이 디스플레이 해상도는 240 x 320 픽셀로, 총 76.800 픽셀 또는 153.600 바이트입니다. MCU와 디스플레이 간의 SPI 연결은 32MHz의 속도로 실행됩니다. 따라서 초당 4MB 또는 초당 2M 픽셀을 전송할 수 있습니다.

디스플레이의 화면 재생률은 76.1Hz이고, 프레임 시간은 13.14ms입니다.

디스플레이의 TE(Tearing Effect) 신호

이는 다음 프레임에서 최대 데이터 전송 속도가 13ms가 됨을 의미입니다. 이 시간 동안 초당 200만 개 픽셀 / 76 fps = 26.280 픽셀/프레임 또는 전체 화면의 34%를 전송할 수 있습니다.
실제로는 프로토콜 오버헤드로 인해 SPI 버스에서 이 정도의 전송 속도를 유지할 수 없기 때문에 전체 프레임의 약 30% 이상이 전송될 것으로 기대할 수 없습니다.

애플리케이션이 해당 픽셀 수보다 많이 업데이트되는 경우에는 하드웨어가 프레임 시간 내에 전송을 완료할 수 없게 됩니다. 결과적으로 업데이트가 완료되기 전에 디스플레이에 업데이트된 프레임이 표시되기 시작합니다. 따라서 이전 프레임과 새 프레임이 혼합되어 표시되는 경우가 발생합니다.

이러한 현상은 애니메이션에 따라 사용자의 눈에 띄지 않을 때도 있고, 용납할 수 없을 만큼 자주 나타날 수도 있습니다.

따라서 업데이트 수준을 30% 미만으로 유지하는 것이 좋습니다. 예를 들면 프레임을 단계별로, 점진적으로 업데이트하는 방법이 있습니다.

이 경우, 보통은 항목을 이동시키는 것보다는 화면에서 항목을 확장하는 것이 더 효과적입니다.

디스플레이 TE(Tearing Effect) 신호

별 모양이 오른쪽으로 이동할 때는 별 모양에 가려져 있던 모든 픽셀을 업데이트해야 합니다. 별 모양을 확장할 때는 새 픽셀만 업데이트해야 합니다. 이전 프레임에서 업데이트된 픽셀은 변경되지 않은 상태로 유지됩니다.

그리기 속도

디스플레이 전송은 최대 32MHz의 속도로 실행됩니다.

직렬 플래시는 디스플레이 전송과 동일한 속도로 실행이 가능합니다. 이는 직렬 플래시가 최대 속도로 디스플레이에 비트를 공급할 만큼 충분히 빠르다는 것을 의미합니다.

단, 플래시에 있는 이미지의 픽셀 형식이 RGB565인 경우에만 가능합니다. 이 때 플래시에서 읽어들인 2바이트는 1픽셀에 해당하며, 1픽셀은 디스플레이의 2바이트에 해당합니다.
플래시의 픽셀 형식이 ARGB8888인 경우, 디스플레이에 픽셀을 생성하려면 플래시에서 두 배의 데이터를 읽어와야 하기 때문에 직렬 플래시가 디스플레이의 속도를 따라갈 수 없게 됩니다.

이런 현상이 발생하면 디스플레이에 데이터를 지속적으로 전송할 수 없게 되고, 프레임에서 디스플레이의 30%를 모두 업데이트하는 것이 불가능해집니다. 이미지를 내부 플래시로 옮기고 픽셀 형식을 변경하는 것은 가능합니다.

다른 위젯은 플래시의 속도에 구애받지 않습니다. 예를 들면 유색의 사각형을 그리는 Box Widget이 그렇습니다. 물론 이 위젯은 속도가 매우 빠르며 디스플레이 전송보다 훨씬 빠릅니다. 선이나 원과 같은 다른 위젯들은 CPU 리소스를 훨씬 더 많이 사용합니다. 이들 위젯은 디스플레이에 전송 되는 속도로 픽셀을 생성할 수 없습니다. 이러한 위젯을 사용하면 애플리케이션에서 매 프레임마다 디스플레이의 30%를 업데이트하는 것이 불가능해집니다.

픽셀 렌더링의 복잡성에 대한 내용은 여기를 참조하십시오.

직렬 플래시를 탑재한 TouchGFX의 제한 사항

직렬 플래시를 탑재한 STM32G0 기반의 TouchGFX에는 애플리케이션 프로그래머가 반드시 숙지해야 할 몇 가지 제한 사항이 있습니다.

텍스처 매퍼

텍스처 매퍼 위젯(Texture Mapper, Animation Texture Mapper, Scalable Image)은 외부 SPI 플래시에 저장된 이미지를 그릴 수 없습니다. 예를 들어 직렬 플래시에 저장된 이미지를 회전할 때 허용 가능한 성능을 얻을 수 없기 때문입니다.

텍스처 매퍼에서 이미지를 사용하려면 이미지를 반드시 내부 플래시나 RAM에 저장해야 합니다. TouchGFX Designer에서 이미지 구성을 수정하는 방법으로 내부 플래시에 이미지를 쉽게 저장할 수 있습니다.

Images 탭으로 이동하여 이미지를 선택합니다. 창의 오른쪽에서 "Section" 속성을 "IntFlashSection"으로 변경합니다.

내부 플래시에 이미지 저장

텍스처 매퍼 코드는 크기가 너무 커서 모든 프로젝트에 포함시키기 어렵습니다. 따라서 STM32G0 프로젝트에서는 기본적으로 비활성화되어 있습니다. 즉, STM32G0 프로젝트에서 텍스처 매퍼를 사용하려면 먼저 텍스처 매퍼를 활성화해야 합니다.

"Config" 탭으로 이동하여 "Framework Features"를 선택하고 해당 텍스처 매퍼 또는 텍스처 매퍼 그룹을 클릭합니다.

특정 이미지 형식에 대해 텍스처 매퍼 활성화

비트맵 캐시를 사용해 이미지를 RAM에 일시적으로 옮길 수도 있습니다.

비트맵 페인터

Line, Circle 그리고 Dynamic Graph 위젯은 이미지에서 색을 적용할 수 있습니다. SPI 플래시에 저장된 이미지에서는 이것이 불가능합니다. 이들 위젯과 함께 사용되는 이미지는 반드시 내부 플래시나 RAM에 저장해야 합니다.

L8 팔레트

L8 형식의 이미지는 직렬 플래시를 탑재한 하드웨어에서 사용할 수 있습니다. 단, 팔레트 데이터가 내부 플래시에 저장되어 있어야 합니다(이 역시 성능 상의 이유로).

TouchGFX Designer에서 "Extra Section"을 "IntFlashSection"으로 변경하는 방법으로 이러한 팔레트를 내부 플래시로 옮길 수 있습니다.

드라이버

TouchGFX 보드 설정은 TouchGFX Generator를 사용해 이루어집니다. TouchGFX Generator에 대한 자세한 내용은 여기를 참조하십시오. TouchGFX Generator는 TouchGFX 프레임워크를 저수준 드라이버 세트(이 TouchGFX 보드 설정 시 이미 구현됨)에 연결하는 HAL 계층을 생성합니다. 이 애플리케이션 템플릿의 하위 수준 드라이버는 프로젝트의 Core/Src 폴더에 있습니다.

드라이버는 세 개의 파일에 있습니다.

드라이버파일
디스플레이Core/Src/MB1642BDisplayDriver.c
플래시Core/Src/MB1642BDataReader.c
버튼Core/Src/MB1642BButtonController.cpp

디스플레이

디스플레이는 표준 SPI 프로토콜을 사용합니다. 디스플레이를 구성하기 위해 다수의 레지스터를 작성할 수 있습니다. 데이터가 디스플레이로 전송될 때 선택한 칩이 어설션됩니다. 추가 GPIO인 DCX는 명령 바이트와 데이터 바이트를 구별하는 데 사용됩니다.

드라이버는 DMA 채널을 사용하여 디스플레이 픽셀 데이터를 전송합니다. 따라서 MCU가 픽셀을 계산하는 동안 전송을 실행할 수 있습니다. DMA 완료 인터럽트는 향후 그리기 작업에서 재사용할 수 있도록 전송된 메모리를 확보하거나, 새 데이터가 이미 있는 경우에 전송을 재시작하는 데 사용됩니다.

CS 핀과 CDX 핀은 소규모 구성 패키지 간에, 그리고 패키지 내에서 토글되어야 하므로, 구성 데이터가 DMA와 함께 전송되지 않습니다.

드라이버는 구성 데이터를 전송할 때 8비트 모드에서 SPI를 사용하지만, 픽셀 데이터를 전송할 때는 16비트 모드로 바뀝니다. 왜냐하면 리틀 엔디안(little endian) 모드에서 MCU 메모리를 읽기 때문입니다. RGB565 형식의 픽셀은 낮은 바이트(G, B)에서 높은 바이트(R, G)순으로 RAM에 저장됩니다. 8비트 SPI가 전송을 위해 메모리 읽기 작업을 할 때 이러한 순서가 유지됩니다. SPI가 16비트 모드일 때 데이터는 메모리에서 16비트 RGB565로 읽히고 디스플레이에서 올바른 순서로 전송됩니다.

16비트 DMA를 사용하지 않는 드라이버는 전송에 앞서 픽셀에서 바이트를 교환해야 합니다.

초기화

디스플레이 초기화는 MB1642BDisplayDriver_DisplayInit(void) 함수에서 찾아볼 수 있습니다.

드라이버는 권장되는 전원 켜기 순서에 따라 디스플레이에 6개의 명령을 전송합니다.

  1. Exit Sleep Mode (11h)
  2. Enter Normal Mode (13h)
  3. Set Memory Access Control (36h) with MX and BGR bits set
  4. Set Pixel Format (3Ah) with format 16 bits
  5. Tearing Effect Line On (35h)
  6. Set Tear Scanline (44h) with line = 0

드라이버는 명령들 간에 100ms 동안 휴면 상태가 됩니다.

TE(Tearing Effect)

디스플레이의 TE(Tearing Effect) 신호는 매우 중요합니다. 애플리케이션이 디스플레이와 올바르게 동기화할 수 있게 해주기 때문입니다. 이렇게 하면 애플리케이션에서 디스플레이 깨짐 현상을 방지하는 데 도움이 됩니다. 디스플레이는 업데이트 주기를 시작할 때마다 신호를 어설션합니다. MCU는 디스플레이에 데이터를 전송할 때도 이 신호를 사용합니다.

TE 신호는 MCU의 외부 인터럽트 입력에 연결됩니다. CubeMx는 이 핀에서 인터럽트를 생성 및 구성합니다.

다음과 같이 드라이버에서 콜백 함수를 실행하면 TouchGFX에서 그리기가 시작됩니다.

MB1642BDisplayDriver.c
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
...
touchgfxSignalVSync();
}

외부 플래시

디스플레이 실드의 SPI NOR 플래시는 표준 SPI 플래시입니다. 드라이버는 디스플레이 드라이버보다 간단합니다. 플래시에서 데이터를 읽어오는 데 특별한 초기화 작업이 필요하지 않습니다.

드라이버는 폴링 SPI(각 바이트에서 대기 중) 또는 DMA를 사용하여 데이터를 읽을 수 있습니다. DMA 수신을 시작하는 시간은 폴링 모드에서 20바이트를 읽어 들이는 데 걸리는 시간과 비슷하기 때문에 짧은 읽기 작업에서는 보통 속도가 더 느려집니다. 반면에 DMA는 인터럽트 중에도 계속 실행되기 때문에 MCU가 픽셀을 렌더링하는 중일 때도 백그라운드에서 실행이 가능합니다. 이러한 이유로 두 가지 메서드가 모두 사용됩니다.

플래시 드라이버에서 디스플레이 드라이버가 아닌 다른 DMA 채널을 사용하면 데이터 수신과 픽셀 전송을 동시에 실행할 수 있습니다.

링커 스크립트

링커는 애플리케이션에서 다양한 데이터가 저장되는 위치를 제어합니다. 이는 링커 스크립트에서 지정됩니다. 다음은 gcc 컴파일러용 링커 스크립트의 첫 부분입니다.

MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 36K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
SPI_FLASH (r) : ORIGIN = 0x90000000, LENGTH = 8M
}

NOR 플래시가 주소 0x90000000에서 시작되도록 선언하고 있습니다. 이 값은 임의로 선택되지만, 플래시 로더에서 요구하는 값입니다.

다음 섹션에서는 이미지(ExtFlashSection) 및 글꼴(FontFlashSection) 데이터를 SPI 플래시에 저장합니다.

  ExtFlashSection :
{
*(ExtFlashSection ExtFlashSection.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >SPI_FLASH

FontFlashSection :
{
*(FontFlashSection FontFlashSection.*)
*(.gnu.linkonce.r.*)
. = ALIGN(0x4);
} >SPI_FLASH

링커 스크립트에 비슷한 섹션을 추가하는 방법으로 SPI 플래시에 다른 데이터를 저장할 수 있습니다.

Flash Loader

G071 TouchGFX 보드 설정에는 STM32CubeProgrammer용 플래시 로더가 포함되어 있습니다. 이 플래시 로더는 SPI NOR 플래시에 데이터를 기록할 수 있습니다.

플래시 로더는 gcc/S25FL032P_STM32G071B-NUCLEO.stldr 파일에서 찾을 수 있습니다.

STM32CubeIde 프로젝트는 IDE에서 직접 플래시가 가능하지만, IAR 또는 Keil 애플리케이션은 TM32CubeProgrammer에서 플래시해야 합니다.

STM32CubeProgrammer에서 플래시 로더가 처음부터 제공되는 것은 아니므로 stldr을 복사하는 방법으로 설치해야 합니다.

설치된 STM32CubeProgrammer에 플래시 로더 복사

이제 STM32CubeProgrammer에서 플래시로더를 정상적으로 선택할 수 있습니다.

설치된 STM32CubeProgrammer에 플래시 로더 복사

Tip
플래시 로더는 Nucleo-G071RB 보드에서 사용되는 특정 GPIO 구성에서만 작동합니다.

직렬 플래시에 대해 다른 GPIO 구성이 맞춤형 하드웨어에서 사용되는 경우, 플래시 로더도 이와 유사하게 수정해야 합니다.

버튼

버튼 드라이버는 매우 간단합니다. MB1642B의 조이스틱과 Nucleo 보드의 파란색 사용자 버튼에 사용되는 5개 GPIO의 상태를 샘플링합니다.

이 버튼 드라이버는 TouchGFX에서 BottonController로 설치됩니다. 즉, TouchGFX Designer에서 바로 버튼을 눌러 상호 작용을 할 수 있습니다. 다음과 같이 사용자 코드에서도 사용할 수 있습니다.

void Screen1View::handleKeyEvent(uint8_t key)
{
if (key == '6')
{
application().gotoScreen2Screen();
}
}

사용되는 키 코드는 다음과 같습니다.

코드
왼쪽'4'
오른쪽'6'
'8'
아래'2'
중앙'5'
파란색 사용자 버튼'0'

이들 키는 키보드 숫자 패드를 사용해 Simulator에서도 사용할 수 있습니다.