鸿蒙Hi3861温湿度实测展示一个新手开发调试过程+AHT20+SSD1306搭建好开发环境,轻松完成点灯任务,想搭建一个像样的应用,然后……决定做一个测量温度和湿度的仪表。一开始觉得这个实现比较简单,通过Hi3861读取AHT20的测量值,然后通过OLED显示温湿度值。首先,我的嵌入式开发知识很少。做了几年Java开发,玩过51单片机,在Linux上部署过一些Web服务。仅此而已,以我的知识无法从宏观的角度思考清楚这件事,只能一步步尝试。如果你也是刚接触嵌入式开发的朋友,那么我的过程或许对你有用。先搭建一个技术大纲:每个电子传感器包括3种端口,(1)1类供电:必须用2条电源线给模块供电,正负极,有的模块有多组供电;(2)Type2control:那么有的模块有一些控制口,每个模块不一样,有的模块没有这样的控制口,可以直接读;(3)Type3通信线:与核心单片机通信的信号端口,根据使用的通信协议不同,端口数量不同。要使用的AHT20和SSD1306都是使用I2C通信协议,所以有两条通信线。我用的模块很简单,主要涵盖这三类端口,所以无论模块长什么样,主要解决的问题都是类似的。主要要做的就是通过通讯口对模块进行数据读写。模块配置:各个模块控制和存储数据由一组8位寄存器控制,每个寄存器有8位,每个位可以存储1或0,组成一个字节值,每个模块都有自己的功能设置和存储设置,可以想象成高级语言中的关键字。寄存器值是什么。如果直接看一组数字,是看不懂什么意思的,所以只能依靠模块提供的技术手册作为指导,边看手册边设置。单片机的开发就是这么简单朴实。关于通信协议:要用到的两个模块都使用I2C通信协议,两条线,一个信号,一个时钟。通信中的双方利用通信线路相互占用,相互发送高电平和低电平来传递消息,即不能同步。在通信中,一方只能发送,另一方只能接收。因为线路少,沟通过程非常繁琐。一方大声询问某地址模块是否在线,然后等待。如果对方收到呼喊,则给出回复。收到回复后,发送指令告诉他要做什么,然后等待。Confirmation,模块收到后发一个确认。。。我在51上模拟过这个过程,好蛋疼,一个时钟信号一个数据信号的个数。。。但是!!!鸿蒙上的繁琐流程一扫而空封装完成后,我们只需要简单调用系统提供的I2C操作方法即可。具体过程完全不需要考虑。用过之后,真的好用,很好用,就是这么简单!所以熟悉一下I2C的基本流程就可以了。这个过程中涉及的具体工作很少。SSD1306首先点亮屏幕。一旦屏幕可以使用,就意味着MCU为你打开了一扇窗。SSD1306不是OLED,它是一块驱动OLED显示屏的控制芯片,很多模块就是一个复杂的单片机,??我们用的OLED屏是由128*64像素组成的,本质上可以简单理解为高端灯光。SSD1306的控制也是通过I2C实现的。虽然支持多种通信协议,但贵如金的Hi3861采用端口占用最少的I2C。我们只需要发送数据给SSD1306,没有反馈值。因此,通信过程相对简单。SSD1306的地址为0x78,0x00用于接收命令,0x40用于接收数据。把这个高度重复的过程做成一个函数,直接调用就可以了。//I2C协议读写函数只有写入要求,cd=0写入命令cd=1写入要写入的数据字节值voidSSD1306_I2C_W(unsignedcharcd,unsignedcharbyt){unsignedintstate=0;//I2C运行状态WifiIotI2cIdxid=WIFI_IOT_I2C_IDX_0;//I2CChannel0unsignedshortdeviceAddr=0x78;//SSD1306地址WifiIotI2cDatai2cData={0};//接收和发送信息的数组详细描述查看wifiiot_i2c.hunsignedcharbuf[]={0x00,byt};//默认0x00写指令集byt为write输入指令if(cd==1)//输入数据{buf[0]=0x40;//0x40表示写入数据字节,即要写入的数据}i2cData.sendBuf=buf;i2cData.sendLen=2;state=I2cWrite(id,deviceAddr,&i2cData);if(state!=WIFI_IOT_SUCCESS){printf([SSD1306_I2C_W]writererror:<%d>!!!\r\n",state);//如果state异常打印错误信息}//returnstate;//也可以作为返回值}驱动命令很多,这是遇到的第一个障碍,看手册,va网上的例子比比皆是,五花八门,虽然大同小异,但更让人摸不着头脑。然后……根据手册的流程图自己写。不要害怕,大胆尝试一下,你就知道行不行。有些设置需要成对出现,一个命令一个参数,但是很多例子放在一起,一边看参数,一边对照命令表。。。崩溃,虽然现在有些命令的功能我还不明白,但是我可以使用手册中的默认值为准,最后也很好用。哈。驱动过程:大部分例子默认使用页面展示方式。我一开始也是用的页面显示方式,后来根据自己的需要改成横排方式。创建一个二维数组来存放显示的信息,显示功能跟Screen功能是分开的,做这个屏幕的时候重点是制作屏幕。这样做的另一个好处是以后复用代码会更方便。这样做是为了简单实现任意坐标显示,以后画波浪显示会更方便。这里还要补充一点。按照之前做小游戏的习惯开始控制画面。好像单片机或者模块都受不了。似乎越简单越好。然后使用一个辅助工具,PCtoLCD2002完美版-(字符模式),这个字体工具超级好用,在这里表达一下我对作者的真挚感情,让最繁琐的工作变得简单。使用的时候注意选项设置,主要是方向,随便写一段代码测试一下。SSD1306每次接收1个字节的数据,表示对一列8个像素点的开关控制,每个字节数据转换成二进制码,如0xFF二进制11111111,每1代表点亮1个像素点。0x00二进制00000000,就是关闭8个像素点。AHT20先看AHT20技术手册,这个手册可以百度找到(国内最小的半导体温湿度传感器AHT20已经研发成功,百度的结果,哈哈),其例程也可以在官网下载.这个模块的功能非常简单。因此手册易于阅读。列举工作流程:1.上电等待40ms2。Send0x71tochecktheAHT20status命令检查状态值[3]如果为1,如果为1,可以发送测量命令。如果为0,需要初始化:发送0xBE+0x08+0x00进行初始化,初始化过程需要等待10ms3,发送0xAC+0x33+0x00测量命令,测量过程需要等待80ms再发送0x71到检查状态值[7]是否为0,若为0则测量完成,否则等待。接收测量结果,接收7字节数据。主要有2个步骤,检查状态,测量和读取结果。看到一篇文章说I2C协议是有专利的,所以一般使用这个协议的产品都会或多或少的改动,但是基本流程是一样的,不影响使用。这只是一个谣言,我还没有证实。.AHT20地址0x38改为二进制格式111000,左移一位结果为1110000。如果最后一位是[0]位设置为0(1110000),则为发送读报文。如果[0]位设置为1(1110001)就是写入信息。公式:0x38<<1|0x1=0x70写地址;0x38<<1|0x0=0x71readaddress;//每个参数都写在函数里面,方便理解和阅读,最后做出最终版本,尽量减少冗余操作//i2c写、读操作;rw=0写入rw=1读取;*buff数据数组,读取为指令集,返回为空数组;leng数组的长度不能为0;voidAHT20_I2C_RW(unsignedcharrw,unsignedchar*buff,unsignedintleng){unsignedintstate=0;//I2C运行状态值,单列是为了方便判断返回值WifiIotI2cIdxid=WIFI_IOT_I2C_IDX_0;//设置I2C使用的通道unsignedshortwritAddr=0x70;//aht20((0x38<<1)|0x0)写地址unsignedshortreadAddr=0x71;//aht20((0x38<<1)|0x1)读地址WifiIotI2cDatai2cData={0};//参考wiffiiot_i2c.h中的描述位置\base\iot_hardware\interfaces\kits\wifiiot_liteif(rw==0)//write{i2cData.sendBuf=buff;//unsignedchar*发送数据指针i2cData.sendLen=leng;//unsignedint发送数据长度state=I2cWrite(id,写地址,&i2cData);//i2cwrite方法会有状态返回值WIFI_IOT_SUCCESS=0,表示成功,出错会返回错误码,需要添加到wifiiot_errno.h头文件}elseif(rw==1)//Read{i2cData.receiveBuf=buff;//unsignedchar*接收数据指针i2cData.receiveLen=leng;//unsignedint接收数据长度state=I2cRead(id,readAddr,&i2cData);//i2c读取方法}if(state!=WIFI_IOT_SUCCESS)//如果返回值不等于WIFI_IOT_SUCCESS打印状态查询wifiiot_errno.hWhatiswrong{printf("[AHT20_I2C_RW]ERROR!!!%d:%d\r\n",rw,state);//打印错误信息}}这里是重点!重点!重点!告诉你,不要收到状态值!不!不!再次发送0x71指令,直接通过I2C读取,会发给你一个状态值。状态值默认[7]位为0,发送测量命令后,变为1,进入测量状态。测量完成后,因为一直没能读到正确的状态值,会被重置为0...#¥%&#¥%#@%#,不好说,完了。//AHT20的返回状态值i为0~7unsignedcharAHT20_Status(unsignedchari){unsignedcharbuff[]={0};unsignedcharleng=1;AHT20_I2C_RW(1,buff,leng);unsignedchars;//返回状态值0,1s=(buff[0]>>i)&0x01;//状态值是1个字节的数据,我们只需要知道某位的具体值即可,i是数字的个数//printf("[AHT20_Status]AHT20_Status0x%x[%d:%d]!!!\r\n",buff4[0],i,s);//这一行调试打印status值returns;}结果会返回7个字节的数据,第一个字Section为状态信息,第2、3、4字节的高4位[7][6][5][4]共同构成湿度值,第4字节的低4位[3][2][1][0],第5、6位组成温度值,最后第7个字节为校验位。那么效果,主要是为了检查接收到的数据在传输过程中是否有错误。具体原理、公式、代码就不详述了。流程过于分散。经过多次测试,代码没有问题。数据,检查时必须包含第一个状态字节。这一步检查不是必须的,但当我开始时,我发现我读到的数字错得离谱。一开始不知道是我代码写的问题还是模块本身的问题。然后写了检查的步骤,最后发现模块没问题,代码没问题,问题是模块上有个气体传感器会发热,所以温度值偏高。真头晕。最后一步是将读取的值转换为正常的十进制值。技术手册中的湿度公式后面有一个%符号,表示湿度的百分比。我想知道×100%是什么意思?足以绊倒你。在转换过程中注意值的类型。本来,这个值是一小部分。如果类型错误,它将被删除。哈。//AHT20测量温湿度值unsignedcharAHT20_Measure(float*ht){//发送测量命令unsignedcharbuff1[]={0xac,0x33,0x00};unsignedcharleng1=3;AHT20_I2C_RW(0,buff1,leng1);//默认状态value[7]位为0,发送测量命令后状态值[7]会被设置为1usleep(80*1000);//等待80ms时钟太快,无法完成测量这次unsignedchart=10;//WaitTimevaluewhile(AHT20_Status(7)!=0)//检查statusvalue的[7]位是否由1变为0,如果没有则等待5ms,如果有设置为0,测量完成{usleep(10*1000);//10ms的时间不能设置太长或太短。如果太长,小型设备很难长时间存储测量结果。如果太短,重复响应也会影响测量的稳定性。一般wait一次就会passif(--t==0)//如果等待的时间很长,仍然没有变为0,说明设备可能出现了异常。为了避免crash,返回0,重新测量这种情况。没遇到过{return0;}}//接收测量结果unsignedcharbuff2[7]={0};unsignedcharleng2=7;AHT20_I2C_RW(1,buff2,leng2);//读取返回结果,共7个字节,第一个字节是状态值的最后一个字节是校验值unsignedchari,j;unsignedcharcrc=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("CRC8NO\r\n");return0;//Incorrectvalidationreceipt1errormessage}unsignedintdat1=0;//Humidityunsignedintdat2=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;//这一大段挪来挪去主要是因为buff2[3]的前4位属于湿度,后4位属于温度//单片机的处理能力芯片微机有限,主要是对寄存器值进行处理,使用位运算,可以节省计算力//处理数据单独列出来方便理解,代码过于简洁不易理解,最后不用了太麻烦了floatum=0;//temperaturefloatem=0;//humidity//2^20=1048576需要先进行类型转换,暂时这样写,然后改成更平滑hum=((float)dat1/(float)1048576)*(float)100;//湿度tem=((float)dat2/(float)1048576)*(float)200-(float)50;//温度ht[0]=hum;ht[1]=tem;return1;//测量完成}软复位,无需关机再开机重启传感器即为软重启。在长时间不活动后重新访问时使用它。这个小应用我基本不会用,还是记录一下吧。输入命令0xBA需要20ms。剩下的工作就是将AHT20和SSD1306的代码整合在一起。这里要说一点,I2C是支持多台设备串联的,所以在一根I2C线上同时使用AHT20和SSD1306是没有问题的。C语言一直不是我的主要语言,所以我是个超级菜,边写边看C语言教程。安装一个Dev-C++编译器,写一段测试代码,看一些功能。当你开始写代码的时候,不要考虑效率,只要考虑如何更适合阅读。开始写的主要目的是试错,一步写一步编译,不要一下子把所有的功能都写完。我觉得鸿蒙系统的编译报错功能很好,每一个错误都能准确指出。专门的查错功能,新手很难上手。可以使用printf串口打印功能,真的很好用,因为鸿蒙是多任务系统,所有功能都是单任务,即使你的代码跑错了,但系统不会崩溃,打印功能仍会为您打印出信息。第一次用,真的觉得很高级,可以直接看到单片机的回复。最后对代码进行迭代优化,最终目的是让代码更好的复用。如果以后需要用到,就不要做重复性的工作了。对于我这样的新手来说,编程过程真的经历了太多的情况。开发过程中发生的各种事情,我会在后面单独的一篇文章中讲到。本人见识有限,只是尝试了一下,错误肯定不少,欢迎指正。一开始觉得做这件事很简单,但是很快就被现实教育了,然后开始认真看大佬们的教程,收获颇丰。在这里真诚的感谢你们。了解更多请访问:与华为官方共建鸿蒙科技社区https://harmonyos.51cto.com/#zz
