??作者:一只大喵咪1201
??專欄:《智能家居項(xiàng)目》
??格言:你只管努力,剩下的交給時(shí)間!
??多任務(wù)系統(tǒng)中使用DHT11
在上篇文章中,本喵僅進(jìn)行了單任務(wù)的DHT11溫濕度傳感器使用,相當(dāng)于裸機(jī)使用。
根據(jù)上面時(shí)序圖計(jì)算接收一次數(shù)據(jù)(5個(gè)字節(jié))的耗時(shí),不考慮主機(jī)發(fā)送起始信號(hào)的耗時(shí):
- 最小時(shí)間:40 + 80 + 80 + (50+28)*40 = 3320us = 3.32ms。
- 最大時(shí)間:40 + 80 + 80 + (50+70)*40 = 5000us = 5ms。
在配置文件FreeRTOSConfig.h
中,configTICK_RATE_HZ
的值是1000,這表示FreeRTOS中有多個(gè)同優(yōu)先級(jí)的就緒任務(wù)時(shí),它們依次執(zhí)行1ms。
- 而讀取DHT11時(shí)最少耗時(shí)3.32ms,如果讀取DHT11的過程被其他任務(wù)打斷的話,必定失敗。
如上圖所示,在smarthouse.c
文件中定義一個(gè)DHT11_UpdateValue
函數(shù)來更新DHT11傳感器的值,并且通過串口打印出來。
如上圖,創(chuàng)建三個(gè)相同優(yōu)先級(jí)的任務(wù),分別執(zhí)行SmartHomeTask
任務(wù),DHT11_UpdataValue
以及other_task
任務(wù)。
此時(shí)三個(gè)任務(wù)按照時(shí)間片輪轉(zhuǎn)的方式在執(zhí)行:
如上圖,此時(shí)雖然ESP8266
在某種巧合下連接成功了,但是DHT11讀取溫濕度數(shù)據(jù)失敗了,始終都是0。
有三種解決方法:
- 讀取時(shí)關(guān)中斷或者關(guān)閉調(diào)度。
- 把DHT11的任務(wù)優(yōu)先級(jí)設(shè)置為最高。
- 修改程序,在中斷里記錄時(shí)間、解析數(shù)據(jù)。
將DHT11任務(wù)優(yōu)先級(jí)設(shè)置為最高必然能成功,因?yàn)橹挥羞@一個(gè)任務(wù)在運(yùn)行,所以只介紹其他兩種解決方法。
??關(guān)閉調(diào)度器
目前項(xiàng)目中存在SmartHouseTask
任務(wù)和DHT11_UpdateValue
任務(wù),在連接WIFI和讀取溫濕度數(shù)據(jù)時(shí),都不能被打斷,必須具有原子性,否則就會(huì)出現(xiàn)錯(cuò)誤。
如上圖所示,在連接WIFI和建立連接之前,先調(diào)用vTaskSuspendAll()
關(guān)閉調(diào)度器,此時(shí)其他任務(wù)無法調(diào)度,操作完畢后再調(diào)用xTaskResumeAll()
打開調(diào)度器,恢復(fù)任務(wù)調(diào)度。
如上圖所示,在DHT11讀取溫濕度數(shù)據(jù)前將調(diào)度器關(guān)閉,讀取完畢后再打開,防止其他任務(wù)來干擾導(dǎo)致讀取出錯(cuò)。
如上圖,此時(shí)WIFI連接和溫濕度數(shù)據(jù)讀取都正常。
??使用中斷
將主機(jī)和DHT11傳送數(shù)據(jù)的總線設(shè)置成外部中斷模式:
如上圖,在主機(jī)發(fā)出起始信號(hào)并將總線拉高時(shí)使能中斷,并且啟動(dòng)定時(shí)器。
DHT11每改變一次總線狀態(tài)就會(huì)觸發(fā)一次中斷,在中斷函數(shù)中記錄中斷發(fā)生的時(shí)間Tx
,如上圖中的T3-T2
就是總線保持高電平時(shí)間,根據(jù)時(shí)間判斷出該bit是0還是1。
- 在發(fā)出起始信號(hào)后使能中斷并啟動(dòng)定時(shí)器的原因:
- 最理想的使能位置是在T3位置。
- 但是這里使能完中斷并啟動(dòng)定時(shí)器以后,可能就過了50us了。
- 會(huì)導(dǎo)致中斷丟失,從而造成數(shù)據(jù)讀取錯(cuò)誤。
所以提前使能中斷,這樣的話一次讀取數(shù)據(jù)的過程中會(huì)產(chǎn)生83次中斷,前3次DHT11做出應(yīng)答時(shí)觸發(fā)的中斷,所以在程序中拋棄這三次中斷,僅處理后面的80次中斷即可。
- 40個(gè)bit,一個(gè)bit會(huì)觸發(fā)兩次中斷。
編程思路:
- 初始化
- 設(shè)置定時(shí)器,精度要達(dá)到1us量級(jí)
- 設(shè)置DHT11數(shù)據(jù)引腳:用作開漏輸出模式
- 設(shè)置DHT11數(shù)據(jù)引腳:用于中斷、雙邊沿觸發(fā)
定時(shí)器前面本喵在上篇文中已經(jīng)初始化過了,這里直接使用DHT11_TIM_Init
來初始化微秒定時(shí)器。
如上圖,定義一個(gè)函數(shù)DHT11_GPIO_Init_As_Output
將數(shù)據(jù)引腳設(shè)置成開漏輸出模式。
如上圖,定義一個(gè)函數(shù)DHT11_GPIO_Init_As_IRQ
將數(shù)據(jù)引腳設(shè)置成中斷模式。
如上圖,在DHT11初始化的時(shí)候,先將引腳初始化為輸出引腳,并創(chuàng)建一個(gè)二進(jìn)制信號(hào)量。
- 在
DHT11_Read
函數(shù)中:- 發(fā)出Start信號(hào)
- 等待ACK
- 使能數(shù)據(jù)引腳的中斷
- 阻塞
如上圖,先提供使能和失能中斷的方式以及啟動(dòng)和停止定時(shí)器的方式,在每次失能定時(shí)器的時(shí)候都要將記錄中斷發(fā)生次數(shù)的變量dht11_edge_cnt
清零。
在啟動(dòng)定時(shí)器的時(shí)候,設(shè)置最大超時(shí)時(shí)間為10ms,因?yàn)樽x取DHT11一次數(shù)據(jù)(40bit)最多耗時(shí)5ms,如果10ms還沒有讀成功就說明出問題了。
如上圖所示,發(fā)出起始信號(hào),將總線拉高后立即打開中斷并啟動(dòng)定時(shí)器,然后去獲取信號(hào)量,這是為了讓該任務(wù)阻塞。
- 在DHT11數(shù)據(jù)引腳的中斷函數(shù)中
- 記錄中斷發(fā)生的時(shí)間,放在數(shù)組里
- 累加中斷次數(shù),發(fā)現(xiàn)40位數(shù)據(jù)都接收完畢后,喚醒任務(wù)
如上圖,先提供一個(gè)能讀取微秒定時(shí)器計(jì)數(shù)值的函數(shù)DHT11_ReadTimer_us
。
如上圖,在中斷函數(shù)中再增加用于處理DHT11產(chǎn)生的中斷。當(dāng)中斷發(fā)生后,獲取一下發(fā)生的時(shí)間并放入到存放時(shí)間的數(shù)組中。
當(dāng)中斷發(fā)生80次以后就喚醒讀取數(shù)據(jù)的任務(wù),調(diào)用DHT11_Notify_Task
,因?yàn)?0個(gè)bit會(huì)產(chǎn)生80次中斷。
如上圖代碼,歸還初始化時(shí)創(chuàng)建的信號(hào)量,喚醒讀取溫濕度數(shù)據(jù)的任務(wù)。
如果中斷發(fā)生了83次就直接返回,不記錄時(shí)間,因?yàn)檫@時(shí)必然接收到了40個(gè)bit,多發(fā)生的3次中斷是DHT11應(yīng)答產(chǎn)生的。
- 任務(wù)被喚醒后:根據(jù)中斷時(shí)間解析出得到的數(shù)據(jù)
在讀取溫濕度數(shù)據(jù)DHT11_Read
時(shí),任務(wù)被喚醒后沒有立刻處理數(shù)據(jù),而是又調(diào)用vTaskDelay(1)
延時(shí)了1ms,這是因?yàn)樵撊蝿?wù)被喚醒時(shí)產(chǎn)生了80次中斷,這80次中斷中包含DHT11應(yīng)答產(chǎn)生的中斷,此時(shí)數(shù)據(jù)產(chǎn)生的中斷就沒有到達(dá)80,所以要再等一會(huì)。
然后再失能中斷并停止定時(shí)器,將數(shù)據(jù)引腳設(shè)置成輸出模式,再去解析數(shù)據(jù)。
如上圖,整體處理裸機(jī)和之前的方式差不多,數(shù)據(jù)左移以后,如果該bit是1則或等,如果是0則不用處理。
但是這里需要處理多出來的3次中斷,83次中斷中只有80次中斷發(fā)生時(shí)記錄的時(shí)間數(shù)據(jù)是有用的,這里通過校驗(yàn)和來舍棄多發(fā)生的3次中斷:
- 從記錄時(shí)間的數(shù)組首部開始,取80個(gè)數(shù)據(jù)解析出溫濕度數(shù)據(jù)。
- 然后使用校驗(yàn)和來判斷,如果校驗(yàn)通過則直接采用,如果不通過就拋棄數(shù)組第一個(gè)時(shí)間數(shù)據(jù),從下一個(gè)數(shù)據(jù)開始再取80個(gè)解析。
- 最多解析3次就能校驗(yàn)得到正確的溫濕度數(shù)據(jù)。
在判斷每個(gè)bit是高電平還是低電平的時(shí)候,讓數(shù)組中后一個(gè)時(shí)間值減去前一個(gè)時(shí)間中,得到的就是兩次中斷之間電平保持的時(shí)間,如果大于40us則是高電平,否則就是低電平。
如上圖代碼中所示,需要的差值僅是高電平保持的時(shí)間,低電平的不需要,也就是只需要算1時(shí)刻減0時(shí)刻和3時(shí)刻減2時(shí)刻的時(shí)間差值。
所以每計(jì)算完一次以后,數(shù)組下標(biāo)直接加2而不是加1。
如上圖,將更新溫濕度任務(wù)中關(guān)閉調(diào)度器的部分屏蔽掉,因?yàn)楝F(xiàn)在使用的是中斷方式。
- 中斷的優(yōu)先級(jí)高于所有任務(wù),所以讀取溫濕度過程中的是實(shí)時(shí)性是可以保證的。
- 在中斷沒有將80個(gè)時(shí)間值處理完畢前,該任務(wù)由于無法申請到信號(hào)量而處于阻塞狀態(tài)。
如上圖,此時(shí)溫濕度數(shù)據(jù)可以正常讀取,溫度是T=16℃
,濕度是H = 38%
,并且WIFI也成功連接。打印的路徑信息是校驗(yàn)錯(cuò)誤時(shí)打印的,這個(gè)數(shù)據(jù)就被拋棄了。
- 由于溫濕度數(shù)據(jù)并不會(huì)劇烈變化,所以不需要很高的實(shí)時(shí)性。
??獲取SNTP服務(wù)器時(shí)間
ESP8266帶有從SNTP服務(wù)器上獲取時(shí)間的功能,查看手冊中相關(guān)用法:
設(shè)置:
如上圖所示是設(shè)置時(shí)域和SNTP服務(wù)器的AT指令,設(shè)置指令格式:
-
AT+CIPSNTPCFG=1,8,"cn.ntp.org.cn","ntp.sjtu.edu.cn","us.pool.ntp.org"
- 1表示使能,相應(yīng)的填0就表示失能。
- 8表示時(shí)域,北京時(shí)間位于東八區(qū),所以是8。
- 引號(hào)中的內(nèi)容是SNTP服務(wù)器的IP地址。
對于SNTP服務(wù)器,可以將3個(gè)都填進(jìn)去,也可以不填使用默認(rèn)的,網(wǎng)上有很多SNTP服務(wù)器的IP地址,這里本喵使用上海市的SNTP服務(wù)器IP地址202.120.2.101
。發(fā)送的指令就是AT+CIPSNTPCFG=1,8,"202.120.2.101"
。
如上圖,將指令使用串口發(fā)送給ESP8266后,回顯OK表明設(shè)置成功。
查詢:
如上圖所示是查詢SNTP時(shí)間的指令AT+CIPSNTPTIME?
,回顯的是查詢到的時(shí)間:
如上圖所示,查詢時(shí)間后返回+CIPSNTPTIME:Fri Nov 17 13:44:30 2023
,當(dāng)前時(shí)間是星期五11月17日13點(diǎn)44分30秒2023年。
接下來就是將獲取SNTP服務(wù)器時(shí)間加入到智能家居項(xiàng)目中了,獲取SNTP服務(wù)器時(shí)間是網(wǎng)卡的功能,所以這部分代碼放在網(wǎng)卡設(shè)備及esp8266驅(qū)動(dòng)層。
如上圖是描述獲取到時(shí)間的結(jié)構(gòu)體,獲取到的時(shí)間Fri Nov 17 13:44:30 2023
中,年是4個(gè)字節(jié),月是3個(gè)字節(jié),日是2個(gè)字節(jié),時(shí)是2個(gè)字節(jié),分是2個(gè)字節(jié),秒是2個(gè)字節(jié),使用數(shù)組存放。由于是字符串,所以每個(gè)數(shù)組多出一個(gè)字節(jié)來放\0
。
如上圖,在網(wǎng)卡設(shè)備結(jié)構(gòu)體中,增加一個(gè)unsigned char isConnected
來表示網(wǎng)卡狀態(tài),0表示W(wǎng)IFI沒有連接,1表示W(wǎng)IFI連接成功,只有WIFI連接成功才能獲取時(shí)間。再增加一個(gè)獲取時(shí)間的函數(shù)指針GetTime
。
如上圖,在esp8266.c
中定義獲取時(shí)間的函數(shù)ESP8266_GetNetTime
,在該函數(shù)中,由于設(shè)置時(shí)區(qū)和服務(wù)器比較耗時(shí),還需要等待,而且也無需重復(fù)設(shè)置,所以只設(shè)置一次即可。
從回顯的字符串中解析出時(shí)間后,要判斷一下是否是是1970年,如果是則說明設(shè)置服務(wù)器和時(shí)區(qū)沒有成功,需要重新設(shè)置一下。
如上圖,初始化ESP8266時(shí)增加網(wǎng)絡(luò)狀態(tài)和獲取時(shí)間函數(shù)指針的初始化。
如上圖,在smarthouse.c
中連接WIFI的函數(shù)中,WIFI連接成功就將isConnected
標(biāo)志置一,表示W(wǎng)IFI連接成功了。
如上圖,在smarthouse.c
中定義一個(gè)GetNetTime
函數(shù)來獲取網(wǎng)絡(luò)時(shí)間,獲取到以后分別在屏幕和串口上顯示。
如上圖所示,創(chuàng)建一個(gè)任務(wù)用來獲取網(wǎng)絡(luò)時(shí)間。
如上圖串口所示,此時(shí)既能獲取溫濕度數(shù)據(jù),又能獲取網(wǎng)絡(luò)時(shí)間,但是此時(shí)還沒有充分利用起來FreeRTOS特性,所以其他功能會(huì)收到影響,這里只是驗(yàn)證獲取時(shí)間的功能是正常的,至于OLED屏幕上的顯示本喵就不拍照了。
??重新設(shè)計(jì)功能框架
如上圖所示是增加了兩個(gè)功能模塊后的框架結(jié)構(gòu)圖。
輸入事件隊(duì)列是信息中軸:
- 按鍵中斷觸發(fā)定時(shí)器中斷,消除抖動(dòng)后往隊(duì)列寫入"按鍵類事件"。
- 使用微信小程序、sscom發(fā)出控制命令,UART3接收到數(shù)據(jù),解析出數(shù)據(jù)后往隊(duì)列寫入"網(wǎng)絡(luò)類事件"。
- SNTP任務(wù):每隔1分鐘通過網(wǎng)絡(luò)獲取時(shí)間,修正本地時(shí)間。
- 1秒鐘周期定時(shí)器:往隊(duì)列寫入"時(shí)間類事件",以便更新OLED上的時(shí)間。
- DHT11任務(wù):每隔幾秒鐘讀取溫濕度,往隊(duì)列寫入"溫濕度類事件"。
- SmartHome任務(wù):
- 連接WIFI
- 讀取輸入事件,控制設(shè)備:LED、風(fēng)扇、OLED
任務(wù)的優(yōu)先級(jí):
- SmartHome任務(wù):優(yōu)先級(jí)最高,它要及時(shí)響應(yīng)用戶的操作,平時(shí)大部分時(shí)間都是阻塞狀態(tài)。
- 定時(shí)器后臺(tái)任務(wù):優(yōu)先級(jí)第2高,用來更新時(shí)間,如果時(shí)間的更新不及時(shí)會(huì)影響產(chǎn)品體驗(yàn)。
- SNTP任務(wù):優(yōu)先級(jí)第3高,它每隔1分鐘執(zhí)行一次,用來修正時(shí)間。
- DHT11任務(wù):優(yōu)先級(jí)最低,溫濕度不會(huì)劇烈變化。
使用一個(gè)軟件定時(shí)器作為1秒鐘周期定時(shí)器,將該定時(shí)器也看作是一個(gè)設(shè)備:
如上圖代碼,創(chuàng)建一個(gè)結(jié)構(gòu)體來描述軟件定時(shí)器,包括設(shè)備編號(hào),定時(shí)周期,回調(diào)函數(shù),軟件定時(shí)器句柄,以及初始化等方法。
如上圖,定義具體的函數(shù),然后來初始化周期定時(shí)器全局變量,回調(diào)函數(shù)使用的是外部的vOneSecTimerFunc
。其他函數(shù)調(diào)用內(nèi)核層相應(yīng)的函數(shù)。
如上圖,在內(nèi)核抽象層使用FreeRTOS提供的操作軟件定時(shí)器的接口即可,在初始化的時(shí)候,將創(chuàng)建軟件定時(shí)器后生成的句柄填入到上一層的全局變量ptTimer->xPeriodTimer
中。
軟件定時(shí)器超時(shí)后會(huì)調(diào)用回調(diào)函數(shù):
如上圖代碼所示,在軟件定時(shí)器的回調(diào)函數(shù)中,會(huì)先獲取一下當(dāng)前網(wǎng)卡的時(shí)間,然后再給這個(gè)時(shí)間增加1秒,因?yàn)檐浖〞r(shí)器1秒鐘超時(shí)一次,在時(shí)間增加的時(shí)候要注意進(jìn)位。
時(shí)間增加完畢后,再將這個(gè)時(shí)間設(shè)置到網(wǎng)卡時(shí)間中,并且構(gòu)造輸入事件寫入到輸入隊(duì)列中,讓最上層的智能家居任務(wù)來顯示這個(gè)時(shí)間到OLED上。
每超時(shí)一次后,超時(shí)次數(shù)OverTimeCounter
加1,當(dāng)前其等于60的時(shí)候到了一分鐘,此時(shí)要喚醒更新時(shí)間任務(wù),從SNTP服務(wù)器獲取新的網(wǎng)絡(luò)時(shí)間來校正本地時(shí)間。
在回調(diào)函數(shù)中獲取網(wǎng)卡時(shí)間的時(shí)候,前提是網(wǎng)卡中已經(jīng)存在時(shí)間了:
如上圖,在獲取網(wǎng)卡時(shí)間時(shí),先xSemaphoreTake
申請信號(hào)量,如果成功則返回網(wǎng)卡時(shí)間的地址,否則就阻塞。
- 使用一個(gè)信號(hào)量,讓更新網(wǎng)絡(luò)時(shí)間的任務(wù)和軟件定時(shí)器回調(diào)函數(shù)互斥訪問網(wǎng)卡時(shí)間。
如上圖,在設(shè)置一次網(wǎng)卡時(shí)間后,就會(huì)增加一下信號(hào)量,好讓軟件定時(shí)器的回調(diào)函數(shù)能訪問,而不會(huì)阻塞。
那么是誰會(huì)設(shè)置網(wǎng)卡時(shí)間呢?
如上圖代碼,在獲取網(wǎng)絡(luò)時(shí)間的任務(wù)運(yùn)行后,會(huì)先獲取一次網(wǎng)絡(luò)時(shí)間,然后設(shè)置到網(wǎng)卡時(shí)間中,此時(shí)定時(shí)器的回調(diào)還是才能獲取到網(wǎng)卡時(shí)間,在這之前處于阻塞狀態(tài)。
除此之外,還創(chuàng)建了一個(gè)xUpDateNetTimeSema
信號(hào)量,用來控制SNTP服務(wù)器上獲取網(wǎng)絡(luò)時(shí)間。在創(chuàng)建成功后先增加一下,方便第一次獲取網(wǎng)絡(luò)時(shí)間,Take
以后,再次到了這里后就會(huì)阻塞。
當(dāng)軟件定時(shí)器超時(shí)60次,也就是一分鐘時(shí):
如上圖,調(diào)用UpdateTimeNotify
時(shí)就會(huì)Give
一下這個(gè)信號(hào)量,此時(shí)就會(huì)喚醒任務(wù)從網(wǎng)絡(luò)上獲取一下時(shí)間來校正本地時(shí)間。
- 更新網(wǎng)絡(luò)時(shí)間的任務(wù)大部分時(shí)間處于阻塞狀態(tài),每一分鐘會(huì)被軟件定時(shí)器的回調(diào)函數(shù)喚醒一次來從SNTP服務(wù)器上獲取一次時(shí)間。
如上圖,在初始化所有子系統(tǒng)和設(shè)備的時(shí)候,需要將軟件定時(shí)器也初始化。
如上圖,在智能家居任務(wù)將輸入事件轉(zhuǎn)換成Json格式的時(shí)候,再增加一個(gè)定時(shí)器輸入的轉(zhuǎn)換,如紅色框中所示。
如上圖,在根據(jù)輸入事件控制設(shè)備時(shí),增加定時(shí)器輸入部分,從網(wǎng)卡中獲取時(shí)間后將時(shí)間顯示到OLED屏幕的最下面。
??總結(jié)
至此本喵就帶著大家實(shí)現(xiàn)了FreeRTOS版本的智能家居項(xiàng)目,它的優(yōu)點(diǎn)主要在于有非常高的實(shí)時(shí)性。
溫濕度部分使用了中斷方式,這也是一種保證實(shí)時(shí)性的手段,有興趣的小伙伴可以自行了解一下中斷編程。文章來源:http://www.zghlxwxcb.cn/news/detail-751399.html
多任務(wù)系統(tǒng)中,每個(gè)任務(wù)在不需要運(yùn)行的時(shí)候,要讓其處于阻塞狀態(tài),讓出CPU資源,可以通過信號(hào)量,隊(duì)列等等方式來實(shí)現(xiàn)。文章來源地址http://www.zghlxwxcb.cn/news/detail-751399.html
到了這里,關(guān)于【智能家居項(xiàng)目】FreeRTOS版本——多任務(wù)系統(tǒng)中使用DHT11 | 獲取SNTP服務(wù)器時(shí)間 | 重新設(shè)計(jì)功能框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!