1.引脚定义
Pin | 名称 | 注释 |
---|---|---|
1 | VDD | 供电 3-5.5V |
2 | GND | 接地,电源负极 |
3 | DATA | 串行数据,单总线 |
4 | NC | 空脚,请悬空 |
2.数据格式
- DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。
- 一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。
- 数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。
- 由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。
3.操作时序
通过研究时序来实现对DHT11读取数据和通信的功能。下面将时序拆解,有助于更好地理解和封装功能。
3.1 配置DHT11为输入模式(即32单片机配置为输出)
void DHT11_Init_Out(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
这里通信数据线选择为PA5
3.2 配置DHT11为输出模式(即32单片机配置为输入)
void DHT11_Init_In(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
3.3 实现通信开始时序
DHT11时序图
分析该时序可知,总线空闲状态为高电平,通信开始信号为主机将总线拉低至少18ms,之后再拉高20-40us;之后的总线控制权交由DHT11控制。功能实现代码如下:
void DHT11_Start(void)
{
DHT11_Init_Out();//主机输出模式
DHT11_BitValue(0);//主机拉低总线
Delay_ms(20);//延时20ms
DHT11_BitValue(1);//主机再次拉高总线
Delay_us(22);//延时20ms
DHT11_Init_In();//开始时序结束,总线交于DHT11控制
}
为了方便更改电位,封装函数
void DHT11_BitValue(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5, (BitAction)BitValue);//强转为BitAction类型
}
3.4 读取一个字节
时序图
首先关于数字0和数字1信号的时序区分
数字0信号
数字1信号
由时序图可以得知,数字1信号时序高电平持续时间为70us,远大于数字0信号的26us-38us,因此我们可以采用在30us时读取该电平信号,若为高电平,则为数字信号1。另外其余的7个bit操作完全相同,所以我们可以用for把他们套起来。
功能实现代码如下:
uint8_t DHT11_ReadByte(void)
{
uint8_t data = 0x00;//给数据付一个初始值
uint8_t i = 0;
for(i=0;i<8;i++)//循环8次,接受一个字节
{
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==RESET);//等待电平跳变为高电平
Delay_us(40);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==SET)//此时检测电平高低,若为高电平
{
data |= (0x80>>i);//将该位置一
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==SET);//等待高电平结束,进入下一位
}
}
return data;//返回一个字节的数据
}
3.5 接收完整数据
此时我们实现了通信开始,传输一个字节的数据,这样的字节我们一共有5个,每个的操作与上一步相同,所以我们依然可以用for循环五次,来接收数据
注意在接收数据之前,我们要等待DHT响应 ,所以要判断是否响应
为了方便编写和阅读代码我们用宏定义替换GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
#define dht11_get GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
void DHT11_Data(uint8_t *temp,uint8_t *shidu)
{
int8_t i = 0;
int8_t arr[5];
DHT11_Start();//通信开始时序
DHT11_BitValue(1);//拉高电平,方便检测DHT进入低电平
//检测响应信号
if(dht11_get == 0)
{
while(dht11_get == 0);//低电平等待跳变高电平
while(dht11_get == 1);//高电平等待跳变低电平
for(i=0;i<5;i++)
{
arr[i] = DHT11_ReadByte();
}
DHT11_BitValue(0);//通信结束,DHT11拉低总线
Delay_us(50);
DHT11_Init_Out();//总线交给主机
DHT11_BitValue(1);//拉高总线进入空闲
if(arr[0]+arr[1]+arr[2]+arr[3]==arr[4])//校验位检测
{
*temp = arr[2];
*shidu = arr[0];
}
}
}
3.6 DHT11.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define dht11_get GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
//电平高低翻转
void DHT11_BitValue(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5, (BitAction)BitValue);
}
//stm32对dht11输出
void DHT11_Init_Out(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//DHT11对stm32输入
void DHT11_Init_In(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//通信开始时序
void DHT11_Start(void)
{
DHT11_Init_Out();
//DHT11_BitValue(1);
DHT11_BitValue(0);
Delay_ms(20);
DHT11_BitValue(1);
Delay_us(22);
DHT11_Init_In();
}
//读取一个字节
uint8_t DHT11_ReadByte(void)
{
uint8_t data = 0x00;
int i = 0;
for(i=0;i<8;i++)
{
while(dht11_get==RESET);
Delay_us(40);
if(dht11_get == SET)
{
data |= (0x80>>i);
while(dht11_get==SET);
}
}
return data;
}
//接受完整数据
void DHT11_Data(uint8_t *temp,uint8_t *shidu)
{
int8_t i = 0;
int8_t arr[5];
DHT11_Start();
DHT11_BitValue(1);
//检测响应信号
if(dht11_get==0)
{
while(dht11_get==0);
while(dht11_get==1);
for(i=0;i<5;i++)
{
arr[i] = DHT11_ReadByte();
}
DHT11_BitValue(0);
Delay_us(50);
DHT11_Init_Out();
DHT11_BitValue(1);
if(arr[0]+arr[1]+arr[2]+arr[3]==arr[4])
{
*temp = arr[2];
*shidu = arr[0];
}
}
}
3.7 DHT11.h
#ifndef __DHT11_H
#define __DHT11_H
uint8_t DHT11_Data(uint8_t *temp,uint8_t *shidu);
#endif
3.8 main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "DHT11.h"
uint8_t temp,shidu;
int main(void)
{
OLED_Init();
Serial_Init();
while (1)
{
Delay_s(1);
DHT11_Data(&temp,&shidu);
Serial_Printf("\r\ntemp=%d", temp);
Serial_Printf("\r\nshidu=%d", shidu);
Serial_Printf("\r\n");
}
}
3.9 serial.c(串口输出)
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Byte)//发送一个字节
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length)//发送多字节
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)//发送字符串
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
3.10 serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_Printf(char *format, ...);
#endif
4.功能实现
oled显示屏实现
5.不足
该程序目前还有更进一步的可能,比如在不同的不同时序单元内加入状态返回值,方便判断和调试,欢迎各位与我交流。