STM32單片機項目實例:基于TouchGFX的智能手表設計(3)嵌入式程序任務調度的設計
目錄
一、嵌入式程序設計
1.1輪詢
1.2?前后臺(中斷+輪詢)
1.3 事件驅動與消息
1.3.1?事件驅動的概念
1.4?定時器觸發+事件驅動型的任務設計
???????1.4.1定時器觸發
???????1.4.2?界面事件驅動
一、嵌入式程序設計
? 大數學家華羅庚先生在《統籌方法》中寫到自己泡茶的故事,也就是大家語文課本的《時間統籌法》一文,在這個故事中,時間統籌法主要是用來做時間管理,優化做事情的流程,節約時間。比如:洗開水壺、燒水需要16分鐘,洗茶壺、洗茶杯、拿茶葉需要4分鐘,這兩件事情先做哪個?這是最常見的家務活舉例,不同的思維方式產生不同結果。
? 如果按照線性思維,先去洗開水壺、燒水需要16分鐘,再去洗茶壺、洗茶杯和拿茶葉需要4分鐘,那一共需要16+4=20分鐘;按照時間統籌法,先洗開水壺,把水放在爐子上燒,然后同時去洗茶壺、茶杯、拿茶葉,等水燒好了,茶具也準備好了,這樣兩件事情一共只需要花費1+15=16分鐘,無形中就節約了4分鐘的時間。
圖 1-1?《時間統籌法》中的家務活舉例
? 用嵌入式系統去看泡茶這件事情,水壺、茶壺、茶杯、茶葉等可以理解為嵌入式系統中的硬件層,洗、拿、燒的動作理解為嵌入式系統中的驅動層,泡茶理解為嵌入式系統中的應用層。
?圖 1-2?從嵌入式系統的角度看待泡茶故事
泡茶故事中,如按照線性思維去操作,泡茶需要經過洗水壺 ->?燒開水 ->?洗茶壺?->?洗茶杯 ?->?拿茶葉 ?->?泡茶總共6個過程,這些過程我們換個詞用“狀態”去表示,洗水壺狀態->?燒開水狀態 ->?洗茶壺狀態?->?洗茶杯狀態 ?->?拿茶葉狀態 ?->?泡茶狀態,這些狀態間的“遷移”依賴于某一時刻發生的有意義的事情(例如洗水壺完成、燒開水完成…),進而發生了狀態的遷移,我們稱之為“事件”。在狀態的“遷移”過程中,我們需要做出其它一些行為,這些行為就是“動作”,例如拿水壺、拿茶壺或者拿茶杯等,“動作”是對事件的響應。對于事件的響應,還依賴于是否滿足一定的“條件”才能發生狀態間的遷移,并不是有求必應的。泡茶的過程在嵌入式程序設計中可以用嵌入式狀態機(FSM)模式進行設計(一款C語言編寫的輕量級的函數指針有限狀態機編程框架,可實現entry和exit動作),嵌入式狀態機是一種基于狀態轉移的程序設計模式,它通過將程序的執行過程分成一系列狀態,以及描述狀態轉移的規則,實現復雜問題的分步解決。在嵌入式系統中,狀態機常用來實現復雜的控制邏輯、事件處理和通信協議等功能,其簡單靈活的設計在嵌入式系統應用中得到了廣泛的運用。
圖 1-3?有限狀態機在泡茶故事中的使用?
? ?嵌入式系統的應用場景,比泡茶的過程更為復雜。例如硬件方面,處理器的單核與多核、外部設備對響應速度與周期性控制、低功耗等要求等。軟件方面裸機與嵌入式系統(RTOS、Linux)開發的不同,以及是否使用中間件(TouchGFX)等。在這些場景下,就需要具備嵌入式程序設計的思想和方法。本文對微控制器裸機任務開發的設計方法進行探討,主要涉及應用程序的輪詢、前后臺、優先級與時間片、有限狀態機、定時器觸發+事件驅動型的任務調度進行講解。
1.1輪詢
對于簡單的應用程序,輪詢(無限循環)的實現比較簡單,在硬件完成初始化后,順序的完成各種任務。在外設的基礎實驗中,常采用這種方式。?輪詢的偽代碼實現方式:
01 int main(void)
02 {
03 /* 硬件相關初始化 */
04 HardwareInit();
05
06 /* 無限循環 */
07 while(1) {
08 /* 任務1 */
09 Task1();
10
11 /* 任務2 */
12 Task2();
13
14 /* 任務3 */
15 Task3();
16 }
17 }
? 在實際的嵌入式系統中,存在周期性(周期100ms,處理時間10ms)與觸發型任務(掃地機器人,懸空檢測,實時性),每個任務的執行時間與實時響應要求不同,在采用輪詢系統進行程序設計時,很難應對這些場景。
1.2?前后臺(中斷+輪詢)
前后臺系統是在輪詢的基礎上加入了中斷。外部事件的記錄在中斷中操作,對事件的響應在輪詢中完成,中斷處理過程稱之為前臺,main函數中的輪詢稱為后臺。如下圖所示:
? ?后臺的程序順序執行,如果產生中斷,那么中斷會打斷后臺程序的正常執行,轉而去執行中斷服務程序。如果事件的處理過程比較簡單,可以直接在中斷服務程序中進行處理;如果事件的處理過程比較復雜,可以在中斷中對事件響應進行標記,進而返回后臺程序進行處理。輪詢的偽代碼實現方式:
01 int main(void)
02 {
03 /* 硬件相關初始化 */
04 HardwareInit();
05
06 /* 無限循環 */
07 while(1) {
08 /* 任務1 */
09 if(Task1標志)
10 {
11 Task1();
12 Task1標志為假;
13 }
14
15 /* 任務2 */
16 if(Task2標志)
17 {
18 Task2();
19 Task2標志為假;
20 }
21
22 /* 任務3 */
23 if(Task3標志)
24 {
25 Task3();
26 Task3標志為假;
27 }
28 }
29 }
30 /**
31 ** Task1的中斷服務程序
32 **/
33 void Task1_Handler(void)
34 {
35 Task1標志為真;
36 }
37 /**
38 ** Task1的中斷服務程序
39 **/
40 void Task2_Handler(void)
41 {
42 Task2標志為真;
43 }
44 /**
45 ** Task1的中斷服務程序
46 **/
47 void Task3_Handler(void)
48 {
49 Task3標志為真;
50 }
? 相較于輪詢系統,前后臺系統可以確保事件的記錄不會丟失,提高了對事件的響應。同時,基于Cortex-M內核的MCU對異常具有可編程的優先級(中斷嵌套)、末尾連鎖以及延遲到達等功能,這可以大大提高程序的實時響應能力。
? 采用前后臺系統進行程序時,對后臺的任務需要進行設計,避免單個任務長時間占有處理器資源。當任務的邏輯比較復雜,任務的拆分難度增加,同時,隨著中斷事件的增加,整個程序的設計與響應的實時性將會降低。???????
1.3 事件驅動與消息
? 嵌入式MCU軟件開發中,我們應具備程序分層設計的思想,程序分層設計能夠降低軟件的復雜度和依賴關系,同時有利于標準化,便于管理各層的程序,提高各層邏輯的復用(軟件工程技術中的復用與解耦,復用可以極大提升軟件開發效率,使得軟件開發可以從 70% 甚至 90% 起步;而解耦可以大幅提升軟件的可維護性和可修改性,降低長期維護成本)。
???????1.3.1?事件驅動的概念
? Hello,World!是很多初學者進行嵌入式操作系統編程時的第一個程序。在嵌入式MCU裸機編程中,UART外設要比GPIO外設更為復雜,初學者的第一個程序往往是點亮LED燈(點燈大師),在GPIO的輸入操作時,通過按鍵輸入去控制LED,偽代碼實現方式如下:
01 int main(void)
02 {
03 /* 硬件相關初始化 */
04 HardwareInit();
05
06 /* 無限循環 */
07 while(1)
08 {
09 /* 按鍵掃描 */
10 if(Key為低)
11 {
12 delay(100ms);//延時100ms,電平穩定后讀取
13 if(Key為低)
14 {
15 LED點亮;
16 }
17 }
18 else
19 {
20 LED熄滅;
21 }
22 /* 其它任務 */
23 ......
24 }
25 }
? 采用該種方式進行程序結構設計時,按鍵輸入的響應依賴于其它任務的執行時間與任務的數量,若其它任務的執行時間是200ms,則可能造成按鍵事件的丟失。在學習EXTI部分的知識后,可以采用中斷的方式進行按鍵事件的響應,偽代碼實現方式如下:
01 int main(void)
02 {
03 /* 硬件相關初始化 */
04 HardwareInit();
05
06 /* 無限循環 */
07 while(1)
08 {
09 /* 其它任務 */
10 ......
11 }
12 }
13 /******************************************************************
14 *FuncName :EXTIx_IRQHandler
15 *Description :EXTIx中斷服務函數
16 *Arguments :void
17 *Return :void
18 *******************************************************************/
19 void EXTIx_IRQHandler ( void )
20 {
21 ......
22 /* 按鍵掃描 */
23 if(Key為低)
24 {
25 delay(100ms);//延時100ms,電平穩定后讀取
26 if(Key為低)
27 {
28 LED點亮;
29 }
30 }
31 else
32 {
33 LED熄滅;
34 }
35 ......
36 }
? 采用該種方式,提高了系統對按鍵輸入的響應,同時也存在優先級設置的問題,在采用STM32的HAL庫開發中,HAL_Delay()延時函數默認采用系統滴答定時器(Systick)的中斷產生計時。上述代碼能夠正常運行的前提是Systick的中斷優先級要比外部中斷線的優先級高,同時,按鍵的觸發頻率也不能太高,這些要求在復雜的系統中,很難得到滿足。
注釋:T1-T4時間內其它低優先級中斷被掛起,降低了系統響應性,同時阻塞了其它任務的執行。T2-T3時間內,處理器的資源被浪費。
? 對于中斷的處理機制,減少中斷響應時間是設計的初衷,我們將思路進行調整,當按鍵按下這個事件發生時,在中斷中對事件進行記錄,在主循環中對記錄的事件進行處理,偽代碼實現方式如下:
01 # define FLG_KEY 0x08
02 volatile uint8_t gu8EvntFlgGrp = 0 ; /*事件標志組*/
03 int main(void)
04 {
05 uint8_t pu8FlgTmp = 0 ;
06 /* 硬件相關初始化 */
07 HardwareInit();
08
09 /* 無限循環 */
10 while(1)
11 {
12 pu8FlgTmp = read_envt_flg_grp(); /*讀取事件標志組*/
13 //
14 if (pu8FlgTmp) /*是否有事件發生? */
15 {
16 if (pu8FlgTmp & FLG_KEY)
17 {
18 LED點亮;
19 }
20 }
21 else
22 {
23 LED熄滅;
24 ; /* 空閑代碼 */
25 }
26
27 }
28 }
29 /******************************************************************
30 *FuncName :read_envt_flg_grp
31 *Description :讀取事件標志組 gu8EvntFlgGrp,讀取完畢后將其清零。
32 *Arguments :void
33 *Return :void
34 *******************************************************************/
35 uint8_t read_envt_flg_grp ( void )
36 {
37 uint8_t pu8FlgTmp = 0 ;
38 /* 關閉全局中斷 */
39 __disable_irq();
40 /* 讀取標志組 */
41 pu8FlgTmp = gu8EvntFlgGrp;
42 /* 清零標志組 */
43 gu8EvntFlgGrp = 0 ;
44 /* 開啟全局中斷 */
45 __enable_irq();
46 //返回值
47 return pu8FlgTmp;
48 }
49 /******************************************************************
50 *FuncName :EXTIx_IRQHandler
51 *Description :EXTIx中斷服務函數
52 *Arguments :void
53 *Return :void
54 *******************************************************************/
55 void EXTIx_IRQHandler ( void )
56 {
57 ......
58 /* 關閉全局中斷 */
59 __disable_irq();
60 HAL_TIM_Base_Start_IT(&htimx); //開啟定時器
61 /* 開啟全局中斷 */
62 __enable_irq();
63 ......
64 }
65 /******************************************************************
66 *FuncName : TIMx_IRQHandler
67 *Description : TIMx中斷服務函數,100ms中斷一次
68 *Arguments : void
69 *Return : void
70 *******************************************************************/
71 void TIMx_IRQHandler ( void )
72 {
73 ......
74 if(Key為低)
75 {
76 /* 關閉全局中斷 */
77 __disable_irq();
78 gu8EvntFlgGrp |= FLG_KEY; /*設置 KEY 事件標志*/
79 HAL_TIM_Base_Stop(&htimx); //關閉定時器
80 /* 開啟全局中斷 */
81 __enable_irq();
82 }
83 ......
84 }
?注釋:T1-T2/ T3-T4快速記錄/處理按鍵觸發事件。T2-T3時間內,處理器的資源被充分利用。
? 上述的按鍵任務中,按鍵的按下與釋放是一種事件,中斷處理流程對事件進行記錄,并產生一個消息,主循環中對消息進行執行并銷毀,是一種比較簡單的事件驅動機制。在嵌入式系統中,事件驅動機制的應用也十分廣泛。下面是UART、TIMER、EXTI與KEY等外設的事件檢測在各自中斷中完成,通過事件驅動機制通知主函數進行處理的完整偽代碼。
1 # define FLG_UART 0x01
2 # define FLG_TMR 0x02
3 # define FLG_EXI 0x04
4 # define FLG_KEY 0x08
5
6 volatile uint8_t gu8EvntFlgGrp = 0 ; /*事件標志組*/
7
8 uint8_t read_envt_flg_grp( void );
9 /************************************************************
10 *FuncName :main
11 *Description :主函數
12 *Arguments :void
13 *Return :void
14 *************************************************************/
15 void main ( void )
16 {
17 uint8_t pu8FlgTmp = 0 ;
18 /* 硬件相關初始化 */
19 HardwareInit();
20 /* 無限循環 */
21 while ( 1 )
22 {
23 pu8FlgTmp = read_envt_flg_grp(); /*讀取事件標志組*/
24 //
25 if (pu8FlgTmp ) /*是否有事件發生? */
26 {
27 if (pu8FlgTmp & FLG_UART)
28 {
29 action_uart_task(); /* 處理串口事件 */
30 }
31 if (pu8FlgTmp & FLG_TMR)
32 {
33 action_timer_task(); /* 處理定時中斷事件 */
34 }
35 if (pu8FlgTmp & FLG_EXI)
36 {
37 action_exti_task(); /* 處理外部中斷事件 */
38 }
39 if (pu8FlgTmp & FLG_KEY)
40 {
41 action_key_task(); /* 處理擊鍵事件 */
42 }
43 }
44 else
45 {
46 ; /* 空閑代碼 */
47 }
48 }
49 }
50 /******************************************************************
51 *FuncName :read_envt_flg_grp
52 *Description :讀取事件標志組 gu8EvntFlgGrp,讀取完畢后將其清零。
53 *Arguments :void
54 *Return :void
55 *******************************************************************/
56 uint8_t read_envt_flg_grp ( void )
57 {
58 uint8_t pu8FlgTmp = 0 ;
59 /* 關閉全局中斷 */
60 __disable_irq();
61 /* 讀取標志組 */
62 pu8FlgTmp = gu8EvntFlgGrp;
63 /* 清零標志組 */
64 gu8EvntFlgGrp = 0 ;
65 /* 開啟全局中斷 */
66 __enable_irq();
67 //返回值
68 return pu8FlgTmp;
69 }
70 /******************************************************************
71 *FuncName :UARTx_IRQHandler
72 *Description :UARTx中斷服務函數
73 *Arguments :void
74 *Return :void
75 *******************************************************************/
76 void UARTx_IRQHandler ( void )
77 {
78 ......
79 push_uart_rcv_buf(new_rcvd_byte); /*新接收的字節存入緩沖區*/
80 /* 關閉全局中斷 */
81 __disable_irq();
82 gu8EvntFlgGrp |= FLG_UART; /*設置 UART 事件標志*/
83 /* 開啟全局中斷 */
84 __enable_irq();
85 ......
86 }
87 /******************************************************************
88 *FuncName : TIMx_IRQHandler
89 *Description : TIMx中斷服務函數
90 *Arguments : void
91 *Return : void
92 *******************************************************************/
93 void TIMx_IRQHandler ( void )
94 {
95 uint8_t u8KeyCode = 0 ;
96 ......
97 /* 關閉全局中斷 */
98 __disable_irq();
99 gu8EvntFlgGrp |= FLG_TMR; /*設置 TMR 事件標志*/
100 /* 開啟全局中斷 */
101 __enable_irq();
102 ......
103 u8KeyCode = read_key(); /*讀鍵盤*/
104 if (u8KeyCode) /*有擊鍵操作? */
105 {
106 push_key_buf(u8KeyCode); /*新鍵值存入緩沖區*/
107 /* 關閉全局中斷 */
108 __disable_irq();
109 gu8EvntFlgGrp |= FLG_KEY; /*設置 KEY 事件標志*/
110 /* 開啟全局中斷 */
111 __enable_irq();
112 }
113 ......
114 }
115 /******************************************************************
116 *FuncName :EXTIx_IRQHandler
117 *Description :EXTIx中斷服務函數
118 *Arguments :void
119 *Return :void
120 *******************************************************************/
121 void EXTIx_IRQHandler ( void )
122 {
123 ......
124 /* 關閉全局中斷 */
125 __disable_irq();
126 gu8EvntFlgGrp |= FLG_EXI; /*設置 EXI 事件標志*/
127 /* 開啟全局中斷 */
128 __enable_irq();
129 ......
130 }
以上事件處理代碼可以做成標準的框架代碼,它能夠應對大部分嵌入式裸機編程的情況。同時,事件驅動機制采用這樣的方式實現也存幾點問題需要注意:
同事件集中爆發時,會丟失后面發生的事件。
不同事件集中爆發,會丟失事件發生的順序。
事件的優先級與多任務并發執行。
? 上圖中,T1、T2、T3是各個任務的事件處理函數,I1、I2、I3是不同事件觸發的IRQ,假定I1、I2、I3分別對應E1、E2、E3事件,當運行T1的事件處理函數時,發生了2次相同的事件,T1事件處理函數被中斷2次,I2在執行的時候,連續兩次置位了相應的事件標志位。
? 當T1的事件處理函數完成后,順序執行T2的事件處理函數,在T2執期間,發生2次不同的事件。T2被中斷2次,I1和I3執行并置位相應的事件標志位。
? 執行T1事件處理函數時,產生的兩次E2事件,由于沒有緩沖機制,在執行T2事件處理函數時,會丟失對E2事件的處理,也就是我們上面的講到的:同事件集中爆發時,會丟失后面發生的事件。
? 執行T2事件處理函數時,產生的E1和E3事件,主循環處理事件的順序是按照程序預先設定的順序,一個一個的處理事件,若集中爆發不同事件,對于事件的發生順序與處理順序會產生不一致的情況,若系統對于事件的發生順序敏感,則無法滿足。
? 為了解決事件的丟失與發生順序,可以在與事件相關的 IRQ中把事件加工成消息,并把它存儲在消息緩沖區中,消息緩沖區設計成以“先入先出”方式管理的環形緩沖隊列。事件生成的消息總是從隊尾入隊,管理程序讀取消息的時候總是從隊頭讀取,這樣,消息在緩沖區中存儲的順序就是事件在時間上發生的順序,先發生的事件總是能先得到響應。IRQ完成這些工作后立即退出。主循環通過查詢消息緩沖區,將存儲的消息信息進行分析與執行,最終完成對本次事件的響應。
? 通過這種方法實現的事件驅動機制能夠解決前面提到的那兩個問題,即不同事件集中爆發時,無法記錄事件發生的前后順序。同一事件集中爆發時,容易遺漏后面發生的事件。對于第一種情況,消息(事件)在緩沖隊列中是以“先入先出”的方式存儲的,存儲順序就代表了事件發生的先后順序。對于第二種情況, 任何被 ISR 捕捉到的事件都會以一個獨立的消息實體存入緩沖隊列, 即使前后兩個是同一個事件, 只要 ISR 反應夠快就不會遺漏事件。實際上, ISR 的主要工作就是填寫消息實體, 然后將其存入緩沖隊列, 做這些工作只占用 CPU 很短的時間。
? 對于事件的優先級與多任務并發執行需求的場景,建議采用實時操作系統(Real Time Operating System,簡稱RTOS),RTOS在調度方式上,分為搶占式調度、時間片調度和合作式調度。采用RTOS可以確保在一定的時間內能夠執行到所有的任務。
???????1.4?定時器觸發+事件驅動型的任務設計
采用裸機方式開發基于TouchGFX的智能手表項目,了解TouchGFX的相關原理是十分重要的, TouchGFX用戶接口遵循Model-View-Presenter(MVP)架構模式,它是Model-View-Controller(MVC)模式的派生模式。 兩者都廣泛用于構建用戶接口應用。MVP模式的主要優勢是:
關注點分離:將代碼分成不同的部分提供,每部分有自己的任務。?這使得代碼更簡單、可重復使用性更高且更易于維護。
單元測試:由于UI的邏輯(Presenter)獨立于視圖(View),因此,單獨測試這些部分會容易很多。
MVP中定義了下列三個類:
Model是一種接口,用于定義要在用戶界面上顯示或有其他形式操作的數據。
View是一種被動接口,用于顯示數據(來自Model),并將用戶指令(事件)傳給Presenter以便根據該數據進行操作。
Presenter的操作取決于Model和View。?它從存儲庫(Model)檢索數據,并將其格式化以便在視圖中顯示。
? 在TouchGFX中,從Model類執行與應用非UI部分(這里稱為后端系統)的通信。后端系統是從UI接收事件和將事件輸入UI的軟件組件,例如采集傳感器的新測量值。后端系統可作為單獨的任務在同一MCU、單獨的處理器、云模塊或其他硬件上運行。從TouchGFX的角度來看,這并不十分重要,只要它是能夠與之通信的組件。?
? 使用的特定通信協議不受TouchGFX管理。它只提供一個在每個TouchGFX嘀嗒時間調用一次的函數,可以在其中處理需要的通信。 ?
屏幕概念
? 在TouchGFX應用中,可以有任意數量的“屏幕”。 TouchGFX中的屏幕是UI元素(小工具)及其相關業務邏輯的邏輯分組。 屏幕包含兩個類:包含該屏幕上顯示的所有小工具的View類,以及包含該屏幕的業務邏輯的Presenter類。
? 可以選擇在單個屏幕的背景下實現整個應用(意味著只有一個View和一個Presenter),建議將UI的不相關部分分割成不同屏幕,原因有兩個:
1. TouchGFX包含存儲器分配方案,可自動分配大多數RAM占用量大的屏幕所需的必要RAM。 只會分配必要的量,此RAM塊將在應用中的所有屏幕之間重復使用。
2. 有多個屏幕會使UI代碼的維護容易得多。
定義屏幕
? 關于應如何將應用劃分成多個屏幕,并沒有具體的規則,但有特定的指南,也許能幫助您決定應當用哪些屏幕構成您的特定應用。 在視覺和功能上無關的UI區域應保存在不同屏幕中。
? 如果是十分簡單的有主溫度讀出顯示屏和配置菜單的恒溫器應用,建議創建“主屏幕”用于溫度讀出,并創建“設置屏幕”用于顯示配置菜單。主屏幕的視圖將包含用于背景圖像的小工具,幾個顯示溫度的文本區和一個用于切換至配置菜單的按鈕。 另一方面,用于配置的視圖可能包含顯示配置選項列表和不同背景圖像的小工具。 如果配置菜單能夠編輯許多不同類型的設置(日期、名稱和鍵盤、溫度、單位等),此屏幕的復雜性將大幅提升。
當前活動屏幕
? 由于 TouchGFX 為屏幕分配內存的方式(只為最大的 View 和最大的 Presenter 分配),一次只能有一個 View 和一個 Presenter 處于活動狀態。?因此,如果您的恒溫器應用程序正在顯示溫度讀數,那么配置菜單屏幕不會在任何地方運行,實際上甚至沒有分配。
? 如果從“后端”(所有執行恒溫器實際工作的非UI代碼)或硬件外設接收到事件,則可以將這些事件傳遞給當前活動屏幕。
? 由于一些事件將只與應用中的特定屏幕有關,因此這提供了有效的隔離方式。 例如,只有主屏幕才能處理通知當前溫度變化的已接收事件(將更新顯示當前溫度的文本區),而由于當前溫度未顯示在配置屏幕上,配置屏幕可以直接丟棄此無關事件。
TouchGFX中的Model-View-Presenter
? TouchGFX 遵循 Model-View-Presenter Design Pattern描述的Model-View-Presenter 設計模式。 TouchGFX 屏幕概念通過繼承自 TouchGFX 中的 View 和 Presenter 類的類與整個 Model-View-Presenter 架構聯系在一起。 因此,在TouchGFX Designer中將新屏幕添加到應用時,會創建新的特定View類和Presenter類以代表該特定屏幕。
TouchGFX應用中MVP類的內容和責任如下:
Model
Model 類是一個永遠存在的單類,它有兩個用途:
1. 保存UI的狀態信息。 在切換屏幕時,View和Presenter的分配會被清除,因此它們不能用于存儲在屏幕轉換時應當保留的信息。 為此,使用Model保存信息。
2. 作為面向后端系統的接口,向/從當前活動屏幕發送事件。
? Model類是自動設置的,具有指向當前活動Presenter的指針。當Model中發生變化時,將變化通知當前活動Presenter。這是通過應用中ModelListener接口中的方法來完成的。
TouchGFX Designer生成的新應用將自動擁有可直接供UI使用的Model類。
View
? View類(或者更具體地說,派生自TouchGFX View類的類)包含了該視圖中顯示的控件作為成員對象。 它還包含setupScreen和tearDownScreen函數,當進入/退出該屏幕時,會自動調用它們。 通常會在setupScreen函數中配置控件。
? View還將包含指向相關Presenter的指針。 該指針通過框架自動設置。 使用此指針,您可以將 UI 事件(如按鈕單擊)傳達給 Presenter。
Presenter
? ?Presenter 類(同樣,一個派生自 TouchGFX Presenter 類的類)負責當前活動屏幕的業務邏輯。 它將接收來自模型的“后端”事件,以及來自視圖的 UI 事件,并決定采取何種行動。 例如,如果從 Model 接收到警報事件,Presenter 可能決定告訴 View 應該顯示警報彈出對話框。
? 基于TouchGFX的智能手表項目的裸機任務采用定時器觸發任務+活動屏幕事件驅動的方式,實現方式如下圖所示:
?采用該框架設計的程序,代碼邏輯清晰,任務僅與活動屏幕下的事件有關,缺點是相關任務的設計不能是阻塞的,也存在某一時間段會有多個任務需要順序執行。該框架下的main函數代碼:
01 int main(void)
02 {
03 /* USER CODE BEGIN 1 */
04
05 /* USER CODE END 1 */
06
07 /* MCU Configuration*/
08
09 /* Reset of all peripherals, Initializes the Flash and the Systick. */
10 HAL_Init();
11
12 /* USER CODE BEGIN Init */
13
14 /* USER CODE END Init */
15
16 /* Configure the system clock */
17 SystemClock_Config();
18
19 /* Configure the System Power */
20 SystemPower_Config();
21
22 /* USER CODE BEGIN SysInit */
23
24 /* USER CODE END SysInit */
25
26 /* Initialize all configured peripherals */
27 MX_GPIO_Init();
28 MX_GPDMA1_Init();
29 MX_ICACHE_Init();
30 MX_OCTOSPI1_Init();
31 MX_SPI1_Init();
32 MX_CRC_Init();
33 MX_I2C1_Init();
34 MX_TIM16_Init();
35 MX_TIM17_Init();
36 MX_USART1_UART_Init();
37 MX_RTC_Init();
38 MX_ADC1_Init();
39 MX_UART5_Init();
40 MX_SPI2_Init();
41 MX_TouchGFX_Init();
42 /* USER CODE BEGIN 2 */
43 //ESP8266初始化,HAL庫使用USART3
44 ESP8266_Init(&huart5,(uint8_t *)gRX3_BufF,115200);
45 ap3216c_init(); //環境光傳感器初始化
46 ILI9341_Init(); //顯示屏初始化
47 FT6336_init(); //觸摸屏初始化
48 mpu_init_dmp(); //mpu6050 dmp初始化
49 System_Time_init();
50 //NOR Flash初始化
51 OSPI_W25Qxx_Init(); //初始化W25Q128
52 OSPI_W25Qxx_mmap(); //設置為內存映射模式
53 HAL_PWREx_EnableVddA();
54 HAL_PWREx_EnableVddIO2();
55
56 //清空任務列表
57 for(gTaskIndex = 0;gTaskIndex < OS_TASKLISTCNT;gTaskIndex++)
58 g_OSTsakList[gTaskIndex]=NULL;
59
60 //讀取ADC值
61 if (HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&gStruADC,ADC_CONVERTED_DATA_BUFFER_SIZE) != HAL_OK)
62 {Error_Handler();}
63 /* USER CODE END 2 */
64
65 /* Infinite loop */
66 /* USER CODE BEGIN WHILE */
67 HAL_TIM_Base_Start_IT(&htim16);//開啟定時器16開啟,系統任務調度開始
68 HAL_TIM_Base_Start_IT(&htim17);//開啟定時器17開啟,設備控制任務開始
69 while (1)
70 {
71 /* USER CODE END WHILE */
72
73 MX_TouchGFX_Process();
74 /* USER CODE BEGIN 3 */
75 //執行任務列表中的的任務
76 for(gTaskIndex = 0;gTaskIndex < OS_TASKLISTCNT;gTaskIndex++)
77 {
78 if((*g_OSTsakList[gTaskIndex]) != NULL)
79 {
80 g_OSTsakList[gTaskIndex]();
81 g_OSTsakList[gTaskIndex] = NULL;
82 }
83 }
84 }
85 /* USER CODE END 3 */
86 }
???????1.4.1定時器觸發
? 通過兩個定時器分別產生5ms與200ms的中斷,周期性的判斷事件的任務標志位,當該事件產生時,將相關事件的任務加入至主任務循環,事件的任務標志組如下:
01 //任務使能標值
02 typedef struct
03 {
04 uint32_t UPDATE_DIAL_EN:1; //表盤頁面任務使能
05 uint32_t UPDATE_SIX_AXIS_EN:1; //六軸運動任務使能
06 uint32_t UPDATE_WIFI_RSSI_EN:1; //WiFi聯網任務使能
07 uint32_t UPDATE_APP_TASK_EN:1; //APP頁面任務使能
08 uint32_t UPDATE_CHIPPAGE:1; //系統信息任務使能
09 uint32_t UPDATE_HEALTHPAGE:1; //健康任務使能
10 uint32_t UPDATE_APPPAGE:1; //應用界面使能
11 uint32_t UPDATE_INFOPAGE:1; //INFO界面使能
12 uint32_t UPDATE_WIFIPAGE:1; //WiFi界面使能
13 uint32_t UPDATE_SETTINGPAGE:1; //Setting界面使能
14 uint32_t UPDATE_MOTORPAGE:1; //Motor界面使能
15 uint32_t UPDATE_BATTERYPAGE:1; //Battery界面使能
16 uint32_t UPDATE_SPORTPAGE:1; //Sport界面使能
17 uint32_t UPDATE_ALIPAYPAGE:1; //Alipay界面使能
18 uint32_t :18;
19 }gTask_MarkEN;
采用STM32的HAL庫開發,在定時器回調函數中判斷事件的任務標志位,并加入至主循環任務,定時器回調函數如下:
01 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
02 {
03 static uint8_t p_Time16Cnt = 0,p_Time17Cnt = 0;
04 /******************************************************************/
05 //定時器16進行5ms任務中斷
06 if (htim->Instance == htim16.Instance)
07 {
08 p_Time16Cnt++;
09 //
10 if(!(p_Time16Cnt % 4)) //20ms(50Hz)進行觸發刷新
11 {
12 touchgfx_signalVSynTimer(); //touchgfx用戶接口
13 }
14 //五項按鍵讀取
15 if(!(p_Time16Cnt % 20)) //100ms進行一次窗口更新
16 {
17 if(gTaskStateBit.TouchPress == 0) //更新五向鍵數據
18 {
19 g_OSTsakList[eUPDATE_FIVEKEY] = Update_FiveKey_Value;
20 }
21 }
22 //1000ms運行一次,系統運行指示燈
23 if(!(p_Time16Cnt % 200))
24 {
25 p_Time16Cnt = 0;
26 HAL_GPIO_TogglePin(BLUE_LED_GPIO_Port,BLUE_LED_Pin);
27 }
28 }
29 /******************************************************************/
30 //定時器17進行100ms任務中斷
31 if (htim->Instance == htim17.Instance)
32 {
33 p_Time17Cnt++;
34 //周期為200ms任務
35 if(!(p_Time17Cnt % 2)) //200ms進行一次下列代碼
36 {
37 if((gTaskEnMark.UPDATE_DIAL_EN || gTaskEnMark.UPDATE_SIX_AXIS_EN)
38 && (gTaskStateBit.TouchPress == 0))
39 {//歐拉角更新
40 g_OSTsakList[eUPDATE_SIX_AXIS] = Update_EulerAngle;
41 }
42 }
43 //周期為300ms任務
44 if(!(p_Time17Cnt % 3)) //300ms進行一次下列代碼
45 {
46 if(gTaskEnMark.UPDATE_WIFI_RSSI_EN) //獲取wifi連接的RSSI值
47 g_OSTsakList[eUPDATE_WIFI_RSSI] = ESP8266_RSSI_Task;
48 }
49 //周期為500ms任務
50 if(!(p_Time17Cnt % 5)) //500ms進行一次下列代碼
51 {
52 if(gTaskEnMark.UPDATE_CHIPPAGE) //系統信息更新
53 g_OSTsakList[eUPDATE_CHIPINFO] = Update_ChipInfo;
54 }
55 //周期為1000ms任務
56 if(!(p_Time17Cnt % 10)) //1s進行一次下列代碼
57 {
58 if(gTaskEnMark.UPDATE_DIAL_EN && (gTaskStateBit.TouchPress == 0))
59 { //系統時間更新
60 g_OSTsakList[eUPDATE_TIME] = Update_System_Time;
61 }
62 }
63 //周期為2000ms任務
64 if(!(p_Time17Cnt % 20)) //2s進行一次下列代碼
65 {
66 if((gTaskEnMark.UPDATE_DIAL_EN || gTaskEnMark.UPDATE_INFOPAGE)
67 && (gTaskStateBit.TouchPress == 0))
68 {//更新電壓、電流、溫濕度、光照度
69 g_OSTsakList[eUPDATE_DIAL_INFO] = Update_DialInfo;
70 }
71 }
72 //周期為3000ms任務
73 if(!(p_Time17Cnt % 30)) //3s進行一次下列代碼
74 {
75 //心率任務會阻塞主程序
76 if(gTaskEnMark.UPDATE_HEALTHPAGE) //獲取健康信息
77 {
78 g_OSTsakList[eUPDATE_HEART_RATE] = Update_HeartRateInfo;
79 }
80 }
81 //周期為10000ms任務
82 if(!(p_Time17Cnt % 100)) //10s進行一次下列代碼
83 {
84 p_Time17Cnt = 0;
85 }
86 }
87 /******************************************************************/
88 /* Prevent unused argument(s) compilation warning */
89 UNUSED(htim);
90 }
???????1.4.2?界面事件驅動
在TouchGFX中,從Model類執行與應用非UI部分(稱為后端系統)的通信。后端系統是從UI接收事件和將事件輸入UI的軟件組件?。在TouchGFX中提供一個在每個TouchGFX嘀嗒時間調用一次的函數,在該函數中處理需要的通信。以下是將信息輸入至UI的軟件組件代碼:
001 void Model::tick()
002 {
003 static uint8_t tickCount = 0; //減少數據上傳的次數,優化界面刷新
004 tickCount++;
005
006 #if defined LINK_HARDWARE
007 //
008 if(gSwitchSpace != 0) gSwitchSpace--;
009 /*************************硬件頁面切換************************/
010 //表盤頁面
011 if(gTaskEnMark.UPDATE_DIAL_EN && (gTaskStateBit.TouchPress == 0)
012 && (!gSwitchSpace))
013 {
014 modelListener->DialPageChange(gFiveKeyFunc);
015 gSwitchSpace = 0x0F; //使能切換時間計數
016 }
017 //應用頁面
018 if(gTaskEnMark.UPDATE_APPPAGE && (gTaskStateBit.TouchPress == 0)
019 && (!gSwitchSpace))
020 {
021 modelListener->AppPageChange(gFiveKeyFunc);
022 gSwitchSpace = 0x0F; //使能切換時間計數
023 }
024 //六軸頁面
025 if(gTaskEnMark.UPDATE_SIX_AXIS_EN && (gTaskStateBit.TouchPress == 0)
026 && (!gSwitchSpace))
027 {
028 modelListener->SixAxisPageChange(gFiveKeyFunc);
029 gSwitchSpace = 0x0F; //使能切換時間計數
030 }
031 //無線頁面
032 if(gTaskEnMark.UPDATE_WIFIPAGE && (gTaskStateBit.TouchPress == 0)
033 && (!gSwitchSpace))
034 {
035 modelListener->WiFiPageChange(gFiveKeyFunc);
036 gSwitchSpace = 0x0F; //使能切換時間計數
037 }
038 //設置頁面
039 if(gTaskEnMark.UPDATE_SETTINGPAGE && (gTaskStateBit.TouchPress == 0)
040 && (!gSwitchSpace))
041 {
042 modelListener->SettingPageChange(gFiveKeyFunc);
043 gSwitchSpace = 0x0F; //使能切換時間計數
044 }
045 //控制頁面
046 if(gTaskEnMark.UPDATE_MOTORPAGE && (gTaskStateBit.TouchPress == 0)
047 && (!gSwitchSpace))
048 {
049 modelListener->MotorPageChange(gFiveKeyFunc);
050 gSwitchSpace = 0x0F; //使能切換時間計數
051 }
052 //信息頁面,溫度、濕度與光強度
053 if(gTaskEnMark.UPDATE_INFOPAGE && (gTaskStateBit.TouchPress == 0)
054 && (!gSwitchSpace))
055 {
056 modelListener->InfoPageChange(gFiveKeyFunc);
057 gSwitchSpace = 0x0F; //使能切換時間計數
058 }
059 //健康頁面
060 if(gTaskEnMark.UPDATE_HEALTHPAGE && (gTaskStateBit.TouchPress == 0)
061 && (!gSwitchSpace))
062 {
063 modelListener->HealthPageChange(gFiveKeyFunc);
064 gSwitchSpace = 0x0F; //使能切換時間計數
065 }
066 //Chip頁面
067 if(gTaskEnMark.UPDATE_CHIPPAGE && (gTaskStateBit.TouchPress == 0)
068 && (!gSwitchSpace))
069 {
070 modelListener->ChipPageChange(gFiveKeyFunc);
071 gSwitchSpace = 0x0F; //使能切換時間計數
072 }
073 //Battery頁面
074 if(gTaskEnMark.UPDATE_BATTERYPAGE && (gTaskStateBit.TouchPress == 0)
075 && (!gSwitchSpace))
076 {
077 modelListener->BatteryPageChange(gFiveKeyFunc);
078 gSwitchSpace = 0x0F; //使能切換時間計數
079 }
080 //Sport頁面退出
081 if(gTaskEnMark.UPDATE_SPORTPAGE && (gTaskStateBit.TouchPress == 0)
082 && (!gSwitchSpace))
083 {
084 modelListener->SportPageExit(gFiveKeyFunc);
085 gSwitchSpace = 0x0F; //使能切換時間計數
086 }
087 //Alipay頁面退出
088 if(gTaskEnMark.UPDATE_ALIPAYPAGE && (gTaskStateBit.TouchPress == 0)
089 && (!gSwitchSpace))
090 {
091 modelListener->AlipayPageChange(gFiveKeyFunc);
092 gSwitchSpace = 0x0F; //使能切換時間計數
093 }
094 //Sport頁面進入
095 if(!HAL_GPIO_ReadPin(USER_KEY_GPIO_Port,USER_KEY_Pin))
096 {
097 modelListener->SportPageEnter(3);
098 }
099 /***********************更新各類信息***********************/
100 //更新時間信息,為使表盤頁面滑動操作正常,在屏幕被點按時不更新數據
101 if(gTaskEnMark.UPDATE_DIAL_EN && (gSystemTime.Seconds != gLastTimeSeconds)
102 &&(gTaskStateBit.TouchPress == 0))
103 {
104 modelListener->updateDate(gSystemDate.Year,gSystemDate.Month,
104 gSystemDate.Date,gSystemDate.WeekDay);
105 modelListener->updateTime(gSystemTime.Hours, gSystemTime.Minutes,
105 gSystemTime.Seconds);
106 //更新新值
107 gLastTimeSeconds = gSystemTime.Seconds;
108 //更新溫度/步數/心率
109 modelListener->updateTempStepHeart(gTemRH_Val.Tem,gSportStep,gHeartRate);
110 }
111 //健康監測信息上傳
112 if(gTaskEnMark.UPDATE_HEALTHPAGE && (gTaskStateBit.TouchPress == 0))
113 {
114 //send samples and calculation result to terminal
115 if(ch_hr_valid || ch_spo2_valid)
116 {
117 modelListener->updateHeartRateInfo(n_heart_rate/4, n_sp02);
118 gHeartRate = n_heart_rate/4; //保存心率數據值表盤頁面
119 }
120 //
121 if(gTaskStateBit.Max30102) //單次測量完成,清除標志
122 {
123 ch_hr_valid =0;
124 ch_spo2_valid=0;
125 gTaskStateBit.Max30102 = 0;
126 }
127 }
128 //更新歐拉角
129 if(gTaskEnMark.UPDATE_SIX_AXIS_EN) //六軸界面活動時上傳
130 {
131 modelListener->updateSixAxis(pitch, roll, yaw);
132 }
133 //只有在系統主頁時,才進行WiFi的RSSI數據讀取
134 if((gTaskEnMark.UPDATE_WIFI_RSSI_EN))
135 {
136 modelListener->updateWiFiRSSI(gWiFiInfo, ao_wifiRSSI.gRSSI);
137 }
138 //更新溫濕度信息
139 if(gTaskEnMark.UPDATE_INFOPAGE) //INFO面活動時上傳
140 {
141 modelListener->updateInfo(gTemRH_Val.Hum, gTemRH_Val.Tem, gAP3216C_Val.ALS);
142 }
143 //更新芯片溫度、參考電壓、Vbat
144 if(gTaskEnMark.UPDATE_CHIPPAGE && (!(tickCount % 5)))
145 {
146 modelListener->updateChipInfor(gChipTempVal, gVrefVal, gVbatVal);
147 }
148 //更新電壓與電流
149 if(gTaskEnMark.UPDATE_BATTERYPAGE)//更新電壓與電流
150 {
151 modelListener->updateBatteryPageInfo(gCurrentVal, gVoltageVal);
152 }
153 #else //Designer仿真
154 timeval timenow;
155 gettimeofday(&timenow, NULL);
156 //仿真更新時間
157 modelListener->updateTime((timenow.tv_sec / 60 / 60) % 24,
158 (timenow.tv_sec / 60) % 60,
159 timenow.tv_sec % 60);
160 #endif
161 }
?以下是用于活動屏幕的產生的任務標志組的設置:
001 //風扇操作
002 void Model::turnFanStatus(bool enable)
003 {
004 #if defined LINK_HARDWARE
005 if(enable == true)//風扇狀態的設置
006 HAL_GPIO_WritePin(EXT_FAN_GPIO_Port,EXT_FAN_Pin,GPIO_PIN_SET);
007 else
008 HAL_GPIO_WritePin(EXT_FAN_GPIO_Port,EXT_FAN_Pin,GPIO_PIN_RESET);
009 #endif
010 }
011 //振動電機操作
012 void Model::setMotorStatus(bool enable)
013 {
014 #if defined LINK_HARDWARE
015 if(enable == true)//振動電機狀態的設置
016 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_SET);
017 else
018 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_RESET);
019 #endif
020 }
021 //排水操作
022 void Model::drainWaterStatus(bool enable)
023 {
024 #if defined LINK_HARDWARE
025 if(enable == true)//振動電機設置
026 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_SET);
027 else
028 HAL_GPIO_WritePin(EXT_MOTOR_GPIO_Port,EXT_MOTOR_Pin,GPIO_PIN_RESET);
029 #endif
030 }
031 //蜂鳴器操作
032 void Model::setBuzzerStatus(bool enable)
033 {
034 #if defined LINK_HARDWARE
035 if(enable == true)//蜂鳴器狀態的設置
036 HAL_GPIO_WritePin(RUN_BEEP_GPIO_Port,RUN_BEEP_Pin,GPIO_PIN_SET);
037 else
038 HAL_GPIO_WritePin(RUN_BEEP_GPIO_Port,RUN_BEEP_Pin,GPIO_PIN_RESET);
039 #endif
040 }
041 /*********************gTaskEnMark賦值*************************/
042 //DialView的任務的狀態
043 void Model::DialPageViewTask(bool enable)
044 {
045 #if defined LINK_HARDWARE
046 if(enable == true)
047 gTaskEnMark.UPDATE_DIAL_EN = 1; //任務使能
048 else
049 gTaskEnMark.UPDATE_DIAL_EN = 0; //任務清除
050 #endif
051 }
052 //ApplicationPageView的任務的狀態
053 void Model::ApplicationPageViewTask(bool enable)
054 {
055 #if defined LINK_HARDWARE
056 if(enable == true)
057 gTaskEnMark.UPDATE_APPPAGE = 1; //任務使能
058 else
059 gTaskEnMark.UPDATE_APPPAGE = 0; //任務清除
060 #endif
061 }
062 //SixAxisPageView的任務的狀態
063 void Model::SixAxisPageViewTask(bool enable)
064 {
065 #if defined LINK_HARDWARE
066 if(enable == true)
067 gTaskEnMark.UPDATE_SIX_AXIS_EN = 1; //任務使能
068 else
069 gTaskEnMark.UPDATE_SIX_AXIS_EN = 0; //任務清除
070 #endif
071 }
072 //InfoPageView任務使能
073 void Model::InfoPageViewTask(bool newStatus)
074 {
075 #if defined LINK_HARDWARE
076 if(newStatus == true)
077 gTaskEnMark.UPDATE_INFOPAGE = 1; //任務使能
078 else
079 gTaskEnMark.UPDATE_INFOPAGE = 0; //任務清除
080 #endif
081 }
082 //ChipPageViewTask的任務的狀態
083 void Model::ChipPageViewTask(bool enable)
084 {
085 #if defined LINK_HARDWARE
086 if(enable == true)
087 gTaskEnMark.UPDATE_CHIPPAGE = 1; //任務使能
088 else
089 gTaskEnMark.UPDATE_CHIPPAGE = 0; //任務清除
090 #endif
091 }
092 //設置健康監測任務
093 void Model::HealthPageViewTask(bool newStatus)
094 {
095 #if defined LINK_HARDWARE
096 if(newStatus == true)
097 gTaskEnMark.UPDATE_HEALTHPAGE = 1; //任務使能
098 else
099 gTaskEnMark.UPDATE_HEALTHPAGE = 0; //任務清除
100 #endif
101 }
102 //WiFi連接的任務狀態
103 void Model::WiFiLinkTask(bool enable)
104 {
105 #if defined LINK_HARDWARE
106 if(enable == true)
107 gTaskEnMark.UPDATE_WIFI_RSSI_EN = 1; //任務使能
108 else
109 gTaskEnMark.UPDATE_WIFI_RSSI_EN = 0; //任務清除
110 #endif
111 }
112 //WiFi界面的任務狀態
113 void Model::WiFiPageViewTask(bool enable)
114 {
115 #if defined LINK_HARDWARE
116 if(enable == true)
117 gTaskEnMark.UPDATE_WIFIPAGE = 1; //任務使能
118 else
119 gTaskEnMark.UPDATE_WIFIPAGE = 0; //任務清除
120 #endif
121 }
122 //Setting界面的任務狀態
123 void Model::SettingPageViewTask(bool enable)
124 {
125 #if defined LINK_HARDWARE
126 if(enable == true)
127 gTaskEnMark.UPDATE_SETTINGPAGE = 1; //任務使能
128 else
129 gTaskEnMark.UPDATE_SETTINGPAGE = 0; //任務清除
130 #endif
131 }
132 //Motor界面的任務狀態
133 void Model::MotorPageViewTask(bool enable)
134 {
135 #if defined LINK_HARDWARE
136 if(enable == true)
137 gTaskEnMark.UPDATE_MOTORPAGE = 1; //任務使能
138 else
139 gTaskEnMark.UPDATE_MOTORPAGE = 0; //任務清除
140 #endif
141 }
142 //Battery界面的任務狀態
143 void Model::BatteryPageViewTask(bool enable)
144 {
145 #if defined LINK_HARDWARE
146 if(enable == true)
147 gTaskEnMark.UPDATE_BATTERYPAGE = 1; //任務使能
148 else
149 gTaskEnMark.UPDATE_BATTERYPAGE = 0; //任務清除
150 #endif
151 }
152 //運動界面的任務狀態
153 void Model::SportPageViewTask(bool enable)
154 {
155 #if defined LINK_HARDWARE
156 if(enable == true)
157 gTaskEnMark.UPDATE_SPORTPAGE = 1; //任務使能
158 else
159 gTaskEnMark.UPDATE_SPORTPAGE = 0; //任務清除
160 #endif
161 }
162 //Aliplay界面任務狀態
163 void Model::AlipayPageViewTask(bool enable)
164 {
165 #if defined LINK_HARDWARE
166 if(enable == true)
167 gTaskEnMark.UPDATE_ALIPAYPAGE = 1; //任務使能
168 else
169 gTaskEnMark.UPDATE_ALIPAYPAGE = 0; //任務清除
170 #endif
171 }