STM32單片機項目實例:基于TouchGFX的智能手表設計(3)嵌入式程序任務調度的設計

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	}

以上事件處理代碼可以做成標準的框架代碼,它能夠應對大部分嵌入式裸機編程的情況。同時,事件驅動機制采用這樣的方式實現也存幾點問題需要注意:

同事件集中爆發時,會丟失后面發生的事件。

不同事件集中爆發,會丟失事件發生的順序。

事件的優先級與多任務并發執行。

? 上圖中,T1T2T3是各個任務的事件處理函數,I1I2I3是不同事件觸發的IRQ,假定I1I2I3分別對應E1E2E3事件,當運行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的操作取決于ModelView。?它從存儲庫(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	}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/208903.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/208903.shtml
英文地址,請注明出處:http://en.pswp.cn/news/208903.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

golang游戲服務器 - tgf系列課程02

環境準備和服務創建 課程介紹了TGF框架的前期的準備工作,啟動一個websocket網關服務,和大廳邏輯節點。 文章最后附有項目案例地址和視頻教程地址,下期預告等信息安裝第三方軟件 tgf框架的服務發現依賴于Consul,所以我們需要先安裝并啟動Consul官網安裝 :訪問官網下載對應的包…

點云從入門到精通技術詳解100篇-針對三維點云分類神經網絡模型的不可感知對抗攻擊

目錄 前言 國內外研究現狀 三維點云分類神經網絡 三維點云傳統攻擊方法

C/C++ 實現動態資源文件釋放

當我們開發Windows應用程序時&#xff0c;通常會涉及到使用資源&#xff08;Resource&#xff09;的情況。資源可以包括圖標、位圖、字符串等&#xff0c;它們以二進制形式嵌入到可執行文件中。在某些情況下&#xff0c;我們可能需要從可執行文件中提取自定義資源并保存為獨立的…

vivado時序方法檢查7

TIMING-25 &#xff1a; 千兆位收發器 (GT) 上的時鐘波形無效 收發器輸出管腳 <pin_name> 上或連接到該管腳的信號線上定義的時鐘 <clock_name> 的波形與收發器設置不一 致&#xff0c; 或者缺少參考時鐘定義。自動衍生時鐘的周期為 <PERIOD> &#xf…

物聯網后端個人第十四周總結

物聯網方面進度 1.登陸超時是因為后端運行的端口和前端監聽的接口不一樣&#xff0c;所以后端也沒有報錯&#xff0c;將二者修改一致即可 2.登錄之后會進行平臺的初始化&#xff0c;但是初始化的時候會卡住,此時只需要將路徑的IP端口后邊的內容去掉即可 3.閱讀并完成了jetlinks…

通過誤差改變控制的兩種策略

如果反饋誤差越來越大&#xff0c;需要改變調節方向以減小誤差并實現更好的控制。以下是兩種常見的調節方向改變的方法&#xff1a; PID控制器中的積分限制&#xff1a;在PID控制中&#xff0c;積分項可以用來減小穩態誤差。然而&#xff0c;當反饋誤差持續增大時&#xff0c;積…

浪潮信息:數字化轉型的策略與實踐

在數字化浪潮的推動下&#xff0c;浪潮信息正致力于將計算創新推向新的高度。作為科技發展的排頭兵&#xff0c;浪潮信息深知算力的重要性&#xff0c;因此不斷探索前所未有的解決方案。在這個過程中&#xff0c;浪潮信息的研發人員和科技工作者如同探險家&#xff0c;勇敢地迎…

RocketMQ安裝和使用

RocketMQ快速入門 下載RocketMQ 下載地址 環境要求 Linux64位系統 JDK1.8(64位) 安裝RocketMQ 解壓 unzip rocketmq-all-4.4.0-bin-release.zip啟動RocketMQ 啟動NameServer # 1.啟動NameServer nohup sh bin/mqnamesrv & # 2.查看啟動日志 tail -f ~/logs/rocke…

學會用bash在linux寫腳本 (二)

接著上一章繼續 數值的對比 判斷語句 循環語句 22.5 比較、對比、判斷 在寫腳本時&#xff0c;有時需要做一些比較&#xff0c;例如&#xff0c;兩個數字誰大誰小&#xff0c;兩個字符串是否相同等。 做對比的表達式有[]、[[]]、test&#xff0c;其中[]和 test這兩種表達式的…

如何通過3000個傳感器幫助大型大學附屬醫院實現遠程環境監測?

