一、接口说明
建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使用合适的上拉电阻。
1 接线图
2 电源引脚
DHT11的供电电压为3~5.5 V。传感器上电后,要等待 1s 以越过不稳定状态,在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。
3 串行接口(单线双向)
DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:
(1)一次完整的数据传输为40bit,高位先出。
(2)数据格式:
8bit湿度整数数据 + 8bit湿度小数数据 + 8bi温度整数数据 + 8bit温度小数数据 + 8bit校验和
(3)数据传送正确时,校验和数据等于
“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位
二、通信过程
(1)如下图所示,用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。
从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
(2)如下图所示,总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号。主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。
(3)如上图所示,总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据。
(4)如下图所示每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1。如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
数字0信号表示方法如下图所示:
数字1信号表示方法如下图所示:
数字0信号与数字1信号的不同之处在于高电平的时间不同,利用这点,我么们可以通过设置电平时间阈值来判断信号的种类。
三、测量分辨率与电气特性
测量分辨率分别为 8bit(温度)、8bit(湿度)。
VDD=5V,T = 25℃,除非特殊标注
注:最新版厂家升级,采样周期为2秒,老版为1秒。
四:源代码
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_usart.h"
#include "sys.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
static GPIO_InitTypeDef GPIO_InitStructure;
static USART_InitTypeDef USART_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
static EXTI_InitTypeDef EXTI_InitStructure;
static TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
static TIM_OCInitTypeDef TIM_OCInitStructure;
static uint32_t g_freq=100,g_duty=50,g_cmp=0,g_beep=0;
static volatile uint8_t g_usart1_recv_buf[128]={0};
static volatile uint32_t g_usart1_recv_cnt = 0;
static volatile uint32_t g_usart1_event=0;
static volatile uint8_t g_usart3_recv_buf[128]={0};
static volatile uint32_t g_usart3_recv_cnt = 0;
static volatile uint32_t g_usart3_event=0;
//重定义fputc函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART3,ch);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
return ch;
}
void delay_us(uint32_t nus)
{
SysTick->CTRL = 0; // 关闭滴答定时器
SysTick->LOAD = (SystemCoreClock/8/1000000)*nus; // 计数值
SysTick->VAL = 0; // 清空当前值和状态标志位
SysTick->CTRL = 1; // 启动定时器工作
// 检测CTRL的第16位是否为0,如果为0表示定时时间到达
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0; //失能(关闭)滴答定时器
}
void delay_ms(uint32_t nms)
{
while(nms--)
{
SysTick->CTRL = 0; // 关闭滴答定时器
SysTick->LOAD = (SystemCoreClock/8/1000); // 计数值
SysTick->VAL = 0; // 清空当前值和状态标志位
// 选择时钟源(如果是5表示选择168M工作,如果是1选择21M工作)并启动定时器工作
SysTick->CTRL = 1;
// 检测CTRL的第16位是否为0,如果为0表示定时时间到达
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0; //失能(关闭)滴答定时器
}
}
void USART1_Init(uint32_t baud)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = baud; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}
void USART3_Init(uint32_t baud)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
/* GPIOB Configuration: PB10 PB11 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Connect USART3_TX pins to PB10 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
/* Connect USART3_RX pins to PB11 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);
/* Enable USART3 clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
USART_InitStructure.USART_BaudRate = baud;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
/* Enable the USARTx Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable USART3 */
USART_Cmd(USART3, ENABLE);
/* Enable the Rx buffer empty interrupt */
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
}
void USART1_SendBytes(uint8_t *pbuf,uint32_t len)
{
while(len--)
{
USART_SendData(USART1,*pbuf++);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
}
void USART1_SendString(uint8_t *pstr)
{
while(pstr && *pstr)
{
USART_SendData(USART1,*pstr++);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
}
void USART3_SendBytes(uint8_t *pbuf,uint32_t len)
{
while(len--)
{
USART_SendData(USART3,*pbuf++);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
}
}
void USART3_SendString(uint8_t *pstr)
{
while(pstr && *pstr)
{
USART_SendData(USART3,*pstr++);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
}
}
// 初始化dht11 PG9为输出模式
void dht11_outputMode(void)
{
//使能GPIOB,GPIOE时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
//PB6初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PG9引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //普通输出模式,
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,驱动LED需要电流驱动
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化GPIOB,把配置的数据写入寄存器
}
// 初始化dht11 PG9为输入模式
void dht11_inputMode(void)
{
//使能GPIOB,GPIOE时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
//PB6初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PG9引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //普通输出模式,
GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化GPIOB,把配置的数据写入寄存器
}
uint8_t dht11_start(void)
{
int i = 0;
// dht11设置为输出模式
dht11_outputMode();
// 输出20ms低电平
PGout(9) = 0;
delay_ms(20);
// 输出30us高电平
PGout(9) = 1;
delay_us(30);
// 切换为输入模式
dht11_inputMode();
// 是否有低电平
while(i < 100)
{
if(PGin(9) == 1) // 检查到低电平有效
{
break;
}
delay_us(1);
i++;
}
if(i == 100)
return 1;
// 检测是否触发高电平
i = 0;
while(i < 100)
{
if(PGin(9) == 0) // 检查到高电平有效
{
break;
}
delay_us(1);
i++;
}
// 超时
if(i == 100)
return 1;
// 正常退出
return 0;
}
// 读取一字节数据
uint8_t dht11_read_byte(void)
{
int i = 0;
uint8_t data = 0;
// 等待高电平完毕
while(PGin(9));
for(i = 0; i < 8; i++)
{
// 检测低电平
while(PGin(9) == 0);
delay_us(40);
if(PGin(9) == 1)
{
// 截取数据
data |= 1 <<(7-i);
// 等待高电平结束
while(PGin(9));
}
}
return data;
}
uint8_t dht11_read_data(char *dht11_data)
{
int i = 0;
int check_sum = 0;
// 激活dht11工作,准备读取数据
while(dht11_start() == 1);
// 读取5字节数据
// 5字节包括温度整数,温度小数,湿度整数,湿度小数,校验位
for(i = 0; i < 5; i++)
{
dht11_data[i] = dht11_read_byte();
}
// 计算校验和
check_sum = dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3];
if(check_sum != dht11_data[4])
{
return 1;
}
return 0;
}
int main(void)
{
char buf[64]={0};
char dht11_data[5] = {0};
//系统定时器初始化,时钟源来自HCLK,且进行8分频,
//系统定时器时钟频率=168MHz/8=21MHz
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//设置中断优先级分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//串口1,波特率115200bps,开启接收中断
USART1_Init(115200);
//串口3,波特率9600bps,开启接收中断
USART3_Init(9600);
while(1)
{
if(dht11_read_data(dht11_data) == 0)
{
//执行串口3事件
if(g_usart3_event)
{
//将接收到的换行符'\n'变为'\0'
//"temp?\n" -> "temp?"
g_usart3_recv_buf[g_usart3_recv_cnt-1] = '\0';
g_usart3_recv_cnt = 0;
//判断接收到的字符串为temp
if(strstr((char *)g_usart3_recv_buf,"temp"))
{
//以等号分割字符串
strtok((char *)g_usart3_recv_buf,"?");
//发送给蓝牙
sprintf(buf,"temp = %d.%d\r\n",dht11_data[2],dht11_data[3]);
USART3_SendString(buf);
}
if(strstr((char *)g_usart3_recv_buf,"humi"))
{
//以等号分割字符串
strtok((char *)g_usart3_recv_buf,"?");
//发送给蓝牙
sprintf(buf,"humi = %d.%d\r\n",dht11_data[0],dht11_data[1]);
USART3_SendString(buf);
}
//串口3继续工作,允许接收数据
g_usart3_event = 0;
}
}
}
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
uint8_t d;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
//从串口1接收数据
g_usart1_recv_buf[g_usart1_recv_cnt]=USART_ReceiveData(USART1);
//记录多少个数据
g_usart1_recv_cnt++;
//检测到换行符或接收的数据满的时候则发送数据
if(g_usart1_recv_buf[g_usart1_recv_cnt-1]=='\n' || g_usart1_recv_cnt>=(sizeof g_usart1_recv_buf)-1)
{
g_usart1_event = 1;
}
}
}
void USART3_IRQHandler(void)
{
uint8_t d;
/* USART in Receiver mode */
if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
{
//从串口3接收数据
g_usart3_recv_buf[g_usart3_recv_cnt]=USART_ReceiveData(USART3);
g_usart3_recv_cnt++;
//检测到换行符或接收的数据满的时候则发送数据 beep=on\n
if(g_usart3_recv_buf[g_usart3_recv_cnt-1]=='\n' || g_usart3_recv_cnt>=(sizeof g_usart3_recv_buf)-1)
{
// printf("usart3\n\r");
g_usart3_event = 1;
}
d=USART_ReceiveData(USART3);
USART1_SendBytes(&d,1);
}
}