最近在做某个小项目时,需要使用DHT11温湿度传感器。传感器使用单总线传输协议需要遵守一定的时序才能正常使用,但是最后却遇到了一个很奇怪的问题——无论怎么调试,结果都DHT11传输回来的信号都只有高电平,根本没有低电平。
在排除了硬件芯片烧毁、电路连接错误的问题之后,发现主要原因由两个:
DHT11使用手册上写着“当总线空闲的时候,总线总是高电平。”以及“开启时序错误,传感器将不会响应”。既然传感器的总线无论怎样都只有高电平没有低电平,会不会是传感器的开启信号根本就是错的?
经过调试,答案的确是“是"。程序的时序有问题,问题的关键在这两个个delay_us()和delay_ms()上。
我原本使用的的两个delay函数是从当初初学时使用的正点原子的代码那边直接copy过来的。查看这两个函数可以发现,都是调用了系统的Ticky时钟来进行计时延迟的。那是使用的stm32芯片型号是STM32F103ZE,然而此刻我所使用的芯片型号是STM32F103C8,型号不同导致系统时钟不同,导致了同样的一个delay_ms(),延迟的时间不一样!!
使用kile5的debug查看延迟1s实际上使用了多少时间。可以看到实际上运行了7.2s!!由此可以得出结论——整个工程的时间延迟都是错的,直接导致启动信号不对,而启动信号的时序都已经错了,难怪之后传感器根本没有响应。
运行前系统时间0.0001866s
运行后系统时间7.2001912s,然而代码中写的应该延迟1s的。
解决办法:最简单的就是将所有延迟数据全部除以7.2,然而这只是治标不治本,对于其他同样需要使用系统时钟的外设(如定时器等),类似的错误将同样存在。所以,最根本的还是更改系统的时钟频率。
这个实际上并不是错误,只是在调试过程走的一条弯路。
概括来说就是,不可在数据位高低电平传输过程中设置断点观察数据,而应该等待接收的一帧数据全部都接收完毕之后再检查数据。前者会导致接收到的数据异常,然而采用后者之后会发现根本没有错误。
推测原因还是DHT11的接收时序比较严格吧,设置断点对接收时序有很大影响。
总结一下,这个传感器并不难使用,但在使用过程中一定要注意时间的延迟问题。
最后附上亲测能用的程序。
DHT11.c:
#include "sys.h"
#include "delay.h"
#include "DHT11.h"
#include "led.h"
u8 temperoture = 0,humiture = 0;
//--------------------------------------------------------
//设置引脚输入
void setpin_input(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//--------------------------------------------------------
//设置引脚输出
void setpin_output(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//--------------------------------------------------------
//开启应答信号
int reset_DHT11(void)
{
u8 count = 0;
setpin_output(); //Êä³öµÍµçƽ
GPIO_ResetBits(GPIOB,GPIO_Pin_10);
delay_ms(3); //ÖÁÉÙ18msµÍµçƽ
GPIO_SetBits(GPIOB,GPIO_Pin_10);
delay_us(4);
setpin_input(); //ÊäÈëģʽ,DHT11¿ªÊ¼Ó¦´ð
while( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0 ) //80us低电平
{
}
while( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1 && count < 100) //80us高电平
{
count++;
}
if(count == 100) //超时,响应失败
{
return 0;
}
else
{
return 1;
}
}
//--------------------------------------------------
//读取一位数据
uint8_t read_bit(void)
{
uint8_t onebit = 0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0); //50us低电平
delay_us(6);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 1) //50us后还是高电平的就是数据1
{
onebit = 1;
}
return onebit;
}
//--------------------------------------------------
//读取数据
void read_data(void)
{
uint8_t data[5] = {0,0,0,0,0};
u8 i,j,flag;
do
{
flag = reset_DHT11();
}
while(flag != 1); //直到响应成功
for(i=0;i<5;i++)
{
for(j=0;j<8;j++)
{
data[i] <<= 1; //左移一位
data[i] |= read_bit(); //按位或
}
}
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10) == 0); //信号结束低电平
setpin_output(); //设置输出模式,挂起信号线
if(data[4] == data[0]+data[1]+data[2]+data[3])
{
humiture = data[0];
temperoture = data[2];
}
delay_us(2);
}
DHT11.h:
#ifndef _DHT11_H_
#define _DHT11_H_
#include "stdio.h"
#include "sys.h"
void setpin_input(void);
void setpin_output(void);
int reset_DHT11(void);
uint8_t read_bit(void);
void read_data(void);
#endif