写在前面:本节我们学习使用两个常见的传感器模块,分别为DHT11温湿度传感器以及BH1750FVI光照传感器,这两种传感器在对于环境监测中具有十分重要的作用,因为其使用简单方便,所以经常被用于STM32的项目之中。今天将使用分享给大家,希望对大家有一些帮助。
一、认识DHT11
1.1基本定义
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11 虽然也是采用单总线协议,但是该协议与 DS18B20 的单总线协议稍微有些不同之处。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个 I/O 口。传感器内部湿度和温度数据 40Bit 的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11 功耗很低,5V 电源电压下,工作平均最大电流 0.5mA。
常见的DHT11模块,其中左边为3引脚的,右图为4引脚,具体 的引脚说明在下文进行讲解;
1.2技术参数
电压范围 | 3.3V-5.5V |
工作电流 | 0.5mA |
输出 | 单总线数字信号 |
测量范围 | 温度:0-50℃; 湿度:20-90%RH |
精度 | 温度:±2℃; 湿度:±5%RH |
分辨率 | 温度:1℃ 湿度:1%RH |
1.3引脚说明
PIN | 名称 | 说明 |
1 | VDD | 供电3.3-5.5V |
2 | GND | 接地,电源负极 |
3 | DATA | 数据线,单总线 |
4 | NC | 空脚,悬空即可 |
根据上述表格可知为什么有两种样子的模块,如果是4引脚的,则说明吧NC印出来了,如果是3引脚的则说明已经模块已经帮助浮空了。
1.4协议与数据格式
DHT11仅有一根数据线,所以单片机与其进行数据传输时,采用的是单总线的传输方式,单总线的详细说明可以看我之前的博客;51单片机---DS1802温度传感器(含源码,小白可入)_ds18b20负温度显示代码-CSDN博客
虽然 DHT11 与 DS18B20 类似,都是单总线访问,但是 DHT11 的访问,相对 DS18B20 来
说简单很多。下面我们先来看看 DHT11 的数据结构。
DHT11 数字温湿度传感器采用单总线数据格式。即单个数据引脚端口完成输入输出双向传输。其数据包由 5byte(40bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先处。DHT11 的数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数部分+8bit 校验和。其中校验和数据为前面四个字节相加。
温湿度计算方法:
湿度:byte4.byte3=45.0%RH
温度:byte2.byte1=28.0℃
校验:byte4+byte3+byte2+byte1=73
传输时序图:
首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(10~35us)时间,然后读取 DHT11 的响应,正常的话,DHT11 会拉低数据线,保持 t3(78~88us)时间,作为响应信号,然后 DHT11 拉高数据线,保持 t4(80~92us)时间后,开始输出数据。
DHT11 输出数字‘0’和‘1’时序,一开始都是 DHT11 拉低数据线 54us,后面拉高数据
线保持的时间就不一样,数字‘0’就是 23~27us,而数字‘1’就是 68~74us。
二、认识BH1750FVI
2.1基本定义
BH1750FVI是一种用于两线式串行总线接口的数字型光强度传感器集成电路。这种集成电路可以根据收集的光线强度数据来调整液晶或者键盘背景灯的亮度。利用它的高分辨率可以探测较大范围的光强度变化。
2.2 技术参数
供电电压 | 3v-5v兼容 |
输出方式 | IIC通信协议 |
测量范围 | 1-65535lx |
误差变动 | ± 20%。 |
工作电流 | >200uA |
工作温度 | -40-80℃ |
2.3引脚说明
PIN | 引脚 | 说明 |
1 | VCC | 供电电压3-5V |
2 | SCL | IIC时钟线 |
3 | SDA | IIC数据线 |
4 | ADDR | 设备IIC的地址线 |
5 | GND | 电源地 |
2.4协议与数据格式
2.4.1BH1750的指令
常见指令有:
断电:0000_0000
通电:0000_0001
连续H分辨模式:0001_0000//分辨率为1lx,测量时间120ms;
连续H分辨模式2:0001_0001//分辨率为0lx,测量时间120ms;
连续L分辨模式:0001_0011//分辨率为4lx,测量时间16ms;
一次H分辨模式:0010_0000//分辨率为1lx,测量时间120ms,测量后设置为断电模式;
一次H分辨模式2:0010_0001//分辨率为0lx,测量时间120ms,测量后设置为断电模式;
一次L分辨模式:0010_0011//分辨率为4lx,测量时间16ms,测量后设置为断电模式;
2.4.2 设备地址
在BH1750FVI的芯片说明中,模块的从地址有两种形式,由ADDR端口决定:
即:如果ADDR=1(高电平),其从地址为1011100;
如果ADDR=0(低电平),其从地址为0100011;
2.4.3工作过程
芯片供电--断电--通电--测量指令(设置测试模式)--进行测量
ADDR=0(低电平):
ADDR=1(高电平):
数据格式:
接收完两个字节还不算完成,因为这个数据还不是测量出来的光照强度值,我们还需要进行计算,计算公式是:光照强度 =(寄存器值[15:0] * 分辨率) / 1.2 (单位:勒克斯lx)
例如:我们读出来的第1个字节是(10000011),第2个字节是(10010000),那么合并之后就是(1000 0011 1001 0000),换算为( 2^15 + 2^9 + 2^8 + 2^7 + 2^4 ) / 1.2 =28067 [ lx ],最后等于28067 lx。
三、编程教学
3.1DHT11
1、使能DHT11数据线的GPIO时钟
2、设置GPIO的工作模式
此处采用开漏输出,并且需要进行上拉;
3、编写信号代码
复位:拉低数据线,至少保持18ms,然后拉高10-35us;
应答:等待DHT11拉低,保持78-88us;
读取:0/1,DHT11 拉低数据线延时 54us,然后拉高数据线延时一定时间,主机通过判断高电平时间得到 0 或者 1。
4、编写DHT11读函数,在读1bit的基础上,读取1字节数据;
3.2BH1750
1、初始化IIC
IIC引脚,功能模式,起始信号,停止信号,发送一个字节,接收一个字节,应答信号,非应答信号,等待应答;
2、通电
通过IIC协议发送通电指令;
3、设置工作模式
通过IIC协议发送对应工作模式指令;
4、读取数据
通过IIC协议读取接收的数据;
四、代码
链接:https://pan.baidu.com/s/1fOfkPlrrwuya5g5AXyOEjw
提取码:1022
下面为主要的一些代码,全部文件在百度网盘中查看。
4.1 main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/DHT/dht11.h"
#include "./BSP/BH1705/BH1705.h"
uint8_t t,h;
int main(void)
{
float light;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化,波特率:115200 */
LED_init(); /* LED初始化 */
dht11_init(); /* dht11初始化 */
init_BH1750(); /* BH1750初始化 */
while(1)
{
light= light_intensity();
printf("光照强度:%4.2f LX\r\n", light);
dht11_read_data(&t,&h);
printf("温度:%d℃ 湿度:%d %\r\n",t,h);
delay_ms(500);
}
}
4.2 dht11.c
#include "./BSP/DHT/dht11.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
/**
* @brief dht11初始化函数
* @param 无
* @retval 返回检查的结果
*/
uint8_t dht11_init(void)
{
__HAL_RCC_GPIOG_CLK_ENABLE();/* 使能时钟此次dht11数据线采用PG11引脚 */
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_OD;/* 设置为开漏模式 */
gpio_init_struct.Pin=GPIO_PIN_11;/* 定义引脚 */
gpio_init_struct.Pull=GPIO_PULLUP;/* 定义上拉 */
gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH ;/*高速输出 */
HAL_GPIO_Init(GPIOG, &gpio_init_struct);
dht11_rest();/* 复位 */
return dht11_check();
}
static void dht11_rest(void)
{
dht11_write_out(0);
delay_ms(20);
dht11_write_out(1);
delay_us(30);
}
/**
* @brief dht11检查函数
* @param 无
* @retval 返回检查的结果
*/
uint8_t dht11_check(void)
{
uint8_t time=0;
uint8_t backdata=0;
while(dht11_read && time<100)
{
time++;
delay_us(1);
}
if(time>=100)
{
backdata=1;
}
else
{
time=0;
while(!dht11_read && time<100)
{
time++;
delay_us(1);
}
if(time>=100)
{
backdata=1;
}
}
return backdata;
}
/**
* @brief dht11读取结果函数1bit
* @param 无
* @retval 返回读取的结果
*/
uint8_t dht11_read_bit(void)
{
uint8_t time=0;
while(dht11_read && time<100)
{
time++;
delay_us(1);
}
time=0;
while(!dht11_read && time<100)
{
time++;
delay_us(1);
}
delay_us(40);
if(dht11_read)
return 1;
else
return 0;
}
/**
* @brief dht11读取结果函数1bety
* @param 无
* @retval 返回读取的结果
*/
uint8_t dht11_read_bety(void)
{
uint8_t i,data=0;
for(i=0;i<8;i++)/* 循环8次,读取8个bit数据 */
{
data<<=1;/* 先读取高位,逐渐左移 */
data|=dht11_read_bit();
}
return data;
}
/**
* @brief dht11整理读取结果函数
* @param 温度地址, 湿度地址
* @retval 无
*/
void dht11_read_data(uint8_t *temp,uint8_t *humi)
{
uint8_t arr[5]={0};/* 定义5个字节数组,且初始值为0 */
uint8_t i;
dht11_rest();
if(dht11_check()==0)/* 判断是否与DHT11连接上 */
{
for(i=0;i<5;i++)/* 循环5次,读取5个字节数据 */
arr[i]=dht11_read_bety();
if( arr[4]==(arr[0]+arr[3]+arr[2]+arr[1]))/* 进行5个字节数据检验 */
{
*temp=arr[2];/* 温度的整数部分 */
*humi=arr[0];/* 湿度的整数部分 */
}
}
}
4.3 bh1705.c
#include "./BSP/BH1705/BH1705.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 初始化IIC
* @param无
* @retval无
*/
void iic_init()
{
__HAL_RCC_GPIOB_CLK_ENABLE(); /* 使能时钟此次IIC采用PB6,PB7引脚 */
GPIO_InitTypeDef gpio_init_struct; /* 定义对应结构体 */
/* 设置IIC中SCL引脚 */
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;/* 推挽输出工作模式 */
gpio_init_struct.Pin=GPIO_PIN_6;/*定义引脚 */
gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;/* 输出速度 */
gpio_init_struct.Pull=GPIO_PULLUP; /* 上拉模式 */
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_OD;/* 开漏输出工作模式 */
gpio_init_struct.Pin=GPIO_PIN_7;/*定义引脚 */
gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;/* 输出速度 */
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
}
static void iic_delay(void)
{
delay_us(2);
/* 2us 的延时, 读写速度在 250Khz 以内 */
}
/**
* @brief IIC起始信号
* @param无
* @retval无
*/
void icc_start()
{
IIC_SCL(1);/*拉高时钟线*/
IIC_SDA(1);/*拉高数据线*/
iic_delay();/*延时*/
IIC_SDA(0);/*拉低数据线*/
iic_delay();/*延时*/
IIC_SCL(0);/*拉低时钟线*/
iic_delay();/*延时*/
}
/**
* @brief IIC停止信号
* @param 无
* @retval 无
*/
void icc_stop()
{
IIC_SDA(0);/*拉低数据线*/
iic_delay();/*延时*/
IIC_SCL(1);/*拉高时钟线*/
iic_delay();/*延时*/
IIC_SDA(1);/*拉高数据线*/
iic_delay();/*延时*/
}
/**
* @brief IIC发送一个字节
* @param 发送的数据
* @retval无
*/
void iic_send_byte(uint8_t data)
{
uint8_t i;
for (i=0;i<8;i++)/*每次发送一位*/
{
IIC_SDA((data&0x80)>>7);/*高位在前*/
iic_delay();
IIC_SCL(1);/*产生一个时钟脉冲*/
iic_delay();
IIC_SCL(0);
iic_delay();
data<<=1;/*左移,用于下次发送*/
}
IIC_SDA(1);/*释放数据*/
}
/**
* @brief IIC接收一个字节
* @param 是否发送接收应答
* @retval 接收到的数据
*/
uint8_t icc_recive_byte(uint8_t ack)
{
uint8_t i,recive;
for(i=0;i<8;i++) /* 接收 1 个字节数据 */
{
recive<<=1; /* 高位先输出,所以先收到的数据位要左移 */
IIC_SCL(1);/*产生一个时钟脉冲*/
iic_delay();
if(IIC_READ_SDA)/*判断数据是0还是1*/
{
recive++;
}
IIC_SCL(0);
iic_delay();
}
if(ack==1)
{
iic_ack();/* 发送应答 */
}
else
{
iic_nack();/* 发送非应答 */
}
return recive;/* 返回接受的数据 */
}
/**
* @brief IIC等待接收应答函数
* @param 无
* @retval 接收应答情况,return0应答,return1非应答
*/
uint8_t icc_wait_ack(void)
{
uint8_t wattime=0;
uint8_t rack=0;
IIC_SDA(1);/* 释放数据线 */
iic_delay();
IIC_SCL(1);/* 产生一个时钟信号 */
iic_delay();
while(IIC_READ_SDA)/* 等待应答 */
{
wattime++;
if(wattime>250)/* 没有应答 */
{
icc_stop();
rack=1;
break;
}
}
IIC_SCL(0);/* 拉低时钟线 */
iic_delay();
return rack;
}
/**
* @brief IIC发送应答信号
* @param 无
* @retval 无
*/
void iic_ack(void)
{
IIC_SDA(0);/*拉低数据线*/
iic_delay();/*延时*/
IIC_SCL(1);/*拉高时钟线,产生一个时钟脉冲*/
iic_delay();/*延时*/
IIC_SCL(0);/*拉低时钟线*/
iic_delay();/*延时*/
IIC_SDA(1);/*释放数据线*/
iic_delay();/*延时*/
}
/**
* @brief IIC发送非应答信号
* @param 无
* @retval 无
*/
void iic_nack(void)
{
IIC_SDA(1);/*拉高数据线*/
iic_delay();/*延时*/
IIC_SCL(1);/*产生时钟信号*/
iic_delay();/*延时*/
IIC_SCL(0);
iic_delay();/*延时*/
}
/**
* @brief BH1750单总线发送函数
* @param 发送的指令
* @retval 无
*/
void single_write_BH1750(uint8_t adress)
{
icc_start();//起始信号
iic_send_byte(SlaverAddress);//发送设备地址+写信号
while(icc_wait_ack()==1);
iic_send_byte(adress);//发送指令
while(icc_wait_ack()==1);
icc_stop();
}
/**
* @brief BH1750读取一个字节数据
* @param 无
* @retval 接收的指令
*/
uint16_t read_data_BH1750()
{
uint8_t H_data;/*定义变量-存放高字节数据*/
uint8_t L_data;/*定义变量-存放低字节数据*/
uint16_t data;/*定义变量-整理数据*/
icc_start();
iic_send_byte(SlaverAddress+1);/*发送设备地址+读信号*/
H_data=icc_recive_byte(1);/*读取高字节数据,并回应答信号,能够继续接受数据*/
L_data=icc_recive_byte(0);/*读取低字节数据,并回非应答信号,不再接受数据*/
icc_stop();
data=((uint16_t)H_data<<8)+L_data;/*整理数据*/
return data; /*返回数据*/
}
/**
* @brief BH1750初始化函数
* @param 无
* @retval 无
*/
void init_BH1750(void)
{
iic_init();
delay_ms(100);
single_write_BH1750(0x01);/*通电*/
}
/**
* @brief BH1750检测数据函数
* @param 无
* @retval 返回检测的数据
*/
float light_intensity(void)
{
float i;
single_write_BH1750(0x01);/*通电*/
single_write_BH1750(0x10);/*设置BH1750工作模式*/
delay_ms(180);/*延时180ms,给检测留够充足的时间*/
i=(float)(read_data_BH1750()) / 1.2 ;/*数据进行转换*/
return i;
}
五、实验现象
dht11+bh1750
如果用的是最小系统或者其他开发板只要保证DHT11的数据位,已经BH1750的SDA、SCK位与代码中一致即可。
总结:本节我们学习了两个常见模块DHT11温湿度传感器与BH1750光照传感器,利用STM32进行数据采集以及利用串口显示在电脑屏幕。为后面的学习制作单片机的项目具有十分重要的参照作用。大家可以进行自己的练习。。
创作不易,还请大家多多点赞支持!!!