0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于STM32單片機設(shè)計的礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)

DS小龍哥-嵌入式技術(shù) ? 來源:DS小龍哥-嵌入式技術(shù) ? 作者:DS小龍哥-嵌入式技 ? 2024-10-15 11:06 ? 次閱讀

一、前言

1.1 項目介紹

項目設(shè)計里用到的全部工具軟件和文檔源碼,都可以在這里下載。

https://pan.quark.cn/s/145a9b3f7f53

【1】項目開發(fā)背景

礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)的開發(fā)背景主要源于對礦井作業(yè)環(huán)境中潛在危險因素的有效監(jiān)控需求。礦山作為重要的資源開采場所,其工作環(huán)境往往存在諸多安全隱患,如瓦斯爆炸、粉塵超標(biāo)等,這些因素不僅威脅著礦工的生命安全,還可能導(dǎo)致嚴重的經(jīng)濟損失和社會影響。因此,建立一個能夠?qū)崟r監(jiān)測礦井內(nèi)環(huán)境狀況,并能在危險發(fā)生前及時預(yù)警的安全監(jiān)測系統(tǒng)顯得尤為重要。

隨著物聯(lián)網(wǎng)技術(shù)的發(fā)展,利用先進的傳感器技術(shù)與無線通信技術(shù)相結(jié)合,可以實現(xiàn)對礦山環(huán)境的全方位監(jiān)控。本項目選擇以STM32F103RCT6單片機作為核心控制器,因其具備高性能、低功耗的特點,非常適合用于此類環(huán)境下的數(shù)據(jù)采集與控制任務(wù)。通過集成DHT11溫濕度傳感器、MQ5氣體傳感器、PM2.5傳感器等,系統(tǒng)能夠?qū)崟r獲取環(huán)境數(shù)據(jù),并依據(jù)預(yù)設(shè)的閾值進行判斷,從而采取相應(yīng)的措施,比如啟動通風(fēng)裝置降低瓦斯?jié)舛然蛲ㄟ^噴淋系統(tǒng)減少空氣中的顆粒物含量。

此外,為了使礦山管理人員能夠遠程監(jiān)控礦井內(nèi)的實際情況,本項目還將通過BC26(NBIOT)模塊將收集到的數(shù)據(jù)上傳至華為云物聯(lián)網(wǎng)平臺,實現(xiàn)了數(shù)據(jù)的云端存儲與分析。同時,借助移動應(yīng)用技術(shù),開發(fā)了一款A(yù)PP,以便于工作人員隨時查看環(huán)境參數(shù)及接收警報信息,進一步增強了系統(tǒng)的實用性和靈活性。

本項目的開發(fā)提供一套高效、可靠的礦山環(huán)境作業(yè)安全監(jiān)測解決方案,通過技術(shù)手段提升礦山安全管理效率,保障礦工的人身安全,促進礦山行業(yè)的可持續(xù)發(fā)展。

image-20240906151540190

【2】設(shè)計實現(xiàn)的功能

(1) 本項目設(shè)計的核心是以STM32F103RCT6單片機作為主控單元,負責(zé)整個系統(tǒng)的協(xié)調(diào)控制,實現(xiàn)對礦山環(huán)境各項關(guān)鍵參數(shù)的監(jiān)測與管理。

(2) 采用DHT11溫濕度傳感器進行環(huán)境溫度和濕度的實時采集,一旦檢測到的數(shù)值超出安全范圍,則通過蜂鳴器發(fā)出警報信號

(3) 使用MQ5氣體傳感器監(jiān)測瓦斯?jié)舛?,?dāng)濃度達到預(yù)設(shè)閾值時,系統(tǒng)將通過控制繼電器啟動風(fēng)扇,以稀釋瓦斯?jié)舛取?/strong>

(4) 配備PM2.5傳感器用以檢測空氣中顆粒物的濃度,當(dāng)濃度超標(biāo)時,激活霧化噴淋系統(tǒng)以降低灰塵含量。

(5) 選用OLED顯示屏作為人機交互界面,實時顯示由各傳感器采集到的環(huán)境數(shù)據(jù)。

(6) 利用BC26(NBIOT)模塊將現(xiàn)場采集到的數(shù)據(jù)上傳至華為云物聯(lián)網(wǎng)平臺,便于遠程監(jiān)控和數(shù)據(jù)分析。

(7) 實現(xiàn)自動模式功能,系統(tǒng)能夠按照預(yù)先設(shè)定的閾值自動監(jiān)測環(huán)境參數(shù),并在必要時觸發(fā)警報或執(zhí)行相應(yīng)控制動作,如啟動風(fēng)扇或噴淋系統(tǒng)。

(8) 提供手動模式功能,允許用戶通過按鍵直接控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)狀態(tài),并且開發(fā)了基于Qt框架的Android平臺手機APP,以便于遠程控制這些設(shè)備的運行狀態(tài)。

(9) 設(shè)計中考慮了系統(tǒng)的穩(wěn)定供電方案,采用5V 2A的外部穩(wěn)壓電源為系統(tǒng)供電。

(10) 風(fēng)扇和霧化降塵設(shè)備均采用5V電源供電,并通過繼電器模塊實現(xiàn)開關(guān)控制。

(11) OLED顯示屏采用SPI協(xié)議進行數(shù)據(jù)傳輸,以確保信息顯示的準確性和實時性。

【3】項目硬件模塊組成

(1) 控制核心模塊:STM32F103RCT6單片機最小系統(tǒng)模塊,作為整個監(jiān)測系統(tǒng)的控制中心

(2) 溫濕度采集模塊:DHT11溫濕度傳感器,用于實時檢測環(huán)境的溫度和濕度。

(3) 氣體檢測模塊:MQ5氣體傳感器,用于監(jiān)測環(huán)境中的瓦斯?jié)舛取?/strong>

(4) 顆粒物檢測模塊:PM2.5傳感器,用于檢測空氣中懸浮顆粒物的濃度。

(5) 顯示模塊:0.96寸OLED顯示屏,采用SPI協(xié)議連接至控制核心,顯示各項環(huán)境參數(shù)。

(6) 報警模塊:蜂鳴器,當(dāng)檢測到環(huán)境參數(shù)異常時發(fā)出聲音警報。

(7) 執(zhí)行機構(gòu)控制模塊:繼電器模塊,用于控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)狀態(tài)。

(8) 通風(fēng)設(shè)備:風(fēng)扇,由繼電器控制,用于降低瓦斯?jié)舛取?/strong>

(9) 降塵設(shè)備:霧化噴淋系統(tǒng),同樣由繼電器控制,用于減少空氣中顆粒物含量。

(10) 遠程通信模塊:BC26(NBIOT)模塊,負責(zé)將采集到的數(shù)據(jù)通過窄帶物聯(lián)網(wǎng)技術(shù)上傳至云端。

(11) 電源供應(yīng)模塊:5V 2A外部穩(wěn)壓電源,為整個系統(tǒng)提供穩(wěn)定的電力支持。

(12) 操作接口:按鍵模塊,允許用戶手動控制設(shè)備的開啟與關(guān)閉。

(13) 移動終端交互模塊:基于Qt開發(fā)的Android平臺手機APP,實現(xiàn)遠程監(jiān)控和控制功能。

【4】需求總結(jié)

項目名稱:基于STM32單片機設(shè)計的礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)
1、本次設(shè)計以 STM32F103RCT6 單片機最小系統(tǒng)模塊作為系統(tǒng)控制核心,確定各種傳感器模塊選型,完成系統(tǒng)硬件結(jié)構(gòu)設(shè)計。
2、采用 DHT11 溫濕度采集模塊進行環(huán)境溫濕度檢測。當(dāng)傳感器檢測到環(huán)境值超過控制系統(tǒng)設(shè)定的閥值參數(shù)時,可觸發(fā)蜂鳴器報警。
3、采用 MQ5氣體傳感器來檢測環(huán)境的瓦斯?jié)舛?。?dāng)瓦斯?jié)舛冗_到閾值時,控制系統(tǒng)控制繼電器開關(guān)模塊動作,實現(xiàn)風(fēng)扇自動控制功能。
4、采用 PM2.5 傳感器檢測環(huán)境中的顆粒物,超過閾值觸發(fā)報警,可打開霧化模塊進行噴淋降低。
5、采用 OLED 顯示屏作為顯示模塊顯示實時數(shù)據(jù)。
6、將采集到的環(huán)境信息通過BC26(NBIOT)模塊將數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)平臺。
7、自動模式功能
根據(jù)預(yù)設(shè)的閾值設(shè)定,監(jiān)測環(huán)境參數(shù),并在超過閾值時觸發(fā)警報和控制設(shè)備使用定時器進行周期性的環(huán)境參數(shù)監(jiān)測。
設(shè)計邏輯判斷程序,根據(jù)環(huán)境參數(shù)觸發(fā)不同的處理動作。
8、手動模式功能
實現(xiàn)按鍵功能,根據(jù)用戶的操作控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)狀態(tài)。
開發(fā)手機 APP,使用Qt作為 Android 平臺的開發(fā)工具
實現(xiàn)與BC26(NBIOT)模塊的通信和數(shù)據(jù)顯示功能。
在手機 APP 上實現(xiàn)遠程控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)功能以及顯示實時監(jiān)測到的環(huán)境參數(shù)和警報信息
9、供電采用 5V 2A外部穩(wěn)壓電源
10、風(fēng)扇和霧化降塵設(shè)備采用5V加濕器模塊,通過繼電器控制開關(guān)。
11OLED顯示屏采用SPI協(xié)議的0.96OLED顯示屏

1.2 設(shè)計思路

設(shè)計思路的核心是圍繞提高礦山作業(yè)環(huán)境的安全性展開,考慮到礦山環(huán)境復(fù)雜多變的特點,本項目構(gòu)建一個能夠?qū)崟r監(jiān)測并有效應(yīng)對潛在危險因素的自動化系統(tǒng)。該系統(tǒng)的設(shè)計從硬件選型到軟件架構(gòu)都遵循了模塊化和易維護的原則,確保了系統(tǒng)的可靠性和擴展性。

