跳轉到主要內容

3. 顯示內部記憶體中的影像緩衝區

動機

在這一步中,通過將像素數據從內部RAM傳輸到顯示器,我們可以看到顯示器上會顯示內容。 此步驟可確保我們能將數據傳輸到顯示器,並且可以不斷更新顯示器內容。

除了將影像資料傳輸到顯示器以外,我們還必須確保可連續將新數據發送到顯示器,且不會在顯示器上出現錯誤。 我們也將測量傳送速率,因為這會影響顯示器的更新率。

正如我們從上一節中學到的那樣,我們將在內部RAM中放置一個影像緩衝區,而該RAM可讀寫。 我們將重複更新該影像緩衝區,並將其傳輸到顯示器。

回顧一下,通過以下公式來計算影像緩衝區:

寬 x 高 x bpp

例如,解析度為480 x 272的16位元普通顯示器將佔用480x272x16/8位元組 即261120位元組。

即使顯示器尺寸過大導致所需的影像緩衝區無法存儲在內部RAM中,也不應跳過此步驟。 而應將顯示控制器配置為僅更新顯示器的一部分。 這樣一來,我們可以調整影像緩衝區所需的RAM,並配置合適的內部RAM大小。

顯示介面的類型對傳輸影像緩衝區所需的設置和程式碼有很大影響。 在本節中,我們將首先討論連接至LTDC的顯示器。 如果您使用如SPI顯示器之類的顯示器,程式碼會有很大區別,但任務和目標皆相同。

目標

本節的目標是將影像緩衝區的內容傳輸到顯示器。 您也應驗證影像緩衝區的內容是否可以修改以及影像緩衝區是否可以連續重新發送。

驗證

以下是本節的驗證點:

驗證點基本原理
顯示影像緩衝區顯示控制器或SPI已設定並執行
顯示已更新的影像緩衝區我們知道如何重複傳送影像緩衝區
顏色正確LTDC相關的GPIO設定正確或顯示器的資料格式與我們的影像緩衝區匹配
更新頻率正確設定更新頻率與前後廊等顯示參數,以取得所需更新頻率

先決條件

以下是此步驟的先決條件:

  • 有關顯示器的資訊,通常為資料手冊
  • 有關MCU和顯示器之間的連接的資訊。

執行

根據顯示器類型,所需設置會有所不同。 但對於所有類型的顯示器,我們都需要內部RAM中有一個影像緩衝區。 一種簡單的配置記憶體的方法是只需宣告一個大小合適的全域陣列:

main.c
/* USER CODE BEGIN PV */
uint16_t framebuffer[480*272]; //16 bpp framebuffer
/* USER CODE END PV */

如果內部RAM不足以容納該陣列,請宣告一個對應於較小解析度的陣列,比方480x200。

將影像緩衝區傳輸到顯示器的方法取決於顯示器類型。 我們現在來瞭解一下這點。

Caution
On some boards, the default RAM address is located in a zone that is not accessible by the LTDC (e.g. on STM32H735G Discovery Kit).

Therefore, the RAM start address must be changed beforehand. This can be done by modifying the linker script. You can find the usable memory addresses and additional details in your chip’s datasheet.

並行RGB顯示器

我們首先將討論連接至MCU LTDC控制器的並行RGB顯示器。

此視窗的設定任務如下:

  • 設定與顯示器有關的GPIO連線
  • 設定LTDC控制器
  • 設定LTDC像素時脈
  • 設置影像緩衝區位址
  • 檢查更新頻率

作為範例,我們將使用STM32F746Discovery evaluation套件。 此板帶有480*272顯示器。

顯示相關GPIO

該顯示器以24 BPP模式執行,所以我們為LTDC和顯示器之間的連線設定24個GPIO。 這在Multimedia -> LTDC-> GPIO設定下的STM32CubeMX中最容易實現:

設定顯示相關的GPIO

除了用於像素傳輸的24個GPIO(如LTDC_B0)以外,我們還設定了4個顯示控制訊號:

信號功能
LTDC_CLK像素時脈 當對24個線中的像素進行取樣時,向顯示器發出信號
LTDC_DE資料啟動。 像素於啟動時傳輸
LTDC_HSYNC水平同步。 允許顯示器找到像素行的起點
LTDC_VSYNC垂直同步。 允許顯示器找到幀的起始點

檢查您的硬體設計並進行相應的配置。

LTDC設定

LTDC設定位於 多媒體 -> LTDC -> 參數設定 下的STM32CubeMX中:

設定LTDC參數

顯示器的有效寬度和高度與解析度對應。 有關同步脈衝寬度和前後廊,請檢查顯示資料手冊。 同時需注意訊號極性。 灰色的值根據其他值計算而來。 這些值被寫入LTDC暫存器(也可在程式碼中找到)。

