抽象層架構
如前一節所述,TouchGFX AL具有一套特定的職責。 這套職責一般是透過RTOS (OSAL)實作,可以在AL (HAL)的硬體部分實作,也可在與TouchGFX引擎同步的AL部分實作。 下表總結了這些在前一節中概述的職責:
職責(Responsibility) | 作業系統或硬體 |
---|---|
將TouchGFX引擎主迴圈與顯示器的傳輸作同步 | 作業系統與硬體 |
回報觸摸銀幕與實體按鈕事件 | 硬體 |
同步影像緩衝區的存取 | 作業系統 |
回報下一個可用的影像緩衝區 | 硬體 |
執行渲染算圖操作 | 硬體 |
影像緩衝區到影示器的傳輸處理 | 硬體 |
以下每個小節對為實現上述職責應所採取的措施作重點介紹。 對於客製的硬體平台,STM32CubeMX中的TouchGFX Generator可以產生大多數的AL程式碼和與其對應的TouchGFX專案。 其餘部分就必須由AL的開發人員依據程式碼註釋,或是依據TouchGFX Generator的通知指示來自行手動實作。 在下一節中閱讀更多有關TouchGFX Generator的資訊。
抽象層物件類別
TouchGFX AL是由TouchGFX引擎透過實體子類別及類別實作檔案(.cpp)存取,其中會實作在TouchGFX引擎所定義的類別成員函數。 這些子類別及類別實作檔案是由TouchGFX Generator所產生。 TouchGFX Generator可產生部分HAL反映STM32CubeMX設定,也可產生所使用RTOS的OSAL。 請閱讀TouchGFX Generator章節,以取得更多詳細資訊。 通常,HAL的架構如下圖所示。
將TouchGFX引擎主迴圈與顯示器的傳輸作同步
這一步驟背後的主要想法,是在渲染完成之後阻塞擱置TouchGFX引擎主迴圈,以確保不再產生其他畫面。 一旦顯示準備就緒,OSAL向被阻塞擱置的引擎主迴圈發出信號以繼續產生顯示圖框(frame)。
為了履行此項責任,TouchGFX AL一般會利用Rendering done (渲染完成)引擎勾點及Display Ready (顯示就緒)中斷加以完成,如抽象層職責一節所述。 OSAL會定義OSWrappers::signalVSync
函數,開發人員可在其中發送旗號給呼叫後等待中的引擎。
Tip
渲染算圖完成
在渲染算圖完成之後, TouchGFX Engine會呼叫渲染算圖完成的鉤子OSWrappers::waitForVSync
。
實作此OSAL方法時,抽象層必須阻塞擱置圖形引擎,直到渲染下一個畫面的時間為止。 實作該阻塞擱置的標準方法是阻止由訊息佇列(message queue)進行讀取。 如果此方法不可行,則HAL開發人員可自由使用任何方法來實作該阻塞擱置。
Tip
OSWrappers::signalVSync
發出訊號時(或OSWrappers::waitForVSync
所用的旗號/佇列發出訊號時),TouchGFX即開始渲染下一個應用程式畫面。 以下基於CMSIS V2的程式碼會造成TouchGFX引擎的阻塞擱置,直到系統中其他部分(通常是與顯示同步的中斷)將一個元素加進佇列當中。
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);
// 然後等待下一次VSYNC。
osMessageQueueGet(vsync_queue, &dummyGet, 0, osWaitForever);
}
如果不使用RTOS,TouchGFX Generator為waitForVSync
提供了以下使用揮發性易失變數(volatile variable)方式的實作。
OSWrappers.cpp (無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
顯示就緒
用於解除主迴圈阻塞擱置的Display ready (顯示就緒)訊號,應該來自顯示控制器的中斷、顯示器本身的中斷或是硬體計時器的中斷。 訊號源取決於顯示類型。
OSWrappers
物件類別為此信號定義了一個函式:OSWrappers::signalVsync
。 該函數的實作必須透過滿足OSWrappers::waitForVSync
中所使用的等待條件,來解除主迴圈的阻塞擱置。
接續上面的CMSIS V2範例,以下程式碼將訊息放入訊息佇列vsync_queue
中以解除TouchGFX引擎的阻塞擱置。
OSWrappers.cpp (CMSIS V2)
void OSWrappers::signalVSync()
{
osMessageQueuePut(vsync_queue, &dummy, 0, 0);
}
這個OSWrappers::signalVSync
物件方法必須在硬體層級透過中斷呼叫,例如LTDC中斷、顯示器外部訊號中斷或硬體計時器中斷。
如果不使用RTOS,則使用變數,並分配一個非零值以打破while循環。
OSWrappers.cpp (無OS)
void OSWrappers::signalVSync()
{
vsync_sem = 1;
}
回報觸摸銀幕與實體按鈕事件
在渲染新的畫面之前,TouchGFX引擎從TouchController
和ButtonController
介面收集外部輸入。
銀幕觸控座標
TouchGFX引擎將觸控控制器的座標轉換為「點擊(click)」、「拖動(drag)」和「手勢(gesture)」事件,並將其傳遞至應用程式。
TouchGFX引擎是以傳送實作的方式存取觸控控制器的觸控座標
以下程式碼由TouchGFX Generator產生:
TouchGFXConfiguration.cpp
static STM32TouchController tc;
...
static TouchGFXHAL hal(dma, display, tc, 390, 390);
在TouchGFX Engine渲染算圖(render)的過程中,引擎呼叫tc物件的sampleTouch()
函式來收集輸入。
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
AL開發人員提供的實作應該讀取銀幕觸控座標的x和y值,並回傳是否偵測到銀幕觸控(真或偽)。
Tip
有多種實作此函式的方法:
- 在sampleTouch() 中輪詢(polling):透過發送請求並等待輪詢結果,可從硬體觸控控制器(通常為I2C)讀取螢幕觸摸狀態。 這會影響應用程式的總體渲染算圖時間,因為I2C的往返傳輸通常長達1ms,在此期間圖形引擎會被阻塞擱置(blocked)。
- 使用中斷:另一種可能是使用中斷。 I2C讀取的指令由定時器定期啟動,或者以回應觸控硬體控制器的外部中斷來啟動。 當I2C資料就緒時(可視為另一個中斷),此資料可透過訊息佇列(message queue)或全域變數將資料提供給
STM32TouchController
。 以下展示了由TouchGFX Generator所產生的STM32TouchController.cpp
程式碼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
介面實作的實體物件,並將指向此實體物件的參照(reference)傳遞至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物件類別當中的樣本方法。 當回傳真值時,鍵值(key value)將被傳遞至目前螢幕(screen)的handleKeyEvent事件處理程式。
Further reading
同步影像緩衝區的存取
多個執行單元(actors)可能都會對影像緩衝區進行存取。
1 | CPU | 在渲染算圖期間讀取和寫入像素 |
2 | DMA2D* | 在硬體輔助的渲染算圖期間讀取和寫入像素 |
3 | LTDC | 在傳輸到並列RGB顯示器期間讀取像素 |
4 | DMA | 在傳輸到SPI顯示器期間讀取像素 |
TouchGFX引擎透過OSWrappers
介面來同步影像緩衝區的存取,而對於同時也希望存取影像緩衝區的週邊(如DMA2D)也必須執行相同操作(透過OSWrapper對影像緩衝區進行存取)。 常見的設計是使用旗號(semaphore)來確保(guard)對影像緩衝區的存取,但也可以使用其它的同步機制。
下表顯示了OSWrappers
物件類別(OSWrappers.cpp)中的函式列表,包含了由TouchGFX Generator產生的函式或由使用者手動生成的函式。
物件方法 | 描述 |
---|---|
takeFrameBufferSemaphore | 由圖形引擎呼叫以獲取對影像緩衝區的獨佔存取。 這將阻塞擱置引擎直到DMA2D完成(若正在執行的話) |
tryTakeFrameBufferSemaphore | 確保已取得旗號鎖定(lock)。 此方法不會阻塞擱置,但下一次呼叫takeFrameBufferSemaphore時會阻塞擱置呼叫者 |
giveFrameBufferSemaphore | 釋放影像緩衝區的旗號鎖定(lock) |
giveFrameBufferSemaphoreFromISR | 在中斷程式當中釋放影像緩衝區的旗號鎖定(lock) |
Tip
回報下一個可用的影像緩衝區
無論採用哪種渲染算圖的策略,TouchGFX引擎在每個時標(tick)都必須知道應將像素渲染到哪個記憶體區域。 使用單一影像緩衝或雙影像緩衝策略時,TouchGFX引擎將根據影像緩衝區的寬度、高度和位元深度(bit depth)將像素資料寫入記憶體區域。 圖形引擎負責雙影像緩衝區配置當中兩個影像緩衝區之間的交換。
可以將影像緩衝區的存取局限在部分的影像緩衝區。 可在HAL物件子類別中重新實作HAL::getTFTCurrentLine()
物件方法(method)。 回傳上面用於圖形引擎繪製而保存的行號。
使用局部影像緩衝區策略時,開發人員需定義TouchGFX引擎在渲染算圖時使用的記憶體區塊(一個或多個)。 由此閱讀更多相關資訊。
Tip
執行渲染算圖
應用程式不是只需要作渲染算圖或是只作圖像顯示。 還有其他工作也需要使用CPU。 TouchGFX的目標之一是盡可能降低CPU資源的使用來繪製使用者介面。 HAL物件類別對許多STM32微控制器(或其他硬體功能)上的DMA2D功能進行抽象化以利圖形引擎的使用。
將圖資(如點陣圖)渲染演算至影像緩衝區時,TouchGFX引擎檢查HAL是否有能力對局部或者全部的點陣圖進行點陣疊圖(blit)至影像緩衝區。 如果有此功能,則將繪圖操作委託給HAL而不是由CPU直接處理。
引擎呼叫物件方法HAL::getBlitCaps()
以取得硬體能力的描述。 可在HAL物件子類別當中重新實作此呼叫以添加硬體能力的描述。
引擎在繪製使用者介面時呼叫HAL物件類別上的操作(如HAL::blitCopy
)將其排入DMA操作佇列。 如果HAL回報不具備所需的能力,則圖形引擎將退而使用軟體方式來渲染算圖。
Tip
影像緩衝區到影示器的傳輸處理
為將影像緩衝區的內容傳輸到顯示器,TouchGFX AL經常使用「區域渲染完畢(Rendering of area complete)」的掛鉤。 一旦部分影像緩衝區的渲染算圖完成後,引擎就會向AL發送信號。 AL可選擇如何將此影像緩衝區這部分的內容傳輸到顯示器。
區域渲染完畢
在程式碼當中的掛鉤(hook)為虛擬函式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
}
}