在硬件層面,選擇了性能穩(wěn)定且易于編程的STM32F103RCT6單片機作為中央處理器,這是因為STM32系列芯片擁有豐富的外設(shè)接口,能夠方便地與各種傳感器和執(zhí)行機構(gòu)進行通信。同時,考慮到礦山環(huán)境的特殊性,傳感器的選擇上優(yōu)先考慮了可靠性與準確性,如DHT11用于溫濕度監(jiān)測,MQ5用于瓦斯?jié)舛葯z測,PM2.5傳感器則用于顆粒物濃度測量。此外,為了實現(xiàn)環(huán)境參數(shù)的直觀展示,選用了OLED顯示屏作為人機交互界面,并通過繼電器模塊來控制風(fēng)扇和霧化噴淋系統(tǒng),以應(yīng)對不同的緊急情況。

軟件方面,系統(tǒng)的設(shè)計著重于邏輯清晰的程序架構(gòu),通過編寫高效的算法來處理來自不同傳感器的數(shù)據(jù),并依據(jù)預(yù)設(shè)的安全閾值進行邏輯判斷。當(dāng)環(huán)境參數(shù)超出正常范圍時,系統(tǒng)會自動觸發(fā)相應(yīng)的警報機制,并啟動相應(yīng)的應(yīng)急措施,例如啟動通風(fēng)設(shè)備降低瓦斯?jié)舛然騿⒂脟娏芟到y(tǒng)減少粉塵。此外,為了便于遠程監(jiān)控,系統(tǒng)集成了BC26(NBIOT)模塊,能夠?qū)⒉杉降臄?shù)據(jù)上傳至華為云物聯(lián)網(wǎng)平臺,同時開發(fā)了配套的手機應(yīng)用程序,使得管理者能夠隨時隨地查看環(huán)境狀況,并進行遠程控制。

整體而言,該項目的設(shè)計思路充分結(jié)合了現(xiàn)代物聯(lián)網(wǎng)技術(shù)和傳統(tǒng)礦山安全管理的需求,力求通過智能化手段提升礦山作業(yè)的安全水平,減少事故發(fā)生的可能性,保障礦山工作的順利進行。

1.3 系統(tǒng)功能總結(jié)

功能類別描述
環(huán)境監(jiān)測實時采集礦山環(huán)境的溫度、濕度、瓦斯?jié)舛取㈩w粒物濃度等數(shù)據(jù)。
自動警報當(dāng)環(huán)境參數(shù)超過預(yù)設(shè)安全閾值時,自動觸發(fā)蜂鳴器警報。
自動控制達到特定閾值時,自動控制風(fēng)扇和霧化噴淋系統(tǒng),以降低瓦斯?jié)舛群皖w粒物含量。
數(shù)據(jù)顯示OLED顯示屏實時顯示采集到的各種環(huán)境參數(shù)。
數(shù)據(jù)上傳通過BC26(NBIOT)模塊將環(huán)境數(shù)據(jù)上傳至華為云物聯(lián)網(wǎng)平臺,便于遠程監(jiān)控和數(shù)據(jù)分析。
定時監(jiān)測使用定時器進行周期性的環(huán)境參數(shù)監(jiān)測,確保數(shù)據(jù)的連續(xù)性和及時性。
手動控制用戶可以通過按鍵手動控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)狀態(tài)。
遠程控制開發(fā)了基于Qt框架的Android平臺手機APP,實現(xiàn)遠程控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)功能。
數(shù)據(jù)可視化在手機APP上顯示實時監(jiān)測到的環(huán)境參數(shù)和警報信息。
穩(wěn)定供電采用5V 2A外部穩(wěn)壓電源為系統(tǒng)提供穩(wěn)定的電力支持。
設(shè)備控制風(fēng)扇和霧化降塵設(shè)備采用5V電源供電,并通過繼電器模塊實現(xiàn)開關(guān)控制。
人機交互OLED顯示屏采用SPI協(xié)議,保證信息顯示的準確性和實時性。

1.4 開發(fā)工具的選擇

【1】設(shè)備端開發(fā)

STM32的編程語言選擇C語言,C語言執(zhí)行效率高,大學(xué)里主學(xué)的C語言,C語言編譯出來的可執(zhí)行文件最接近于機器碼,匯編語言執(zhí)行效率最高,但是匯編的移植性比較差,目前在一些操作系統(tǒng)內(nèi)核里還有一些低配的單片機使用的較多,平常的單片機編程還是以C語言為主。C語言的執(zhí)行效率僅次于匯編,語法理解簡單、代碼通用性強,也支持跨平臺,在嵌入式底層、單片機編程里用的非常多,當(dāng)前的設(shè)計就是采用C語言開發(fā)。

開發(fā)工具選擇Keil,keil是一家世界領(lǐng)先的嵌入式微控制器軟件開發(fā)商,在2015年,keil被ARM公司收購。因為當(dāng)前芯片選擇的是STM32F103系列,STMF103是屬于ARM公司的芯片構(gòu)架、Cortex-M3內(nèi)核系列的芯片,所以使用Kile來開發(fā)STM32是有先天優(yōu)勢的,而keil在各大高校使用的也非常多,很多教科書里都是以keil來教學(xué),開發(fā)51單片機、STM32單片機等等。目前作為MCU芯片開發(fā)的軟件也不只是keil一家獨大,IAR在MCU微處理器開發(fā)領(lǐng)域里也使用的非常多,IAR擴展性更強,也支持STM32開發(fā),也支持其他芯片,比如:CC2530,51單片機的開發(fā)。從軟件的使用上來講,IAR比keil更加簡潔,功能相對少一些。如果之前使用過keil,而且使用頻率較多,已經(jīng)習(xí)慣再使用IAR是有點不適應(yīng)界面的。

image-20221210225339928

【2】上位機開發(fā)

上位機的開發(fā)選擇Qt框架,編程語言采用C++;Qt是一個1991年由Qt Company開發(fā)的跨平臺C++圖形用戶界面應(yīng)用程序開發(fā)框架。它既可以開發(fā)GUI程序,也可用于開發(fā)非GUI程序,比如控制臺工具和服務(wù)器。Qt是面向?qū)ο蟮目蚣?,使用特殊的代碼生成擴展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,并且允許真正地組件編程。Qt能輕松創(chuàng)建具有原生C++性能的連接設(shè)備、用戶界面(UI)和應(yīng)用程序。它功能強大且結(jié)構(gòu)緊湊,擁有直觀的工具和庫。

image-20230218001243591

image-20230218001219105

1.5 模塊的技術(shù)詳情介紹

【1】BC26-NBIOT模塊

BC26-NBIOT模塊是一款專為窄帶物聯(lián)網(wǎng)(Narrow Band Internet of Things, NB-IoT)設(shè)計的無線通信模塊,適用于低功耗廣域網(wǎng)絡(luò)(LPWAN)的應(yīng)用場景。該模塊主要針對物聯(lián)網(wǎng)市場的需求而開發(fā),尤其適用于那些需要長距離通信、低功耗、低成本和高容量的應(yīng)用場合,如智能城市、環(huán)境監(jiān)測、智能家居等領(lǐng)域。

BC26-NBIOT模塊具備良好的網(wǎng)絡(luò)覆蓋能力,能夠在較遠的距離內(nèi)保持穩(wěn)定的通信連接,這對于礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)來說至關(guān)重要。由于礦山內(nèi)部結(jié)構(gòu)復(fù)雜,通信條件苛刻,傳統(tǒng)的無線通信技術(shù)可能難以滿足要求,而NB-IoT技術(shù)憑借其優(yōu)秀的穿透能力和低功耗特性,可以在這種環(huán)境下實現(xiàn)可靠的通信。

BC26-NBIOT模塊支持全球主流運營商的NB-IoT頻段,這意味著它可以無縫接入不同的網(wǎng)絡(luò)環(huán)境,為用戶提供靈活的部署選項。這使得礦山監(jiān)測系統(tǒng)不僅可以在國內(nèi)使用,也可以在全球范圍內(nèi)實施,增強了系統(tǒng)的通用性和適用性。

在能耗方面,BC26-NBIOT模塊設(shè)計有低功耗模式,可以在不活躍期間大幅降低功耗,這對于延長電池壽命或減少系統(tǒng)整體功耗非常重要。尤其是在礦山這樣的環(huán)境中,由于電源可能不是隨時可得,低功耗特性就顯得尤為關(guān)鍵。

模塊還提供了豐富的接口,包括UART、GPIO、PWM等,便于與其他傳感器或執(zhí)行器進行連接和數(shù)據(jù)交換。這使得開發(fā)者可以根據(jù)具體的應(yīng)用需求,靈活地構(gòu)建起復(fù)雜的物聯(lián)網(wǎng)系統(tǒng)。同時,BC26-NBIOT模塊通常支持AT命令集,這簡化了開發(fā)過程,使得開發(fā)人員能夠更快地上手進行開發(fā)工作。

BC26-NBIOT模塊以其卓越的通信性能、廣泛的兼容性和低功耗特性,成為礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)中的理想選擇,能夠有效地支持數(shù)據(jù)的遠程傳輸和系統(tǒng)的遠程管理,提高了礦山作業(yè)的安全性和管理效率。

【2】DHT11溫濕度模塊

DHT11溫濕度模塊是一種經(jīng)濟實惠且廣泛使用的數(shù)字溫濕度傳感器,它集成了溫度和濕度感應(yīng)元件以及一個信號轉(zhuǎn)換電路。這款模塊因其簡單易用、成本低廉而被眾多DIY愛好者和專業(yè)開發(fā)者所青睞,在智能家居、氣象站、農(nóng)業(yè)自動化等多種應(yīng)用場景中都有廣泛的應(yīng)用。

DHT11模塊的核心是由一個NTC熱敏電阻和一個濕度敏感電容組成的復(fù)合傳感器。NTC熱敏電阻用于檢測環(huán)境溫度的變化,而濕度敏感電容則用于檢測空氣中的水分含量。這些原始數(shù)據(jù)經(jīng)過內(nèi)部電路的處理后,通過單線串行接口輸出給外部微控制器。這種集成化的處理方式大大簡化了傳感器的使用,使得開發(fā)人員無需關(guān)心內(nèi)部的具體實現(xiàn)細節(jié)。

