新聞中心
想了解更多內(nèi)容,請訪問:

豐臺網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、自適應(yīng)網(wǎng)站建設(shè)等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)成立與2013年到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos./#zz
使用的器件:Hi3861 + AHT20 + SSD1306
配好開發(fā)環(huán)境輕松做完點燈任務(wù)后,便想搞個像樣點的應(yīng)用,然后……決定做一個測量溫濕度計,開始覺得這個實現(xiàn)比較簡單,通過Hi3861讀取AHT20測出的溫濕度值,然后通過OLED顯示出來。
首先我的嵌入式開發(fā)知識非常少,曾經(jīng)做過幾年Java開發(fā),玩過一點51單片機,在Linux上部署過一些Web服務(wù)。僅此而已,所以我的知識無法宏觀上把這個事情想的很清楚,只能一步一步的試。如果你也是新入手嵌入式開發(fā)的朋友,那我的過程可能會對你有借鑒。
先構(gòu)建一個技術(shù)輪廓:每種電子傳感器都是包括3類端口,(1)第1類電源:必需要2根電源線給模塊供電,正負極,有的模塊有多組供電;(2)第2類控制:然后有的模塊會有一些控制端口,每種模塊都不經(jīng)相同,有的模塊直接讀數(shù)是沒有這種控制端口的;(3)第3類通信線:跟核心單片機通信的信號端口,根據(jù)采用的通信協(xié)議不同,端口數(shù)量不同。要使用的AHT20和SSD1306采用的都是I2C通信協(xié)議,所以都是2根通信線。
我所用到的模塊都很簡單,主要涵蓋的都是這3類端口,所以不管看到模塊打扮成什么樣,所要解決的主要問題都是類似的。主要做的就是通過通信端口向模塊讀寫數(shù)據(jù)。
模塊的配置:各種模塊控制和存儲數(shù)據(jù)都是由一組組8位的寄存器控制的,每個寄存器里有8位,每一位可以存儲1或者0,組成1個字節(jié)值,每種模塊都有自己的功能設(shè)置和存儲設(shè)置,可以想象成高階語言里的關(guān)鍵字,寄存器值就是它本來的樣子,一組組數(shù)字直接看是不會看懂它代表什么意思的,所以要依靠模塊提供的技術(shù)手冊做指導,一邊看手冊一邊設(shè)置,單片機開發(fā)就這是這么樸實無華。
關(guān)于通信協(xié)議:要使用的這2個模塊采用的都是I2C通信協(xié)議,2根線一根信號一根時鐘,通信雙方就是通過互相占用通信線,相互發(fā)送高低電平傳遞消息,就是他們不能同步通信的,一方發(fā)送一方只能接收。因為用的線少所以通信過程非常繁瑣,一方喊話問某地址的模塊是否在線,然后等待,對方如果收到喊話,然后給個應(yīng)答,當收到應(yīng)答,再發(fā)送指令告訴他準備干什么,然后等待確認,模塊收到后發(fā)確認…………,這個過程我在51上模擬過,好痛苦啊,一個時鐘信號一個數(shù)據(jù)信號的數(shù)……,但是!!!在鴻蒙上所有的繁瑣過程都被封裝好了,我們只需要簡單的調(diào)用系統(tǒng)提供的I2C操作方法,具體過程完全不用考慮,經(jīng)過使用,真的好用,非常好用,好簡單啊!所以I2C基本流程熟悉一下即可,在開發(fā)過程中具體的工作非常少。
SSD1306
首先是點亮屏幕,一旦能使用屏幕了,等于單片機對你打開了一扇窗戶。SSD1306并不是OLED,它是驅(qū)動OLED顯示的控制芯片,很多模塊本身就是一個復雜的單片機,我們用的OLED屏幕是128*64像素組成的,本質(zhì)上你可以簡單的理解為高階點燈。對SSD1306的控制也是通過I2C實現(xiàn)的,雖然它支持很多種通信協(xié)議,但是惜端如金的Hi3861采用了端口占用最少的I2C。
我們只是需要向SSD1306發(fā)送數(shù)據(jù),沒有反饋值。所以通信過程比較簡單SSD1306的地址0x78,0x00為接收命令,0x40為接收數(shù)據(jù)。把這個高度重復的過程做到1個函數(shù)里,直接調(diào)用就好。
- // I2C協(xié)議 讀寫函數(shù) 只有寫需求, cd = 0 寫指令 cd = 1 寫數(shù)據(jù) byt 要寫入的值
- void SSD1306_I2C_W(unsigned char cd, unsigned char byt)
- {
- unsigned int state = 0; // I2C 運行 狀態(tài)
- WifiIotI2cIdx id = WIFI_IOT_I2C_IDX_0; //I2C 通道 0
- unsigned short deviceAddr = 0x78; // SSD1306 地址
- WifiIotI2cData i2cData = {0}; // 接收發(fā)送信息的數(shù)組 查 wifiiot_i2c.h 看詳細說明
- unsigned char buf[] = {0x00, byt}; //默認 0x00 寫入 指令集 byt 要寫入的指令
- if(cd == 1) // 輸入 數(shù)據(jù)
- {
- buf[0] = 0x40; // 0x40 表示寫入的是數(shù)據(jù) byt 就是要寫入的數(shù)據(jù)
- }
- i2cData.sendBuf = buf;
- i2cData.sendLen = 2;
- state = I2cWrite(id, deviceAddr, &i2cData);
- if(state != WIFI_IOT_SUCCESS)
- {
- printf("[SSD1306_I2C_W] write error : < %d > !!! \r\n", state); // 如果狀態(tài)異常 就打印 錯誤信息
- }
- // return state; // 也可以作為返回值
- }
驅(qū)動命令比較多,這是遇到的第一個障礙,看了手冊,還有網(wǎng)絡(luò)上各種例子,各式各樣,雖然大同小異但是更是一頭霧水。然后……以手冊流程圖為準自己寫。不要怕,大膽試,好不好用試了才知道。
有的設(shè)置是需要成對出現(xiàn)的,一個命令配一個參數(shù),但是很多例子全部放在一起,一邊看參數(shù)一邊對照命令表……崩潰,雖然我現(xiàn)在也沒搞懂有些命令的功能,但是以手冊默認值為準,最后運行的很好。哈。
驅(qū)動流程:
大多例子都是默認用頁顯示的方式,開始我也是用頁顯示的方式,用用就根據(jù)自己的需要改成水平方式了,建1個2維數(shù)組存放顯示的信息,顯示函數(shù)跟畫面函數(shù)分離,這樣做畫面的時候?qū)W⒆霎嬅?。這樣還有個好處,就是以后代碼的重用會比較方便。這樣做還是為了簡單實現(xiàn)在任意坐標顯示,以后畫個波顯示更方便一點。
這里補充1點,我開始按以前做小游戲的習慣做的畫面控制,單片機還是模塊好像都吃不消,看來還是越簡單越好。
然后要用到1個輔助工具,PCtoLCD2002完美版-(字符模式),這個字模工具超好用,這里向作者表示由衷的感情,讓最繁瑣的工作變得非常輕松。使用的時候注意點選項設(shè)置,主要是方向,寫段代碼測一下就好了。
SSD1306一次接收1個字節(jié)的數(shù)據(jù),表示對1列8個像素的開關(guān)控制,每個字節(jié)數(shù)據(jù)轉(zhuǎn)成二進制代碼,比如0xFF二進制1111 1111,每個1都代表點亮1個像素。0x00二進制0000 0000,就是關(guān)閉8個像素。
AHT20
先看一下AHT20的技術(shù)手冊,這個手冊可以百度到(國內(nèi)最小的半導體溫濕度傳感器AHT20研發(fā)成功,百度的結(jié)果,哈哈),在官網(wǎng)還可以下載到它的例程,這個模塊功能很簡單,所以手冊看的很輕松。
列一下工作流程:
1、上電等待40ms
2、發(fā)送0x71 查看AHT20狀態(tài)指令
查看狀態(tài)值 [3] 是否為 1
如果為1可以發(fā)送測量指令
如果為0需要初始化:發(fā)送0xBE + 0x08 + 0x00初始化,初始化過程需要等待10ms
3、發(fā)送0xAC + 0x33 + 0x00測量指令,測量過程需要等待80ms
再發(fā)送0x71查看狀態(tài)值[7]是否為0,如果為0表示測量完畢,否者等待。
接收測量結(jié)果,收到7個字節(jié)的數(shù)據(jù)。
主要2個步驟,查狀態(tài),以及測量讀結(jié)果。
我看到一個文章講I2C協(xié)議是有專利權(quán)的,所以一般的產(chǎn)品使用這個協(xié)議都會或多或少的改一點,但是基本過程是一樣的,并不會影響使用,這只是傳聞我并沒有證實。
AHT20地址0x38換成2進制格式 111000,向左移1位結(jié)果是1110000,如果在最后1位就是[0]位設(shè)為0(1110000),就是發(fā)送讀信息,如果[0]位設(shè)置為1(1110001)就是寫信息。
公式:0x38<<1 | 0x1 = 0x70 寫地址; 0x38<<1 |0x0 = 0x71 讀地址;
- // 每個參數(shù) 都寫在函數(shù)里 是為了方便理解閱讀 最后做最終版 要盡量減少冗余操作
- // i2c寫入、讀出操作; rw=0 寫入 rw=1 讀出; *buff 數(shù)據(jù)數(shù)組,讀入就是指令集,返回就是空數(shù)組; leng 數(shù)組的長度 不可以為0;
- void AHT20_I2C_RW(unsigned char rw, unsigned char *buff, unsigned int leng)
- {
- unsigned int state = 0; // I2C 運行 狀態(tài)值 ,單列出來是為了方便作為返回值 做判斷
- WifiIotI2cIdx id = WIFI_IOT_I2C_IDX_0; //設(shè)置I2C使用的通道
- unsigned short writAddr = 0x70; // aht20 ((0x38<<1)|0x0) 寫入地址
- unsigned short readAddr = 0x71; // aht20 ((0x38<<1)|0x1) 讀出地址
- WifiIotI2cData i2cData = {0}; // 參考 wiffiiot_i2c.h 的說明 位置在 \base\iot_hardware\interfaces\kits\wifiiot_lite
- if(rw == 0) // 寫入
- {
- i2cData.sendBuf = buff; //unsigned char* 發(fā)送 數(shù)據(jù) 指針
- i2cData.sendLen = leng; //unsigned int 發(fā)送 數(shù)據(jù) 長度
- state = I2cWrite(id, writAddr, &i2cData); //i2c寫入方法 會有1個狀態(tài)返回值 WIFI_IOT_SUCCESS = 0 代表成功 出錯會返回錯誤代碼 需要加入 wifiiot_errno.h 頭文件
- }
- else if(rw == 1) // 讀出
- {
- i2cData.receiveBuf = buff; //unsigned char* 接收 數(shù)據(jù) 指針
- i2cData.receiveLen = leng; //unsigned int 接收 數(shù)據(jù) 長度
- state = I2cRead(id, readAddr, &i2cData); //i2c讀出方法
- }
- if(state != WIFI_IOT_SUCCESS) // 如果返回值 不等于 WIFI_IOT_SUCCESS 打印state 查詢 wifiiot_errno.h 看錯哪了
- {
- printf("[AHT20_I2C_RW] ERROR !!! %d : %d \r\n", rw, state); // 打印 錯誤信息
- }
- }
這里要重點!重點!重點!的說一下,接收狀態(tài)值,不要!不要!不要!再發(fā)送0x71指令了,直接I2C讀,就會給你傳1個狀態(tài)值,默認的狀態(tài)值的第[7]位是0,當你發(fā)送測量指令的時候,會變成1,進入測量狀態(tài),當測量完以后會重新置為0。開始因為一直無法讀取到正確的狀態(tài)值……#¥%¥%#@%#,一言難盡啊,已經(jīng)過去了。
- // 返回 AHT20 的狀態(tài)值 i是第幾位 0~7
- unsigned char AHT20_Status(unsigned char i)
- {
- unsigned char buff[] = {0};
- unsigned char leng = 1;
- AHT20_I2C_RW(1, buff, leng);
- unsigned char s; //返回狀態(tài)值 0、1
- s = (buff[0] >> i) & 0x01; //狀態(tài)值是1個字節(jié)數(shù)據(jù),我們只需要知道某位的具體值就行 i就是第幾位
- //printf("[AHT20_Status] AHT20_Status 0x%x [%d : %d] !!! \r\n", buff4[0], i, s); // 此行 調(diào)試用 打印 狀態(tài)值
- return s;
- }
結(jié)果會返回7個字節(jié)數(shù)據(jù),第1個字節(jié)是狀態(tài)信息,第2個、第3個、以及第4個字節(jié)的高4位[7][6][5][4]共同組成了濕度值,第4個字節(jié)的低4位[3][2][1][0]、第5位、第6位組成溫度值,最后第7個字節(jié)是效驗位。
然后效驗,主要是目的是檢驗接收到的數(shù)據(jù)是否在傳輸?shù)倪^程中出現(xiàn)錯誤,具體原理和公式以及代碼不仔細說了,過程太碎了,幾次測試,代碼沒問題,具體過程百度一下好多資料,檢驗的時候一定要包括第1個狀態(tài)字節(jié)。
檢驗這步驟不是必須的,但是我開始的時候發(fā)現(xiàn)讀到的數(shù)錯的離譜,開始不清楚是我代碼寫的問題還是模塊本身的問題,然后就把檢驗這個步驟也寫上了,最后發(fā)現(xiàn)模塊沒問題,代碼也沒問題,問題是模塊上有個氣體傳感器會發(fā)熱,所以溫度值高。真的好暈。
最后一步就是就是把讀到的數(shù)值轉(zhuǎn)化為正常的10進制值。
技術(shù)手冊濕度的公式后面有個%號,那個是濕度百分比的意思,我還糾結(jié)過×100%有啥意義呢?當你沒最后完全走通的時候就像在黑暗里摸索,一個小坑都能把你絆的夠嗆。
轉(zhuǎn)化的過程要注意數(shù)值的類型,本來這個取值就是取很小的零頭,如果類型用錯了,就給抹掉了。哈。
- // AHT20 測量溫濕度值
- unsigned char AHT20_Measure(float *ht)
- {
- // 發(fā)送 測量指令
- unsigned char buff1[] = {0xac, 0x33, 0x00};
- unsigned char leng1 = 3;
- AHT20_I2C_RW(0, buff1, leng1); // 默認狀態(tài)值第[7]位是0,發(fā)送完測量指令后狀態(tài)值[7]會置1
- usleep(80*1000); //等待 80ms 時鐘偏快的 所以這個時間內(nèi) 總是不能完成測量
- unsigned char t = 10; //等待時間的值
- while(AHT20_Status(7) != 0) //檢查 狀態(tài)值第[7]位是否從1變?yōu)?,如果沒變就等待5ms,如果已經(jīng)置0說明測量完成
- {
- usleep(10*1000); //10ms 這個時間不要設(shè)置太長,也不要設(shè)置太短,太長時間,小器件很難長時間存儲測量結(jié)果,太短反復應(yīng)答也能會影響測量的穩(wěn)定性,一般情況等1次就會過
- if(--t == 0) // 如果等待時間很長依然沒有變0,說明設(shè)備可能出現(xiàn)異常,為了避免死機,返回0,重新測量 這個情況我還沒遇到
- {
- return 0;
- }
- }
- // 接收 測量結(jié)果
- unsigned char buff2[7] = {0};
- unsigned char leng2 = 7;
- AHT20_I2C_RW(1, buff2, leng2); // 讀返回結(jié)果,一共7個字節(jié),第1個字節(jié)是狀態(tài)值 最后1個字節(jié)是效驗值
- unsigned char i, j;
- unsigned char crc = 0xFF; // 效驗 初值
- // CRC 效驗 固定算法
- for(i=0; i<6; i++)
- {
- crc ^= (buff2[i]);
- for(j=8; j>0; --j)
- {
- if(crc & 0x80)
- {
- crc = (crc << 1) ^ 0x31;
- }
- else
- {
- crc = (crc << 1);
- }
- }
- }
- if(buff2[6] != crc) //CRC值不對 說明傳輸過程可能有干擾 出錯了
- {
- //printf(" CRC8 NO \r\n");
- return 0; // 效驗不正確 回執(zhí)1個錯誤信息
- }
- unsigned int dat1 = 0; // 濕度
- unsigned int dat2 = 0; // 溫度
- dat1 = (dat1 | buff2[1]) << 8;
- dat1 = (dat1 | buff2[2]) << 8;
- dat1 = (dat1 | buff2[3]) >> 4;
- dat2 = (dat2 | buff2[3]) << 8;
- dat2 = (dat2 | buff2[4]) << 8;
- dat2 = (dat2 | buff2[5]) & 0xfffff;
- // 這1大段搬來搬去的 主要是因為 buff2[3] 前4位屬于濕度 后4位屬于 溫度
- // 單片機處理能力有限,主要是針對寄存器值的處理,使用位運算,這樣能節(jié)省算力
- // 處理數(shù)據(jù) 單列出來 便于理解,代碼寫的太簡練 不容易看懂 最終 不需要這么繁瑣
- float hum = 0; // 溫度
- float tem = 0; // 濕度
- // 2^20=1048576 要先類型轉(zhuǎn)換 暫時先這么寫 以后再改得順溜點
- hum = ((float)dat1 / (float)1048576) * (float)100; // 濕度
- tem = ((float)dat2 / (float)1048576) * (float)200 - (float)50; // 溫度
- ht[0] = hum;
- ht[1] = tem;
- return 1; // 測量完畢
- }
軟復位,無需關(guān)閉再次打開電源的情況下重新啟動傳感器。就是軟重啟,長時間停用的再次訪問的時候使用,我這個小應(yīng)用基本用不到,但是還是寫上吧。輸入指令0xBA需要等待20ms。
剩下工作就是把AHT20的代碼和SSD1306的代碼整合到一起。這里要說1點,I2C支持串聯(lián)多個設(shè)備,所以AHT20和SSD1306在一條I2C線上共同使用是沒有問題的。
C語言從來不是我主要的使用語言,所以超級菜啊,一邊寫一邊看C 語言教程。裝個Dev-C++編譯器,有些功能先寫個測試代碼看看。開始寫代碼,不要考慮效率問題,就只想怎么更適合閱讀。開始寫主要的目的是試錯,寫一步編譯一步,不要一次寫全所有功能。
我覺得鴻蒙系統(tǒng)編譯報錯功能非常好,我的每個錯誤都能被準確的指出來。開始專用的查BUG功能對新手來說很難,可以用printf串口打印功能就行,真的好用,因為鴻蒙是多任務(wù)系統(tǒng),所有的功能都是一個單獨的任務(wù),即使你的代碼運行跑壞了,但是系統(tǒng)不會崩,打印功能依然會給你打印出信息來。第一次用真覺得好高級,能直接看到單片機的回話了。
最后不斷對代碼迭代優(yōu)化,最終的目的讓代碼可以更好的被重用。以后還要用的嘛,重復的工作就不要做了。編程過程對我這種新手來說,真是經(jīng)歷情況太多,開發(fā)過程發(fā)生的各種事以后以后單開一篇碎碎念再講吧。
知識有限又剛剛嘗試,所以肯定會有很多錯誤,歡迎給我指正。開始覺得弄這個很簡單,但是很快被現(xiàn)實教育,然后開始認真讀各位大佬的教程,收獲很大,這里由衷的感謝。
想了解更多內(nèi)容,請訪問:
和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos./#zz
分享標題:鴻蒙Hi3861測溫濕度顯示一個新手開發(fā)調(diào)試過程
瀏覽地址:http://fisionsoft.com.cn/article/dheoigp.html


咨詢
建站咨詢
