核心问题:如何根据电路和时序来写程序
接口协议:单总线(意味着一根数据线实现接收和发送,所以相应的GPIO口要设置为2种模式(输入/输出))
电路:如果没有在电路中作5k上拉,可以把GPIO模式设置成GPIO_Mode_IPU实现软件上拉的效果时序:时序解读:初始化(MCU复位+DHT11响应)、数据传输、结束
(1)初始化
初始化可分为2部分,即主机复位+从机响应
主机复位的时序图为红框所示:主机至少拉低18us,然后主机拉高20~40us
(注意;此时的GPIO口为输出模式,并且为了模拟上拉效果,使用GPIO_Mode_IPU)
void DHT11_User_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}
/*GPIO初始化*/
void DHT_Mode(uint16_t mode)//mode==1时,配置为输出模式; mode==0,配置为输入模式
{
GPIO_InitTypeDef GPIO_InitStructure;
if(mode == 1)
{
GPIO_InitStructure.GPIO_Pin = DHT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
}
else
{
GPIO_InitStructure.GPIO_Pin = DHT;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//软件实现5K上拉电阻
}
GPIO_Init(GPIOB,&GPIO_InitStructure); //调用库函数初始化GPIOB
}
/*向dht11发送启动信号,这也叫复位信号*/
void Send_Start(void)
{
DHT_Mode(OUT);//OUT被宏定义为1,即DHT_Mode(1):输出模式
DHT_Low;//#define DHT_Low GPIO_ResetBits(DHT_PROT,DHT)
delay_ms(18);//拉低总线,保持18ms
DHT_High;//释放总线,#define DHT_High GPIO_SetBits(DHT_PROT,DHT)
delay_us(40);//释放总线后要等待20~40us
}
DHT11响应的时序是绿色框所示
先拉低80us,再拉高80us
这里为了省事(不严谨),不管dht11有没有应答,都用程序来模拟“先拉低80us,再拉高80us”的信号,让单片机认为dht11应答成功
(注意:此时DHT11发送应答信号,是从机向主机发送信号,所以GPIO口配置为输入模式)
/*传感器发送应答信号*/
//当器件检测到MCU复位信号后,拉低总线80us表示应答。再拉高总线80us开始传输数据
//这里强制认为dht11应答成功
void DHT_Send_Response(void)
{
DHT_Mode(INT);//单片机的引脚改为接收信号的状态
//检测从机低电平的时间长度,如果100us内从机不应答就退出while,继续往下执行
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 0) && (Time < 100))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
//检测从机高电平的时间长度
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1) && (Time < 100))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持1
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
}
(2)数据传输
数据无非是0/1
dht11向主机发送高低电平
50us的低电平+26~28us高电平,表示数据0
dht11向主机发送高低电平
50us的低电平+70us高电平,表示数据1
(注意:把数据中该写1的部分写1之后,其余部分自动写0,所以程序中只要写1的部分。为什么?我也不知道,B站的一个视频是这么说的)
/*dht11读取1字节数据*/
uint8_t DHT11_Read_Byte(void)
{
uint8_t data = 0;
uint8_t i;
for(i=0;i<8;i++)//按位接受8位数据
{
DHT_Mode(INT);
//来1个50us的延时,50us的低电平是数据传输的开始
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 0) && (Time < 50))//每1us,Time自增1次 判断50us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
data <<= 1;
delay_us(40);//26~28us的高电平指数据0 70us的高电平指数据1 40是自己取的介于26~28与70之间的值
//40us后依然为高电平就是数据1
//这里只要写1就行,程序设置什么时候写1。剩下的自动填充为0
if(GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1)
{
data |=0x01;//数据从低位往高位移
//等待高电平结束
//30us+delay_us(40)==70us。70us的高电平即是数据1
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1) && (Time < 30))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
}
}
return data;
}
(3)数据传输结束后,通过程序来解读温湿度并校验
一次完整的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和
数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度
/*DATA[0]~DATA[4]:8位的湿度正数数据 8位的湿度小数数据 8位温度整数数据 8位温度小数数据 8位的校验和*/
//读取温湿度的值并校验
void DHT11_Read_Data(uint8_t *humi,uint8_t *temp)
{
uint8_t i;
uint8_t DATA[5] = {0,0,0,0,0};
Send_Start();//初始化
DHT_Send_Response();//dht11应答成功
//dht11应答成功之后就开始传输数据了
for( i=0;i<5;i++)//读取40位数据
{
DATA[i] = DHT11_Read_Byte();
}
delay_ms(1);
if((DATA[0]+DATA[1]+DATA[2]+DATA[3] == DATA[4]))//如果校验没问题则输出测量结果
{
*humi = DATA[0];
*temp = DATA[2];
}
else//否则数据清零
{
for( i=0;i<5;i++)
{
DATA[i] = 0;
}
}
}
整体程序代码块
dht11.c
#include "dht11.h"
#include "delay.h"
uint16_t Time = 0;
/*开时钟*/
void DHT11_User_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}
/*GPIO初始化*/
void DHT_Mode(uint16_t mode)//mode==1时,配置为输出模式; mode==0,配置为输入模式
{
GPIO_InitTypeDef GPIO_InitStructure;
if(mode == 1)
{
GPIO_InitStructure.GPIO_Pin = DHT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
}
else
{
GPIO_InitStructure.GPIO_Pin = DHT;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//软件实现5K上拉电阻
}
GPIO_Init(GPIOB,&GPIO_InitStructure); //调用库函数初始化GPIOB
}
/*向dht11发送启动信号,这也叫复位信号*/
void Send_Start(void)
{
DHT_Mode(OUT);//OUT被宏定义为1,即DHT_Mode(1):输出模式
DHT_Low;//#define DHT_Low GPIO_ResetBits(DHT_PROT,DHT)
delay_ms(18);//拉低总线,保持18ms
DHT_High;//释放总线,#define DHT_High GPIO_SetBits(DHT_PROT,DHT)
delay_us(40);//释放总线后要等待20~40us
}
/*传感器发送应答信号*/
//当器件检测到MCU复位信号后,拉低总线80us表示应答。再拉高总线80us开始传输数据
//这里强制认为dht11应答成功
void DHT_Send_Response(void)
{
DHT_Mode(INT);//单片机的引脚改为接收信号的状态
//检测从机低电平的时间长度,如果100us内从机不应答就退出while,继续往下执行
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 0) && (Time < 100))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
//检测从机高电平的时间长度
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1) && (Time < 100))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持1
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
}
/*dht11读取1字节数据*/
uint8_t DHT11_Read_Byte(void)
{
uint8_t data = 0;
uint8_t i;
for(i=0;i<8;i++)//按位接受8位数据
{
DHT_Mode(INT);
//来1个50us的延时,50us的低电平是数据传输的开始
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 0) && (Time < 50))//每1us,Time自增1次 判断50us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
data <<= 1;
delay_us(40);//26~28us的高电平指数据0 70us的高电平指数据1 40是自己取的介于26~28与70之间的值
//40us后依然为高电平就是数据1
//这里只要写1就行,程序设置什么时候写1。剩下的自动填充为0
if(GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1)
{
data |=0x01;//数据从低位往高位移
//等待高电平结束
//30us+delay_us(40)==70us。70us的高电平即是数据1
while((GPIO_ReadInputDataBit(DHT_PROT,DHT) == 1) && (Time < 30))//每1us,Time自增1次 判断100us内,总线信号有没有一直保持0
{
Time++;
delay_us(1);
}
Time = 0;//上述while循环的判断结束后,Time清零
}
}
return data;
}
/*DATA[0]~DATA[4]:8位的湿度正数数据 8位的湿度小数数据 8位温度整数数据 8位温度小数数据 8位的校验和*/
//读取温湿度的值并校验
void DHT11_Read_Data(uint8_t *humi,uint8_t *temp)
{
uint8_t i;
uint8_t DATA[5] = {0,0,0,0,0};
Send_Start();//初始化
DHT_Send_Response();//dht11应答成功
//dht11应答成功之后就开始传输数据了
for( i=0;i<5;i++)//读取40位数据
{
DATA[i] = DHT11_Read_Byte();
}
delay_ms(1);
if((DATA[0]+DATA[1]+DATA[2]+DATA[3] == DATA[4]))//如果校验没问题则输出测量结果
{
*humi = DATA[0];
*temp = DATA[2];
}
else//否则数据清零
{
for( i=0;i<5;i++)
{
DATA[i] = 0;
}
}
}
dht11…h
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
#define DHT GPIO_Pin_5
#define DHT_PROT GPIOB
#define OUT 1
#define INT 0
#define DHT_High GPIO_SetBits(DHT_PROT,DHT)
#define DHT_Low GPIO_ResetBits(DHT_PROT,DHT)
void DHT11_User_Config(void);
void DHT_Mode(uint16_t mode);
void Send_Start(void);
void DHT_Send_Response(void);
uint8_t DHT11_Read_Byte(void);
void DHT11_Read_Data(uint8_t *humi,uint8_t *temp);
#endif
main.c
#include "stm32f10x.h"
#include "stm32f10x_tim.h"
#include "GPIOLIKE51.h"//sys.h和GPIOLIKE51.h基本一致,delay.h中包含了sys.h所以不再使用GPIOLIKE51.h
#include "delay.h"
#include "oled.h"
#include "dht11.h"
//extern const unsigned char BMP1[];
//extern const unsigned char BMP2[];
uint8_t humi,temp;
int main(void)
{
float length;
delay_init();
delay_ms(1500);
OLED_Init();
OLED_Clear();
DHT11_User_Config();
while(1)
{
DHT11_Read_Data(&humi,&temp);
OLED_ShowNum(80,3,humi,3,16);
OLED_ShowNum(80,5,temp,3,16);
}
}
oled的驱动文件就不贴了