在硬件接口方面,DHT11模塊通常配備四個引腳,分別是電源正極(VCC)、電源地(GND)、信號輸出(DATA)和預(yù)留的空引腳。其中,VCC引腳提供工作電壓,通常為3.3V到5V之間;GND引腳接地;DATA引腳則是用于與外部微控制器進行數(shù)據(jù)通信的串行接口。為了保證數(shù)據(jù)傳輸?shù)姆€(wěn)定性,通常會在DATA引腳與GND之間接一個上拉電阻。

在軟件層面上,DHT11模塊的操作相對簡單,它遵循一種特定的通信協(xié)議。當(dāng)微控制器想要讀取溫濕度數(shù)據(jù)時,需要向DHT11發(fā)送一個啟動信號,然后等待DHT11回應(yīng)一個確認信號。之后,DHT11會依次發(fā)送濕度整數(shù)部分、濕度小數(shù)部分、溫度整數(shù)部分、溫度小數(shù)部分以及一個校驗位。開發(fā)人員只需要編寫簡單的函數(shù)來發(fā)送啟動信號,并接收和解析返回的數(shù)據(jù)即可。

DHT11模塊具有價格優(yōu)勢和易于使用的特性,它的精度不高,濕度測量范圍為20%RH至90%RH,精度±5%RH;溫度測量范圍為0℃至50℃,精度±2℃。

【3】PM2.5粉塵模塊

PM2.5粉塵模塊是一種專門用于檢測空氣中細顆粒物(Particulate Matter 2.5,簡稱PM2.5)濃度的傳感器。PM2.5是指直徑小于或等于2.5微米的顆粒物,這類顆粒物因為體積小、面積大、活性強,容易攜帶污染物,對人體健康尤其是呼吸系統(tǒng)有著較大的危害。因此,監(jiān)測PM2.5濃度對于環(huán)境保護和個人健康具有重要意義。

PM2.5粉塵模塊通?;诠馍⑸湓砉ぷ?。當(dāng)空氣中的顆粒物通過傳感器時,內(nèi)置的光源(通常是紅外LED)會照射這些顆粒物,導(dǎo)致光的散射。傳感器內(nèi)部裝有一個光電二極管,用來接收散射光,并將其轉(zhuǎn)換成電信號。通過分析這些電信號的強度,就可以估算出空氣中PM2.5顆粒物的濃度。這種檢測方法簡單、快速,適用于各種便攜式或固定式的空氣質(zhì)量監(jiān)測設(shè)備。

市場上常見的PM2.5粉塵模塊如PMS5003、SDS011等,它們通常具備較小的尺寸和較低的功耗,適合集成到各種物聯(lián)網(wǎng)設(shè)備中。這些模塊一般都提供標(biāo)準的串行通信接口(如TTL UART),可以直接與微控制器(如STM32系列)相連,進行數(shù)據(jù)的讀取和處理。此外,一些高級模塊還支持I2C或SPI接口,提供更多的配置選項和更高的數(shù)據(jù)傳輸速率。

在硬件設(shè)計上,PM2.5粉塵模塊內(nèi)部集成了氣流通道、光源、光接收器以及信號處理電路。為了保證測量結(jié)果的準確性,模塊內(nèi)部通常設(shè)有風(fēng)機來確??諝饽軌蚓鶆蛄鲃硬⑼ㄟ^傳感器區(qū)域。此外,為了防止外界干擾,傳感器通常會配備有防塵網(wǎng)或過濾器,以保護內(nèi)部元件不受污染。

從軟件角度來看,使用PM2.5粉塵模塊相對簡單。開發(fā)人員只需要按照模塊提供的數(shù)據(jù)手冊編寫相應(yīng)的驅(qū)動程序,就能實現(xiàn)對模塊的初始化和數(shù)據(jù)讀取。大多數(shù)模塊都會提供一整套的通信協(xié)議,其中包括了如何發(fā)送查詢命令以及如何解析返回的數(shù)據(jù)格式。例如,一些模塊會以ASCII碼形式返回數(shù)據(jù),包含PM2.5、PM10等不同粒徑顆粒物的濃度值,以及其他輔助信息如溫度、濕度等。

值得注意的是,雖然PM2.5粉塵模塊在一定程度上能夠提供準確的顆粒物濃度數(shù)據(jù),但在實際應(yīng)用中,還需要考慮諸如環(huán)境溫度、濕度等因素對測量結(jié)果的影響。此外,為了確保數(shù)據(jù)的長期穩(wěn)定性和準確性,定期對傳感器進行校準也是非常必要的。

PM2.5粉塵模塊作為一種有效的顆粒物濃度監(jiān)測工具,已經(jīng)廣泛應(yīng)用于家庭、辦公室、工廠等各種環(huán)境下的空氣質(zhì)量監(jiān)測系統(tǒng)中,為人們提供了便捷的方式來監(jiān)控和改善生活環(huán)境質(zhì)量。

二、BC26-NBIOT模塊調(diào)試過程

2.1 模塊調(diào)試接線

image-20240511223245187

image-20240511223256788

image-20240511223308721

2.2 測試模塊

第一步接上之后,串口調(diào)試助手選擇波特率為115200,勾選軟件上的發(fā)送新行選項。發(fā)送AT過去,正常模塊會返回OK。

只有收到了OK,才表示模塊工作正常。

image-20240511204301353

2.3 上電初始化操作

1】查詢模塊是否正常
AT

OK


【2】獲取卡號,查詢卡是否插好
AT+CIMI

460041052911195

OK


【3】激活網(wǎng)絡(luò)
AT+CGATT=1

OK


【4】獲取網(wǎng)絡(luò)激活狀態(tài)
AT+CGATT?

+CGATT: 1

OK


【5】查詢網(wǎng)絡(luò)質(zhì)量
AT+CSQ

+CSQ: 26,0

OK

【6】 檢查網(wǎng)絡(luò)狀態(tài)
AT+CEREG=?   //
+CEREG: 0,1 //找網(wǎng)成功
OK

2.4 開啟GPS定位

如果需要使用GPS定位就開,不需要使用就不用管。

使用GPS定位還需要將模塊上的GPS天線接好,否則也是沒有信號的。

官方文檔:

image-20220220191400115

1】激活GPS,要等一段時間
AT+QGNSSC=1

OK


【2】查詢激活狀態(tài),1表示成功激活
AT+QGNSSC?

+QGNSSC: 1

OK


【3】獲取一次GPS定位語句
AT+QGNSSRD="NMEA/RMC"
+QGNSSRD: $GNRMC,120715.00,A,3150.78179,N,11711.93433,E,0.000,,310818,,,A,V*19
OK

三、部署華為云物聯(lián)網(wǎng)平臺

**華為云官網(wǎng): **https://www.huaweicloud.com/

**打開官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 **設(shè)備接入IoTDA。

image-20221204193824815

3.1 物聯(lián)網(wǎng)平臺介紹

華為云物聯(lián)網(wǎng)平臺(IoT 設(shè)備接入云服務(wù))提供海量設(shè)備的接入和管理能力,將物理設(shè)備聯(lián)接到云,支撐設(shè)備數(shù)據(jù)采集上云和云端下發(fā)命令給設(shè)備進行遠程控制,配合華為云其他產(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è)備接口,實現(xiàn)設(shè)備的快速接入;同時提供強大的開放能力,支撐行業(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,實現(xiàn)設(shè)備數(shù)據(jù)采集、命令下發(fā)、設(shè)備管理等業(yè)務(wù)場景。

img

3.2 開通物聯(lián)網(wǎng)服務(wù)

**地址: **https://www.huaweicloud.com/product/iothub.html

image-20221204194233414

點擊立即創(chuàng)建

image-20240117134653452

正在創(chuàng)建標(biāo)準版實例,需要等待片刻。

image-20240117134729401

創(chuàng)建完成之后,點擊實例名稱。 可以看到標(biāo)準版實例的設(shè)備接入端口和地址。

image-20240425180759670

在上面也能看到 免費單元的限制。

image-20240425180817704

開通之后,點擊總覽,也能查看接入信息。 我們當(dāng)前設(shè)備準備采用MQTT協(xié)議接入華為云平臺,這里可以看到MQTT協(xié)議的地址和端口號等信息。

image-20240425180845461

總結(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是證書加密端口,單片機無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺。

3.3 創(chuàng)建產(chǎn)品

(1)創(chuàng)建產(chǎn)品

image-20230109164412041

(2)填寫產(chǎn)品信息

根據(jù)自己產(chǎn)品名字填寫,下面的設(shè)備類型選擇自定義類型。

image-20240612094809689

(3)產(chǎn)品創(chuàng)建成功

image-20240612095148945

創(chuàng)建完成之后點擊查看詳情。

image-20240612095134263

(4)添加自定義模型

產(chǎn)品創(chuàng)建完成之后,點擊進入產(chǎn)品詳情頁面,翻到最下面可以看到模型定義。

模型簡單來說: 就是存放設(shè)備上傳到云平臺的數(shù)據(jù)。

你可以根據(jù)自己的產(chǎn)品進行創(chuàng)建。

比如:

煙霧可以叫  MQ2
溫度可以叫  Temperature
濕度可以叫  humidity
火焰可以叫  flame
其他的傳感器自己用單詞簡寫命名即可。 這就是你的單片機設(shè)備端上傳到服務(wù)器的數(shù)據(jù)名字。

先點擊自定義模型。

image-20240612095517900

再創(chuàng)建一個服務(wù)ID。

image-20240612095542749

接著點擊新增屬性。

image-20240612095648815

image-20240612095711898

3.4 添加設(shè)備

產(chǎn)品是屬于上層的抽象模型,接下來在產(chǎn)品模型下添加實際的設(shè)備。添加的設(shè)備最終需要與真實的設(shè)備關(guān)聯(lián)在一起,完成數(shù)據(jù)交互。

(1)注冊設(shè)備

image-20240425181935561

(2)根據(jù)自己的設(shè)備填寫

image-20240612100115167

(3)保存設(shè)備信息

創(chuàng)建完畢之后,點擊保存并關(guān)閉,得到創(chuàng)建的設(shè)備密匙信息。該信息在后續(xù)生成MQTT三元組的時候需要使用。

image-20240612100128061

(4)設(shè)備創(chuàng)建完成

image-20240612100147232

(5)設(shè)備詳情

image-20240612100202960

image-20240612100217236

3.5 MQTT協(xié)議主題訂閱與發(fā)布

(1)MQTT協(xié)議介紹

當(dāng)前的設(shè)備是采用MQTT協(xié)議與華為云平臺進行通信。

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

img

業(yè)務(wù)流程:

img

(2)華為云平臺MQTT協(xié)議使用限制

描述限制
支持的MQTT協(xié)議版本3.1.1
與標(biāo)準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進行消息發(fā)布與訂閱
每個訂閱請求的最大訂閱數(shù)無限制

(3)主題訂閱格式

幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

image-20221207153310037

對于設(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

image-20221207153637391

根據(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}}]}