得益于ELPRO提供的可擴展、可信賴和可靠的環境監測解決方案&#xff0c;一家領先的大學研究醫院系統在COVID-19新冠肺炎大流行初始迅速為員工遠程工作做好了準備。 在本案例研究中&#xff0c;您將了解大城市的一家大型大學附屬醫院如何做到&#xff1a; 建立了遠程溫度控制數…

身份統一管理創新與優化 ——華為云OneAccess應用身份管理服務的2023年

2023年&#xff0c;隨著云計算、物聯網、人工智能等技術的快速發展&#xff0c;企業面臨著數字化轉型的巨大挑戰與機遇。身份統一管理是企業數字化轉型的基礎&#xff0c;也是業務發展的關鍵。如何高效、安全、靈活地實現身份統一管理&#xff0c;成為企業亟待解決的首要課題。…

解決MySQL字段名與關鍵字沖突

如果字段名與MySQL內部關鍵字相同&#xff0c;可能會導致語法錯誤、數據訪問問題甚至系統崩潰。 1、避免使用MySQL關鍵字作為字段名。 2、使用反引號&#xff08;backticks&#xff09;&#xff1a; 如果使用一個與MySQL關鍵字相同的字段名&#xff0c;可以使用反引號將其括起…

boost-字符串處理-判斷-查找-裁剪-刪除-替換-分割-合并

文章目錄 1.判斷1.1.equals1.2.all1.3.starts_with1.4.ends_with1.5.contains 2.大小寫轉換3.字符串刪除4.字符串替換5.字符串查找6.字符串修剪7.字符串分割8.字符串合并9.總結 1.判斷 判別式函數和分類函數大多數都是以is_開頭&#xff0c;這些函數如下&#xff1a; 判別式函…

ElasticSearch之線程池

ElasticSearch節點可用的CPU核的數量&#xff0c;通常可以交給ElasticSearch來自行檢測和判定&#xff0c;另外可以在elasticsearch.yml中顯式指定。樣例如下&#xff1a; node.processors: 2如下表格中的processors即CPU核的數量。 線程池的列表 線程池名稱類型線程數量隊列…

屏蔽百度首頁推薦和熱搜的實戰方案

大家好,我是愛編程的喵喵。雙985碩士畢業,現擔任全棧工程師一職,熱衷于將數據思維應用到工作與生活中。從事機器學習以及相關的前后端開發工作。曾在阿里云、科大訊飛、CCF等比賽獲得多次Top名次。現為CSDN博客專家、人工智能領域優質創作者。喜歡通過博客創作的方式對所學的…

電視節目中活動滅燈系統是如何實現的

活動滅燈系統主要用于各種需要亮燈或滅燈的活動節目&#xff0c;如招聘滅燈、相親滅燈等。有多種燈光顏色供選擇&#xff0c;本設備通過按鈕燈軟件組合實現&#xff0c;用戶可以自己設置亮燈或滅燈規則。 軟件功能&#xff1a; 1、后臺統一控制亮燈&#xff0c;重新開始下輪…

華為交換機基本配置

一、配置時間 sys ntp-service unicast-server 192.168.1.1 ntp-service unicast-server 192.168.1.2 clock timezone UTC add 8 clock timezone CST add 08:00:00 undo ntp-service disable q手動設置一個時間 clock datetime 13:43:00 2023-10-10save ysys保存&#xff01;保…

某60內網滲透之域管權限維持[金票利用]

內網滲透 文章目錄 內網滲透域管權限維持【金票利用】實驗目的實驗環境實驗工具實驗原理實驗內容域管權限維持【金票利用】實驗步驟攻擊域管權限維持【金票利用】 實驗目的 讓學員通過該系統的練習主要掌握:利用金票來維持域管理員的權限。 實驗環境 操作機 Windows 7,域…

微信小程序 - 格式化操作 moment.js格式化常用使用方法總結大全

格式化操作使用 1. 首先&#xff0c;下載一個第三方庫 moment npm i moment --save 注&#xff1a;在微信小程序中無法直接npm 下載 導入 的&#xff08;安裝一個就需要構建一次&#xff09; 解決&#xff1a;菜單欄 --> 工具 --> 構建 npm 點擊即可&#xff08;會…

線性回歸模型標準公式

用一組特征 x ( i ) { x^{(i)}} x(i)來預測或估計一個響應變量 y ( i ) y^{(i)} y(i)&#xff0c;公式如下&#xff1a; y ( i ) θ T x ( i ) ? ( i ) y^{(i)} \theta^T x^{(i)} \epsilon^{(i)} y(i)θTx(i)?(i) 各名詞解釋&#xff1a; y ( i ) y^{(i)} y(i)&#xf…