【屏幕显示DHT11数据】
面向对象的思想编写硬件驱动程序,DHT11采集环境中的温湿度数据。使用FreeRTOS提供的任务间通信、同步、互斥,将DHT11的数据传递给显示任务。显示任务中,使用emWin中间件,制作屏幕的各种界面,并将DHT11的数据显示到屏幕上。
项目开发流程:
第一个项目内容:屏幕显示DHT11数据
面向对象的思想编写硬件驱动程序,DHT11采集环境中的温湿度数据。使用FreeRTOS提供的任务间通信、同步、互斥,将DHT11的数据传递给显示任务。显示任务中,使用emWin中间件,制作屏幕的各种界面,并将DHT11的数据显示到屏幕上。
项目框架:
TFT彩屏
TFT-LCD 即薄膜晶体管液晶显示器。其英文全称为:Thin Film Transistor-Liquid Crystal Display。TFT-LCD与无源 TN-LCD、STN-LCD 的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关。
TFT-LCD具有:亮度好、对比度高、层次感强、颜色鲜艳等特点。应用于电视、手机、电脑、平板等各种电子产品。
分辨率:240*320 驱动IC:ILI9341 自带触摸屏(电阻触摸屏) 16位80并口驱动 16位真彩显示(65536色)
TFT彩屏引脚定义
TFT写数据
TFT读数据
TFT控制框图
DHT11模块简介
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。
传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。
DHT11电路图
DHT11参数
测量分辨率分别为 8bit(温度)、8bit(湿度)。
DHT11传输数据:串行接口(单线双向)
DHT11采用单总线协议与stm32通信。DHT11只有在接收到开始信号后才会触发温湿度采集。数据采集完毕且无开始信号后,DHT11自动切换到低速模式(复位信号触发DHT11从低速到高速模式)
(1)一次完整的数据传输为40bit,高位先出。
(2)数据格式:
8bit湿度整数数据 + 8bit湿度小数数据 + 8bi温度整数数据 + 8bit温度小数数据 + 8bit校验和
(3)数据传送正确时,校验和数据等于
“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位
注:DHT11传输时,时序非常严格,不允许各种意外情况打断。
DHT11时序 (通信过程)
如下图所示,用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。
从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
如下图所示,总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号。主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。
如上图所示,总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据。
DHT11数据位格式
如下图所示每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1。如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
数字0信号表示方法如下图所示: 数字1信号表示方法如下图所示:
数字0信号与数字1信号的不同之处在于高电平的时间不同,利用这点,我么们可以通过设置电平时间阈值来判断信号的种类。
DHT11驱动程序
DHT11.c
配置输入输出GPIO:
复位DHT11
复位DHT11就是发送DHT11起始信号,告诉传感器通讯开始。
检查DHT11是否正常
检查DHT11是否正常,正常的话会在单片机发送起始信号完成后,传感器返回80us低电平,然后发送80us高电平。即证明DHT11工作正常,该函数工作正常返回0,否则返回1,该函数中利用了while循环检测在一定时间内的电平变化,此类用法在后面也会经常用到。
读取一位数据(返回值0/1)
该函数采用两个while循环是等待每个周期的电平变化,先等待低电平到来,后等待高电平到来,延时40us后判断引脚电平,来判断该位数据为1或0。之所以是40微秒是因为传感器数字0的信号持续时间为26-28us,数字1的信号持续时间为70us,选择一个中值来区分两种信号,当然也可以选择其他值,但最好在40us附近,在while循环中选择循环100次也就是100us,是因为防止当单片机由于某些原因迟迟收不到传感器电平信号,造成死机。
读取一个字节(返回值:读到的数据)
循环读入一个字节的数据,并将每一步新加入的数据放置在最低位。
读取DHT11数据(读取成功返回0,失败返回1)
读取数据将数据存入数组,这里仅保留了温度数据的整数位,注意数据较验方法,校验和数据等于“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
完整版.c文件
#include "dht11.h"
#include "delay.h"
#define DHT11_DelayMs(t) Delay_Ms(t)
#define DHT11_DelayUs(t) Delay_Us(t)
#define DHT11_PIN_HIGH 1
#define DHT11_PIN_LOW 0
#if defined (STM32F40_41xxx)
#include "stm32f4xx.h"
//配置输入输出GPIO:
//温湿度模块输入函数
//浮空输入+设置IO口速度+选择端口
#define __DHT11_CONFIG_IO_INPUT(DHT11) { GPIO_InitTypeDef GPIO_InitStructure; \
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; \
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; \
GPIO_InitStructure.GPIO_Pin = DHT11->pin; \
GPIO_Init(DHT11->port, &GPIO_InitStructure); \
}
//温湿度模块输入函数
//推挽输出+设置IO口速度+选择端口
#define __DHT11_CONFIG_IO_OUTPUT(DHT11) { GPIO_InitTypeDef GPIO_InitStructure; \
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; \
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; \
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; \
GPIO_InitStructure.GPIO_Pin = DHT11->pin; \
GPIO_Init(DHT11->port, &GPIO_InitStructure); \
}
#define __DHT11_IO_SET(DHT11, value) { if (value == DHT11_PIN_HIGH) \
GPIO_SetBits(DHT11->port, DHT11->pin); \
else \
GPIO_ResetBits(DHT11->port, DHT11->pin); \
}
#define DHT11_IO_H(DHT11) {__DHT11_IO_SET(DHT11, DHT11_PIN_HIGH)}
#define DHT11_IO_L(DHT11) {__DHT11_IO_SET(DHT11, DHT11_PIN_LOW)}
#define DHT11_IO_IN(DHT11) GPIO_ReadInputDataBit(DHT11->port, DHT11->pin)
#endif
/******************************************************************************
* @brief 复位DHT11
*
* @param[in] dht11 : dht11结构体指针
*
* @return 0, 表示正常, 其他值表示失败
*
******************************************************************************/
//根据DHT11时序图
//复位DHT11:发送DHT11起始信号,告诉传感器通讯开始
static int DHT11_Rst(DHT11_t *dht11) //DHT端口复位,发出起始信号(IO发送)
{
__DHT11_CONFIG_IO_OUTPUT(dht11); //设置引脚为输出模式
DHT11_IO_L(dht11); //拉低DQ
DHT11_DelayMs(20); //拉低至少18ms 拉低20ms
DHT11_IO_H(dht11); //DQ=1
DHT11_DelayUs(30); //主机拉高20~40us
__DHT11_CONFIG_IO_INPUT(dht11); //设置引脚为输入模式
//随后主机开始读取
return 0;
}
//复位DHT11可以理解为开始
/******************************************************************************
* @brief 等待DHT11的回应
*
* @param[in] dht11 : dht11结构体指针
*
* @return 0, 存在, 返回1:未检测到DHT11的存在
*
******************************************************************************/
//检查DHT11是否正常:检查DHT11是否正常,正常的话会在单片机发送起始信号完成后,传感器返回80us低电平,然后发送80us高电平。
static int DHT11_Check(DHT11_t *dht11) //读取引脚的状态 retry临界值
{
int retry = 0;
while (DHT11_IO_IN(dht11) && (retry < 100)) { //DHT11会拉低40-80us
retry++;
DHT11_DelayUs(1);
};
if (retry >= 100) { //超过100ms说明统计没有相应
return -2;
} else {
retry = 0;
}
while (!DHT11_IO_IN(dht11) && (retry < 100)) { //DHT11拉低后会再次拉高40-80us
retry++;
DHT11_DelayUs(1);
};
if (retry >= 100) {
return -3;
}
return 0;
}
/******************************************************************************
以上没有问题就可以传输数据了
* @brief 从DHT11读取一个位
*
* @param[in] dht11 : dht11结构体指针
*
* @return 0, 1 读取一位数据(返回值0/1)
*该函数采用两个while循环是等待每个周期的电平变化,先等待低电平到来,
*等待高电平到来,延时40us后判断引脚电平,来判断该位数据为1或0。
******************************************************************************/
static uint8_t DHT11_ReadBit(DHT11_t *dht11)
{
int retry = 0;
while (DHT11_IO_IN(dht11) && (retry < 100)) { //等待变为低电平
retry++;
DHT11_DelayUs(1);
}
retry = 0;
while (!DHT11_IO_IN(dht11) && (retry < 100)) { //等待变高电平
retry++;
DHT11_DelayUs(1);
}
DHT11_DelayUs(40);//等待40us
if (DHT11_IO_IN(dht11)) {
return 1;
} else {
return 0;
}
}
/******************************************************************************
* @brief 从DHT11读取一个字节
*
* @param[in] dht11 : dht11结构体指针
*
* @return 读到的数据
*读取一个字节(返回值:读到的数据)
循环读入一个字节的数据,并将每一步新加入的数据放置在最低位。
******************************************************************************/
static uint8_t DHT11_ReadByte(DHT11_t *dht11)
{
uint8_t i, dat;
dat = 0;
for (i = 0; i < 8; i++) {
dat <<= 1;
dat |= DHT11_ReadBit(dht11);
}
return dat;
}
/******************************************************************************
* @brief 从DHT11读取一次数据 把协议全部放进去
* temp:温度值(范围:0~50°)
* humi:湿度值(范围:20%~90%)
*
* @param[in] dht11 : dht11结构体指针
*
* @return 0, 表示正常, 其他值表示失败
*读取DHT11数据(读取成功返回0,失败返回1)
读取数据将数据存入数组,这里仅保留了温度数据的整数位,注意数据较验方法,校验和数据
等于“8bit湿度整数数据+8bit湿度小数数据 +8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
******************************************************************************/
int DHT11_ReadData(DHT11_t *dht11)
{
if (!dht11 || !dht11->init)
return -1; //初始化引脚之后
uint8_t buf[5];
uint8_t i;
DHT11_Rst(dht11); //复位传感器
if (DHT11_Check(dht11) == 0) {
for (i = 0; i < 5; i++) { //读取40位数据 40位 五个字节 连续读五次
buf[i] = DHT11_ReadByte(dht11);
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { //校验 相等说明传输数据么有问题
dht11->humidity = buf[0];
dht11->temperature = buf[2]; //放入温湿度
}
} else {
return -2;
}
return 0;
}
/******************************************************************************
* @brief 初始化DHT11的IO口 DQ 同时检测DHT11的存在.
*DHT11初始化
在上电后,对IO端口初始化,和检查DHT11状态。
* @param[in] dht11 : dht11结构体指针
*
* @return 0, 表示正常, 其他值表示失败
*
******************************************************************************/
int DHT11_Init(DHT11_t *dht11, DHT11_GPIO_Port_t port, uint32_t pin) //对外提供两个接口 触发温湿度传感器 读取数据
{
if(!dht11)
return -1;
//配置引脚,默认输出
#if defined (STM32F40_41xxx)
assert_param(IS_GPIO_ALL_PERIPH(port));
if (port == GPIOA) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); }
else if (port == GPIOB) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); }
else if (port == GPIOC) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); }
else if (port == GPIOD) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); }
else if (port == GPIOE) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); }
else if (port == GPIOF) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); }
else if (port == GPIOG) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); }
else return -1;
__DHT11_CONFIG_IO_OUTPUT(dht11);
#endif
dht11->port = port;
dht11->pin = pin;
dht11->temperature = 0.0;
dht11->humidity = 0.0;
dht11->init = true;
DHT11_IO_H(dht11);
DHT11_Rst(dht11);
return DHT11_Check(dht11);
}
DHT11.h
#ifndef __DHT11_H
#define __DHT11_H
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#if defined (STM32F40_41xxx)
#include "stm32f4xx.h"
typedef GPIO_TypeDef* DHT11_GPIO_Port_t;
#else
#error dht11.h: No processor defined!
#endif
typedef struct dht11_t
{
DHT11_GPIO_Port_t port; //所使用的端口
uint32_t pin; //所使用的引脚
double temperature; //温度值
double humidity; //湿度
bool init; //初始化标志
}DHT11_t;
int DHT11_Init(DHT11_t* dht11, DHT11_GPIO_Port_t port, uint32_t pin); //初始化DHT11 使用哪个引脚
int DHT11_ReadData(DHT11_t* dht11); //获取DHT11的数据
#endif
main.c
#include "main.h"
DHT11_t gDht11; //定义一个温湿度结构体
char show_buffer[100];
int main(void)
{
Debug_Init(115200);
Delay_Init();
printf("DHT11 start\r\n");
LCD_Init(); //初始化屏幕
LCD_Clear(RED); //屏幕显示红色
Delay_Ms(1000);
LCD_Clear(~RED);
Delay_Ms(1000);
/* 初始化DHT11 */
int ret = DHT11_Init(&gDht11, GPIOA, GPIO_Pin_6); //DHT11接到PA6引脚上去
printf("ret: %d\r\n", ret);
/* 每秒读取一次温湿度传感器,并通过串口发送 */
while (1) {
DHT11_ReadData(&gDht11); //读取温湿度传感器的数值
memset(show_buffer, 0, sizeof(show_buffer));
sprintf(show_buffer, "T: %0.2f H: %0.2f\r\n", gDht11.temperature, gDht11.humidity);
printf("%s", show_buffer); //显示
LCD_ShowString(0, 0, 240, 30, 24, show_buffer); //通过LCD屏幕显示
Delay_Ms(1000);
}
}
总结
介绍DHT11温湿度传感器,及STM32版本驱动函数的编写
后续将加入freertos和emwin