一、前言
1.1 項目介紹
【1】項目開發(fā)背景
在當(dāng)今智能化和物聯(lián)網(wǎng)技術(shù)高速發(fā)展的時代背景下,傳統(tǒng)家電的智能化改造成為家電行業(yè)轉(zhuǎn)型升級的重要趨勢。智能冰箱作為智能家居系統(tǒng)的重要組成部分,其智能化程度的提升不僅能夠?yàn)橛脩魩砀颖憬?、舒適的生活體驗(yàn),還能有效提升冰箱的使用效率和安全性?;谶@一背景,設(shè)計了一款基于華為云IOT物聯(lián)網(wǎng)平臺的智能冰箱系統(tǒng)。
本項目通過集成先進(jìn)的傳感器技術(shù)、無線通信技術(shù)以及智能控制算法,實(shí)現(xiàn)對冰箱內(nèi)部環(huán)境的全方位監(jiān)測和智能控制。系統(tǒng)支持對冷藏區(qū)、保鮮區(qū)、冷凍區(qū)三個區(qū)域的溫度和濕度進(jìn)行實(shí)時監(jiān)測,并配備有害氣體傳感器以分析霉菌含量,確保食品存儲環(huán)境的安全衛(wèi)生。同時,通過OLED顯示屏,用戶能夠直觀地了解冰箱內(nèi)部的環(huán)境狀態(tài)。
為了實(shí)現(xiàn)數(shù)據(jù)的遠(yuǎn)程監(jiān)測和控制,系統(tǒng)支持WIFI上云功能,將采集到的數(shù)據(jù)實(shí)時上傳至華為云物聯(lián)網(wǎng)云平臺。通過Qt(C++)設(shè)計的手機(jī)APP,用戶可以隨時隨地調(diào)用華為云IOT的API接口,獲取冰箱上傳的數(shù)據(jù),實(shí)現(xiàn)對冰箱運(yùn)行狀態(tài)的遠(yuǎn)程監(jiān)控。此外,用戶還可以通過手機(jī)APP遠(yuǎn)程設(shè)置冰箱各區(qū)域的溫度,實(shí)現(xiàn)個性化的溫度控制。
在智能控制方面,系統(tǒng)具備自動降溫處理功能。當(dāng)冰箱內(nèi)部溫度超過設(shè)定的閾值時,系統(tǒng)會自動啟動制冷泵進(jìn)行降溫處理,并通過手機(jī)APP發(fā)出紅色字體報警和本地蜂鳴器聲音報警,提醒用戶注意。此外,保鮮區(qū)還配備了紫外線消毒燈,用戶可以根據(jù)需要開啟消毒功能,對冰箱內(nèi)部進(jìn)行殺菌消毒,保障食品的安全存儲。
本項目的開發(fā)不僅體現(xiàn)了智能家居技術(shù)在家電領(lǐng)域的應(yīng)用價值,也為冰箱行業(yè)的智能化升級提供了新的解決方案。通過本項目的實(shí)施,將為用戶提供更加智能化、安全化、舒適化的生活體驗(yàn),推動智能家居產(chǎn)業(yè)的持續(xù)發(fā)展。
【2】設(shè)計實(shí)現(xiàn)的功能
(1)多區(qū)域精確溫濕度控制與監(jiān)測:冰箱被劃分為冷藏區(qū)、保鮮區(qū)、冷凍區(qū)三個獨(dú)立區(qū)域,每個區(qū)域均配置有SHT30溫濕度傳感器,能夠?qū)崟r監(jiān)測并顯示當(dāng)前溫度與濕度。用戶可通過手機(jī)APP遠(yuǎn)程設(shè)定各區(qū)域的理想溫濕度范圍,系統(tǒng)將自動調(diào)節(jié)以維持最佳儲存條件。
(2)有害氣體及霉菌含量監(jiān)測:通過集成的ADC模擬量采集輸出的有害氣體傳感器,系統(tǒng)能夠分析冷藏室內(nèi)是否存在過量的有害氣體及霉菌,及時預(yù)警,保護(hù)食品免受污染,保障用戶健康。
(3)實(shí)時數(shù)據(jù)展示與遠(yuǎn)程監(jiān)控:冰箱門上的0.96寸OLED顯示屏實(shí)時展示當(dāng)前的溫濕度信息及系統(tǒng)狀態(tài),同時,這些數(shù)據(jù)會通過ESP8266-WIFI模塊上傳至華為云IoT物聯(lián)網(wǎng)平臺。用戶通過專門設(shè)計的Qt(C++)手機(jī)APP,可以隨時隨地查看冰箱狀態(tài),實(shí)現(xiàn)遠(yuǎn)程監(jiān)控。
(4)智能警報與應(yīng)急處理:若冰箱內(nèi)溫度超過用戶預(yù)設(shè)的閾值,系統(tǒng)將自動啟動降溫程序,并通過手機(jī)APP推送警告信息,APP界面字體變紅以示警急。同時,冰箱內(nèi)置的高電平觸發(fā)蜂鳴器會發(fā)出聲音報警,確保問題得到即時關(guān)注。
(5)紫外線消毒功能:針對保鮮區(qū),設(shè)計有紫外線消毒燈,用戶可通過APP遠(yuǎn)程控制開啟,定期對冰箱內(nèi)部進(jìn)行消毒,有效殺滅細(xì)菌,保持儲藏環(huán)境的衛(wèi)生。
(6)靈活的電源管理與制冷控制:系統(tǒng)采用外部220V市電輸入,經(jīng)穩(wěn)壓模塊轉(zhuǎn)換為5V 2A直流電源,為所有組件穩(wěn)定供電。制冷泵通過高可靠性的繼電器控制,確保按需啟動,節(jié)能高效。
(7)用戶友好與個性化設(shè)置:手機(jī)APP提供直觀易用的界面,用戶可根據(jù)個人需求設(shè)定各區(qū)域的溫度,查看歷史數(shù)據(jù),甚至接收食品保質(zhì)期提醒,定制化服務(wù)讓生活更加智能化。
【3】項目硬件模塊組成
(1)主控單元:
- STM32F103RCT6微控制器:作為整個系統(tǒng)的控制中心,負(fù)責(zé)處理傳感器數(shù)據(jù)、執(zhí)行邏輯判斷、控制各個外設(shè)工作。該MCU具有高性能、低功耗的特點(diǎn),支持豐富的外設(shè)接口,滿足系統(tǒng)復(fù)雜控制需求。
(2)環(huán)境監(jiān)測模塊:
- SHT30溫濕度傳感器:分別安裝于冷藏區(qū)、保鮮區(qū)、冷凍區(qū),用于精確測量各區(qū)域的溫度和濕度,為溫度控制和環(huán)境監(jiān)測提供數(shù)據(jù)基礎(chǔ)。
- 有害氣體傳感器:通過ADC模擬量輸出,監(jiān)測冰箱內(nèi)部可能存在的有害氣體及霉菌含量,確保食品安全。
(3)顯示與交互模塊:
- 0.96寸OLED顯示屏:采用SPI通訊協(xié)議,實(shí)時顯示冰箱內(nèi)部的溫濕度信息、工作狀態(tài)等,便于用戶直觀了解冰箱運(yùn)行情況。
(4)無線通信模塊:
- ESP8266-WIFI模組:負(fù)責(zé)將冰箱內(nèi)部采集的數(shù)據(jù)通過Wi-Fi網(wǎng)絡(luò)上傳至華為云IoT物聯(lián)網(wǎng)平臺,實(shí)現(xiàn)遠(yuǎn)程監(jiān)控和數(shù)據(jù)交互。
(5)電源管理模塊:
- 外部電源適配器:將220V交流電轉(zhuǎn)換為適合電子設(shè)備使用的5V直流電。
- 穩(wěn)壓電源模塊:進(jìn)一步穩(wěn)定直流電壓至5V 2A,為系統(tǒng)提供穩(wěn)定可靠的電源供應(yīng)。
(6)報警與指示模塊:
- 高電平觸發(fā)有源蜂鳴器:當(dāng)冰箱溫度超出設(shè)定范圍時,主控單元控制蜂鳴器發(fā)出報警聲,提醒用戶注意。
(7)控制執(zhí)行模塊:
- 繼電器模塊:用于控制制冷泵的開關(guān),根據(jù)主控單元的指令自動調(diào)節(jié)制冷系統(tǒng)的工作狀態(tài)。
- 紫外線消毒燈控制電路:通過高低電平信號控制紫外線消毒燈的開閉,實(shí)現(xiàn)遠(yuǎn)程或定時消毒功能。
【4】摘要
本項目設(shè)計并實(shí)現(xiàn)一款基于華為云IoT物聯(lián)網(wǎng)平臺的智能冰箱系統(tǒng),通過集成先進(jìn)的傳感技術(shù)、無線通信技術(shù)和云端服務(wù),顯著提升家庭食品存儲的智能化水平。系統(tǒng)采用STM32F103RCT6作為主控芯片,結(jié)合SHT30溫濕度傳感器、有害氣體傳感器等,實(shí)現(xiàn)對冰箱冷藏區(qū)、保鮮區(qū)、冷凍區(qū)的環(huán)境參數(shù)實(shí)時監(jiān)測與調(diào)控。利用ESP8266-WiFi模塊,將數(shù)據(jù)上傳至華為云平臺,用戶可通過Qt(C++)開發(fā)的手機(jī)APP遠(yuǎn)程監(jiān)控冰箱狀態(tài)、調(diào)整設(shè)置并接收異常報警。此外,系統(tǒng)還配備了紫外線消毒燈進(jìn)行定期消毒,以及溫度超標(biāo)時的自動降溫處理與本地蜂鳴器報警功能,全方位保障食品新鮮與安全。本項目展現(xiàn)了智能家居領(lǐng)域的最新進(jìn)展,為用戶帶來更加健康、便捷的生活體驗(yàn)。
關(guān)鍵字:智能冰箱、華為云IoT、STM32、溫濕度傳感器、有害氣體監(jiān)測、WiFi通信、Qt手機(jī)APP、紫外線消毒、遠(yuǎn)程控制、智能報警。
1.2 設(shè)計思路
(1)需求分析與功能定義:明確項目目標(biāo),基于市場調(diào)研與用戶需求分析,確定冰箱系統(tǒng)應(yīng)具備的智能監(jiān)測、遠(yuǎn)程控制、自動報警、健康消毒等功能模塊。明確各區(qū)域(冷藏、保鮮、冷凍)的具體要求,如溫濕度控制精度、有害物質(zhì)檢測標(biāo)準(zhǔn)等。
(2)系統(tǒng)架構(gòu)設(shè)計:設(shè)計一個分層的系統(tǒng)架構(gòu),底層為硬件控制層,包括主控單元(STM32)、傳感器網(wǎng)絡(luò)、執(zhí)行器(繼電器、蜂鳴器、紫外線燈)等;中間層為數(shù)據(jù)處理與邏輯控制層,負(fù)責(zé)數(shù)據(jù)采集、處理與決策;上層為云服務(wù)與用戶交互層,通過華為云IoT平臺實(shí)現(xiàn)數(shù)據(jù)上傳、遠(yuǎn)程控制及APP交互。
(3)硬件選型與模塊集成:根據(jù)功能需求選擇合適的硬件組件,如選用性能穩(wěn)定的STM32F103RCT6作為主控芯片,SHT30以實(shí)現(xiàn)高精度溫濕度監(jiān)測,ESP8266-WiFi模塊保證穩(wěn)定的數(shù)據(jù)傳輸,以及OLED顯示屏和手機(jī)APP提高用戶交互體驗(yàn)。設(shè)計合理的電路布局,確保各模塊間高效協(xié)同工作。
(4)軟件開發(fā)與算法設(shè)計:開發(fā)嵌入式軟件,編寫驅(qū)動程序以控制硬件運(yùn)作,設(shè)計數(shù)據(jù)處理算法,實(shí)現(xiàn)數(shù)據(jù)的有效過濾、分析與存儲。開發(fā)手機(jī)APP,利用Qt框架實(shí)現(xiàn)用戶友好的界面設(shè)計,調(diào)用華為云IoT API,實(shí)現(xiàn)遠(yuǎn)程監(jiān)控、設(shè)置調(diào)整和報警通知功能。
(5)云端服務(wù)對接與數(shù)據(jù)安全:與華為云IoT平臺對接,設(shè)計云端數(shù)據(jù)處理流程,包括數(shù)據(jù)接收、存儲、分析與反饋。重視數(shù)據(jù)加密與用戶隱私保護(hù),確保數(shù)據(jù)傳輸過程中的安全性和私密性。
(6)測試與優(yōu)化:進(jìn)行全面的系統(tǒng)測試,包括硬件兼容性測試、功能驗(yàn)證、穩(wěn)定性測試以及用戶體驗(yàn)測試。根據(jù)測試結(jié)果進(jìn)行必要的軟硬件優(yōu)化,確保系統(tǒng)穩(wěn)定可靠,滿足用戶需求。
(7)用戶反饋與持續(xù)迭代:項目完成后,收集用戶反饋,對系統(tǒng)進(jìn)行持續(xù)的維護(hù)與升級,引入新功能或優(yōu)化現(xiàn)有功能,以適應(yīng)不斷變化的市場需求和技術(shù)進(jìn)步。
1.3 系統(tǒng)功能總結(jié)
功能分類 | 具體功能描述 |
---|---|
環(huán)境監(jiān)測 | - 實(shí)時監(jiān)測冷藏區(qū)、保鮮區(qū)、冷凍區(qū)的溫度與濕度 - 分析檢測區(qū)域內(nèi)有害氣體及霉菌含量 |
數(shù)據(jù)展示 | - OLED顯示屏實(shí)時顯示各區(qū)域溫濕度數(shù)據(jù)及系統(tǒng)狀態(tài) - 手機(jī)APP遠(yuǎn)程查看冰箱監(jiān)測數(shù)據(jù) |
遠(yuǎn)程控制 | - 通過手機(jī)APP遠(yuǎn)程設(shè)置各區(qū)域理想溫度 - 開啟/關(guān)閉紫外線消毒燈進(jìn)行消毒處理 |
智能調(diào)節(jié) | - 自動調(diào)節(jié)溫度,當(dāng)實(shí)際溫度偏離設(shè)定值時啟動降溫處理 |
報警提示 | - 溫度過高時,手機(jī)APP顯示紅色預(yù)警,冰箱本地蜂鳴器報警 |
云端互聯(lián) | - 通過ESP8266-WiFi模塊將數(shù)據(jù)上傳至華為云IoT平臺 - 實(shí)現(xiàn)遠(yuǎn)程數(shù)據(jù)訪問與管理 |
電源與安全 | - 穩(wěn)定的電源管理系統(tǒng),220V轉(zhuǎn)5V直流供電 - 數(shù)據(jù)傳輸加密,保障用戶隱私安全 |
健康管理 | - 定期紫外線消毒,減少細(xì)菌滋生,保障食品安全與健康 |
用戶交互 | - Qt(C++)開發(fā)的手機(jī)APP,界面友好,操作便捷 |
系統(tǒng)兼容性 | - 硬件模塊間高度兼容,支持后續(xù)擴(kuò)展與升級 |
1.4 開發(fā)工具的選擇
【1】設(shè)備端開發(fā)
STM32的編程語言選擇C語言,C語言執(zhí)行效率高,大學(xué)里主學(xué)的C語言,C語言編譯出來的可執(zhí)行文件最接近于機(jī)器碼,匯編語言執(zhí)行效率最高,但是匯編的移植性比較差,目前在一些操作系統(tǒng)內(nèi)核里還有一些低配的單片機(jī)使用的較多,平常的單片機(jī)編程還是以C語言為主。C語言的執(zhí)行效率僅次于匯編,語法理解簡單、代碼通用性強(qiáng),也支持跨平臺,在嵌入式底層、單片機(jī)編程里用的非常多,當(dāng)前的設(shè)計就是采用C語言開發(fā)。
開發(fā)工具選擇Keil,keil是一家世界領(lǐng)先的嵌入式微控制器軟件開發(fā)商,在2015年,keil被ARM公司收購。因?yàn)楫?dāng)前芯片選擇的是STM32F103系列,STMF103是屬于ARM公司的芯片構(gòu)架、Cortex-M3內(nèi)核系列的芯片,所以使用Kile來開發(fā)STM32是有先天優(yōu)勢的,而keil在各大高校使用的也非常多,很多教科書里都是以keil來教學(xué),開發(fā)51單片機(jī)、STM32單片機(jī)等等。目前作為MCU芯片開發(fā)的軟件也不只是keil一家獨(dú)大,IAR在MCU微處理器開發(fā)領(lǐng)域里也使用的非常多,IAR擴(kuò)展性更強(qiáng),也支持STM32開發(fā),也支持其他芯片,比如:CC2530,51單片機(jī)的開發(fā)。從軟件的使用上來講,IAR比keil更加簡潔,功能相對少一些。如果之前使用過keil,而且使用頻率較多,已經(jīng)習(xí)慣再使用IAR是有點(diǎn)不適應(yīng)界面的。
【2】上位機(jī)開發(fā)
上位機(jī)的開發(fā)選擇Qt框架,編程語言采用C++;Qt是一個1991年由Qt Company開發(fā)的跨平臺C++圖形用戶界面應(yīng)用程序開發(fā)框架。它既可以開發(fā)GUI程序,也可用于開發(fā)非GUI程序,比如控制臺工具和服務(wù)器。Qt是面向?qū)ο蟮目蚣?,使用特殊的代碼生成擴(kuò)展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴(kuò)展,并且允許真正地組件編程。Qt能輕松創(chuàng)建具有原生C++性能的連接設(shè)備、用戶界面(UI)和應(yīng)用程序。它功能強(qiáng)大且結(jié)構(gòu)緊湊,擁有直觀的工具和庫。
二、部署華為云物聯(lián)網(wǎng)平臺
華為云官網(wǎng): https://www.huaweicloud.com/
打開官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 設(shè)備接入IoTDA
。
2.1 物聯(lián)網(wǎng)平臺介紹
華為云物聯(lián)網(wǎng)平臺(IoT 設(shè)備接入云服務(wù))提供海量設(shè)備的接入和管理能力,將物理設(shè)備聯(lián)接到云,支撐設(shè)備數(shù)據(jù)采集上云和云端下發(fā)命令給設(shè)備進(jìn)行遠(yuǎn)程控制,配合華為云其他產(chǎn)品,幫助我們快速構(gòu)筑物聯(lián)網(wǎng)解決方案。
使用物聯(lián)網(wǎng)平臺構(gòu)建一個完整的物聯(lián)網(wǎng)解決方案主要包括3部分:物聯(lián)網(wǎng)平臺、業(yè)務(wù)應(yīng)用和設(shè)備。
物聯(lián)網(wǎng)平臺作為連接業(yè)務(wù)應(yīng)用和設(shè)備的中間層,屏蔽了各種復(fù)雜的設(shè)備接口,實(shí)現(xiàn)設(shè)備的快速接入;同時提供強(qiáng)大的開放能力,支撐行業(yè)用戶構(gòu)建各種物聯(lián)網(wǎng)解決方案。
設(shè)備可以通過固網(wǎng)、2G/3G/4G/5G、NB-IoT、Wifi等多種網(wǎng)絡(luò)接入物聯(lián)網(wǎng)平臺,并使用LWM2M/CoAP、MQTT、HTTPS協(xié)議將業(yè)務(wù)數(shù)據(jù)上報到平臺,平臺也可以將控制命令下發(fā)給設(shè)備。
業(yè)務(wù)應(yīng)用通過調(diào)用物聯(lián)網(wǎng)平臺提供的API,實(shí)現(xiàn)設(shè)備數(shù)據(jù)采集、命令下發(fā)、設(shè)備管理等業(yè)務(wù)場景。
2.2 開通物聯(lián)網(wǎng)服務(wù)
地址: https://www.huaweicloud.com/product/iothub.html
點(diǎn)擊立即創(chuàng)建
。
正在創(chuàng)建標(biāo)準(zhǔn)版實(shí)例,需要等待片刻。
創(chuàng)建完成之后,點(diǎn)擊實(shí)例名稱。 可以看到標(biāo)準(zhǔn)版實(shí)例的設(shè)備接入端口和地址。
在上面也能看到 免費(fèi)單元的限制。
開通之后,點(diǎn)擊總覽
,也能查看接入信息。 我們當(dāng)前設(shè)備準(zhǔn)備采用MQTT協(xié)議接入華為云平臺,這里可以看到MQTT協(xié)議的地址和端口號等信息。
總結(jié):
端口號: MQTT (1883)| MQTTS (8883)
接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
**根據(jù)域名地址得到IP地址信息: **
打開Windows電腦的命令行控制臺終端,使用ping
命令。ping
一下即可。
Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有權(quán)利。
C:Users11266>ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字節(jié)的數(shù)據(jù):
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=35ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=39ms TTL=93
117.78.5.125 的 Ping 統(tǒng)計信息:
數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
最短 = 35ms,最長 = 39ms,平均 = 36ms
C:Users11266>
MQTT協(xié)議接入端口號有兩個,1883是非加密端口,8883是證書加密端口,單片機(jī)無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺。
2.3 創(chuàng)建產(chǎn)品
(1)創(chuàng)建產(chǎn)品
(2)填寫產(chǎn)品信息
根據(jù)自己產(chǎn)品名字填寫,下面的設(shè)備類型選擇自定義類型。
(3)產(chǎn)品創(chuàng)建成功
創(chuàng)建完成之后點(diǎn)擊查看詳情。
(4)添加自定義模型
產(chǎn)品創(chuàng)建完成之后,點(diǎn)擊進(jìn)入產(chǎn)品詳情頁面,翻到最下面可以看到模型定義。
模型簡單來說: 就是存放設(shè)備上傳到云平臺的數(shù)據(jù)。
你可以根據(jù)自己的產(chǎn)品進(jìn)行創(chuàng)建。
比如:
煙霧可以叫 MQ2
溫度可以叫 Temperature
濕度可以叫 humidity
火焰可以叫 flame
其他的傳感器自己用單詞簡寫命名即可。 這就是你的單片機(jī)設(shè)備端上傳到服務(wù)器的數(shù)據(jù)名字。
先點(diǎn)擊自定義模型。
再創(chuàng)建一個服務(wù)ID。
接著點(diǎn)擊新增屬性。
2.4 添加設(shè)備
產(chǎn)品是屬于上層的抽象模型,接下來在產(chǎn)品模型下添加實(shí)際的設(shè)備。添加的設(shè)備最終需要與真實(shí)的設(shè)備關(guān)聯(lián)在一起,完成數(shù)據(jù)交互。
(1)注冊設(shè)備
(2)根據(jù)自己的設(shè)備填寫
(3)保存設(shè)備信息
創(chuàng)建完畢之后,點(diǎn)擊保存并關(guān)閉,得到創(chuàng)建的設(shè)備密匙信息。該信息在后續(xù)生成MQTT三元組的時候需要使用。
(4)設(shè)備創(chuàng)建完成
(5)設(shè)備詳情
2.5 MQTT協(xié)議主題訂閱與發(fā)布
(1)MQTT協(xié)議介紹
當(dāng)前的設(shè)備是采用MQTT協(xié)議與華為云平臺進(jìn)行通信。
MQTT是一個物聯(lián)網(wǎng)傳輸協(xié)議,它被設(shè)計用于輕量級的發(fā)布/訂閱式消息傳輸,旨在為低帶寬和不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中的物聯(lián)網(wǎng)設(shè)備提供可靠的網(wǎng)絡(luò)服務(wù)。MQTT是專門針對物聯(lián)網(wǎng)開發(fā)的輕量級傳輸協(xié)議。MQTT協(xié)議針對低帶寬網(wǎng)絡(luò),低計算能力的設(shè)備,做了特殊的優(yōu)化,使得其能適應(yīng)各種物聯(lián)網(wǎng)應(yīng)用場景。目前MQTT擁有各種平臺和設(shè)備上的客戶端,已經(jīng)形成了初步的生態(tài)系統(tǒng)。
MQTT是一種消息隊列協(xié)議,使用發(fā)布/訂閱消息模式,提供一對多的消息發(fā)布,解除應(yīng)用程序耦合,相對于其他協(xié)議,開發(fā)更簡單;MQTT協(xié)議是工作在TCP/IP協(xié)議上;由TCP/IP協(xié)議提供穩(wěn)定的網(wǎng)絡(luò)連接;所以,只要具備TCP協(xié)議棧的網(wǎng)絡(luò)設(shè)備都可以使用MQTT協(xié)議。 本次設(shè)備采用的ESP8266就具備TCP協(xié)議棧,能夠建立TCP連接,所以,配合STM32代碼里封裝的MQTT協(xié)議,就可以與華為云平臺完成通信。
華為云的MQTT協(xié)議接入幫助文檔在這里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
業(yè)務(wù)流程:
(2)華為云平臺MQTT協(xié)議使用限制
描述 | 限制 |
---|---|
支持的MQTT協(xié)議版本 | 3.1.1 |
與標(biāo)準(zhǔn)MQTT協(xié)議的區(qū)別 | 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg |
MQTTS支持的安全等級 | 采用TCP通道基礎(chǔ) + TLS協(xié)議(最高TLSv1.3版本) |
單帳號每秒最大MQTT連接請求數(shù) | 無限制 |
單個設(shè)備每分鐘支持的最大MQTT連接數(shù) | 1 |
單個MQTT連接每秒的吞吐量,即帶寬,包含直連設(shè)備和網(wǎng)關(guān) | 3KB/s |
MQTT單個發(fā)布消息最大長度,超過此大小的發(fā)布請求將被直接拒絕 | 1MB |
MQTT連接心跳時間建議值 | 心跳時間限定為30至1200秒,推薦設(shè)置為120秒 |
產(chǎn)品是否支持自定義Topic | 支持 |
消息發(fā)布與訂閱 | 設(shè)備只能對自己的Topic進(jìn)行消息發(fā)布與訂閱 |
每個訂閱請求的最大訂閱數(shù) | 無限制 |
(3)主題訂閱格式
幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
對于設(shè)備而言,一般會訂閱平臺下發(fā)消息給設(shè)備 這個主題。
設(shè)備想接收平臺下發(fā)的消息,就需要訂閱平臺下發(fā)消息給設(shè)備 的主題,訂閱后,平臺下發(fā)消息給設(shè)備,設(shè)備就會收到消息。
如果設(shè)備想要知道平臺下發(fā)的消息,需要訂閱上面圖片里標(biāo)注的主題。
以當(dāng)前設(shè)備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主題發(fā)布格式
對于設(shè)備來說,主題發(fā)布表示向云平臺上傳數(shù)據(jù),將最新的傳感器數(shù)據(jù),設(shè)備狀態(tài)上傳到云平臺。
這個操作稱為:屬性上報。
幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根據(jù)幫助文檔的介紹, 當(dāng)前設(shè)備發(fā)布主題,上報屬性的格式總結(jié)如下:
發(fā)布的主題格式:
$oc/devices/{device_id}/sys/properties/report
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
發(fā)布主題時,需要上傳數(shù)據(jù),這個數(shù)據(jù)格式是JSON格式。
上傳的JSON數(shù)據(jù)格式如下:
{
"services": [
{
"service_id": <填服務(wù)ID>,
"properties": {
"<填屬性名稱1>": <填屬性值>,
"<填屬性名稱2>": <填屬性值>,
..........
}
}
]
}
根據(jù)JSON格式,一次可以上傳多個屬性字段。 這個JSON格式里的,服務(wù)ID,屬性字段名稱,屬性值類型,在前面創(chuàng)建產(chǎn)品的時候就已經(jīng)介紹了,不記得可以翻到前面去查看。
根據(jù)這個格式,組合一次上傳的屬性數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.6 MQTT三元組
MQTT協(xié)議登錄需要填用戶ID,設(shè)備ID,設(shè)備密碼等信息,就像我們平時登錄QQ,微信一樣要輸入賬號密碼才能登錄。MQTT協(xié)議登錄的這3個參數(shù),一般稱為MQTT三元組。
接下來介紹,華為云平臺的MQTT三元組參數(shù)如何得到。
(1)MQTT服務(wù)器地址
要登錄MQTT服務(wù)器,首先記得先知道服務(wù)器的地址是多少,端口是多少。
幫助文檔地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT協(xié)議的端口支持1883和8883,它們的區(qū)別是:8883 是加密端口更加安全。但是單片機(jī)上使用比較困難,所以當(dāng)前的設(shè)備是采用1883端口進(jìn)連接的。
根據(jù)上面的域名和端口號,得到下面的IP地址和端口號信息: 如果設(shè)備支持填寫域名可以直接填域名,不支持就直接填寫IP地址。 (IP地址就是域名解析得到的)
華為云的MQTT服務(wù)器地址:117.78.5.125
華為云的MQTT端口號:1883
如何得到IP地址?如何域名轉(zhuǎn)IP? 打開Windows的命令行輸入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元組
華為云提供了一個在線工具,用來生成MQTT鑒權(quán)三元組: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打開這個工具,填入設(shè)備的信息(也就是剛才創(chuàng)建完設(shè)備之后保存的信息),點(diǎn)擊生成,就可以得到MQTT的登錄信息了。
下面是打開的頁面:
填入設(shè)備的信息: (上面兩行就是設(shè)備創(chuàng)建完成之后保存得到的)
直接得到三元組信息。
得到三元組之后,設(shè)備端通過MQTT協(xié)議登錄鑒權(quán)的時候,填入?yún)?shù)即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
2.7 模擬設(shè)備登錄測試
經(jīng)過上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設(shè)備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實(shí)的設(shè)備來登錄平臺。測試與服務(wù)器通信是否正常。
(1)填入登錄信息
打開MQTT客戶端軟件,對號填入相關(guān)信息(就是上面的文本介紹)。然后,點(diǎn)擊登錄,訂閱主題,發(fā)布主題。
(2)打開網(wǎng)頁查看
完成上面的操作之后,打開華為云網(wǎng)頁后臺,可以看到設(shè)備已經(jīng)在線了。
點(diǎn)擊詳情頁面,可以看到上傳的數(shù)據(jù):
到此,云平臺的部署已經(jīng)完成,設(shè)備已經(jīng)可以正常上傳數(shù)據(jù)了。
(3)MQTT登錄測試參數(shù)總結(jié)
MQTT服務(wù)器: 117.78.5.125
MQTT端口號: 183
//物聯(lián)網(wǎng)服務(wù)器的設(shè)備信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//訂閱與發(fā)布的主題
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //訂閱
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //發(fā)布
發(fā)布的數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.8 創(chuàng)建IAM賬戶
創(chuàng)建一個IAM賬戶,因?yàn)榻酉聛黹_發(fā)上位機(jī),需要使用云平臺的API接口,這些接口都需要token進(jìn)行鑒權(quán)。簡單來說,就是身份的認(rèn)證。 調(diào)用接口獲取Token時,就需要填寫IAM賬號信息。所以,接下來演示一下過程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】獲取項目憑證 ** 點(diǎn)擊左上角用戶名,選擇下拉菜單里的我的憑證
項目憑證:
28add376c01e4a61ac8b621c714bf459
【2】創(chuàng)建IAM用戶
鼠標(biāo)放在左上角頭像上,在下拉菜單里選擇統(tǒng)一身份認(rèn)證
。
點(diǎn)擊左上角創(chuàng)建用戶
。
創(chuàng)建成功:
【3】創(chuàng)建完成
用戶信息如下:
主用戶名 l19504562721
IAM用戶 ds_abc
密碼 DS12345678
2.9 獲取影子數(shù)據(jù)
幫助文檔:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
設(shè)備影子介紹:
設(shè)備影子是一個用于存儲和檢索設(shè)備當(dāng)前狀態(tài)信息的JSON文檔。
每個設(shè)備有且只有一個設(shè)備影子,由設(shè)備ID唯一標(biāo)識
設(shè)備影子僅保存最近一次設(shè)備的上報數(shù)據(jù)和預(yù)期數(shù)據(jù)
無論該設(shè)備是否在線,都可以通過該影子獲取和設(shè)置設(shè)備的屬性
簡單來說:設(shè)備影子就是保存,設(shè)備最新上傳的一次數(shù)據(jù)。
我們設(shè)計的軟件里,如果想要獲取設(shè)備的最新狀態(tài)信息,就采用設(shè)備影子接口。
如果對接口不熟悉,可以先進(jìn)行在線調(diào)試:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在線調(diào)試接口,可以請求影子接口,了解請求,與返回的數(shù)據(jù)格式。
調(diào)試完成看右下角的響應(yīng)體,就是返回的影子數(shù)據(jù)。
設(shè)備影子接口返回的數(shù)據(jù)如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
調(diào)試成功之后,可以得到訪問影子數(shù)據(jù)的真實(shí)鏈接,接下來的代碼開發(fā)中,就采用Qt寫代碼訪問此鏈接,獲取影子數(shù)據(jù),完成上位機(jī)開發(fā)。
鏈接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
三、STM32設(shè)備端代碼設(shè)計
3.1 RTC時鐘配置代碼
#include "delay.h"
#include "usart.h"
#include "rtc.h"
#include "calendar.h"
//實(shí)時時鐘配置
//初始化RTC時鐘,同時檢測時鐘是否工作正常
//BKP->DR1用于保存是否第一次配置的設(shè)置
//返回0:正常
//其他:錯誤代碼
u8 RTC_Init(void)
{
//檢查是不是第一次配置時鐘
u8 temp=0;
if(BKP->DR1!=0X5050)//第一次配置
{
RCC->APB1ENR|=1<<28; //使能電源時鐘
RCC->APB1ENR|=1<<27; //使能備份時鐘
PWR->CR|=1<<8; //取消備份區(qū)寫保護(hù)
RCC->BDCR|=1<<16; //備份區(qū)域軟復(fù)位
RCC->BDCR&=~(1<<16); //備份區(qū)域軟復(fù)位結(jié)束
RCC->BDCR|=1<<0; //開啟外部低速振蕩器
while((!(RCC->BDCR&0X02))&&temp<250)//等待外部時鐘就緒
{
temp++;
delay_ms(10);
};
if(temp>=250)return 1;//初始化時鐘失敗,晶振有問題
RCC->BDCR|=1<<8; //LSI作為RTC時鐘
RCC->BDCR|=1<<15;//RTC時鐘使能
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步
RTC->CRH|=0X01; //允許秒中斷
RTC->CRH|=0X02; //允許鬧鐘中斷
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
RTC->CRL|=1<<4; //允許配置
RTC->PRLH=0X0000;
RTC->PRLL=32767; //時鐘周期設(shè)置(有待觀察,看是否跑慢了?)理論值:32767
RTC_Set(2015,1,14,17,42,55); //設(shè)置時間
RTC->CRL&=~(1<<4); //配置更新
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
BKP->DR1=0X5050;
printf("FIRST TIMEn");
}else//系統(tǒng)繼續(xù)計時
{
while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步
RTC->CRH|=0X01; //允許秒中斷
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
printf("OKn");
}
MY_NVIC_Init(0,0,RTC_IRQn,2);//優(yōu)先級設(shè)置
RTC_Get();//更新時間
return 0; //ok
}
//RTC時鐘中斷
//每秒觸發(fā)一次
void RTC_IRQHandler(void)
{
OSIntEnter();
if(RTC->CRL&0x0001) //秒鐘中斷
{
RTC_Get(); //更新時間
//printf("sec:%drn",calendar.sec);
}
if(RTC->CRL&0x0002) //鬧鐘中斷
{
RTC->CRL&=~(0x0002); //清鬧鐘中斷
RTC_Get(); //更新時間
printf("Alarm Time:%d-%d-%d %d:%d:%dn",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//輸出鬧鈴時間
alarm.ringsta|=1<<7; //開啟鬧鈴
}
RTC->CRL&=0X0FFA; //清除溢出,秒鐘中斷標(biāo)志
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
OSIntExit();
}
//判斷是否是閏年函數(shù)
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//閏年 31 29 31 30 31 30 31 31 30 31 30 31
//非閏年 31 28 31 30 31 30 31 31 30 31 30 31
//year:年份
//返回值:該年份是不是閏年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0) //必須能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00結(jié)尾,還要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//設(shè)置時鐘
//把輸入的時鐘轉(zhuǎn)換為秒鐘
//以1970年1月1日為基準(zhǔn)
//1970~2099年為合法年份
//返回值:0,成功;其他:錯誤代碼.
//月份數(shù)據(jù)表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正數(shù)據(jù)表
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
//syear,smon,sday,hour,min,sec:年月日時分秒
//返回值:設(shè)置結(jié)果。0,成功;1,失敗。
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒鐘相加
{
if(Is_Leap_Year(t))seccount+=31622400;//閏年的秒鐘數(shù)
else seccount+=31536000; //平年的秒鐘數(shù)
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒鐘數(shù)相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒鐘數(shù)相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//閏年2月份增加一天的秒鐘數(shù)
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒鐘數(shù)相加
seccount+=(u32)hour*3600;//小時秒鐘數(shù)
seccount+=(u32)min*60; //分鐘秒鐘數(shù)
seccount+=sec;//最后的秒鐘加上去
//設(shè)置時鐘
RCC->APB1ENR|=1<<28;//使能電源時鐘
RCC->APB1ENR|=1<<27;//使能備份時鐘
PWR->CR|=1<<8; //取消備份區(qū)寫保護(hù)
//上面三步是必須的!
RTC->CRL|=1<<4; //允許配置
RTC->CNTL=seccount&0xffff;
RTC->CNTH=seccount>>16;
RTC->CRL&=~(1<<4);//配置更新
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
RTC_Get();//設(shè)置完之后更新一下數(shù)據(jù)
return 0;
}
//初始化鬧鐘
//以1970年1月1日為基準(zhǔn)
//1970~2099年為合法年份
//syear,smon,sday,hour,min,sec:鬧鐘的年月日時分秒
//返回值:0,成功;其他:錯誤代碼.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒鐘相加
{
if(Is_Leap_Year(t))seccount+=31622400;//閏年的秒鐘數(shù)
else seccount+=31536000; //平年的秒鐘數(shù)
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒鐘數(shù)相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒鐘數(shù)相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//閏年2月份增加一天的秒鐘數(shù)
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒鐘數(shù)相加
seccount+=(u32)hour*3600;//小時秒鐘數(shù)
seccount+=(u32)min*60; //分鐘秒鐘數(shù)
seccount+=sec;//最后的秒鐘加上去
//設(shè)置時鐘
RCC->APB1ENR|=1<<28;//使能電源時鐘
RCC->APB1ENR|=1<<27;//使能備份時鐘
PWR->CR|=1<<8; //取消備份區(qū)寫保護(hù)
//上面三步是必須的!
RTC->CRL|=1<<4; //允許配置
RTC->ALRL=seccount&0xffff;
RTC->ALRH=seccount>>16;
RTC->CRL&=~(1<<4);//配置更新
while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成
return 0;
}
//得到當(dāng)前的時間,結(jié)果保存在calendar結(jié)構(gòu)體里面
//返回值:0,成功;其他:錯誤代碼.
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC->CNTH;//得到計數(shù)器中的值(秒鐘數(shù))
timecount<<=16;
timecount+=RTC->CNTL;
temp=timecount/86400; //得到天數(shù)(秒鐘數(shù)對應(yīng)的)
if(daycnt!=temp)//超過一天了
{
daycnt=temp;
temp1=1970; //從1970年開始
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是閏年
{
if(temp>=366)temp-=366;//閏年的秒鐘數(shù)
else break;
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=0;
while(temp>=28)//超過了一個月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//當(dāng)年是不是閏年/2月份
{
if(temp>=29)temp-=29;//閏年的秒鐘數(shù)
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒鐘數(shù)
calendar.hour=temp/3600; //小時
calendar.min=(temp%3600)/60; //分鐘
calendar.sec=(temp%3600)%60; //秒鐘
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//獲取星期
return 0;
}
//獲得現(xiàn)在是星期幾
//功能描述:輸入公歷日期得到星期(只允許1901-2099年)
//year,month,day:公歷年月日
//返回值:星期號
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100; yearL=year%100;
// 如果為21世紀(jì),年份數(shù)加100
if (yearH>19)yearL+=100;
// 所過閏年數(shù)只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}
3.2 SPI協(xié)議封裝
#include "spi.h"
//以下是SPI模塊的初始化代碼,配置成主機(jī)模式,訪問W25Q128/NRF24L01
//SPI口初始化
//這里針是對SPI2的初始化
void SPI2_Init(void)
{
RCC->APB2ENR|=1<<3; //PORTB時鐘使能
RCC->APB1ENR|=1<<14; //SPI2時鐘使能
//這里只針對SPI口初始化
GPIOB->CRH&=0X000FFFFF;
GPIOB->CRH|=0XBBB00000; //PB13/14/15復(fù)用
GPIOB->ODR|=0X7<<13; //PB13/14/15上拉
SPI2->CR1|=0<<10; //全雙工模式
SPI2->CR1|=1<<9; //軟件nss管理
SPI2->CR1|=1<<8;
SPI2->CR1|=1<<2; //SPI主機(jī)
SPI2->CR1|=0<<11; //8bit數(shù)據(jù)格式
SPI2->CR1|=1<<1; //空閑模式下SCK為1 CPOL=1
SPI2->CR1|=1<<0; //數(shù)據(jù)采樣從第二個時間邊沿開始,CPHA=1
//對SPI2屬于APB1的外設(shè).時鐘頻率最大為36M.
SPI2->CR1|=3<<3; //Fsck=Fpclk1/256
SPI2->CR1|=0<<7; //MSBfirst
SPI2->CR1|=1<<6; //SPI設(shè)備使能
SPI2_ReadWriteByte(0xff);//啟動傳輸
}
//SPI2速度設(shè)置函數(shù)
//SpeedSet:0~7
//SPI速度=fAPB1/2^(SpeedSet+1)
//APB1時鐘一般為36Mhz
void SPI2_SetSpeed(u8 SpeedSet)
{
SpeedSet&=0X07; //限制范圍
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SpeedSet<<3; //設(shè)置SPI2速度
SPI2->CR1|=1<<6; //SPI設(shè)備使能
}
//SPI2 讀寫一個字節(jié)
//TxData:要寫入的字節(jié)
//返回值:讀取到的字節(jié)
u8 SPI2_ReadWriteByte(u8 TxData)
{
u16 retry=0;
while((SPI2->SR&1<<1)==0) //等待發(fā)送區(qū)空
{
retry++;
if(retry>=0XFFFE)return 0; //超時退出
}
SPI2->DR=TxData; //發(fā)送一個byte
retry=0;
while((SPI2->SR&1<<0)==0) //等待接收完一個byte
{
retry++;
if(retry>=0XFFFE)return 0; //超時退出
}
return SPI2->DR; //返回收到的數(shù)據(jù)
}
//SPI1初始化
void SPI1_Init(void)
{
RCC->APB2ENR|=1<<2; //PORTA時鐘使能
RCC->APB2ENR|=1<<12; //SPI1時鐘使能
//這里只針對SPI口初始化
GPIOA->CRL&=0X000FFFFF;
GPIOA->CRL|=0XBBB00000; //PA5.6.7復(fù)用
GPIOA->ODR|=0X7<<5; //PA5.6.7上拉
SPI1->CR1|=0<<10;//全雙工模式
SPI1->CR1|=1<<9; //軟件nss管理
SPI1->CR1|=1<<8;
SPI1->CR1|=1<<2; //SPI主機(jī)
SPI1->CR1|=0<<11;//8bit數(shù)據(jù)格式
SPI1->CR1|=1<<1; //CPOL=0時空閑模式下SCK為1 CPOL=1
SPI1->CR1|=1<<0; //數(shù)據(jù)采樣從第二個時間邊沿開始,CPHA=1
SPI1->CR1|=7<<3; //Fsck=Fcpu/256
SPI1->CR1|=0<<7; //MSBfirst
SPI1->CR1|=1<<6; //SPI設(shè)備使能
SPI1_ReadWriteByte(0xff);//啟動傳輸
}
//SPI1 速度設(shè)置函數(shù)
//SpeedSet:0~7
//SPI速度=fAPB2/2^(SpeedSet+1)
//APB2時鐘一般為72Mhz
void SPI1_SetSpeed(u8 SpeedSet)
{
SpeedSet&=0X07; //限制范圍
SPI1->CR1&=0XFFC7;
SPI1->CR1|=SpeedSet<<3; //設(shè)置SPI1速度
SPI1->CR1|=1<<6; //SPI設(shè)備使能
}
//SPIx 讀寫一個字節(jié)
//TxData:要寫入的字節(jié)
//返回值:讀取到的字節(jié)
u8 SPI1_ReadWriteByte(u8 TxData)
{
while((SPI1->SR&1<<1)==0);//等待發(fā)送區(qū)空
SPI1->DR=TxData; //發(fā)送一個byte
while((SPI1->SR&1<<0)==0); //等待接收完一個byte
return SPI1->DR; //返回收到的數(shù)據(jù)
}
3.3 IIC協(xié)議封裝
#include "myiic.h"
#include "delay.h"
//初始化IIC
void IIC_Init(void)
{
RCC->APB2ENR|=1<<3; //先使能外設(shè)IO PORTB時鐘
GPIOB->CRL&=0X00FFFFFF; //PB6/7 推挽輸出
GPIOB->CRL|=0X33000000;
GPIOB->ODR|=3<<6; //PB6,7 輸出高
}
//產(chǎn)生IIC起始信號
void IIC_Start(void)
{
SDA_OUT(); //sda線輸出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//鉗住I2C總線,準(zhǔn)備發(fā)送或接收數(shù)據(jù)
}
//產(chǎn)生IIC停止信號
void IIC_Stop(void)
{
SDA_OUT();//sda線輸出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//發(fā)送I2C總線結(jié)束信號
delay_us(4);
}
//等待應(yīng)答信號到來
//返回值:1,接收應(yīng)答失敗
// 0,接收應(yīng)答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA設(shè)置為輸入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//時鐘輸出0
return 0;
}
//產(chǎn)生ACK應(yīng)答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不產(chǎn)生ACK應(yīng)答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC發(fā)送一個字節(jié)
//返回從機(jī)有無應(yīng)答
//1,有應(yīng)答
//0,無應(yīng)答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低時鐘開始數(shù)據(jù)傳輸
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //對TEA5767這三個延時都是必須的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//讀1個字節(jié),ack=1時,發(fā)送ACK,ack=0,發(fā)送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA設(shè)置為輸入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//發(fā)送nACK
else
IIC_Ack(); //發(fā)送ACK
return receive;
}
3.4 ADC代碼封裝
#include "adc.h"
#include "delay.h"
//初始化ADC1
//這里我們僅以規(guī)則通道為例
//我們默認(rèn)僅開啟通道1
void Adc_Init(void)
{
//先初始化IO口
RCC->APB2ENR|=1<<2; //使能PORTA口時鐘
GPIOA->CRL&=0XFFFFFF0F;//PA1 anolog輸入
RCC->APB2ENR|=1<<9; //ADC1時鐘使能
RCC->APB2RSTR|=1<<9; //ADC1復(fù)位
RCC->APB2RSTR&=~(1<<9);//復(fù)位結(jié)束
RCC->CFGR&=~(3<<14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設(shè)置為12M,ADC最大時鐘不能超過14M!
//否則將導(dǎo)致ADC準(zhǔn)確度下降!
RCC->CFGR|=2<<14;
ADC1->CR1&=0XF0FFFF; //工作模式清零
ADC1->CR1|=0<<16; //獨(dú)立工作模式
ADC1->CR1&=~(1<<8); //非掃描模式
ADC1->CR2&=~(1<<1); //單次轉(zhuǎn)換模式
ADC1->CR2&=~(7<<17);
ADC1->CR2|=7<<17; //軟件控制轉(zhuǎn)換
ADC1->CR2|=1<<20; //使用用外部觸發(fā)(SWSTART)!!! 必須使用一個事件來觸發(fā)
ADC1->CR2&=~(1<<11); //右對齊
ADC1->CR2|=1<<23; //使能溫度傳感器
ADC1->SQR1&=~(0XF<<20);
ADC1->SQR1|=0<<20; //1個轉(zhuǎn)換在規(guī)則序列中 也就是只轉(zhuǎn)換規(guī)則序列1
//設(shè)置通道1的采樣時間
ADC1->SMPR2&=~(3*1); //通道1采樣時間清空
ADC1->SMPR2|=7<<(3*1); //通道1 239.5周期,提高采樣時間可以提高精確度
ADC1->SMPR1&=~(7<<3*6);//清除通道16原來的設(shè)置
ADC1->SMPR1|=7<<(3*6); //通道16 239.5周期,提高采樣時間可以提高精確度
ADC1->CR2|=1<<0; //開啟AD轉(zhuǎn)換器
ADC1->CR2|=1<<3; //使能復(fù)位校準(zhǔn)
while(ADC1->CR2&1<<3); //等待校準(zhǔn)結(jié)束
//該位由軟件設(shè)置并由硬件清除。在校準(zhǔn)寄存器被初始化后該位將被清除。
ADC1->CR2|=1<<2; //開啟AD校準(zhǔn)
while(ADC1->CR2&1<<2); //等待校準(zhǔn)結(jié)束
//該位由軟件設(shè)置以開始校準(zhǔn),并在校準(zhǔn)結(jié)束時由硬件清除
}
//獲得ADC1某個通道的值
//ch:通道值 0~16
//返回值:轉(zhuǎn)換結(jié)果
u16 Get_Adc(u8 ch)
{
//設(shè)置轉(zhuǎn)換序列
ADC1->SQR3&=0XFFFFFFE0;//規(guī)則序列1 通道ch
ADC1->SQR3|=ch;
ADC1->CR2|=1<<22; //啟動規(guī)則轉(zhuǎn)換通道
while(!(ADC1->SR&1<<1));//等待轉(zhuǎn)換結(jié)束
return ADC1->DR; //返回adc值
}
//獲取通道ch的轉(zhuǎn)換值,取times次,然后平均
//ch:通道編號
//times:獲取次數(shù)
//返回值:通道ch的times次轉(zhuǎn)換結(jié)果平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
//得到溫度值
//返回值:溫度值(擴(kuò)大了100倍,單位:℃.)
short Get_Temprate(void)
{
u32 adcx;
short result;
double temperate;
adcx=Get_Adc_Average(ADC_CH_TEMP,20); //讀取通道16,20次取平均
temperate=(float)adcx*(3.3/4096); //電壓值
temperate=(1.43-temperate)/0.0043+25; //轉(zhuǎn)換為溫度值
result=temperate*=100; //擴(kuò)大100倍.
return result;
}
//初始化ADC3
//這里我們僅以規(guī)則通道為例
//我們默認(rèn)僅開啟通道6
void Adc3_Init(void)
{
RCC->APB2ENR|=1<<15; //ADC3時鐘使能
RCC->APB2RSTR|=1<<15; //ADC復(fù)位
RCC->APB2RSTR&=~(1<<15);//復(fù)位結(jié)束
RCC->CFGR&=~(3<<14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設(shè)置為12M,ADC最大時鐘不能超過14M!
//否則將導(dǎo)致ADC準(zhǔn)確度下降!
RCC->CFGR|=2<<14;
ADC3->CR1&=0XF0FFFF; //工作模式清零
ADC3->CR1|=0<<16; //獨(dú)立工作模式
ADC3->CR1&=~(1<<8); //非掃描模式
ADC3->CR2&=~(1<<1); //單次轉(zhuǎn)換模式
ADC3->CR2&=~(7<<17);
ADC3->CR2|=7<<17; //軟件控制轉(zhuǎn)換
ADC3->CR2|=1<<20; //使用用外部觸發(fā)(SWSTART)!!! 必須使用一個事件來觸發(fā)
ADC3->CR2&=~(1<<11); //右對齊
ADC3->SQR1&=~(0XF<<20);
ADC3->SQR1|=0<<20; //1個轉(zhuǎn)換在規(guī)則序列中 也就是只轉(zhuǎn)換規(guī)則序列1
//設(shè)置通道1的采樣時間
ADC3->SMPR2&=~(7<<(3*6));//通道6采樣時間清空
ADC3->SMPR2|=7<<(3*6); //通道6 239.5個周期,提高采樣時間可以提高精確度
ADC3->CR2|=1<<0; //開啟AD轉(zhuǎn)換器
ADC3->CR2|=1<<3; //使能復(fù)位校準(zhǔn)
while(ADC1->CR2&1<<3); //等待校準(zhǔn)結(jié)束
//該位由軟件設(shè)置并由硬件清除。在校準(zhǔn)寄存器被初始化后該位將被清除。
ADC3->CR2|=1<<2; //開啟AD校準(zhǔn)
while(ADC3->CR2&1<<2); //等待校準(zhǔn)結(jié)束
//該位由軟件設(shè)置以開始校準(zhǔn),并在校準(zhǔn)結(jié)束時由硬件清除
}
//獲得ADC3某個通道的值
//ch:通道值 0~16
//返回值:轉(zhuǎn)換結(jié)果
u16 Get_Adc3(u8 ch)
{
//設(shè)置轉(zhuǎn)換序列
ADC3->SQR3&=0XFFFFFFE0;//規(guī)則序列1 通道ch
ADC3->SQR3|=ch;
ADC3->CR2|=1<<22; //啟動規(guī)則轉(zhuǎn)換通道
while(!(ADC3->SR&1<<1));//等待轉(zhuǎn)換結(jié)束
return ADC3->DR; //返回adc值
}
3.5 串口配置代碼
#include "sys.h"
#include "usart3.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"
#include "timer.h"
#include "ucos_ii.h"
#include "malloc.h"
//串口接收緩存區(qū)
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收緩沖,最大USART3_MAX_RECV_LEN個字節(jié).
//通過判斷接收連續(xù)2個字符之間的時間差不大于10ms來決定是不是一次連續(xù)的數(shù)據(jù).
//如果2個字符接收間隔超過10ms,則認(rèn)為不是1次連續(xù)數(shù)據(jù).也就是超過10ms沒有接收到
//任何數(shù)據(jù),則表示此次接收完畢.
//接收到的數(shù)據(jù)狀態(tài)
//[15]:0,沒有接收到數(shù)據(jù);1,接收到了一批數(shù)據(jù).
//[14:0]:接收到的數(shù)據(jù)長度
vu16 USART3_RX_STA=0;
void USART3_IRQHandler(void)
{
u8 res;
OSIntEnter();
if(USART3->SR&(1<<5))//接收到數(shù)據(jù)
{
res=USART3->DR;
if((USART3_RX_STA&(1<<15))==0)//接收完的一批數(shù)據(jù),還沒有被處理,則不再接收其他數(shù)據(jù)
{
if(USART3_RX_STA<USART3_MAX_RECV_LEN) //還可以接收數(shù)據(jù)
{
TIM7->CNT=0; //計數(shù)器清空
if(USART3_RX_STA==0) //使能定時器7的中斷
{
TIM7->CR1|=1<<0; //使能定時器7
}
USART3_RX_BUF[USART3_RX_STA++]=res; //記錄接收到的值
}else
{
USART3_RX_STA|=1<<15; //強(qiáng)制標(biāo)記接收完成
}
}
}
OSIntExit();
}
//初始化IO 串口3
//pclk1:PCLK1時鐘頻率(Mhz)
//bound:波特率
void usart3_init(u32 pclk1,u32 bound)
{
RCC->APB2ENR|=1<<3; //使能PORTB口時鐘
GPIOB->CRH&=0XFFFF00FF; //IO狀態(tài)設(shè)置
GPIOB->CRH|=0X00008B00; //IO狀態(tài)設(shè)置
RCC->APB1ENR|=1<<18; //使能串口時鐘
RCC->APB1RSTR|=1<<18; //復(fù)位串口3
RCC->APB1RSTR&=~(1<<18);//停止復(fù)位
//波特率設(shè)置
USART3->BRR=(pclk1*1000000)/(bound);// 波特率設(shè)置
USART3->CR1|=0X200C; //1位停止,無校驗(yàn)位.
//使能接收中斷
USART3->CR1|=1<<5; //接收緩沖區(qū)非空中斷使能
MY_NVIC_Init(0,1,USART3_IRQn,2);//組2
TIM7_Int_Init(99,7199); //10ms中斷
TIM7->CR1&=~(1<<0); //關(guān)閉定時器7
USART3_RX_STA=0; //清零
}
//串口3,printf 函數(shù)
//確保一次發(fā)送數(shù)據(jù)不超過USART3_MAX_SEND_LEN字節(jié)
void u3_printf(char* fmt,...)
{
u16 i,j;
u8 *pbuf;
va_list ap;
pbuf=mymalloc(SRAMIN,USART3_MAX_SEND_LEN); //申請內(nèi)存
if(!pbuf) //內(nèi)存申請失敗
{
printf("u3 malloc errorrn");
return ;
}
va_start(ap,fmt);
vsprintf((char*)pbuf,fmt,ap);
va_end(ap);
i=strlen((const char*)pbuf); //此次發(fā)送數(shù)據(jù)的長度
for(j=0;j<i;j++) //循環(huán)發(fā)送數(shù)據(jù)
{
while((USART3->SR&0X40)==0); //循環(huán)發(fā)送,直到發(fā)送完畢
USART3->DR=pbuf[j];
}
myfree(SRAMIN,pbuf); //釋放內(nèi)存
}
四、上位機(jī)開發(fā)
為了方便查看設(shè)備上傳的數(shù)據(jù),接下來利用Qt開發(fā)一款A(yù)ndroid手機(jī)APP 和 Windows上位機(jī)。
使用華為云平臺提供的API接口獲取設(shè)備上傳的數(shù)據(jù),進(jìn)行可視化顯示,以及遠(yuǎn)程控制設(shè)備。
4.1 Qt開發(fā)環(huán)境安裝
Qt的中文官網(wǎng): https://www.qt.io/zh-cn/
QT5.12.6的下載地址:https://download.qt.io/archive/qt/5.12/5.12.6
打開下載鏈接后選擇下面的版本進(jìn)行下載:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
軟件安裝時斷網(wǎng)安裝,否則會提示輸入賬戶。
安裝的時候,第一個復(fù)選框里勾選一個mingw 32
編譯器即可,其他的不管默認(rèn)就行,直接點(diǎn)擊下一步繼續(xù)安裝。
選擇MinGW 32-bit 編譯器: (一定要看清楚了)
說明: 我這里只是介紹PC端,也就是Windows系統(tǒng)下的Qt環(huán)境搭建。 Android的開發(fā)環(huán)境比較麻煩,如果想學(xué)習(xí)Android開發(fā),想編譯Android程序的APP,需要自己去搭建Android環(huán)境。
也可以看下面這篇文章,不過這個文章是在Qt開發(fā)專欄里付費(fèi)的,需要訂閱專欄才可以看。 如果不想付費(fèi)看,也可以自行找其他教程,自己搭建好必須的環(huán)境就行了
Android環(huán)境搭建的博客鏈接: https://blog.csdn.net/xiaolong1126626497/article/details/117254453
4.2 新建上位機(jī)工程
前面2講解了需要用的API接口,接下來就使用Qt設(shè)計上位機(jī),設(shè)計界面,完成整體上位機(jī)的邏輯設(shè)計。
【1】新建工程
【2】設(shè)置項目的名稱。
【3】選擇編譯系統(tǒng)
【4】選擇默認(rèn)繼承的類
【5】選擇編譯器
【6】點(diǎn)擊完成
【7】工程創(chuàng)建完成
4.3 設(shè)計UI界面與工程配置
【1】打開UI文件
打開默認(rèn)的界面如下:
【2】開始設(shè)計界面
根據(jù)自己需求設(shè)計界面。
4.5 編譯Windows上位機(jī)
點(diǎn)擊軟件左下角的綠色三角形按鈕進(jìn)行編譯運(yùn)行。
編譯之后的效果:
4.6 配置Android環(huán)境
如果想編譯Android手機(jī)APP,必須要先自己配置好自己的Android環(huán)境。(搭建環(huán)境的過程可以自行百度搜索學(xué)習(xí))
然后才可以進(jìn)行下面的步驟。
【1】選擇Android編譯器
【2】創(chuàng)建Android配置文件
創(chuàng)建完成。
【3】配置Android圖標(biāo)與名稱
【3】編譯Android上位機(jī)
Qt本身是跨平臺的,直接選擇Android的編譯器,就可以將程序編譯到Android平臺。
然后點(diǎn)擊構(gòu)建。
成功之后,在目錄下可以看到生成的apk
文件,也就是Android手機(jī)的安裝包,電腦端使用QQ
發(fā)送給手機(jī)QQ,手機(jī)登錄QQ接收,就能直接安裝。
生成的apk
的目錄在哪里呢? 編譯完成之后,在控制臺會輸出APK文件的路徑。
知道目錄在哪里之后,在Windows的文件資源管理器里,找到路徑,具體看下圖,找到生成的apk文件。
D:/linux-share-dir/QT/build-app_Huawei_Eco_tracking-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk
五、總結(jié)
本項目設(shè)計的智能冰箱系統(tǒng),集成了物聯(lián)網(wǎng)與智能控制技術(shù),為用戶提供一個高效、健康、互動性強(qiáng)的智能家居體驗(yàn)。
總結(jié)其核心功能如下:
(1)智能環(huán)境監(jiān)測與調(diào)節(jié):系統(tǒng)通過部署在不同區(qū)域的溫濕度傳感器與有害氣體分析裝置,實(shí)時監(jiān)測冷藏、保鮮、冷凍區(qū)的環(huán)境狀態(tài),確保食物處于最適宜的保存條件下。當(dāng)檢測到異常狀況,如溫度偏高,系統(tǒng)能自動觸發(fā)制冷機(jī)制,并通過手機(jī)APP推送紅色預(yù)警,同時冰箱內(nèi)置的蜂鳴器也會報警,實(shí)現(xiàn)多維度提醒。
(2)遠(yuǎn)程控制與數(shù)據(jù)可視化:借助華為云IoT平臺,用戶可以通過定制的Qt(C++)手機(jī)APP遠(yuǎn)程監(jiān)控冰箱狀態(tài),隨時隨地調(diào)整各區(qū)域溫度設(shè)置,查看歷史數(shù)據(jù)記錄。冰箱門上的OLED顯示屏則提供了直觀的現(xiàn)場數(shù)據(jù)顯示,增強(qiáng)了用戶體驗(yàn)。
(3)紫外線消毒與健康管理:特別設(shè)計的紫外線消毒燈功能,允許用戶根據(jù)需要啟動消毒程序,有效消滅冰箱內(nèi)部的細(xì)菌和霉菌,為家庭飲食健康筑起一道防線。
(4)高效能與低能耗:采用高效的STM32系列微控制器和穩(wěn)壓電源模塊,確保系統(tǒng)運(yùn)行穩(wěn)定且節(jié)能。通過精準(zhǔn)的溫控策略,避免了不必要的能源消耗。
(5)用戶友好與個性化設(shè)置:手機(jī)APP界面簡潔友好,支持用戶根據(jù)生活習(xí)慣自定義冰箱設(shè)置,如溫度偏好、消毒頻率等,滿足不同家庭成員的需求。