文章目录
- 需求
- 一、DHT11温湿度传感器
- 二、模块配置流程
- 1.配置时钟和IO
- 2.读取数据
- 3.数据处理
- 三、导入语音模块
- 四、关键代码
- 总结
需求
1.完成DHT11温湿度检测模块的配置。
2.处理DHT11获取的数据,在串口打印处理后的实时数据。
2.通过Su-03t语音识别模块实现实时温湿度的问答。
一、DHT11温湿度传感器
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有枀高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。
引脚说明:
1、VDD 供电 3.3~5.5V DC
2、DATA 串行数据,单总线
3、NC 空脚
4、GND 接地,电源负枀
二、模块配置流程
1.配置时钟和IO
由原理图可知该模块连接的是stm32的PG11引脚所以此时我们只需要配置PG11的引脚即可。
根据该模块手册中的数据时序图可知:该引脚需要既能输入也能输出,所以为了能够同时满足输入和输出,我们将PG11引脚配置成开漏模式。
代码如下(示例):
void DHT11_Config()
{
//配置为开漏模式
//开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
//配置io
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStruct);
DHT11HIGH();
}
配置完成后拉高电平是为了满足主机“拉高等待”状态。
为了方便后续操作,先重定义一下拉高电平,拉低电平和读操作。
#define DHT11HIGH() GPIO_SetBits(GPIOG,GPIO_Pin_11)
#define DHT11LOW() GPIO_ResetBits(GPIOG,GPIO_Pin_11)
#define DHT11read() GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_11)
2.读取数据
根据官方提供的外设读取步骤一步步进行操作即可
由图可知要先拉高电平
再拉低电平并持续18ms
再输出高电平,然后用while检测83us的低电平应答信号。
收到应答信号后,再用while检测87us的高电平外设准备接受信号。
接收到高电平外设准备接受信号后就可以接收40位的数据了。
直接定义一个数组进行for循环接收。
由于该模块发送的数据为高低电平信号,并且是根据高电平持续时间的不同来判断究竟是高电平还是低电平,此时我们只需要加个延迟,比差值大 ,比74us小即可,此处我用的是45us。
3.数据处理
由手册可知,为了能够准确的将温湿度转换为10进制
此刻我们先用for循环将40个数据进行8位分组处理。
for(i=0;i<40;i++)
{ //0~7 8~15 16~23 24~31 32~39
data[i/8]+=(arr[i]<<(7-i%8));
}
分完组后在计算下和校验一下。
最后根据手册说明将16位数据转换为10进制并打印出来。
//根据DHT11时序读取数据
void DHT11_ReadData()
{
uint16_t i = 0;
uint16_t timeout = 0;
uint8_t data[5] = {0};
uint8_t arr[50] = {0};
//输出一个最少18ms的低电平,最大30ms
DHT11HIGH();
DHT11LOW();
Delay_nms(18);
DHT11HIGH();
//检测有持续83us低电平和87us的高电平
timeout=0;
while(DHT11read()==1)//检测83us低电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
timeout=0;
while(DHT11read()==0)//检测87us的高电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
//读取40位数据
for(i=0;i<40;i++)
{
timeout=0;
while(DHT11read()==1)//等待us低电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
timeout=0;
while(DHT11read()==0)//等待能判断的高电平标志
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
Delay_nus(45);
arr[i]=DHT11read();
}
for(i=0;i<40;i++){ //0~7 8~15 16~23 24~31 32~39
data[i/8]+=(arr[i]<<(7-i%8));
}
if(((data[0]+data[1]+data[2]+data[3])&0xff) != data[4]){
return;
}
hum=data[0]+data[1]/10.0;
tem=data[2]+(data[3]&0x7f)/10.0;
if((data[3]&0x80) != 0){
tem = 0-tem;
}
printf("读取完毕:湿度:%.1f 温度:%.1f\r\n",hum,tem);
return;
}
三、导入语音模块
先在su03t.c文件中外部声明一下处理后的温度和湿度。
extern float hum;
extern float tem;
查看语音模块固件烧录时定义的指令,找到湿度温度播报的指令
可以看到温度指令为06,湿度指令为07。
最后将该数据和指令添加到int Su03tDealData()函数中就完成了。
四、关键代码
main.c
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "kqm.h"
#include "string.h"
#include "su03t.h"
#include "dht11.h"
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
SysTick_Config(72000);
Kqm_U4Config();
Su03t_U5Config();
DHT11_Config();
while(1)
{
if(led2cnt[0]>=led2cnt[1])
{//过去2s
led2cnt[0]=0;
DHT11_ReadData();
}
KQM_DealData();
Su03tDealData();
}
}
dht11.c
#include "dht11.h"
float hum,tem;
//PG11要能够切换输入输出
void DHT11_Config()
{
//配置为开漏模式
//开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
//配置io
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStruct);
DHT11HIGH();
}
//根据DHT11时序读取数据
void DHT11_ReadData()
{
uint16_t i = 0;
uint16_t timeout = 0;
uint8_t data[5] = {0};
uint8_t arr[50] = {0};
//输出一个最少18ms的低电平,最大30ms
DHT11HIGH();
DHT11LOW();
Delay_nms(18);
DHT11HIGH();
//检测有持续83us低电平和87us的高电平
timeout=0;
while(DHT11read()==1)//检测83us低电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
timeout=0;
while(DHT11read()==0)//检测87us的高电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
//读取40位数据
for(i=0;i<40;i++)
{
timeout=0;
while(DHT11read()==1)//等待us低电平
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
timeout=0;
while(DHT11read()==0)//等待能判断的高电平标志
{
timeout++;
Delay_nus(1);
if(timeout>=100)
{
return ;
}
}
Delay_nus(45);
arr[i]=DHT11read();
}
for(i=0;i<40;i++){ //0~7 8~15 16~23 24~31 32~39
data[i/8]+=(arr[i]<<(7-i%8));
}
if(((data[0]+data[1]+data[2]+data[3])&0xff) != data[4]){
return;
}
hum=data[0]+data[1]/10.0;
tem=data[2]+(data[3]&0x7f)/10.0;
if((data[3]&0x80) != 0){
tem = 0-tem;
}
printf("读取完毕:湿度:%.1f 温度:%.1f\r\n",hum,tem);
return;
}
dht11.h
#ifndef _DHT11_H_
#define _DHT11_H_
#include "stm32f10x.h"
#include "delay.h"
#include "stdio.h"
#include "string.h"
void DHT11_Config();
void DHT11_ReadData();
#define DHT11HIGH() GPIO_SetBits(GPIOG,GPIO_Pin_11)
#define DHT11LOW() GPIO_ResetBits(GPIOG,GPIO_Pin_11)
#define DHT11read() GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_11)
#endif
su03t.c
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
#include "su03t.h"
typedef struct{
uint8_t u5_recv[10];//保存数据数组
uint8_t u5_cnt;//数组下标
uint8_t u5_tflag;
}UART5DATA;//数据类型
void Su03t_U5Config()//串口5 PC12(TX) PD2(RX)
{
//开时钟U5 PD12(TX) PD2(RX)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);
//配置PC12(TX)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推完输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
//PD2(RX)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStruct);
//配置串口5 波特率9600 数据位8,校验位0,停止位1
USART_InitTypeDef USART_InitStruct = {0};//可以通过结构体类型跳转
USART_InitStruct.USART_BaudRate = 9600;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流不开
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//打开接收
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(UART5,&USART_InitStruct);
//使能串口
USART_Cmd(UART5,ENABLE);
//配置串口4的中断(采用中断接收)
USART_ITConfig(UART5,USART_IT_RXNE,ENABLE);//使能串口4 的接收非空中断
USART_ITConfig(UART5,USART_IT_IDLE,ENABLE);//总线空闲中断
NVIC_SetPriority(UART5_IRQn,6);//设置优先级0~15
NVIC_EnableIRQ(UART5_IRQn);//使能中断通道
}
//串口5发送单字节函数
void Uart5Senddata(uint8_t data)
{
//等待发送完成
while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==0);
//如果上次发送完成,就发送
USART_SendData(UART5,data);
}
//串口5发送数组函数
void U5_Sendarr(uint8_t * data,uint32_t len)
{
uint32_t i=0;
for(i=0;i<len;i++){
Uart5Senddata(*data);
data++;
}
}
UART5DATA u5_data={0};
void UART5_IRQHandler()//串口5中断执行
{
uint8_t data=0;
//判断接收中断是否发生
if(USART_GetITStatus(UART5,USART_IT_RXNE)==SET)
{
data = UART5->DR;
//USART1->DR = data;//回显
u5_data.u5_recv[u5_data.u5_cnt]=data;
u5_data.u5_cnt++;
u5_data.u5_cnt%=10;
}
//触发空闲中断,表示总线空闲,接收完毕
if(USART_GetITStatus(UART5,USART_IT_IDLE)==SET)
{
data = UART5->SR;//清理空闲中断,先读SR再读DR
data = UART5->DR;
u5_data.u5_tflag=1;
}
}
//将double转换成8位类型数组arr
void DoubleToUint8(double data,uint8_t *arr)
{
uint8_t *p = (uint8_t *)&data;
uint8_t i=0;
for(i=0;i<8;i++)
{
arr[i]=p[i];
printf("%02x ",*(p+i));
}
printf("\r\n");
return;
}
extern float voc;
extern float ch2o;
extern float co2;
extern float hum;
extern float tem;
int Su03tDealData()
{
if(u5_data.u5_tflag!=1)
{
return 1;
}
if(u5_data.u5_recv[0]!=0xAA||u5_data.u5_recv[1]!=0x55)
{
printf("数据帧头出错\r\n");
return 2;
}
if(u5_data.u5_recv[4]!=0xAA||u5_data.u5_recv[3]!=0x55)
{
printf("数据帧尾出错\r\n");
return 3;
}
switch(u5_data.u5_recv[2])
{
case 1:printf("接收01,空气质量指令\r\n");
Su03tSendMsg(1,voc);//voc
break;
case 2: printf(" 接收02,甲醛指令\r\n");
Su03tSendMsg(2,ch2o);//voc
break;
case 3: printf(" 接收03,Co2指令\r\n");
Su03tSendMsg(3,co2);//voc
break;
case 6: printf(" 接收06,温度指令\r\n");
Su03tSendMsg(6,tem);
break;
case 7: printf(" 接收07,湿度指令\r\n");
Su03tSendMsg(7,hum);
break;
}
memset(&u5_data,0,sizeof(u5_data));
return 0;
}
//拼接指令函数 AA 55 04 00 00 00 00 00 80 37 40 55 AA
void Su03tSendMsg(uint8_t cmd,double data)
{
uint8_t msg[13]={0};//存放要发送的指令
msg[0]=0xAA;
msg[1]=0x55;
msg[2]=cmd;
DoubleToUint8(data,&msg[3]);
msg[11]=0x55;
msg[12]=0xAA;
//通过串口5发送
U5_Sendarr(msg,13);
}
su03t.h
#ifndef _SU03T_H_
#define _SU03T_H_
#include "stm32f10x.h"
void Su03t_U5Config();
void Uart5Senddata(uint8_t data);
void U5_Sendarr(uint8_t * data,uint32_t len);
void UART5_IRQHandler();
void DoubleToUint8(double data,uint8_t *arr);
int Su03tDealData();
void Su03tSendMsg(uint8_t cmd,double data);
#endif
总结
1.先根据原理图和该模块的手册,配置对应的时钟和io。
2.进行数据读取,严格按照该模块手册中的时序一步一步来。
3.最后进行数据处理并导入到语音模块。