项目描述:DHT11温湿度传感器采集温湿度数据分别显示在LED屏幕和通过串口打印出来。
外设:DHT11温湿度传感器、CH340 USB转TTL、OLED0.96 显示屏。
控制芯片:STM32F103C8T6核心板。
集成环境:keil5
一、DHT11传感器采集温度的原理
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。
![](https://img-blog.csdnimg.cn/direct/2f7b398cb2464240bd7172dde7a8e150.png)
以上就是我们通常使用接线方式,stm32和dht11进行数据通信和同步,就是通过DATA串行接口(单线双向)。
![](https://img-blog.csdnimg.cn/direct/085c7d9362fe421195cc3c2282975991.png)
知道了相应的数据格式,我们等下在分析数据的时候,就容易处理很多.
接下来,就是不可避免的要理解它们通讯的时序图,只有理解之后,我们才能写好代码。废话不多说 直接上图。
![](https://img-blog.csdnimg.cn/direct/e67e4f2b5f514cef84afc5138fcfae90.png)
第一阶段:主机至少拉低18ms。
第二阶段:主机拉高20-40us。
一二阶段 stm32f103 的GPIO 应该是处于输出模式。
第三阶段:DHT开始拉低电平回应,这个过程持续80us左右,不一定就是80us整整。
第四阶段:DHT拉高电平 持续80us。
接下来就是开始传输40bits的数据。
一二阶段 stm32f103 的GPIO 应该是处于输入模式。
过程大概就是这样子。在这个过程时,
要特别注意的GPIO 输入输出模式的改变。
过程就类似于回合制游戏一样 我打你一下,你打我一下,还是挺好理解的。
知道原理之后 直接开始写代码。
1、配置输入输出模式。
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体
void DHT11_OutputMode(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
//GPIO Configure
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //GPIO引脚,使能PB12引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
}
void DHT11_InputMode(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
//GPIO Configure
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //GPIO模式,赋值为输入浮空模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //GPIO引脚,使能PB12引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
}
2、开始通讯
uint32_t DHT11_Start(void)
{
uint32_t i = 0;
//开启输出模式
DHT11_OutputMode();
//给PB12引脚赋值 主机拉低
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
//等待20ms
Delay_ms(20);
//给PB12引脚赋值 主机拉高
GPIO_SetBits(GPIOB,GPIO_Pin_12);
//等待30us
Delay_us(30);
//开启输入模式
DHT11_InputMode();
i = 0;
while(i < 100)
{
//检测PB12引脚的低电平
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)== 0)
break;
Delay_us(1);
i++;
}
//检测DHT11响应是否超时
if(i >= 100)
{
return 1;
}
//Led_Disp(1,0); //led1亮
i = 0;
while(i <= 100)
{
//检测PB12引脚的高电平
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12) == 1)
break;
Delay_us(1);
i++;
}
//检测DHT11响应是否超时
if(i >= 100)
{
return 1;
}
i = 0;
while(i < 100)
{
//检测PB12引脚的低电平
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)== 0)
break;
Delay_us(1);
i++;
}
//检测DHT11响应是否超时
if(i >= 100)
{
return 1;
}
//响应成功
return 0;
}
3、响应成功之后,开始接受数据。
uint8_t dht11_read_byte(void)
{
uint8_t d=0;
uint32_t i=0;
//?????????
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)==1);
for(i=0; i<8; i++)
{
//?????
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)==0);
//??40us
Delay_us(40);
//??PG9?????,???????bit1
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)==1)
{
d|=1<<(7-i);
//?????????
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)==1);
}
}
return d;
}
uint32_t dht11_read_data(uint8_t *pbuf)
{
uint32_t i=0;
uint8_t check_sum=0;
//唤醒DHT11
while(DHT11_Start()==1);
//读取数据
for(i=0; i<5; i++)
{
pbuf[i] = dht11_read_byte();
}
//????????
check_sum = pbuf[0]+pbuf[1]+pbuf[2]+pbuf[3];
//校验和
if(check_sum != pbuf[4])
return 1;
return 0;
}
不过,写代码的时候,一定要确保自己的延时函数一定要精确,后续发生数据读取不出来的问题很多都是因为延时函数不准确。
二、串口通信
这个我讲起来就很多啦,我们直接上链接
这个博主就讲得差不多。约定好一定的波特率
其实就是对USART的配置、数据传输函数、USART中断服务函数。
下面就是一个简单的示例:
void Usart_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟
/*GPIOA Configue*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//PA.9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出50HZ
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//不使用校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流
USART_InitStructure.USART_Mode = USART_Mode_Tx;//Tx_modes
USART_Init(USART1, &USART_InitStructure);//将结构体变量交给USART_Init,配置USART1
/* Enable USART */
USART_Cmd(USART1, ENABLE);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收数据的中断
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* Enable the USART1 Interrupt */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Usart_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Usart_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
其他的就是数据传输函数 下面也是一个简单的示例,其他函数都是建立在它的基础上。
void Usart_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
三、OLED显示
这个就不太好说了,直接上代码
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
测试整合
main.c
#include "stm32f10x.h"
#include "stdio.h"
#include "Delay.h"
#include "Dht11.h"
#include "usart.h"
#include "OLED.h"
int main(void)
{
uint8_t tempArray[5] = {0};//温湿度数据
OLED_Init();
Usart_Init();
while(1)
{
Delay_ms(50);//间隔500ms
if(dht11_read_data(tempArray) == 0)//读取数据
{
//温度
OLED_ShowString(1,1,"temp:");
OLED_ShowNum(2,1,tempArray[2],2);
//湿度
OLED_ShowString(3,1,"humi:");
OLED_ShowNum(4,1,tempArray[0],2);
}
Delay_ms(500);
Usart_Printf("temp:%d\t\n",tempArray[2]);
Usart_Printf("humi:%d\t\n",tempArray[0]);
}
}
总结
总结就是理解DHT11采集温湿度的原理,就很好做出来。
是挺抽象的,我的文章
最后是接线情况
DHT11_DATA------------->PB12
DHT11_GND------------->GND
DHT11_VCC------------->VCC
TXD------------->PA10
RXD------------->PA9
USB_TTL_GND------------->GND