3.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

image-20240509193207359

MQTT協(xié)議的端口支持1883和8883,它們的區(qū)別是:8883 是加密端口更加安全。但是單片機上使用比較困難,所以當(dāng)前的設(shè)備是采用1883端口進連接的。

根據(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

image-20240425182610048

(2)生成MQTT三元組

**華為云提供了一個在線工具,用來生成MQTT鑒權(quán)三元組: **https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打開這個工具,填入設(shè)備的信息(也就是剛才創(chuàng)建完設(shè)備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。

下面是打開的頁面:

image-20240425183025893

填入設(shè)備的信息: (上面兩行就是設(shè)備創(chuàng)建完成之后保存得到的)

直接得到三元組信息。

image-20240509193310020

得到三元組之后,設(shè)備端通過MQTT協(xié)議登錄鑒權(quán)的時候,填入?yún)?shù)即可。

ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237

3.7 模擬設(shè)備登錄測試

**經(jīng)過上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設(shè)備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設(shè)備來登錄平臺。測試與服務(wù)器通信是否正常。 **

(1)填入登錄信息

打開MQTT客戶端軟件,對號填入相關(guān)信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發(fā)布主題。

image-20240509193457358

(2)打開網(wǎng)頁查看

完成上面的操作之后,打開華為云網(wǎng)頁后臺,可以看到設(shè)備已經(jīng)在線了。

image-20240612100508790

點擊詳情頁面,可以看到上傳的數(shù)據(jù):

image-20240612100529581

到此,云平臺的部署已經(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}}]}

3.8 創(chuàng)建IAM賬戶

創(chuàng)建一個IAM賬戶,因為接下來開發(fā)上位機,需要使用云平臺的API接口,這些接口都需要token進行鑒權(quán)。簡單來說,就是身份的認證。 調(diào)用接口獲取Token時,就需要填寫IAM賬號信息。所以,接下來演示一下過程。

**地址: **https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users

**【1】獲取項目憑證 ** 點擊左上角用戶名,選擇下拉菜單里的我的憑證

image-20240509193646253

image-20240509193701262

項目憑證:

28add376c01e4a61ac8b621c714bf459

【2】創(chuàng)建IAM用戶

鼠標(biāo)放在左上角頭像上,在下拉菜單里選擇統(tǒng)一身份認證。

image-20240509193729078

點擊左上角創(chuàng)建用戶。

image-20240509193744287

image-20240314153208692

image-20240314153228359

image-20240314153258229

創(chuàng)建成功:

image-20240314153315444

【3】創(chuàng)建完成

image-20240509193828289

用戶信息如下:

主用戶名  l19504562721
IAM用戶  ds_abc
密碼     DS12345678

3.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è)備影子接口。

如果對接口不熟悉,可以先進行在線調(diào)試:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow

在線調(diào)試接口,可以請求影子接口,了解請求,與返回的數(shù)據(jù)格式。

調(diào)試完成看右下角的響應(yīng)體,就是返回的影子數(shù)據(jù)。

image-20240509194152229

設(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ù)的真實鏈接,接下來的代碼開發(fā)中,就采用Qt寫代碼訪問此鏈接,獲取影子數(shù)據(jù),完成上位機開發(fā)。

image-20240509194214716

鏈接如下:

https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow

四、上位機開發(fā)

為了方便查看設(shè)備上傳的數(shù)據(jù),接下來利用Qt開發(fā)一款A(yù)ndroid手機APP 和 Windows上位機。

使用華為云平臺提供的API接口獲取設(shè)備上傳的數(shù)據(jù),進行可視化顯示,以及遠程控制設(shè)備。

4.1 Qt開發(fā)環(huán)境安裝

**Qt的中文官網(wǎng): **https://www.qt.io/zh-cn/image-20221207160550486

image-20221207160606892

QT5.12.6的下載地址:https://download.qt.io/archive/qt/5.12/5.12.6

或者去網(wǎng)盤里下載:https://pan.quark.cn/s/145a9b3f7f53

打開下載鏈接后選擇下面的版本進行下載:

qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details

軟件安裝時斷網(wǎng)安裝,否則會提示輸入賬戶。

安裝的時候,第一個復(fù)選框里勾選一個mingw 32編譯器即可,其他的不管默認就行,直接點擊下一步繼續(xù)安裝。

image-20221203151742653

選擇MinGW 32-bit 編譯器: (一定要看清楚了)

image-20221203151750344

說明: 我這里只是介紹PC端,也就是Windows系統(tǒng)下的Qt環(huán)境搭建。 Android的開發(fā)環(huán)境比較麻煩,如果想學(xué)習(xí)Android開發(fā),想編譯Android程序的APP,需要自己去搭建Android環(huán)境。

也可以看下面這篇文章,不過這個文章是在Qt開發(fā)專欄里付費的,需要訂閱專欄才可以看。 如果不想付費看,也可以自行找其他教程,自己搭建好必須的環(huán)境就行了

Android環(huán)境搭建的博客鏈接:https://blog.csdn.net/xiaolong1126626497/article/details/117254453

4.2 新建上位機工程

前面2講解了需要用的API接口,接下來就使用Qt設(shè)計上位機,設(shè)計界面,完成整體上位機的邏輯設(shè)計。

【1】新建工程

image-20240117144052547

【2】設(shè)置項目的名稱。

image-20240509195711965

【3】選擇編譯系統(tǒng)

image-20240117144239681

【4】選擇默認繼承的類

image-20240117144302275

【5】選擇編譯器

image-20240314162137170

【6】點擊完成

image-20240117144354252

【7】工程創(chuàng)建完成

image-20230421094133333

4.3 設(shè)計UI界面與工程配置

【1】打開UI文件

image-20230421094815236

打開默認的界面如下:

image-20240425194845233

【2】開始設(shè)計界面

根據(jù)自己需求設(shè)計界面。

4.5 編譯Windows上位機

點擊軟件左下角的綠色三角形按鈕進行編譯運行。

image-20240509202031739

編譯之后的效果:

image-20240903163952415

4.6 配置Android環(huán)境

如果想編譯Android手機APP,必須要先自己配置好自己的Android環(huán)境。(搭建環(huán)境的過程可以自行百度搜索學(xué)習(xí))

然后才可以進行下面的步驟。

【1】選擇Android編譯器

image-20240425232651515

image-20240509202408776

【2】創(chuàng)建Android配置文件

image-20240117144604025

image-20240117144635052

image-20240117144652014

創(chuàng)建完成。

【3】配置Android圖標(biāo)與名稱

image-20240612100947190

【3】編譯Android上位機

Qt本身是跨平臺的,直接選擇Android的編譯器,就可以將程序編譯到Android平臺。

然后點擊構(gòu)建。

image-20240509202534407

成功之后,在目錄下可以看到生成的apk文件,也就是Android手機的安裝包,電腦端使用QQ發(fā)送給手機QQ,手機登錄QQ接收,就能直接安裝。

生成的apk的目錄在哪里呢? 編譯完成之后,在控制臺會輸出APK文件的路徑。

知道目錄在哪里之后,在Windows的文件資源管理器里,找到路徑,具體看下圖,找到生成的apk文件。

image-20240509202712295

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

五、STM32代碼開發(fā)

5.1 MQTT協(xié)議設(shè)計

#include "MQTTClient.h"
#include < string.h >
#include < stdlib.h >

#if !defined(_WINDOWS)
	#include < sys/time.h >
  	#include < sys/socket.h >
	#include < unistd.h >
  	#include < errno.h >
#else
#include < winsock2.h >
#include < ws2tcpip.h >
#define MAXHOSTNAMELEN 256
#define EAGAIN WSAEWOULDBLOCK
#define EINTR WSAEINTR
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define ENOTCONN WSAENOTCONN
#define ECONNRESET WSAECONNRESET
#endif

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))


char* topics[] =  {"TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"};
char* wildtopics[] = {"TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"};
char* nosubscribe_topics[] = {"nosubscribe",};

struct Options
{
	char* connection;         /**< connection to system under test. */
	char* clientid1;
	char* clientid2;
	char* username;
	char* password;
	int verbose;
	int MQTTVersion;
	int iterations;
	int run_dollar_topics_test;
	int run_subscribe_failure_test;
} options =
{
	"tcp://localhost:1883",
	"myclientid",
	"myclientid2",
	NULL,
	NULL,
	0,
	MQTTVERSION_3_1_1,
	1,
	0,
	0,
};


void usage(void)
{
	printf("options:n  connection, clientid1, clientid2, username, password, MQTTversion, iterations, verbosen");
	exit(EXIT_FAILURE);
}

