3. 내부 RAM에서 프레임 버퍼를 가지는디스플레이
개요
이 단계에서는 내부 RAM에서 디스플레이로 픽셀 데이터를 전송함으로써 디스플레이를 구동시켜보겠습니다. 이를 통해 데이터를 디스플레이에 전송할 수 있고, 디스플레이 내용을 지속적으로 업데이트할 수 있는지 확인하겠습니다.
또한 이미지 데이터를 디스플레이에 전송하는 것 외에도 어떤 오류도 없이 디스플레이에 새 데이터를 지속적으로 전송할 수 있는지 확인할 것입니다. 또한 전송 속도가 디스플레이에서 확보할 수 있는 프레임률에 영향을 미치므로 전송 속도도 함께 측정해보겠습니다.
이전 섹션에서 알아보았듯이 내부 RAM은 읽기와 쓰기가 모두 가능하다는 점에서 내부 RAM에 프레임 버퍼를 배치하겠습니다. 이러한 프레임 버퍼를 업데이트하여 디스플레이에 반복 전송해보겠습니다.
프레임 버퍼의 크기는 아래 공식에 따라 계산된다는 점을 기억하세요:
너비 x 높이 x bpp
예를 들어 해상도가 480 x 272인 일반적인 16비트 디스플레이는 480x272x16/8 바이트, 즉, 261120 바이트의 공간을 차지합니다.
디스플레이 크기로 미루어 볼 때 프레임 버퍼를 내부 RAM에 저장하기에 그 크기가 너무 큰 경우에는 이 단계를 건너뛰면 안 됩니다. 대신에 디스플레이의 일부만 업데이트하도록 디스플레이 컨트롤러를 구성하십시오. 이렇게 하면 프레임 버퍼에 필요한 RAM의 용량을 조정하고 이를 내부 RAM에 맞출 수 있습니다.
디스플레이 인터페이스의 유형은 프레임 버퍼 전송에 필요한 설정과 코드에 상당한 영향을 미칩니다. 이 섹션에서는 첫 번째 대상으로 LTDC에 연결된 디스플레이를 살펴보겠습니다. 예를 들어 SPI 디스플레이를 사용하고 있다면 코드가 전혀 다를 것입니다. 하지만 작업과 목표는 똑같습니다.
목표
이 섹션의 목표는 디스플레이에 프레임 버퍼를 전송하는 것입니다. 또한 프레임 버퍼의 내용을 수정해서 계속해서 재전송할 수 있다는 것도 확인할 것입니다.
확인
다음은 이 섹션에서 확인해야 할 사항입니다:
확인사항 | 확인근거 |
---|---|
프레임 버퍼의 표시 여부 | 디스플레이 컨트롤러 또는 SPI의 구성 및 동작 여부 |
업데이트된 프레임 버퍼의 표시 여부 | 프레임 버퍼를 반복적으로 전송하는 방법을 알고 있는지 여부 |
색상이 올바른지 여부 | GPIO가 정확한지 여부(LTDC)와 디스플레이의 데이터 형식이 프레임 버퍼와 일치하는지 여부 |
프레임률이 올바른지 여부 | 픽셀 클록과 포치(porch)가 필요한 프레임률을 확보할 수 있도록 구성되어 있는지 여부 |
전제 조건
다음은 이 단계에 대한 전제 조건입니다.
- 디스플레이 정보(일반적으로 제조사 데이터 시트의 정보)
- MCU와 디스플레이 간 연결 정보
실행
디스플레이 유형에 따라 필요한 설정이 다릅니다. 그러나 어떤 유형이든지 내부 RAM에 프레임 버퍼가 필요합니다. 올바른 크기로 전역 배열을 선언하기만 하면 간단하게 메모리를 할당할 수 있습니다.
main.c
/* USER CODE BEGIN PV */
uint16_t framebuffer[480*272]; //16 bpp framebuffer
/* USER CODE END PV */
내부 RAM의 크기가 배열을 수용하기에 충분하지 않으면 480x200과 같이 해상도를 낮춰서 배열을 선언하십시오.
디스플레이에 프레임 버퍼를 전송하기 위한 메서드는 디스플레이 유형에 따라 다릅니다. 그럼 어떻게 다른지 지금부터 살펴보겠습니다.
병렬 RGB 디스플레이
MCU에서 LTDC 컨트롤러에 연결된 병렬 RGB 디스플레이에 대해 먼저 살펴보겠습니다.
이와 같은 디스플레이에 대한 구성 작업은 다음과 같습니다.
- 디스플레이에 대한 GPIO 연결 구성
- LTDC 컨트롤러 구성
- LTDC 픽셀 클록 구성
- 프레임 버퍼 주소 설정
- 프레임률 확인
설명 예제에서와 같이 STM32F746Discovery 평가 키트를 사용하겠습니다. 이 보드에는 480*272 디스플레이가 장착되어 있습니다.
디스플레이 GPIO
이 디스플레이는 24 BPP 모드에서 실행되므로 LTDC와 디스플레이 간의 연결을 위해 24개의 GPIO를 구성합니다. STM32CubeMX에 Multimedia -> LTDC -> GPIO Settings 아래에서 가장 손쉽게 구성할 수 있습니다.
픽셀 전송(예: LTDC_B0)을 위한 24개의 GPIO 외에 4개의 디스플레이 제어 신호를 함께 구성하겠습니다.
신호 | 함수 |
---|---|
LTDC_CLK | 픽셀 클록. 24개 라인에서 픽셀을 샘플링해야 할 타이밍에서 디스플레이에 신호를 전송 |
LTDC_DE | 데이터 활성화 활성화되면 픽셀이 전송됨 |
LTDC_HSYNC | 수평 동기화 디스플레이에서 픽셀 라인 시작점을 찾을 수 있도록 함 |
LTDC_VSYNC | 수직 동기화 디스플레이에서 프레임 시작점을 찾을 수 있도록 함 |
하드웨어 설계를 확인하고 해당하는 구성 작업을 수행하십시오.
LTDC 구성
LTDC 구성은 STM32CubeMX에 Multimedia -> LTDC -> Parameter Settings 아래에서 확인할 수 있습니다.
활성 너비와 높이는 디스플레이 해상도에 따라 결정됩니다. 동기화 펄스 너비와 포치 너비는 디스플레이 데이터 시트에서 확인하십시오. 또한 신호 극성도 함께 확인하십시오. 그레이로 표시된 값들은 다른 값에서 계산됩니다. 이들 값은 LTDC 레지스터에 기록되므로 코드에서 확인이 가능합니다.
이제 Multimedia -> LTDC -> Layer Settings 아래 LTDC 계층 구성으로 이동하십시오.
이 테스트를 위해 TouchGFX에서는 보통 1개의 계층만 사용합니다. Layer 0의 해상도는 프레임 버퍼의 크기에 맞아야 합니다. 프레임 버퍼 주소는 나중에 설정해야 하므로 여기서는 주소를 변경하지 않고 그대로 두십시오.
프레임 버퍼 배열을 디스플레이 해상도보다 작게 선언한 경우에는 프레임 버퍼의 크기에 맞게 계층 크기를 조정합니다. LTDC는 프레임 버퍼에서 사용할 수 없는 디스플레이 픽셀에 대한 배경 색상을 전송합니다. 배경 색상(Parameter Settings 탭에서)을 빨간색과 같이 인식 가능한 값(파란색: 0x00, 녹색: 0x00, 빨간색: 0xFF)으로 설정하는 것이 좋습니다.
클록 구성
클록 구성 역시 중요합니다. 클록은 모든 GPIO와 LTDC에서 활성화되어야 합니다. 픽셀 클록은 디스플레이에서 허용되는 범위에 있어야 합니다.
LTDC는 HCLK, PCLK2, LCD_CLK 등 3개의 클록을 사용합니다.
프레임 버퍼 주소 설정
STM32CubeMX에서 계층 0의 프레임 버퍼 주소를 0xC0000000로 구성했습니다. 이 주소를 내부 RAM의 배열 주소로 변경해야 합니다. STM32Cube 펌웨어 HAL 함수 중 하나를 사용하면 쉽게 변경할 수 있습니다.
main.c
/* USER CODE BEGIN 2 */
HAL_LTDC_SetAddress(&hltdc, (uint32_t)framebuffer, LTDC_LAYER_1);
/* USER CODE END 2 */
HAL 함수에서는 계층에 1, 2라고 번호가 붙지만, STM32CubeMX에서는 0, 1이라고 붙습니다. 아니면 MX_LTDC_Init(void) 함수에서 STM32CubeMX가 생성한 코드에 따라 LTDC가 완전히 구성됩니다.
LTDC 컨트롤러는 디스플레이에 프레임 버퍼를 반복 전송합니다. 표시되는 이미지는 프레임 버퍼의 값에 따라 다릅니다. 프레임 버퍼에서 다른 값 또는 패턴을 전송해보십시오. 예를 들어 memset을 사용하면 프레임 버퍼를 0xFF로 클리어해서 흰색 디스플레이를 얻을 수 있습니다.
Note
LTDC 인터럽트 활성화
LTDC 컨트롤러는 각 프레임에 대해 인터럽트를 발생시키는데, NVIC(코어 인터럽트 컨트롤러)에서 이 인터럽트가 활성화되어 있어야 애플리케이션에서 인터럽트를 처리할 수 있습니다.
STM32CubeMX에서 확인 표시를 설정하는 것 외에도, 코드에서 LTDC 인터럽트를 모두 수동으로 활성화해야 합니다.
main.c
/* USER CODE BEGIN LTDC_Init 2 */
LTDC->IER |= LTDC_IER_LIE; // Enable LTDC interrupts
/* USER CODE END LTDC_Init 2 */
인터럽트 핸들러에서 인터럽트가 발생할 때마다 인터럽트를 다시 활성화해야 합니다.
stm32f7xx_it.c
void LTDC_IRQHandler(void)
{
/* USER CODE BEGIN LTDC_IRQn 0 */
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
/* USER CODE BEGIN LTDC_IRQn 1 */
HAL_LTDC_ProgramLineEvent(&hltdc,0);
/* USER CODE END LTDC_IRQn 1 */
}
프레임률 확인
이 LTDC 인터럽트는 애플리케이션을 구동하는 데 사용됩니다. 디버거를 사용해 이 인터럽트가 발생되었는지 확인해야 합니다.
인터럽트 간에 소요된 시간은 모든 픽셀 및 포치에 대한 클록 생성 시간을 합산한 것입니다. 포치를 조정해서 프레임률을 조정할 수 있습니다. 포치는 LTDC 구성의 일부였습니다. 사용자 지정을 통해 Vfp(Vertical Front Porch) 를 개선하여 프레임률을 낮출 수 있습니다.
인터럽트 핸들러에서 HAL_GetTick() 을 사용하면 손쉽게 프레임률을 측정할 수 있습니다.
stm32f7xx_it.c
volatile int last = 0;
volatile int diff = 0;
void LTDC_IRQHandler(void)
{
/* USER CODE BEGIN LTDC_IRQn 0 */
int now = HAL_GetTick();
diff = now - last;
last = now;
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
...
초당 60개 프레임에서는 각 프레임 간에 1000ms/60 = 16ms의 시간이 필요하다는 점에 유의하십시오.
SPI 디스플레이
지금부터는 SPI 버스에 연결된 디스플레이에 대해 알아보겠습니다.
이와 같은 디스플레이에 대한 구성 작업은 다음과 같습니다.
- SPI 주변 장치 및 GPIO 구성
- 클록 확인
- 필요한 드라이버 코드 작성 또는 검색
SPI 구성
STM32CubeMX를 시작하고 SPI를 활성화합니다. 아래 그림은 STM32G081 프로젝트의 예입니다.
디스플레이 데이터 시트에서 사용되는 SPI 형식(데이터 크기 및 비트 순서) 을 확인하십시오. 16비트 워드는 프레임 버퍼에서 리틀 엔디언(Little-endian) 바이트 순서로 저장됩니다. 이 형식을 수락하도록 디스플레이를 구성할 수 있는지 확인하십시오. 그렇지 않으면 전송 중에 데이터를 변환해야 합니다. 또한 클록 극성과 클록 위상에 주의하십시오. 이들은 디스플레이 데이터 시트에 지정되어 있습니다.
SPI 클록(비트 전송률) 은 FCLK에 대한 분배기(divider) 로 제어됩니다. 최소 제산기는 2입니다. 예를 들어 MCU가 64MHz에서 실행 중인 경우 SPI 최대 비트 전송률은 32MBit/s가 됩니다.
GPIO 탭에서 SPI 주변 장치에 대해 선택한 GPIO를 확인할 수 있습니다.
오른쪽의 Pinout 보기에서 GPIO를 선택합니다.
이제 남은 작업은 SPI 채널에서 디스플레이를 구성하고 프레임 버퍼를 전송하는 것입니다. STM32CubeMX는 디스플레이에 크게 의존하기 때문에 이 코드를 생성할 수 없습니다. 많은 디스플레이에서 명령 시퀀스를 전송하고 특정한 전원 켜기 시퀀스를 따라야 합니다. 그런 다음에는 보통 색상 모드를 설정하고 디스플레이를 ON으로 전환합니다. 이 모든 설정은 공급업체가 제공하는 데이터 시트 또는 예제에 지정되어 있어야 합니다.
STM32Cube 펌웨어에는 SPI 통신을 사용하는 예제가 포함되어 있습니다. STM32Cube HAL에는 다양한 헬퍼 함수가 포함되어 있습니다. 데이터를 전송하기 위한 기본 함수는 다음과 같습니다:
stm32g0xx_hal_spi.h
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
통신이 안정적으로 수행될 때까지 이러한 함수를 사용하는 것이 좋습니다. 추후에 전용 함수를 작성하는 방식으로 성능을 개선할 수 있습니다.
SPI를 인식하는 오실로스코프나 SPI to USB logger는 SPI 디스플레이 드라이버를 작성하는 데 매우 유용할 수 있습니다.
Note
디스플레이 색상 확인
디스플레이에 프레임 버퍼를 전송할 수 있는 시점이 되면 디스플레이 색상을 면밀히 확인하는 것이 좋습니다.
프레임 버퍼에 색상을 기록하고 육안 검사를 통해 디스플레이를 확인하는 방법입니다. 아래에 몇 가지 예가 나와 있습니다:
테스트 | 설명 |
---|---|
빨간색 | 프레임 버퍼에서 빨간색을 설정합니다. 디스플레이도 빨간색이 되어야 합니다. |
녹색 | 프레임 버퍼에서 녹색을 설정합니다. 디스플레이도 녹색이 되어야 합니다. |
파란색 | 프레임 버퍼에서 파란색을 설정합니다. 디스플레이도 파란색이 되어야 합니다. |
암색 | 50% 빨간색에 대한 암색(예: 0x8000)은 디스플레이에서 어둡게 보여야 합니다. |
색상 변경 | 매초마다 프레임 버퍼를 변경하여 디스플레이가 함께 업데이트가 되는지 확인합니다. |
RGB565 프레임 버퍼에서는 다음과 같은 체계에 따라 색상을 지정합니다.
uint8_t r = 0xff, g = 0x00, b = 0x00; // Solid red
uint16_t col = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3); // Convert colors to RGB565
// Put colors into the framebuffer
for(int i = 0; i < W*H; i++)
{
framebuffer[i] = col;
}
24BPP 디스플레이의 경우, 바이트 포인터를 사용해 코드를 보다 정확하게 공식화했습니다(색상은 BGR 순으로 저장).
uint8_t framebuffer[480*272*3]; //24 bit framebuffer
...
uint8_t *fb = framebuffer;
while(fb < (uint8_t*)(framebuffer + (480*272*3)))
{
*fb++ = 0x00; // Write blue color and increment pointer by one byte
*fb++ = 0x00; // Write green color
*fb++ = 0xFF; // Write red color
}
LTDC 라인 이벤트 콜백을 사용해 매초 프레임 버퍼 변경:
main.c
/* USER CODE BEGIN 4 */
uint8_t r = 0x00, g = 0x00, b = 0x00;
uint16_t col = 0;
uint8_t color = 1;
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef* hltdc)
{
static int count = 0;
count++;
if (count >= 60)
{
count = 0;
switch (color)
{
case 1:
r = 0xFF; g = 0x00; b = 0x00;
color = 2;
break;
case 2:
r = 0x00; g = 0xFF; b = 0x00;
color = 3;
break;
case 3:
r = 0x00; g = 0x00; b = 0xFF;
color = 1;
break;
default:
break;
}
col = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3); // Convert colors to RGB565
// Put colors into the framebuffer
for(int i = 0; i < W*H; i++)
{
framebuffer[i] = col;
}
}
}
/* USER CODE END 4 */