> 文档中心 > OpenHarmony学习笔记——Hi3861使用DHT11获取温湿度

OpenHarmony学习笔记——Hi3861使用DHT11获取温湿度

文章目录

  • 前言
  • DHT11简介
  • 通信流程
  • 硬件连接
  • 编程实现
  • 效果一览
  • 总结

前言

此文主要是使用Hi3861的GPIO口,模拟1—Wire时序,获取类单总线协议器件DHT11的温湿度信息,由于小熊派官方的温湿度采集是用的I2C接口的SHT30来实现的,而笔者手头没有这个模块,只有一个DHT11,结合前面的经验,Hi3861的代码逻辑跟STM32差不多,理论上是可以移植的,于是就有了以下写bug的时光。

DHT11简介

DHT11内部包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接,在使用过程中,这个内置的8位单片机会实时采集温湿度并做好转换,等到主机端的起始信号就开始发送40位的数据。学习过单总线的其他器件的同学都知道,单总线的一般通信流程是遵循初始化、ROM、命令功能命令三部曲来实现的,而DHT11由于内部有单片机的存在,所以不需要写入ROM和命令来实现功能,这需要一个起始信号即可实现数据传输。想要深入了解的同学可以参考此文——DHT11使用笔记。这里重点关注下其传输的一帧数据的内容。
在这里插入图片描述
数据传送正确时校验和数据等于“ 8bit 湿度整数数据 +8bit 湿度小数数据
+8bi 温度整数数据 +8bit 温度小数数据 ”所得结果的末8位。

通信流程

对于编程,最主要还是要搞清楚通信流程,尤其是这种使用GPIO进行模拟时序的。DHT11的通信流程如下:
1.总线初始化;(主机拉低总线,时长不小于18ms,然后拉高总线,使得总线回到高电平20-40us,等待从机应答)。
2.DHT11应答;(DHT11将总线拉低80us,然后再拉高80us)。
3.输出数据帧(一次完整的数据传输为40bit,高位先出)。
在这里插入图片描述
实测波形如下,后面框起来的是应答信号和40位的数据(图片截取自立创EDA的【DHT11温湿度计】)。
请添加图片描述
有关这个波形的详细讲解可以去看立创EDA的——【DHT11温湿度计】在示波器上的波形!。

硬件连接

由于采用的是模拟1-Wire的时序,所以可以选取任意一个GPIO口来实现,笔者这里使用的是GPIO11,根据芯片手册最好是使用带上拉电阻的模块。
在这里插入图片描述
接线如下:

Hi3861开发板 DHT11引脚
GPIO11 OUT
5V +
GND -

在这里插入图片描述

编程实现

GPIO API简介

弄清楚了上述的通信流程,可以发现,整个过程中,传输数据的GPIO管脚需要进行输入与输出的切换,在产生起始信号时,GPIO需要配置为输出模式,产生高低电平的切换;在检测应答信号及接收数据时,需要配置成浮空输入模式检测总线的高低电平状态。在这个过程中需要使用到Hi3861的GPIO API接口函数。有关GPIO控制的接口函数目录位置为base\iot_hardware\interfaces\kits\wifiiot_lite
wifiiot_gpio.hwifiiot_gpio_ex.h包含了所有和GPIO操作有关的函数接口。
在这里插入图片描述
这里简介一下需要使用到函数,详细用法直接.h的注释吧,和STM32的GPIO类似:
在这里插入图片描述

复位总线

在产生起始信号时的配置方法:
首先,GpioInit()初始化GPIO;然后配置为普通IO模式;再然后配置为输出模式,最后根据时序要求配置输出高低电平。
笔者这里GPIO2是用来做运行指示灯的。代码如下:

