跳轉到主要內容

後端通信

在大多數應用中,UI需以某種方式連接到系統的其餘部分,並發送和接收資料。 這可能會連接硬體外設(感測器資料、類比/數位轉換和串列通信等)或其他軟體模組。

本文將介紹實現此類連接的推薦解決方案。

第一種方法是一種“快而不精”的方法,主要用於原型開發,而第二種方法是一種在架構上較完善的方法,可正確地連接UI與現實世界中的剩餘元件。

在本文末,我們將連結使用兩種方法的範例。

Model類

所有TouchGFX應用都有Model類,除了存儲UI狀態資訊,還用作面向周圍系統的介面。 這裡我們指的是兩種硬體外設,但也與系統中的其他OS任務進行通信。 好的設計通常不會在各自的View類中訪問其他軟體模組或硬體。

Further reading
如需瞭解更多關於Model的知識,請參考:MVP模式

Model類非常適合放置任何此類介面程式碼,原因在於:

  1. Model類有 tick() 函數,會在每一幀自動呼叫,並且可實現用於查找來自其他子模組的事件或對事件作出反應。
  2. Model類具有指向當前活動Presenter的指標,為的是能夠將傳入事件通知UI。

系統介面

與周圍系統連接的方式有兩種:一種是從GUI任務直接採樣,另一種是從二級任務採樣

從GUI任務採樣

連接周圍系統的最佳方式取決於您需要的採樣頻率、時間消耗和時間嚴格性的要求。

如果這些方面的要求並不嚴格,那麼最簡單的方法就是在 Model::tick 函數中直接對周圍系統進行採樣。

如果採樣頻率低於幀率(通常約為60Hz),您可以添加計數器,並且只在每次的第N個計數點採樣。 如果這樣做,則採樣操作必須稍微快一些(通常為1ms或更短),否則會影響幀率,因為採樣是在GUI任務的背景下執行的並且會延遲幀繪製。

從二級任務採樣

或者,如果不方便將與周圍系統的交互直接放在GUI任務的背景下,可以新建負責執行採樣的OS任務。

您可以根據特定場景的需要,將該任務配置為以準確的時間間隔運行。 此外,根據您的需求,此新任務的優先順序可以低於或高於GUI任務。

如果優先順序更高,則可以確保它會準確地在指定時間運行,無論GUI任務在執行什麼操作。 有一個缺點是,如果是CPU占用量大的進程,可能會影響UI的幀率。

另一方面,如果採樣對時間的要求不嚴格,則可以分配低於GUI任務的優先順序,這樣UI幀率將永遠不受周圍系統採樣的影響。 在渲染時,GUI任務將休眠很長時間(如在等待基於DMA的像素傳輸完成時),允許優先順序較低的任務頻繁運行,這對絕大多數應用而言足夠了。

如果您使用二級任務法,建議您使用RTOS提供的任務間消息傳送系統。 大多數(如果不是全部)RTOSes具有佇列/郵件機制,可從一個任務向另一個任務發送資料(通常為使用者定義的C語言結構體、位元組陣列或簡單的整數)。 為了將新資料傳遞給GUI任務,為UI任務設置郵箱或訊息佇列,並使用此消息傳送系統將資料發送給GUI任務。 然後可以 Model::tick 輪詢GUI任務的郵箱,查看是否有任何新資料到達。 如果有,讀取資料並相應地更新UI。

向UI傳播資料

無論您使用從GUI任務採樣還是從二級任務採樣Model::tick 函數都是GUI任務發現要在UI中顯示的新資料的地方。 除了充當周圍系統的介面,Model類還負責保存狀態資料(如前文所述),因此可能有些狀態變數也需要更新。

我們來考慮一個簡單的範例:溫度感測器連接到系統,當前溫度將顯示在UI上。 在準備中,我們將增加Model類以便支援:

Model.hpp
class Model
{
public:
// Function that allow your Presenters to read current temperature.
int getCurrentTemperature() const { return currentTemperature; }

// Called automatically by framework every tick.
void tick();
...
private:
// Variable storing last received temperature;
int currentTemperature;
...
};

在上述情況下, Presenter 能夠詢問Model當前溫度,以便在進入顯示溫度的螢幕時Presenter在UI(View)中設置此值。 現在,需要能夠在接收到新的溫度資訊時再次更新UI。 為此,我們利用Model有指向當前活動Presenter指標的特性。 該指標的類型是介面(ModelListener),為了反映合適的應用特定的事件,您可以進行修改:

ModelListener.hpp
class ModelListener
{
public:
// Call this function to notify that temperature has changed.
// Per default, use an empty implementation so that only those
// Presenters interested in this specific event need to
// override this function.
virtual void notifyTemperatureChanged(int newTemperature) {}
};

現在,我們已經連接了此介面,剩餘的工作是執行傳入“新溫度”事件的實際採樣 Model::tick