void getopts(int argc, char** argv)
{
	int count = 1;
	
	while (count < argc)
	{
		if (strcmp(argv[count], "--dollar_topics_test") == 0 || strcmp(argv[count], "--$") == 0)
		{
			options.run_dollar_topics_test = 1;
			printf("Running $ topics testn");
		}
		else if (strcmp(argv[count], "--subscribe_failure_test") == 0 || strcmp(argv[count], "-s") == 0)
		{
			options.run_subscribe_failure_test = 1;
			printf("Running subscribe failure testn");
		}
		else if (strcmp(argv[count], "--connection") == 0)
		{
			if (++count < argc)
			{
				options.connection = argv[count];
				printf("Setting connection to %sn", options.connection);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--clientid1") == 0)
		{
			if (++count < argc)
			{
				options.clientid1 = argv[count];
				printf("Setting clientid1 to %sn", options.clientid1);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--clientid2") == 0)
		{
			if (++count < argc)
			{
				options.clientid2 = argv[count];
				printf("Setting clientid2 to %sn", options.clientid2);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--username") == 0)
		{
			if (++count < argc)
			{
				options.username = argv[count];
				printf("Setting username to %sn", options.username);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--password") == 0)
		{
			if (++count < argc)
			{
				options.password = argv[count];
				printf("Setting password to %sn", options.password);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--MQTTversion") == 0)
		{
			if (++count < argc)
			{
				options.MQTTVersion = atoi(argv[count]);
				printf("Setting MQTT version to %dn", options.MQTTVersion);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--iterations") == 0)
		{
			if (++count < argc)
			{
				options.iterations = atoi(argv[count]);
				printf("Setting iterations to %dn", options.iterations);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--verbose") == 0)
		{
			options.verbose = 1;
			printf("nSetting verbose onn");
		}
		count++;
	}
}


#if defined(_WIN32) || defined(_WINDOWS)
#define msleep Sleep
#define START_TIME_TYPE DWORD
static DWORD start_time = 0;
START_TIME_TYPE start_clock(void)
{
	return GetTickCount();
}
#elif defined(AIX)
#define mqsleep sleep
#define START_TIME_TYPE struct timespec
START_TIME_TYPE start_clock(void)
{
	static struct timespec start;
	clock_gettime(CLOCK_REALTIME, &start);
	return start;
}
#else
#define msleep(A) usleep(A*1000)
#define START_TIME_TYPE struct timeval
/* TODO - unused - remove? static struct timeval start_time; */
START_TIME_TYPE start_clock(void)
{
	struct timeval start_time;
	gettimeofday(&start_time, NULL);
	return start_time;
}
#endif

#define LOGA_DEBUG 0
#define LOGA_INFO 1
#include < stdarg.h >
#include < time.h >
#include < sys/timeb.h >
void MyLog(int LOGA_level, char* format, ...)
{
	static char msg_buf[256];
	va_list args;
#if defined(_WIN32) || defined(_WINDOWS)
	struct timeb ts;
#else
	struct timeval ts;
#endif
	struct tm timeinfo;

	if (LOGA_level == LOGA_DEBUG && options.verbose == 0)
	  return;

#if defined(_WIN32) || defined(_WINDOWS)
	ftime(&ts);
	localtime_s(&timeinfo, &ts.time);
#else
	gettimeofday(&ts, NULL);
	localtime_r(&ts.tv_sec, &timeinfo);
#endif
	strftime(msg_buf, 80, "%Y%m%d %H%M%S", &timeinfo);

#if defined(_WIN32) || defined(_WINDOWS)
	sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm);
#else
	sprintf(&msg_buf[strlen(msg_buf)], ".%.3lu ", ts.tv_usec / 1000L);
#endif

	va_start(args, format);
	vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args);
	va_end(args);

	printf("%sn", msg_buf);
	fflush(stdout);
}

int tests = 0;
int failures = 0;


void myassert(char* filename, int lineno, char* description, int value, char* format, ...)
{
	++tests;
	if (!value)
	{
		int count;
		va_list args;

		++failures;
		printf("Assertion failed, file %s, line %d, description: %sn", filename, lineno, description);

		va_start(args, format);
		count = vprintf(format, args);
		va_end(args);
		if (count)
			printf("n");

		//cur_output += sprintf(cur_output, "< failure type="%s" >file %s, line %d < /failure >n", 
        //               description, filename, lineno);
	}
    else
    	MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description);  
}


#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d)
#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e)

typedef struct
{
	char* topicName;
	int topicLen;
	MQTTClient_message* m;
} messageStruct;

messageStruct messagesArrived[1000];
int messageCount = 0;

int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m)
{
	messagesArrived[messageCount].topicName = topicName;
	messagesArrived[messageCount].topicLen = topicLen;
	messagesArrived[messageCount++].m = m;
	MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.",
					messageCount, topicName, m- >payloadlen, (char*)(m- >payload));
	return 1;
}


void clearMessages(void)
{
	int i;

	for (i = 0; i < messageCount; ++i)
	{
		MQTTClient_free(messagesArrived[i].topicName);
		MQTTClient_freeMessage(&messagesArrived[i].m);
	}
	messageCount = 0;
}

void cleanup(void)
{
	// clean all client state
	char* clientids[] = {options.clientid1, options.clientid2};
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Cleaning up");

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;
	
	for (i = 0; i < 2; ++i)
	{
		rc = MQTTClient_create(&aclient, options.connection, clientids[i], MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
		assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

		rc = MQTTClient_connect(aclient, &opts);
		assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

		rc = MQTTClient_disconnect(aclient, 100);
		assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

		MQTTClient_destroy(&aclient);
	}

	// clean retained messages 
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, "#", 0);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(2000); // wait for all retained messages to arrive

	rc = MQTTClient_unsubscribe(aclient, "#");
	assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	for (i = 0; i < messageCount; ++i)
	{
		if (messagesArrived[i].m- >retained)
		{
			MyLog(LOGA_INFO, "Deleting retained message for topic %s", (char*)messagesArrived[i].topicName);
			rc = MQTTClient_publish(aclient, messagesArrived[i].topicName, 0, "", 0, 1, NULL);
			assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
		}
	}

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	clearMessages();

	MyLog(LOGA_INFO, "Finished cleaning up");
}


int basic_test(void)
{
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting basic test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, topics[0], 0);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 0", 0, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 1", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(1000);

	rc = MQTTClient_disconnect(aclient, 10000);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("3 Messages received", messageCount == 3, "messageCount was %d", messageCount);
	clearMessages();

	/*opts.MQTTVersion = MQTTVERSION_3_1;
	rc = MQTTClient_connect(aclient, &opts); // should fail - wrong protocol version
	assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc);*/
	
	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Basic test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int offline_message_queueing_test(void)
{
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	MQTTClient bclient;

	MyLog(LOGA_INFO, "Offline message queueing test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	opts.cleansession = 1;
	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(bclient, topics[1], 5, "qos 0", 0, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(bclient, topics[2], 5, "qos 1", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(bclient, topics[3], 5, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(2000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&bclient);

	opts.cleansession = 0;
	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);  // receive the queued messages

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	assert("2 or 3 messages received", messageCount == 3 || messageCount == 2, "messageCount was %d", messageCount);

	MyLog(LOGA_INFO, "This server %s queueing QoS 0 messages for offline clients", (messageCount == 3) ? "is" : "is not");

	clearMessages();

	MyLog(LOGA_INFO, "Offline message queueing test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int retained_message_test(void)
{ 
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Retained message test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);

    // set retained messages
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[1], 5, "qos 0", 0, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[2], 5, "qos 1", 1, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(aclient, topics[3], 5, "qos 2", 2, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(2000);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("3 messages received", messageCount == 3, "messageCount was %d", messageCount);

	for (i = 0; i < messageCount; ++i)
	{
		assert("messages should be retained", messagesArrived[i].m- >retained, "retained was %d", 
			messagesArrived[i].m- >retained);
		MQTTClient_free(messagesArrived[i].topicName);
		MQTTClient_freeMessage(&messagesArrived[i].m);
	}
	messageCount = 0;

    // clear retained messages
	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[1], 0, "", 0, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[2], 0, "", 1, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(aclient, topics[3], 0, "", 2, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(200); // wait for QoS 2 exchange to be completed
	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(200);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Retained message test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}

#define SOCKET_ERROR -1

int test6_socket_error(char* aString, int sock)
{
#if defined(_WIN32)
	int errno;
#endif

#if defined(_WIN32)
	errno = WSAGetLastError();
#endif
	if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK)
	{
		if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET))
			printf("Socket error %d in %s for socket %d", errno, aString, sock);
	}
	return errno;
}

int test6_socket_close(int socket)
{
	int rc;

#if defined(_WIN32)
	if (shutdown(socket, SD_BOTH) == SOCKET_ERROR)
		test6_socket_error("shutdown", socket);
	if ((rc = closesocket(socket)) == SOCKET_ERROR)
		test6_socket_error("close", socket);
#else
	if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR)
		test6_socket_error("shutdown", socket);
	if ((rc = close(socket)) == SOCKET_ERROR)
		test6_socket_error("close", socket);
#endif
	return rc;
}

typedef struct
{
	int socket;
	time_t lastContact;
#if defined(OPENSSL)
	SSL* ssl;
	SSL_CTX* ctx;
#endif
} networkHandles;


typedef struct
{
	char* clientID;					/**< the string id of the client */
	char* username;					/**< MQTT v3.1 user name */
	char* password;					/**< MQTT v3.1 password */
	unsigned int cleansession : 1;	/**< MQTT clean session flag */
	unsigned int connected : 1;		/**< whether it is currently connected */
	unsigned int good : 1; 			/**< if we have an error on the socket we turn this off */
	unsigned int ping_outstanding : 1;
	int connect_state : 4;
	networkHandles net;
/* ... */
} Clients;


typedef struct
{
	char* serverURI;
	Clients* c;
	MQTTClient_connectionLost* cl;
	MQTTClient_messageArrived* ma;
	MQTTClient_deliveryComplete* dc;
	void* context;

	int connect_sem;
	int rc; /* getsockopt return code in connect */
	int connack_sem;
	int suback_sem;
	int unsuback_sem;
	void* pack;
} MQTTClients;


int will_message_test(void)
{
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient_willOptions wopts =  MQTTClient_willOptions_initializer;
	MQTTClient aclient, bclient;

	MyLog(LOGA_INFO, "Will message test");

	tests = failures = 0;

	opts.keepAliveInterval = 2;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	opts.will = &wopts;
	opts.will- >message = "client not disconnected";
	opts.will- >qos = 1;
	opts.will- >retained = 0;
	opts.will- >topicName = topics[2];

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	opts.keepAliveInterval = 20;
	opts.will = NULL;
	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(bclient, topics[2], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(100);

   	test6_socket_close(((MQTTClients*)aclient)- >c- >net.socket); 

	while (messageCount == 0 && ++count < 10)
    	msleep(1000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&bclient);

	assert("will message received", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Will message test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int overlapping_subscriptions_test(void)
{
  /* overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
   the server may send back one message with the highest QoS of any matching subscription, or one message for
   each subscription with a matching QoS. */

	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	char* topicList[] = {wildtopics[6], wildtopics[0]};
	int qosList[] = {2, 1};

	MyLog(LOGA_INFO, "Starting overlapping subscriptions test");

	clearMessages();
	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribeMany(aclient, 2, topicList, qosList);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[3], strlen("overlapping topic filters") + 1, 
                "overlapping topic filters", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);

	assert("1 or 2 messages received", messageCount == 1 || messageCount == 2, "messageCount was %d", messageCount);

    if (messageCount == 1)
	{
		MyLog(LOGA_INFO, "This server is publishing one message for all matching overlapping subscriptions, not one for each.");
		assert("QoS should be 2", messagesArrived[0].m- >qos == 2, "QoS was %d", messagesArrived[0].m- >qos);
	}
    else
	{
		MyLog(LOGA_INFO, "This server is publishing one message per each matching overlapping subscription.");
		assert1("QoSs should be 1 and 2", 
			(messagesArrived[0].m- >qos == 2 && messagesArrived[1].m- >qos == 1) ||
      		(messagesArrived[0].m- >qos == 1 && messagesArrived[1].m- >qos == 2),
		"QoSs were %d %d", messagesArrived[0].m- >qos, messagesArrived[1].m- >qos);
	}

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Overlapping subscription test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int keepalive_test(void)
{
	/* keepalive processing.  We should be kicked off by the server if we don't send or receive any data, and don't send
	any pings either. */

	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient_willOptions wopts =  MQTTClient_willOptions_initializer;
	MQTTClient aclient, bclient;

	MyLog(LOGA_INFO, "Starting keepalive test");

	tests = failures = 0;
	clearMessages();

	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	opts.will = &wopts;
	opts.will- >message = "keepalive expiry";
	opts.will- >qos = 1;
	opts.will- >retained = 0;
	opts.will- >topicName = topics[4];

	opts.keepAliveInterval = 20;
	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(bclient, topics[4], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	opts.keepAliveInterval = 2;
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	while (messageCount == 0 && ++count < 20)
    	msleep(1000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("Should have will message", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Keepalive test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int redelivery_on_reconnect_test(void)
{
	/* redelivery on reconnect. When a QoS 1 or 2 exchange has not been completed, the server should retry the 
	 appropriate MQTT packets */

	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting redelivery on reconnect test");

	tests = failures = 0;
	clearMessages();

	opts.keepAliveInterval = 0;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[6], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
	MQTTClient_yield();

    // no background processing because no callback has been set
	rc = MQTTClient_publish(aclient, topics[1], 6, "qos 1", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[3], 6, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 0);

	assert("No messages should have been received yet", messageCount == 0, "messageCount was %d", messageCount);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	while (messageCount < 2 && ++count < 5)
		msleep(1000);

	assert("Should have 2 messages", messageCount == 2, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Redelivery on reconnect test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int zero_length_clientid_test(void)
{
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting zero length clientid test");

	tests = failures = 0;
	clearMessages();

	opts.keepAliveInterval = 0;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, "", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("rc 2 from connect", rc == 2, "rc was %d", rc); // this should always fail

	opts.cleansession = 1;
	rc = MQTTClient_connect(aclient, &opts);
	assert("Connack rc should be 0 or 2", rc == MQTTCLIENT_SUCCESS || rc == 2, "rc was %d", rc);

	MyLog(LOGA_INFO, "This server %s support zero length clientids", (rc == 2) ? "does not" : "does");

	if (rc == MQTTCLIENT_SUCCESS)
		rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Zero length clientid test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int dollar_topics_test(void)
{
  /* $ topics. The specification says that a topic filter which starts with a wildcard does not match topic names that
   	begin with a $.  Publishing to a topic which starts with a $ may not be allowed on some servers (which is entirely valid),
   	so this test will not work and should be omitted in that case.
	*/
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	char dollartopic[20];

	MyLog(LOGA_INFO, "Starting $ topics test");

	sprintf(dollartopic, "$%s", topics[1]);

    clearMessages();

	opts.keepAliveInterval = 5;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000); // wait for any retained messages, hopefully
    clearMessages();

	rc = MQTTClient_publish(aclient, topics[1], 20, "not sent to dollar topic", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, dollartopic, 20, "sent to dollar topic", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);
	assert("Should have 1 message", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "$ topics test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int subscribe_failure_test(void)
{
  /* Subscribe failure.  A new feature of MQTT 3.1.1 is the ability to send back negative reponses to subscribe
   requests.  One way of doing this is to subscribe to a topic which is not allowed to be subscribed to.
  */
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	int subqos = 2;

	MyLog(LOGA_INFO, "Starting subscribe failure test");

    clearMessages();

	opts.keepAliveInterval = 5;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribeMany(aclient, 1, &nosubscribe_topics[0], &subqos);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
	assert("0x80 rc from subscribe", subqos == 0x80, "subqos was %d", subqos);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Subscribe failure test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int main(int argc, char** argv)
{
	int i;
	int all_failures = 0;

	getopts(argc, argv);

	for (i = 0; i < options.iterations; ++i)
	{
		cleanup();
		all_failures += basic_test() + 
						offline_message_queueing_test() +
		                retained_message_test() +
		                will_message_test() +
		                overlapping_subscriptions_test() +
		                keepalive_test() +
						redelivery_on_reconnect_test() + 
						zero_length_clientid_test();

		if (options.run_dollar_topics_test)
			all_failures += dollar_topics_test();
		
		if (options.run_subscribe_failure_test)
			all_failures += subscribe_failure_test();
	}

	MyLog(LOGA_INFO, "Test suite %s", (all_failures == 0) ?  "succeeded" : "failed");
}

5.2 OLED顯示屏驅(qū)動代碼

#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "delay.h"

//OLED模式設(shè)置
//0: 4線串行模式  (模塊的BS1,BS2均接GND)
//1: 并行8080模式 (模塊的BS1,BS2均接VCC)
#define OLED_MODE 	1 
		    						  
//---------------------------OLED端口定義--------------------------  					   
#define OLED_CS  PDout(6)
#define OLED_RST PGout(15) 	
#define OLED_RS  PDout(3)
#define OLED_WR  PGout(14)		  
#define OLED_RD  PGout(13)	   
//PC0~7,作為數(shù)據(jù)線
#define DATAOUT(x) GPIOC- >ODR=(GPIOC- >ODR&0xff00)|(x&0x00FF); //輸出

//使用4線串行接口時使用 
#define OLED_SCLK PCout(0)
#define OLED_SDIN PCout(1)
		     
#define OLED_CMD  0	//寫命令
#define OLED_DATA 1	//寫數(shù)據(jù)
//OLED控制用函數(shù)
void OLED_WR_Byte(u8 dat,u8 cmd);	    
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Refresh_Gram(void);  		    
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPoint(u8 x,u8 y,u8 t);
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size);	



//OLED的顯存
//存放格式如下.
//[0]0 1 2 3 ... 127	
//[1]0 1 2 3 ... 127	
//[2]0 1 2 3 ... 127	
//[3]0 1 2 3 ... 127	
//[4]0 1 2 3 ... 127	
//[5]0 1 2 3 ... 127	
//[6]0 1 2 3 ... 127	
//[7]0 1 2 3 ... 127 		   
u8 OLED_GRAM[128][8];	 

//更新顯存到LCD		 
void OLED_Refresh_Gram(void)
{
	u8 i,n;		    
	for(i=0;i< 8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //設(shè)置頁地址(0~7)
		OLED_WR_Byte (0x00,OLED_CMD);      //設(shè)置顯示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //設(shè)置顯示位置—列高地址   
		for(n=0;n< 128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); 
	}   
}
#if OLED_MODE==1	//8080并口 
//向SSD1306寫入一個字節(jié)。
//dat:要寫入的數(shù)據(jù)/命令
//cmd:數(shù)據(jù)/命令標(biāo)志 0,表示命令;1,表示數(shù)據(jù);
void OLED_WR_Byte(u8 dat,u8 cmd)
{
	DATAOUT(dat);	    
 	OLED_RS=cmd;
	OLED_CS=0;	   
	OLED_WR=0;	 
	OLED_WR=1;
	OLED_CS=1;	  
	OLED_RS=1;	 
} 	    	    
#else
//向SSD1306寫入一個字節(jié)。
//dat:要寫入的數(shù)據(jù)/命令
//cmd:數(shù)據(jù)/命令標(biāo)志 0,表示命令;1,表示數(shù)據(jù);
void OLED_WR_Byte(u8 dat,u8 cmd)
{	
	u8 i;			  
	OLED_RS=cmd; //寫命令 
	OLED_CS=0;		  
	for(i=0;i< 8;i++)
	{			  
		OLED_SCLK=0;
		if(dat&0x80)OLED_SDIN=1;
		else OLED_SDIN=0;
		OLED_SCLK=1;
		dat< <=1;   
	}				 
	OLED_CS=1;		  
	OLED_RS=1;   	  
} 
#endif
	  	  
//開啟OLED顯示    
void OLED_Display_On(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
	OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//關(guān)閉OLED顯示     
void OLED_Display_Off(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
	OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}		   			 
//清屏函數(shù),清完屏,整個屏幕是黑色的!和沒點亮一樣!!!	  
void OLED_Clear(void)  
{  
	u8 i,n;  
	for(i=0;i< 8;i++)for(n=0;n< 128;n++)OLED_GRAM[n][i]=0X00;  
	OLED_Refresh_Gram();//更新顯示
}
//畫點 
//x:0~127
//y:0~63
//t:1 填充 0,清空				   
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
	u8 pos,bx,temp=0;
	if(x >127||y >63)return;//超出范圍了.
	pos=7-y/8;
	bx=y%8;
	temp=1< 7-bx);
	if(t)OLED_GRAM[x][pos]|=temp;
	else OLED_GRAM[x][pos]&=~temp;	    
}
//x1,y1,x2,y2 填充區(qū)域的對角坐標(biāo)
//確保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63	 	 
//dot:0,清空;1,填充	  
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)  
{  
	u8 x,y;  
	for(x=x1;x<=x2;x++)
	{
		for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
	}													    
	OLED_Refresh_Gram();//更新顯示
}
//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白顯示;1,正常顯示				 
//size:選擇字體 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{      			    
	u8 temp,t,t1;
	u8 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字體一個字符對應(yīng)點陣集所占的字節(jié)數(shù)
	chr=chr-' ';//得到偏移后的值		 
    for(t=0;t< csize;t++)
    {   
		if(size==12)temp=asc2_1206[chr][t]; 	 	//調(diào)用1206字體
		else if(size==16)temp=asc2_1608[chr][t];	//調(diào)用1608字體
		else if(size==24)temp=asc2_2412[chr][t];	//調(diào)用2412字體
		else return;								//沒有的字庫
        for(t1=0;t1< 8;t1++)
		{
			if(temp&0x80)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp< <=1;
			y++;
			if((y-y0)==size)
			{
				y=y0;
				x++;
				break;
			}
		}  	 
    }          
}
//m^n函數(shù)
u32 mypow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}				  
//顯示2個數(shù)字
//x,y :起點坐標(biāo)	 
//len :數(shù)字的位數(shù)
//size:字體大小
//mode:模式	0,填充模式;1,疊加模式
//num:數(shù)值(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t< len;t++)
	{
		temp=(num/mypow(10,len-t-1))%10;
		if(enshow==0&&t< (len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
				continue;
			}else enshow=1; 
		 	 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); 
	}
} 
//顯示字符串
//x,y:起點坐標(biāo)  
//size:字體大小 
//*p:字符串起始地址 
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{	
    while((*p<='~')&&(*p >=' '))//判斷是不是非法字符!
    {       
        if(x >(128-(size/2))){x=0;y+=size;}
        if(y >(64-size)){y=x=0;OLED_Clear();}
        OLED_ShowChar(x,y,*p,size,1);	 
        x+=size/2;
        p++;
    }  
	
}	
//初始化SSD1306					    
void OLED_Init(void)
{ 	 	 			 	 				 
	RCC- >APB2ENR|=1< 4;    //使能PORTC時鐘 
	RCC- >APB2ENR|=1< 5;    //使能PORTD時鐘 
	RCC- >APB2ENR|=1< 8;    //使能PORTG時鐘

  	GPIOD- >CRL&=0XF0FF0FFF;//PD3,6 推挽輸出 
  	GPIOD- >CRL|=0X03003000;	 
  	GPIOD- >ODR|=1< 3;
  	GPIOD- >ODR|=1< 6;	    
#if OLED_MODE==1			//8080并口模式 
	GPIOC- >CRL=0X33333333; 	//PC0~7 OUT
	GPIOC- >ODR|=0X00FF;
		
  	GPIOG- >CRH&=0X000FFFFF;	//PG13,14,15 OUT
  	GPIOG- >CRH|=0X33300000;	 
	GPIOG- >ODR|=7< 13; 
#else						//4線SPI模式
	GPIOC- >CRL&=0XFFFFFF00; //PC0,1 OUT
	GPIOC- >CRL|=0X00000033;	    
 	GPIOC- >ODR|=3< 0;

 	GPIOG- >CRH&=0X0FFFFFFF;	//RST	   
 	GPIOG- >CRH|=0X30000000;	 
	GPIOG- >ODR|=1< 15;
#endif									  
	OLED_CS=1;
	OLED_RS=1;	 
	
	OLED_RST=0;
	delay_ms(100);
	OLED_RST=1; 
					  
	OLED_WR_Byte(0xAE,OLED_CMD); //關(guān)閉顯示
	OLED_WR_Byte(0xD5,OLED_CMD); //設(shè)置時鐘分頻因子,震蕩頻率
	OLED_WR_Byte(80,OLED_CMD);   //[3:0],分頻因子;[7:4],震蕩頻率
	OLED_WR_Byte(0xA8,OLED_CMD); //設(shè)置驅(qū)動路數(shù)
	OLED_WR_Byte(0X3F,OLED_CMD); //默認0X3F(1/64) 
	OLED_WR_Byte(0xD3,OLED_CMD); //設(shè)置顯示偏移
	OLED_WR_Byte(0X00,OLED_CMD); //默認為0

	OLED_WR_Byte(0x40,OLED_CMD); //設(shè)置顯示開始行 [5:0],行數(shù).
													    
	OLED_WR_Byte(0x8D,OLED_CMD); //電荷泵設(shè)置
	OLED_WR_Byte(0x14,OLED_CMD); //bit2,開啟/關(guān)閉
	OLED_WR_Byte(0x20,OLED_CMD); //設(shè)置內(nèi)存地址模式
	OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,頁地址模式;默認10;
	OLED_WR_Byte(0xA1,OLED_CMD); //段重定義設(shè)置,bit0:0,0- >0;1,0- >127;
	OLED_WR_Byte(0xC0,OLED_CMD); //設(shè)置COM掃描方向;bit3:0,普通模式;1,重定義模式 COM[N-1]- >COM0;N:驅(qū)動路數(shù)
	OLED_WR_Byte(0xDA,OLED_CMD); //設(shè)置COM硬件引腳配置
	OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
		 
	OLED_WR_Byte(0x81,OLED_CMD); //對比度設(shè)置
	OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默認0X7F (亮度設(shè)置,越大越亮)
	OLED_WR_Byte(0xD9,OLED_CMD); //設(shè)置預(yù)充電周期
	OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
	OLED_WR_Byte(0xDB,OLED_CMD); //設(shè)置VCOMH 電壓倍率
	OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

	OLED_WR_Byte(0xA4,OLED_CMD); //全局顯示開啟;bit0:1,開啟;0,關(guān)閉;(白屏/黑屏)
	OLED_WR_Byte(0xA6,OLED_CMD); //設(shè)置顯示方式;bit0:1,反相顯示;0,正常顯示	    						   
	OLED_WR_Byte(0xAF,OLED_CMD); //開啟顯示	 
	OLED_Clear();
}

5.3 PM2.5與MQ5采集代碼

#include "adc.h"
#include "delay.h"					   

//初始化ADC1
//這里我們僅以規(guī)則通道為例
//我們默認僅開啟通道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準確度下降! 
	RCC- >CFGR|=2< 14;      	 
	ADC1- >CR1&=0XF0FFFF;   //工作模式清零
	ADC1- >CR1|=0< 16;      //獨立工作模式  
	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- >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- >CR2|=1< 0;	   //開啟AD轉(zhuǎn)換器	 
	ADC1- >CR2|=1< 3;       //使能復(fù)位校準  
	while(ADC1- >CR2&1< 3); //等待校準結(jié)束 			 
    //該位由軟件設(shè)置并由硬件清除。在校準寄存器被初始化后該位將被清除。 		 
	ADC1- >CR2|=1< 2;        //開啟AD校準	   
	while(ADC1- >CR2&1< 2);  //等待校準結(jié)束
	//該位由軟件設(shè)置以開始校準,并在校準結(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;
}

5.4 DHT11溫濕度采集代碼

#include "dht11.h"
#include "delay.h"
//IO方向設(shè)置
#define DHT11_IO_IN()  {GPIOG- >CRH&=0XFFFF0FFF;GPIOG- >CRH|=8< 
#define DHT11_IO_OUT() {GPIOG- >CRH&=0XFFFF0FFF;GPIOG- >CRH|=3< 
////IO操作函數(shù)											   
#define	DHT11_DQ_OUT PGout(11) //數(shù)據(jù)端口	PG11 
#define	DHT11_DQ_IN  PGin(11)  //數(shù)據(jù)端口	PG11


u8 DHT11_Init(void);		//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//讀取溫濕度
u8 DHT11_Read_Byte(void);	//讀出一個字節(jié)
u8 DHT11_Read_Bit(void);	//讀出一個位
u8 DHT11_Check(void);		//檢測是否存在DHT11
void DHT11_Rst(void);		//復(fù)位DHT11    


//復(fù)位DHT11
void DHT11_Rst(void)	   
{                 
	DHT11_IO_OUT(); 	//SET OUTPUT
    DHT11_DQ_OUT=0; 	//拉低DQ
    delay_ms(20);    	//拉低至少18ms
    DHT11_DQ_OUT=1; 	//DQ=1 
	delay_us(30);     	//主機拉高20~40us
}
//等待DHT11的回應(yīng)
//返回1:未檢測到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
    while (DHT11_DQ_IN&&retry< 100)//DHT11會拉低40~80us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry >=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry< 100)//DHT11拉低后會再次拉高40~80us
	{
		retry++;
		delay_us(1);
	};
	if(retry >=100)return 1;	    
	return 0;
}
//從DHT11讀取一個位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry< 100)//等待變?yōu)榈碗娖?/span>
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry< 100)//等待變高電平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}
//從DHT11讀取一個字節(jié)
//返回值:讀到的數(shù)據(jù)
u8 DHT11_Read_Byte(void)    
{        
    u8 i,dat;
    dat=0;
	for (i=0;i< 8;i++) 
	{
   		dat< <=1; 
	    dat|=DHT11_Read_Bit();
    }						    
    return dat;
}
//從DHT11讀取一次數(shù)據(jù)
//temp:溫度值(范圍:0~50°)
//humi:濕度值(范圍:20%~90%)
//返回值:0,正常;1,讀取失敗
u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
{        
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==0)
	{
		for(i=0;i< 5;i++)//讀取40位數(shù)據(jù)
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;	    
}
//初始化DHT11的IO口 DQ 同時檢測DHT11的存在
//返回1:不存在
//返回0:存在    	 
u8 DHT11_Init(void)
{
	RCC- >APB2ENR|=1< 8;    //使能PORTG口時鐘 
	GPIOG- >CRH&=0XFFFF0FFF;//PORTG.11 推挽輸出
	GPIOG- >CRH|=0X00003000;
	GPIOG- >ODR|=1< 11;      //輸出1				    
	DHT11_Rst();
	return DHT11_Check();
}

六、總結(jié)

該項目開發(fā)一種基于STM32單片機的礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng),以提高礦山作業(yè)的安全性和效率。系統(tǒng)集成了多種傳感器,包括DHT11溫濕度傳感器、MQ5氣體傳感器和PM2.5傳感器,用于實時監(jiān)測礦山環(huán)境中的關(guān)鍵參數(shù),如溫度、濕度、瓦斯?jié)舛群皖w粒物含量。通過這些傳感器,系統(tǒng)能夠及時發(fā)現(xiàn)潛在的安全隱患,并采取必要的預(yù)防措施。

在硬件設(shè)計方面,系統(tǒng)采用了STM32F103RCT6單片機作為核心控制器,配合蜂鳴器、繼電器模塊、OLED顯示屏等組件,形成了一個完整的監(jiān)測與控制系統(tǒng)。當(dāng)檢測到的環(huán)境參數(shù)超過預(yù)設(shè)的安全閾值時,系統(tǒng)能夠自動觸發(fā)報警,并通過控制風(fēng)扇和霧化噴淋系統(tǒng)來降低瓦斯?jié)舛群皖w粒物含量,從而保障礦工的生命安全。

此外,為了實現(xiàn)遠程監(jiān)控與管理,項目還引入了BC26(NBIOT)模塊,將環(huán)境數(shù)據(jù)上傳至華為云物聯(lián)網(wǎng)平臺,并開發(fā)了一款基于Qt框架的Android平臺手機應(yīng)用程序。該應(yīng)用程序不僅能夠?qū)崟r顯示環(huán)境參數(shù),還能接收警報信息,并允許用戶遠程控制風(fēng)扇和霧化降塵設(shè)備的開關(guān)狀態(tài),極大地提升了系統(tǒng)的實用性和靈活性。

該礦山環(huán)境作業(yè)安全監(jiān)測系統(tǒng)通過集成先進的傳感技術(shù)和物聯(lián)網(wǎng)技術(shù),實現(xiàn)了對礦山環(huán)境的全方位監(jiān)控,能夠在危險發(fā)生之前提供預(yù)警,并采取有效的防護措施,對于提升礦山作業(yè)的安全管理水平具有重要意義。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 傳感器
    +關(guān)注

    關(guān)注

    2561

    文章

    52305

    瀏覽量

    762362
  • 單片機
    +關(guān)注

    關(guān)注

    6060

    文章

    44836

    瀏覽量

    645293
  • STM32
    +關(guān)注

    關(guān)注

    2287

    文章

    10988

    瀏覽量

    361666
  • 安全監(jiān)測系統(tǒng)

    關(guān)注

    0

    文章

    31

    瀏覽量

    1969
收藏 人收藏

    評論

    相關(guān)推薦
    熱點推薦

    礦山地質(zhì)災(zāi)害3種超前預(yù)測技術(shù)

    的處理提供有效的依據(jù)。X 射線攝影新技術(shù)通過X射線攝影新技術(shù)對地下礦山深部的礦柱能夠?qū)崿F(xiàn)有利的監(jiān)測,連續(xù)的探測能夠?qū)Ω邞?yīng)力區(qū)進行監(jiān)測,可以在開采之前為掘進創(chuàng)造良好的條件,保證在掘進作業(yè)
    發(fā)表于 12-26 16:09

    基于Python的礦山遙感監(jiān)測系統(tǒng)開發(fā)的方法

    與RS功能的應(yīng)用系統(tǒng)。本文從礦山遙感監(jiān)測需求出發(fā),分析礦山遙感監(jiān)測中需要集成的GIS和RS功能,介紹實用性極強的
    發(fā)表于 08-11 06:21

    基于單片機的稻田環(huán)境監(jiān)測系統(tǒng)具有哪些功能

    、土壤濕度以及光照度,并發(fā)送給主機顯示2、繼電器1:土壤濕度過低打開水泵繼電器2:溫度過低加熱繼電器3:光照度過低開燈標(biāo)簽:STM32單片機、土壤監(jiān)測、zigbee、遠程監(jiān)測題目擴展:
    發(fā)表于 12-08 08:28

    基于STM32單片機的室內(nèi)環(huán)境監(jiān)測系統(tǒng)的設(shè)計資料分享

    更改)具有一個靜音按鍵標(biāo)簽:51單片機、LCD1602、SGP30、DHT11題目擴展:環(huán)境監(jiān)測系統(tǒng)、空氣質(zhì)量監(jiān)測、氣體監(jiān)測資料預(yù)覽效果圖:
    發(fā)表于 02-21 07:14

    【新聞】廣和通+山源科技:5G智慧礦山,讓井下作業(yè)安全高效

    智慧能源--智慧礦山 為了在保障曠工安全操作的基礎(chǔ)上,改善其工作流程,提升其工作效率,并進一步推動煤礦井下“少人化、無人化”的作業(yè)流程,山源科技在廣和通和高通的助力下,致力于成為引領(lǐng)智慧礦山
    發(fā)表于 10-16 14:46

    使用STM32單片機設(shè)計智能小車的資料合集免費下載

    本文檔的使用STM32單片機設(shè)計智能小車的資料合集免費下載
    發(fā)表于 03-05 08:00 ?72次下載

    使用STM32單片機設(shè)計實現(xiàn)糧倉環(huán)境監(jiān)測系統(tǒng)的資料說明

    在糧食倉儲過程中,為給糧食提供較好的倉儲環(huán)境,往往需要對糧倉環(huán)境進行實時的監(jiān)測,因此設(shè)計基于STM32單片機為核心控制單元的實時
    發(fā)表于 04-21 08:00 ?31次下載
    使用<b class='flag-5'>STM32</b><b class='flag-5'>單片機設(shè)</b>計實現(xiàn)糧倉<b class='flag-5'>環(huán)境監(jiān)測</b><b class='flag-5'>系統(tǒng)</b>的資料說明

    地下礦山監(jiān)測監(jiān)控系統(tǒng),礦山安全施工保障

    針對當(dāng)前非煤礦山安全生產(chǎn)風(fēng)險監(jiān)測預(yù)警系統(tǒng)聯(lián)網(wǎng)建設(shè)和非煤礦山、尾礦庫安全生產(chǎn)工作要求,必須加快推進
    的頭像 發(fā)表于 01-03 10:33 ?1325次閱讀

    基于STM32單片機的家庭環(huán)境監(jiān)測系統(tǒng)設(shè)計

    本次家庭環(huán)境監(jiān)測系統(tǒng)的設(shè)計使用STM32單片機作為控制中心,通過ESP8266上傳和下發(fā)數(shù)據(jù),通過MQ-2測量室內(nèi)煙霧濃度,通過DHT11測量溫濕度,當(dāng)溫度超過極限時,通過繼電器控制風(fēng)
    的頭像 發(fā)表于 07-25 11:08 ?4669次閱讀
    基于<b class='flag-5'>STM32</b><b class='flag-5'>單片機</b>的家庭<b class='flag-5'>環(huán)境監(jiān)測</b><b class='flag-5'>系統(tǒng)</b>設(shè)計

    基于STM32單片機的人流量監(jiān)測系統(tǒng)設(shè)計

    本次人流量監(jiān)測系統(tǒng)的設(shè)計使用STM32單片機作為控制中心,通過光電管進行掃描,
    發(fā)表于 08-29 09:22 ?1562次閱讀
    基于<b class='flag-5'>STM32</b><b class='flag-5'>單片機</b>的人流量<b class='flag-5'>監(jiān)測</b><b class='flag-5'>系統(tǒng)</b>設(shè)計

    使用單片機設(shè)計電池安全檢測系統(tǒng)案例

    電子發(fā)燒友網(wǎng)站提供《使用單片機設(shè)計電池安全檢測系統(tǒng)案例.pdf》資料免費下載
    發(fā)表于 11-13 10:36 ?0次下載
    使用<b class='flag-5'>單片機設(shè)</b>計電池<b class='flag-5'>安全</b>檢測<b class='flag-5'>系統(tǒng)</b>案例

    中偉視界:礦山智能化安全生產(chǎn),未戴自救器檢測AI算法助力保護作業(yè)人員安全

    礦山作業(yè)環(huán)境復(fù)雜危險,確保作業(yè)人員佩戴自救器是重要措施之一。未戴自救器檢測AI算法通過圖像識別和人工智能技術(shù),實時監(jiān)控井下人員的自救器佩戴情況,有效避免了
    的頭像 發(fā)表于 07-17 13:10 ?529次閱讀
    中偉視界:<b class='flag-5'>礦山</b>智能化<b class='flag-5'>安全</b>生產(chǎn),未戴自救器檢測AI算法助力保護<b class='flag-5'>作業(yè)</b>人員<b class='flag-5'>安全</b>

    礦山邊坡監(jiān)測:實時守護礦山邊坡安全

    隨著我國經(jīng)濟的快速發(fā)展,礦產(chǎn)資源的需求量不斷增長,礦山開采規(guī)模不斷擴大。然而,在礦山開采過程中,邊坡穩(wěn)定性問題日益凸顯,成為威脅礦山安全的主要因素之一。為了
    的頭像 發(fā)表于 08-17 11:24 ?1513次閱讀

    基于STM32單片機礦井礦工作業(yè)安全監(jiān)測設(shè)計

    本項目設(shè)計一個基于STM32單片機的礦井作業(yè)安全監(jiān)測系統(tǒng)。該
    的頭像 發(fā)表于 01-17 16:45 ?640次閱讀

    基于單片機中藥存放環(huán)境監(jiān)測系統(tǒng)的實現(xiàn)

    本項目基于STM32F103C8T6單片機作為核心控制單元,通過多種傳感器對環(huán)境進行實時采集,并通過液晶顯示屏和Wi-Fi無線通訊將監(jiān)測數(shù)據(jù)展示給用戶,達到
    的頭像 發(fā)表于 01-23 09:42 ?496次閱讀
    基于<b class='flag-5'>單片機</b>中藥存放<b class='flag-5'>環(huán)境監(jiān)測</b><b class='flag-5'>系統(tǒng)</b>的實現(xiàn)

    電子發(fā)燒友

    中國電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會員交流學(xué)習(xí)
    • 獲取您個性化的科技前沿技術(shù)信息
    • 參加活動獲取豐厚的禮品