#define DHT11_GPIO  WIFI_IOT_IO_NAME_GPIO_11IO操作函数   #defineDHT11_DQ_OUT_High GpioSetOutputVal(DHT11_GPIO, 1); //设置GPIO输出高电平#defineDHT11_DQ_OUT_Low GpioSetOutputVal(DHT11_GPIO, 0); //设置GPIO输出低电平  /****************************************设置端口为输出*****************************************/void DHT11_IO_OUT(void){    //设置GPIO_11为输出模式    GpioSetDir(DHT11_GPIO, WIFI_IOT_GPIO_DIR_OUT);}//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在     u8 DHT11_Init(void){ //初始化GPIO    GpioInit();    //设置GPIO_2的复用功能为普通GPIO    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_2, WIFI_IOT_IO_FUNC_GPIO_2_GPIO);    //设置GPIO_2为输出模式    GpioSetDir(WIFI_IOT_GPIO_IDX_2, WIFI_IOT_GPIO_DIR_OUT); //设置GPIO_2输出高电平点亮LED灯    GpioSetOutputVal(WIFI_IOT_GPIO_IDX_2, 1);    //设置GPIO_11的复用功能为普通GPIO IoSetFunc(DHT11_GPIO, WIFI_IOT_IO_FUNC_GPIO_11_GPIO);    //设置GPIO_11为输出模式    GpioSetDir(DHT11_GPIO, WIFI_IOT_GPIO_DIR_OUT); //设置GPIO_11输出高电平    GpioSetOutputVal(DHT11_GPIO, 1);    DHT11_Rst();  //复位DHT11 return DHT11_Check();//等待DHT11的回应} //复位DHT11void DHT11_Rst(void)   {  DHT11_IO_OUT(); //SET OUTPUT   DHT11_DQ_OUT_Low; //拉低DQ    hi_udelay(20000);//拉低至少18ms   DHT11_DQ_OUT_High; //DQ=1 hi_udelay(35);     //主机拉高20~40us}

DHT11应答

在等待应答阶段,需要将GPIO口配置为浮空输入模式,并且需要检测GPIO的状态来判断DHT11连接是否正常。
代码流程:
首先将GPIO切换为输入模式,然后检测GPIO状态,判断总线的高低电平状态是否满足应答信号。
代码如下:

//获取GPIO输入状态u8 GPIOGETINPUT(WifiIotIoName id,WifiIotGpioValue *val){    GpioGetInputVal(id,val);    return *val;}/****************************************设置端口为输入*****************************************/void DHT11_IO_IN(void){    GpioSetDir(DHT11_GPIO, WIFI_IOT_GPIO_DIR_IN);//配置为输入模式    IoSetPull( DHT11_GPIO, WIFI_IOT_IO_PULL_NONE);//配置为浮空输入}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void)    {   u8 retry=0;  DHT11_IO_IN();//SET INPUT     while (GPIOGETINPUT(DHT11_GPIO,&DHT11_DQ_IN)&&retry<100)//DHT11会拉低40~80us{retry++;hi_udelay(1);}; if(retry>=100)return 1;else retry=0; while ((!GPIOGETINPUT(DHT11_GPIO,&DHT11_DQ_IN))&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;hi_udelay(1);};if(retry>=100)return 1;   return 0;}

数据读取

在检测到DHT11的应答后,DHT11会紧接着发送40位BIT的数据,需要通过固定时间间隔去读取总线状态来获取。
编程流程:
继续配置为输入模式,间隔固定时间去读取总线电平,进而得到数据,最后根据数据格式解析出所需内容。
代码如下:

//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void)  { u8 retry=0;  while(GPIOGETINPUT(DHT11_GPIO,&DHT11_DQ_IN)&&retry<100){//等待变为低电平 retry++; hi_udelay(1);    }    retry=0;    while((!GPIOGETINPUT(DHT11_GPIO,&DHT11_DQ_IN))&&retry<100){//等待变高电平 retry++; hi_udelay(1);    }    hi_udelay(40);//等待40us//用于判断高低电平,即数据1或0    if(GPIOGETINPUT(DHT11_GPIO,&DHT11_DQ_IN))return 1; else return 0;}//从DHT11读取一个字节//返回值:读到的数据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读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 DHT11_Read_Data(u8 *temp,u8 *humi)    {  u8 buf[5]={ 0 };u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{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;    }

以上三步对应的示波器实际效果如下图(图片截取自立创EDA):
请添加图片描述
注:这里的实现需要导入hi_time.h头文件,调用hi_udelay()实现而不能用usleep()函数。
至此就可以通过DHT11获取到温湿度了,上述代码有极少部分缺失,如有需要,私信笔者,建议大家自己试着配配看。

效果一览

在这里插入图片描述

总结

本文主要是使用使用Hi3861的GPIO口去模拟单总线的时序,进而读取到DHT11的温湿度值,整体代码可以参考任何一个STM32版本的代码,需要特别注意的是,整个过程中需要使用阻塞式的延时hi_udelay(1),而不能使用非阻塞式的usleep(),非阻塞式的延时会产生任务流转,会影响时序,造成数据读取失败,笔者踩了这个坑,分享给大家避雷用。