Model.cpp
void Model::tick()
{
// Pseudo-code for sampling data
if (OS_Poll(GuiTaskMBox))
{
// Here we assume that you have defined a "Message" struct containing type and data,
// along with some event definitions.
struct Message msg = OS_Read(GuiTaskMBox);
if (msg.eventType == EVT_TEMP_CHANGED)
{
// We received information that temperature has changed.
// First, update Model state variable
currentTemperature = msg.data;

// Second, notify the currently active Presenter that temperature has changed.
// The modelListener pointer points to the currently active Presenter.
if (modelListener != 0)
{
modelListener->notifyTemperatureChanged(currentTemperature);
}
}
}
}

以上方法可確保兩點:

  1. currentTemperature 變數總是最新值,因此Presenter可隨時獲取當前溫度。
  2. Presenter 立即通知溫度變化,並能採取合適措施。

MVP模式的一大優勢是根據當前所在螢幕實現單獨的通知處理。 例如,假設在顯示某類設置功能表 (如MainMenuPresenter/MainMenuView為啟動狀態) 時發生了溫度變化事件,此時當前溫度無關緊要。

由於 notifyTemperatureChanged 函數具有預設的空執行,此通知僅會被 MainMenuPresenter忽略。. 另一方面,如果有 TemperatureControlPresenter ,您可以在該Presenter中重寫 notifyTemperatureChanged 函數,並通知View它應顯示更新後的溫度:

TemperatureControlPresenter.hpp
class TemperatureControlPresenter : public ModelListener
{
public:
// override the empty function.
virtual void notifyTemperatureChanged(int newTemperature) {
view.setTemp(newTemperature);
}
};

當然,View類 TemperatureControlView必須實現 setTemp 方法。

從UI向周圍系統發送資料

反方向從UI向周圍系統傳輸資料/事件時,將通過Model以大致上相同的方式來執行。 繼續前面的例子,如果我們需要增加配置新的目標溫度的能力,我們將向Model添加以下內容:

Model.hpp
void setNewTargetTemperature(int newTargetTemp)
{
// Pseudo-code for sending an event to a task responsible for controlling temperature.
struct Message msg;
msg.eventType = EVT_SET_TARGET_TEMP;
msg.data = newTargetTemp;
OS_Send(SystemTaskMBox, &msg);
}

如果使用者在UI中設置新的目標溫度,View可通知保有指向Model物件指標的Presenter,從而能夠呼叫 setNewTargetTemperature 函數中直接對周圍系統進行採樣。

範例

下面的範例是為特定於開發板的演示(BSD),但演示的許多程式碼可重複用於其他演示板和自訂硬體。 對於這些範例,我們在STM32CubeMX中創建任務和佇列。 我們然後填充生成的任務,並在 main_user.c中實現範例用戶程式碼。. 範例使用STM32CubeMX BSP庫來控制STM32評估套件上的LED、使用者按鈕、以及其他外設。

來自GUI任務

一個範例應用、一個BSD,位於最新版本的TouchGFX Designer下,具體路徑:演示-> 開發板特定演示-> STM32F46G探索套件控制LED(從GUI)。

該應用演示如何對按鈕進行採樣和控制LED。 Model類對按鈕採樣,並更新LED以與應用狀態相匹配。

來自其他任務

一個範例應用、一個BSD,位於最新版本的TouchGFX Designer下,具體路徑:演示-> 開發板特定演示-> STM32H7B3I評估板模擬採樣器任務。

該應用演示如何在單獨的執行緒中對模擬輸入進行採用。 該範例使用MVP架構將模擬值傳輸到View。

一個範例應用、一個BSD,位於最新版本的TouchGFX Designer下,具體路徑:演示-> 開發板特定演示-> STM32F46G探索套件任務間通信。

該應用演示了任務間通信,以及與UI之間的傳播。 在您自己進行設置時,它也許能激發您的靈感。 該範例在用C程式碼實現的後端系統與C++ TouchGFX GUI之間進行通信。 該範例在FreeRTOS之上的STM32F746G-DISCO板上運行。

來自多個任務

一個範例應用、一個BSD,位於最新版本的TouchGFX Designer下,具體路徑:演示-> 開發板特定演示-> STM32F769I探索套件多工通信演示。

該應用對按鈕狀態進行採樣,並在按下按鈕時通過GUI訊息佇列傳遞消息。 因此,我們可以通過按住按鈕來推進應用中的動畫。

該應用使用三個FreeRTOS任務。 一個用於GUI,另外兩個分別用於兩個外設(LED和使用者按鈕)。

來自任務和外部中斷線路

一個範例應用、一個BSD,位於最新版本的TouchGFX Designer下,具體路徑:演示-> 開發板特定演示-> STM32F769I探索套件外部中斷線路演示。

該應用專為STM32F769I-DISCO開發板而設計,它與LED和使用者按鈕交互,以便展示如何將C程式碼和硬體外設集成到TouchGFX應用中。

該應用以EXTI模式配置按鈕(外部中斷線路0)。 其行為是在按下按鈕時接收中斷,此後中斷清零。 這不允許出現與GPIO模式下相同的行為,而會是單步動畫,原因在於只在接收到中斷時通過GUI訊息佇列發送消息。

該應用使用兩個FreeRTOS任務。 一個用於GUI,一個用於LED。 (多工演示中的Button任務在該應用中仍處於活動狀態,用於舉例說明外設交互程式碼已移至中斷處理函數中)。