FMC與SPI顯示介面
以下方案介紹了利用TouchGFXGenerator選擇透過FMC或SPI與LCD相連的客製的顯示介面,並為其編寫TouchGFX驅動程式通常所涉及的步驟。
Further reading
FMC與SPI為不帶內建顯示控制器的MCU編寫TouchGFX顯示驅動程式的過程是相同的。 本節描述的方案以ST7789H2LCD控制器為例。
一旦根據CubeMX中的開發板規格配置了FMC或SPI,透過選擇客製化顯示介面,TouchGFXGenerator可用於生成HAL,開發人員可以編寫自訂程式碼,將更新過的應用程式影像緩衝區內容傳輸到顯示器。
下圖顯示了選擇客製化顯示介面時的TouchGFXGenerator設定。 該配置會向TouchGFXGenerator指示,開發人員希望完成相關顯示配置後手動將像素從影像緩衝區傳輸到顯示器,並生成用於完成該操作的處理器。
Note
驅動程式必須能夠將像素傳輸到顯示器,並可以控制顯示器的記憶體寫入位置。 檢查其資料手冊以找到如下面所述的命令以及更多詳細資訊。
通常,對於自帶GRAM的顯示器(如8080或SPI介面顯示器),驅動程式的工作方式如下:
- 根據要重繪的影像緩衝區,將「顯示游標」和「活動視窗」移動到GRAM中與此區域對應的位置。
- 準備將傳入的像素資料寫入GRAM。
- 傳送像素資料。
傳輸影像緩衝區內容
在影像緩衝區內容被更新後,TouchGFXEngine會呼叫HAL::flushFrameBuffer(Rectr)
。 當開發人員必須為自訂的顯示介面編寫驅動程式時,此函式可被忽略。
void TouchGFXHAL::flushFrameBuffer(const Rect& rect)
{
/* Set Cursor */
__ST7789H2_SetDisplayWindow(rect.x, rect.y, rect.width, rect.height);
/* Prepare to write to LCD RAM */
ST7789H2_WriteReg(ST7789H2_WRITE_RAM, (uint8_t*)NULL, 0);
/* Send Pixels */
this->copyFrameBufferBlockToLCD(rect);
}
以下__ST7789H2_SetDisplayWindow
函數用以寫入特定寄存器來設定GRAM中虛擬「游標」的x
和y
座標,對於使用GRAM來說這很常見的用法。
extern "C"
void __ST7789H2_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
{
uint8_t parameter[4];
/* CASET: Column Address Set */
parameter[0] = 0x00;
parameter[1] = Xpos;
parameter[2] = 0x00;
parameter[3] = Xpos + Width - 1;
ST7789H2_WriteReg(ST7789H2_CASET, parameter, 4);
/* RASET: Row Address Set */
parameter[0] = 0x00;
parameter[1] = Ypos;
parameter[2] = 0x00;
parameter[3] = Ypos + Height - 1;
ST7789H2_WriteReg(ST7789H2_RASET, parameter, 4);
}
以下TouchGFXHAL::copyFrameBufferBlockToLCD
函數是一個私有函數,該函數一次發送一行更新過的區域(Rect
),並更新相應的影像緩衝區指標。
void TouchGFXHAL::copyFrameBufferBlockToLCD(const Rect& rect)
{
__IO uint16_t* ptr;
uint32_t height;
// This can be accelerated using regular DMA hardware
for (height = 0; height < rect.height ; height++)
{
ptr = getClientFrameBuffer() + rect.x + (height + rect.y) * BSP_LCD_GetXSize();
LCD_IO_WriteMultipleData((uint16_t*)ptr, rect.width);
}
}
TouchGFXGenerator將產生一個函數advanceFrameBufferToRect
,並根據Rect
在影像緩衝區中的位置來更新ptr
指標,而不用手動更新ptr
指標。
inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const
{
// Advance vertically Advance horizontally
fbPtr += rect.y * lcd().framebufferStride() + rect.x * 2;
return fbPtr;
}
從HAL::flushFrameBuffer()回傳
在函數返回後,TouchGFXEngine繼續繪製剩餘的內容。 如果開發人員希望使用DMA將像素傳輸到顯示器,他們必須透過等待DMA完成中斷所發出的semaphore訊號來確保HAL::flushFrameBuffer(Rect& rect)
不會立即傳回。
以下的偽程式範例示範了在使用DMA的情況下如何建構HAL::flushFrameBuffer()
。 此程式碼使用了FreeRTOS的screen_frame_buffer_sem
。
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
uint16_t* fb = HAL::lockFrameBuffer();
//Prepare display
prepare();
//Try to take a display semaphore - Always free at this point
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Set up DMA
screenDMAEnable();
// Wait for the DMA transfer to complete
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Unlock framebuffer and give semaphore back
HAL::unlockFrameBuffer();
xSemaphoreGive(screen_frame_buffer_sem);
}
TouchGFX驅動程式/撕裂效果信號
從上面的TouchGFXGenerator配置中可以看出,應用中的「滴答計時源」也被設為「客制」,對於不內置TFT控制器的MCU來說,這也算是常見設置。
如抽象層架構部分所述,通常在發出顯示訊號時,用OSWrappers::signalVSync()
來解除TouchGFXEngine主迴圈阻塞。
對有串列或8080顯示介面的顯示器,內建顯示控制器通常會產生一個週期性撕裂效果(TE)信號,該訊號可以連接到MCU上的GPIO。 在這種情況下,通常將MCU配置為當GPIO收到該信號時觸發中斷。 然後,該「撕裂效果」中斷將解除對TouchGFXEngine主循環的阻塞,以便渲染下一幀。 請記得將GPIO配置為輸入,並在CubeMX中啟動該腳的外部中斷。
extern "C"
void TE_Handler(void)
{
...
/* Unblock TouchGFX Engine Main Loop to render next frame */
OSWrappers::signalVSync();
...
}
結論
開發人員可以透過TouchGFXGenerator選擇客製化顯示介面,並自行編寫代碼實現將像素從應用程式的影像緩衝區傳輸到顯示器之目的。
TouchGFXGenerator將產生functionTouchGFXHAL::flushFrameBuffer(Rect& rect)
函數,當渲染完成影像緩衝區的一塊區域後,TouchGFX會自動呼叫該函數,同時,開發人員將更新過的影像緩衝區資料透過FMC、SPI或其他途徑傳送到顯示器。
選擇客製化顯示介面也需要開發人員實現客製的TouchGFX滴答計時驅動,該驅動程式發出OSWrappers::signalVSync()
信號,以解除對TouchGFXEngine主迴圈的阻塞。 通常,與不帶TFT控制器的MCU一起使用的顯示器會提供連接至MCU的撕裂效果信號。