將UI連接到系統
在大多數應用程序中,UI需要以某種方式連接到系統的其余部分,並發送和接收數據。這可以與硬件外圍設備(傳感器數據,A / D轉換,串行通信等)接口,也可以與其他軟件模塊接口。
本文介紹了實現此連接的推薦解決方案。
第一種方法是“快速且骯臟的”方法,主要用於原型制作,而第二種方法是在架構上合理地將UI與現實應用程序中的其余組件連接的一種方法。
在本文的最后,我們鏈接到使用這兩種方法的示例。
模型類
所有TouchGFX應用程序都有一個Model類,該類除了存儲UI狀態信息之外,還旨在充當您周圍系統的接口。這樣,我們既指硬件外圍設備,也指系統中的其他OS任務。通常,訪問各個View類中的其他軟件模塊或硬件不是一個好的設計。
Model類非常適合放置任何此類接口代碼,因為:
- Model類具有tick()函數,該函數會在每幀中自動調用,並且可以實現為查找其他子模塊中的事件並對事件做出反應。
- Model類具有指向您當前活動的Presenter的指針,以便能夠將傳入事件通知UI。
硬件接口
方法1:直接從GUI任務采樣
與硬件接口的最佳方法取決於您需要采樣的頻率,采樣的時間和時間的緊迫性。如果您在這些方面的要求比較寬松,那么最簡單的方法就是直接在Model::tick
功能中對硬件進行采樣 。如果采樣發生的頻率低於幀速率(通常在60Hz左右),則可以添加一個計數器,並且僅在第N個時鍾間隔進行一次采樣。用這種方法完成后,您的采樣操作必須快一些(通常為1ms或更短),否則您的幀速率將開始受到影響,因為采樣是在GUI任務的上下文中完成的,並且會延遲繪制幀。
方法2:從輔助任務采樣
或者,如果不希望將硬件交互直接放在GUITask的上下文中,則可以創建一個新的OS任務來執行采樣。您可以配置此任務以在特定情況下所需的確切時間間隔上運行。另外,根據您的需要,此新任務的優先級可以比GUI任務低或高。如果它具有更高的優先級,那么可以保證它在指定的時間准確運行,而不管GUI任務在做什么。這樣做的缺點是,如果這是占用CPU的進程,則可能會影響UI的幀速率。另一方面,如果采樣不是時間緊迫的,則可以為任務分配比GUI任務更低的優先級,這樣UI幀速率就不會受到硬件采樣的影響。
如果您使用輔助任務方法,我們建議您利用RTOS提供的任務間消息傳遞系統。大多數(如果不是全部)RTOS具有隊列/郵件機制,可讓您將數據(通常是用戶定義的C結構,字節數組或簡單整數)從一個任務發送到另一個任務。為了將新數據傳遞到GUI任務,請為UI tast設置郵箱或消息隊列,然后使用此消息傳遞系統將數據發送到GUI任務。然后,您可以Model::tick
輪詢GUI任務的郵箱,以檢查是否有新數據到達。以防萬一,讀取數據並相應地更新UI。
將數據傳播到UI
無論使用方法1還是方法2,該Model::tick
功能都是GUITask知道要在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; ... };
通過上述操作,您Presenters
可以向模型詢問當前溫度,從而允許演示者在進入顯示溫度的屏幕時在UI(視圖)中設置此值。我們現在需要做的是能夠在收到新的溫度信息時再次更新UI。為此,我們利用了模型具有指向您當前活動的演示者的指針這一事實。該指針的類型是接口(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 Method 1 or Method 2. Depends on your concrete Operating System 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); } } } }
上面的方法可以確保兩件事:
- currentTemperature變量始終是最新的,以便您的Presenter可以隨時獲取當前溫度。
- 主持人會立即收到有關溫度變化的通知,並可以采取適當的措施。
MVP模式的優點之一是,您可以根據當前所處的屏幕來單獨處理通知。例如,假設在顯示某種與當前溫度無關的設置菜單(例如,MainMenuPresenter / MainMenuView處於活動狀態)時發生了溫度變化事件。
由於notifyTemperatureChanged函數具有默認的空實現,因此MainMenuPresenter完全忽略了此通知。另一方面,如果您有TemperatureControlPresenter,則可以在此演示者中重寫notifyTemperatureChanged函數,並通知View它應顯示更新的溫度:
TemperatureControlPresenter.hpp: class TemperatureControlPresenter : public ModelListener { public: // override the empty function. virtual void notifyTemperatureChanged(int newTemperature) {
view.setTemp(newTemperature);
} };
將數據從UI傳輸到周圍的系統
數據/事件從UI傳輸到周圍系統的相反方向是通過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中設置新的目標溫度,則視圖可以通知Presenter,該Presenter擁有指向Model對象的指針,因此可以調用該 setNewTargetTemperature
函數。
例子
方法1-從GUI任務
下載此鏈接可找到STM32F746的工作示例,該示例演示如何在Model類中對按鈕進行采樣並直接控制LED。該示例使用MVP體系結構在兩個視圖和Model類之間傳遞值和事件。Model類對按鈕進行采樣並更新LED以匹配應用程序的狀態。
下載此鏈接可找到STM32F429的工作示例,其中顯示了如何對Model類中的按鈕進行采樣。該示例使用MVP架構將按鈕事件傳輸到視圖。
方法2-從其他任務
方法3-從多個任務(4.9.3)
該工作示例已在2018年5月28日舉行的TouchGFX網絡研討會“與硬件集成”中進行了演示。
該應用程序是為STM32F769-DISCO板設計的,並與LED和用戶按鈕交互,以顯示如何將C代碼和硬件外圍設備集成到您的TouchGFX應用程序中。
應用程序以GPIO模式配置按鈕。行為是在btntask.c中采樣按鈕的狀態,如果按下按鈕,則將消息通過GUI消息隊列傳遞。這使我們可以通過按住按鈕來在應用程序中推進動畫。
該應用程序使用三個FreeRTOS任務。一個用於GUI,每個用於外圍設備(LED和用戶按鈕)。
方法4-從任務和外部中斷線(4.9.3)
該工作示例已在2018年5月28日舉行的TouchGFX網絡研討會“與硬件集成”中進行了演示。
該應用程序是為STM32F769-DISCO板設計的,並與LED和用戶按鈕交互,以顯示如何將C代碼和硬件外圍設備集成到您的TouchGFX應用程序中。
該應用程序將按鈕配置為EXTI模式(外部中斷線0)。行為是在按下按鈕后接收中斷,然后清除中斷。這不允許與GPIO中的行為相同,但是我們將單步執行動畫,因為僅在接收到中斷時才通過gui消息隊列發送消息。
該應用程序使用兩個FreeRTOS任務。一個用於GUI,一個用於LED。(方法3中的Button任務在此應用程序中保持活動狀態,以說明外圍設備交互代碼已移至中斷處理程序中)。
【原文】