現在請到 多媒體-> LTDC -> 層設定 的LTDC層設定:

設定LTDC層參數

對於該測試以及在TouchGFX中,我們通常僅使用一層。 第0層的解析度應與影像緩衝區大小匹配。 之後會設置影像緩衝區位址,在這裡暫時先跳過。

如果您宣告了小於顯示器解析度的影像緩衝區陣列,則調整圖層大小,以便與影像緩衝區尺寸匹配。 LTDC將傳輸在影像緩衝區中沒有的顯示器像素背景顏色。 建議將背景色彩(位在「Parameter Settings」(參數設定)索引標籤)設定為紅色等容易識別的顏色(藍色:0x00、綠色:0x00、紅色:0xFF)。

時鐘設定

時鐘設定也很重要。 必須啟動所有GPIO和LTDC的時鐘。 像素時脈必須在顯示器可接受的範圍內。

時鐘配置

LTDC取決於3個時鐘:HCLK,PCLK2與LCD_CLK。

設定影像緩衝區位址

在STM32CubeMX中,我們將第0層的影像緩衝區位址設定為0xC0000000。 我們需要將其更改為內部RAM中的陣列位址。 在這使用STM32Cube韌體HAL函數可以輕鬆實現:

main.c
  /* USER CODE BEGIN 2 */
HAL_LTDC_SetAddress(&hltdc, framebuffer, LTDC_LAYER_1);
/* USER CODE END 2 */

HAL函數中的層數編號為1、2,而STM32CubeMX中的層數編號為0、1。 另外,LTDC完全由STM32CubeMX產生程式碼中的 MX_LTDC_Init(void) 函數來設定。

LTDC控制器將影像緩衝區重複發送至顯示器。 顯示的圖像取決於影像緩衝區中的值。 嘗試影像緩衝區的不同值或模式。 例如,使用memset將影像緩衝區清除為0xFF,以顯示全白的螢幕。

Note
在某些顯示器上,必須開啟背光才能使圖形幀可見。

啟用LTDC中斷

LTDC控制器為每幀提供一個中斷,中斷必須在NVIC(核心中斷控制器) 中啟用,以便能夠處理應用程式中的中斷。

LTDC中斷配置

除了在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配置的一部分。 通常通過增強前廊來降低更新頻率。

測量更新頻率的簡單方法為使用中斷處理程式中的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幀的情況下,各個幀之間應該有1000 ms / 60 = 16 ms。

SPI顯示器

現在我們將討論與SPI匯流連接的顯示器。

此視窗的設定任務如下:

  • 設定SPI外設與GPIO
  • 檢查時鐘
  • 編寫或找到必要的驅動程式程式碼

SPI 設定

在STM32CubeMX中啟動並啟動SPI。 這些影像來自STM32G081專案:

SPI 設定

檢查使用的SPI格式的顯示器資料手冊(資料大小和位順序)。 請記住,16位元字以小端位元組序存儲在影像緩衝區中。 檢查是否可以將顯示器配置為此格式。 如果不可以,則必須在傳輸期間轉換資料。 另請注意時鐘極性和時鐘相位。 這些參數已在顯示器數據手冊中指定。

SPI時鐘(波特率)由FCLK的除頻器控制。 最小除頻器為2。 如果MCU正在執行 (如64MHz),則最大SPI波特率為32 MBit/s。

在GPIO選項上,您可以檢查SPI外設的GPIO設定:

SPI GPIO設定

在右方的引腳排列檢視中選擇GPIO:

SPI GPIO選擇

現在剩下的任務為配置顯示器並在SPI通道上傳輸影像緩衝區。 STM32CubeMX無法為您生成此程式碼,因為它在很大程度上取決於顯示器。 對於許多顯示器,必須發送一系列指令,並遵循特定的上電時序。 之後,通常需要設置顏色模式,然後將顯示器打開。 所有這些須在供應商所提供的數據手冊或範例中指定。

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示波器或SPI USB記錄器會非常有用。

Note
SPI從低頻啟動,以避免雜訊問題。

檢查顯示器顏色

此時,您可以將影像緩衝區內容傳輸到顯示器,建議您徹底檢查顯示器顏色。

該想法就是將顏色寫入影像緩衝區,並通過視覺來檢查顯示器。 以下為一些範例:

測試說明
紅色在影像緩衝區中設置紅色。 顯示器也必須為紅色。
綠色在影像緩衝區中設置綠色。 顯示器也必須為綠色。
藍色在影像緩衝區中設置藍色。 顯示器也必須為藍色。
深色如果設置為深色(如0x8000)(50%紅色),則必須在顯示器上顯示黑色。
更改顏色每秒更改一次影像緩衝區,然後查看顯示器是否更新。

要將顏色放入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行事件callback更改每秒影像緩衝區:

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 */