- 温湿度传感器
- 时序图
- 实验连线
- 代码解析
- 实验结果
本实验使用的温湿度传感器模块是DHT11,实物图如下:
模块参数:
该模块无法测量零下温度,但一般日常使用是没有问题的。
模块内部是函数模数转换器芯片(8位),将采集到的模拟量转换成数字量显示,由于该模块外部有一个塑料壳,所以温度改变会延迟几秒。
该模块一共有4个引脚,但是NC引脚不使用,可以省去
STM32芯片引脚模式可以设为上拉输入模式,即GPIO_Mode_IPU,因此不用外加上拉电阻。
上拉电阻的作用:上拉电阻可以不确定的信号通过一个电阻钳位在高电平,电阻同时起限流作用。
芯片的管脚加上拉电阻可以提高输出电平,从而提高芯片输入信号的噪声容限,增强抗干扰能力。
DHT11模块与单片机通信传输数据依靠的是DATA这跟线,属于半双工通信,如果传输完数据要做延时,避免数据冲突。
数据传输格式:
编程的时候可以将这40bit数据分成5个字节(40=5*8),使用数组存储字节数据
时序图非常关键,反映数据通信过程,编程就是根据时序图编程
①通信开始
DATA总线空闲状态处为高电平,主机(MCU)把总线拉低至少18ms等待从机(DHT11模块)响应,然后再拉高20-40us,此后再延时一段时间,使电平处于稳定状态(时序图中斜坡代表电平不稳定,可以通过延时使之稳定后再进行下一步 *** 作),这一段作为起始信号。
如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。
②通信过程
如果从机模块接收到起始信号,则会发出响应信号回应主机,此时需要将主机模式设置为上拉输入模式,接收从机的发回来的响应信号。
如果读取总线为低电平,说明DHT11发送响应信号;如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常。
DHT11发送响应信号后,再把总线拉高80us,准备发送数据。
数据0和1的前置电平都以50us的低电平,之后会发生变化区分,通过延时高电平的长短决定数据位是0还是1。格式见下面图示:
比如延时40us,若检测到DATA上的电平为高电平则是数据1,如果是低电平则是数据0。
③通信结束
当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
这里完成了一个字节的数据传递,只需要循环5次则完成了一次完整数据的传输,通过数组获取温湿度数据。
实验连线 代码解析这里关于延时做一个说明,这里使用的是系统滴答定时器SYSTICK,否则计数会不精确,导致出错。
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
#define USE_DWT_DELAY 1 /* 使用dwt内核精确延时 */
#if USE_DWT_DELAY
#define USE_TICK_DELAY 0 /* 不使用SysTick延时 */
#else
#define USE_TICK_DELAY 1 /* 使用SysTick延时 */
#endif
/*简单任务管理*/
#define TASK_ENABLE 0
#define NumOfTask 3
#if USE_DWT_DELAY
//#define Delay_ms(ms) CPU_TS_Tmr_Delay_MS(ms)
//#define Delay_us(us) CPU_TS_Tmr_Delay_US(us)
///* 最大延时 60s=2的32次方/72000000 */
//#define Delay_s(s) CPU_TS_Tmr_Delay_S(s)
/* 获取内核时钟频率 */
#define GET_CPU_ClkFreq() (SystemCoreClock)
#define SysClockFreq (SystemCoreClock)
/* 为方便使用,在延时函数内部调用CPU_TS_TmrInit函数初始化时间戳寄存器,
这样每次调用函数都会初始化一遍。
把本宏值设置为0,然后在main函数刚运行时调用CPU_TS_TmrInit可避免每次都初始化 */
#define CPU_TS_INIT_IN_DELAY_FUNCTION 1
/*******************************************************************************
* 函数声明
******************************************************************************/
uint32_t CPU_TS_TmrRd(void);
void CPU_TS_TmrInit(void);
//使用以下函数前必须先调用CPU_TS_TmrInit函数使能计数器,或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION
//最大延时值为60秒
void CPU_TS_Tmr_Delay_US(uint32_t us);
#define CPU_TS_Tmr_Delay_MS(ms) CPU_TS_Tmr_Delay_US(ms*1000)
#define CPU_TS_Tmr_Delay_S(s) CPU_TS_Tmr_Delay_MS(s*1000)
#endif
#endif
delay.c
#include "delay.h"
#if USE_DWT_DELAY
#define DWT_CR *(__IO uint32_t *)0xE0001000
#define DWT_CYCCNT *(__IO uint32_t *)0xE0001004
#define DEM_CR *(__IO uint32_t *)0xE000EDFC
#define DEM_CR_TRCENA (1 << 24)
#define DWT_CR_CYCCNTENA (1 << 0)
/**
* @brief 初始化时间戳
* @param 无
* @retval 无
* @note 使用延时函数前,必须调用本函数
*/
void CPU_TS_TmrInit(void)
{
/* 使能DWT外设 */
DEM_CR |= (uint32_t)DEM_CR_TRCENA;
/* DWT CYCCNT寄存器计数清0 */
DWT_CYCCNT = (uint32_t)0u;
/* 使能Cortex-M DWT CYCCNT寄存器 */
DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;
}
/**
* @brief 读取当前时间戳
* @param 无
* @retval 当前时间戳,即DWT_CYCCNT寄存器的值
*/
uint32_t CPU_TS_TmrRd(void)
{
return ((uint32_t)DWT_CYCCNT);
}
/**
* @brief 采用CPU的内部计数实现精确延时,32位计数器
* @param us : 延迟长度,单位1 us
* @retval 无
* @note 使用本函数前必须先调用CPU_TS_TmrInit函数使能计数器,
或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION
最大延时值为8秒,即8*1000*1000
*/
void CPU_TS_Tmr_Delay_US(__IO uint32_t us)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
/* 在函数内部初始化时间戳寄存器, */
#if (CPU_TS_INIT_IN_DELAY_FUNCTION)
/* 初始化时间戳并清零 */
CPU_TS_TmrInit();
#endif
ticks = us * (GET_CPU_ClkFreq() / 1000000); /* 需要的节拍数 */
tcnt = 0;
told = (uint32_t)CPU_TS_TmrRd(); /* 刚进入时的计数器值 */
while(1)
{
tnow = (uint32_t)CPU_TS_TmrRd();
if(tnow != told)
{
/* 32位计数器是递增计数器 */
if(tnow > told)
{
tcnt += tnow - told;
}
/* 重新装载 */
else
{
tcnt += UINT32_MAX - told + tnow;
}
told = tnow;
/*时间超过/等于要延迟的时间,则退出 */
if(tcnt >= ticks)break;
}
}
}
#endif
systick
include "systick.h"
static __IO u32 TimingDelay;
/**
* @brief 启动系统滴答定时器 SysTick
* @param 无
* @retval 无
*/
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
// if (SysTick_Config(SystemFrequency / 100000)) // ST3.0.0库版本
if (SysTick_Config(SystemCoreClock / 1000000)) // ST3.5.0库版本
{
/* Capture error */
while (1);
}
// 关闭滴答定时器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
/**
* @brief us延时程序,10us为一个单位
* @param
* @arg nTime: Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
* @retval 无
*/
void Delay_us(__IO u32 nTime)
{
TimingDelay = nTime;
// 使能滴答定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while(TimingDelay != 0);
}
/**
* @brief 获取节拍程序
* @param 无
* @retval 无
* @attention 在 SysTick 中断函数 SysTick_Handler()调用
*/
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include "stdio.h"
/*串口1参数宏定义*/
#define USART1_CLK RCC_APB2Periph_USART1//串口时钟
#define USART1_BAUDRATE 9600//波特率
//串口1 GPIO引脚宏定义
#define USART1_GPIO_CLK RCC_APB2Periph_GPIOA
#define USART1_GPIO_TX_PORT GPIOA
#define USART1_GPIO_TX_PIN GPIO_Pin_9
#define USART1_GPIO_RX_PORT GPIOA
#define USART1_GPIO_RX_PIN GPIO_Pin_10
//中断
#define USART1_IRQ USART1_IRQn
#define USART1_IRQHandler USART1_IRQHandler
//函数
void USART1_Config(void);
void USART_SendByte( USART_TypeDef * pUSARTx, uint8_t data);
void USART_SendString(USART_TypeDef* USARTx, char *DataString);
void NVIC_Configuration(void);
int fputc(int ch, FILE *f);
#endif /* __USART_H */
usart.c
#include "usart.h"
//串口
void USART1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* config USART1 clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/* USART1 GPIO config */
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* USART1 mode config */
USART_InitStructure.USART_BaudRate = USART1_BAUDRATE;
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(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //配置串口1的中断触发方法:接收一个字节触发中断
USART_Cmd(USART1, ENABLE);
}
/*中断函数*/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/* Enable the USARTy Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/***************** 发送一个字节 **********************/
void USART_SendByte(USART_TypeDef * pUSARTx, uint8_t data)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,data);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/***************** 发送字符串 **********************/
void USART_SendString(USART_TypeDef* USARTx, char *DataString)
{
int i = 0;
USART_ClearFlag(USARTx,USART_FLAG_TC); //发送字符前清空标志位(否则缺失字符串的第一个字符)
while(DataString[i] != ')'//字符串结束符 USART_SendData
{
(,USARTx[DataString]i);//每次发送字符串的一个字符 while
(USART_GetFlagStatus(,USARTx)USART_FLAG_TC== 0 );//等待数据发送成功 USART_ClearFlag
(,USARTx)USART_FLAG_TC;//发送字符后清空标志位 ++
i;}
}
//重定向c库函数printf到串口,重定向后可使用printf函数
int
fputc (int, ch* FILE )f/* 发送一个字节数据到串口 */
{
USART_SendData
(,USART1( )uint8_t) ch;/* 等待发送完毕 */
while
( USART_GetFlagStatus(,USART1) USART_FLAG_TXE== ) RESET;return
( )ch;}
//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int
fgetc (*FILE )f/* 等待串口输入数据 */
{
while
( USART_GetFlagStatus(,USART1) USART_FLAG_RXNE== ) RESET;return
( int)USART_ReceiveData()USART1;}
ifndef
dht11.h
#define __DHT11_H
#include __DHT11_H
#"stm32f10x.h" /************************** DHT11 数据类型定义********************************/
typedef
struct ;
{
uint8_t humi_int//湿度的整数部分 ;
uint8_t humi_deci//湿度的小数部分 ;
uint8_t temp_int//温度的整数部分 ;
uint8_t temp_deci//温度的小数部分 ;
uint8_t check_sum//校验和 }
;DHT11_Data_TypeDef/************************** DHT11 连接引脚定义********************************/
define
#define DHT11_Dout_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define DHT11_Dout_GPIO_CLK RCC_APB2Periph_GPIOE
#define DHT11_Dout_GPIO_PORT GPIOE
#/************************** DHT11 函数宏定义********************************/ DHT11_Dout_GPIO_PIN GPIO_Pin_6
define
#//0 DHT11_Dout_0 GPIO_ResetBits ( DHT11_Dout_GPIO_PORT, DHT11_Dout_GPIO_PIN ) define
#//1 DHT11_Dout_1 GPIO_SetBits ( DHT11_Dout_GPIO_PORT, DHT11_Dout_GPIO_PIN ) define
#//读取电平 DHT11_Dout_IN() GPIO_ReadInputDataBit ( DHT11_Dout_GPIO_PORT, DHT11_Dout_GPIO_PIN )/************************** DHT11 函数声明 ********************************/
void
DHT11_Init (void) ;dht11_read
uint8_t (*DHT11_Data_TypeDef )DHT11_Data;endif
#include
dht11.c
#"dht11.h" include
#"delay.h" ;
uint8_t humi_int//湿度的整数部分 ;
uint8_t humi_deci//湿度的小数部分 ;
uint8_t temp_int//温度的整数部分 ;
uint8_t temp_deci//温度的小数部分 ;
uint8_t check_sum//校验和 /* 可以在下面的宏定义中把后面的延时函数替换换SysTick的延时函数,就是想用那个就换成那个的 */
define
#define DHT11_DELAY_US(us) CPU_TS_Tmr_Delay_US(us)
#static DHT11_DELAY_MS(ms) CPU_TS_Tmr_Delay_MS(ms)
void ( DHT11_GPIO_Config void ) ;static
void ( DHT11_Mode_IPU void ) ;static
void ( DHT11_Mode_Out_PP void ) ;static
DHT11_ReadByte uint8_t (void);void
( DHT11_Init void ) (
{
DHT11_GPIO_Config );;
DHT11_Dout_1// 初始状态DHT11为高电平 }
/*
* 函数名:DHT11_GPIO_Config
* 描述 :配置DHT11用到的I/O口
*/
static
void ( DHT11_GPIO_Config void ) /*定义一个GPIO_InitTypeDef类型的结构体*/
{
;
GPIO_InitTypeDef GPIO_InitStructureRCC_APB2PeriphClockCmd
(, DHT11_Dout_GPIO_CLK) ENABLE ;//开启外设时钟 .
GPIO_InitStructure=GPIO_Pin ; DHT11_Dout_GPIO_PIN//引脚.
GPIO_InitStructure=GPIO_Mode ; GPIO_Mode_Out_PP//推挽输出 .
GPIO_InitStructure=GPIO_Speed ; GPIO_Speed_50MHz//速度 (
GPIO_Init , DHT11_Dout_GPIO_PORT& )GPIO_InitStructure ;/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/}
/*
* 函数名:DHT11_Mode_IPU
* 描述 :使DHT11-DATA引脚变为上拉输入模式
*/
static
void DHT11_Mode_IPU (void);
{
GPIO_InitTypeDef GPIO_InitStructure//调用并定义库函数结构体.
GPIO_InitStructure=GPIO_Pin ; DHT11_Dout_GPIO_PIN//选择引脚.
GPIO_InitStructure=GPIO_Mode ; GPIO_Mode_IPU//设置引脚模式为浮空输入模式GPIO_Init
(,DHT11_Dout_GPIO_PORT& )GPIO_InitStructure;/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/}
/*
* 函数名:DHT11_Mode_Out_PP
* 描述 :使DHT11-DATA引脚变为推挽输出模式
*/
static
void DHT11_Mode_Out_PP (void);
{
GPIO_InitTypeDef GPIO_InitStructure//调用并定义库函数结构体.
GPIO_InitStructure=GPIO_Pin ; DHT11_Dout_GPIO_PIN//选择引脚 .
GPIO_InitStructure=GPIO_Mode ; GPIO_Mode_Out_PP//设置引脚模式为通用推挽输出.
GPIO_InitStructure=GPIO_Speed ; GPIO_Speed_50MHz//速度GPIO_Init
(,DHT11_Dout_GPIO_PORT& )GPIO_InitStructure;//调用库函数,初始化DHT11_Dout_GPIO_PORT }
/*
* 从DHT11读取一个字节,MSB先行
*/
static
DHT11_ReadByte uint8_t (void),
{
uint8_t i= temp0;//存储数据for
(=i0;<i8;++i)/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
{
while
(DHT11_Dout_IN()==)Bit_RESET;/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
DHT11_DELAY_US
(40);//延时x us 这个延时需要大于数据0持续的时间即可 if
(DHT11_Dout_IN()==1)/* x us后仍为高电平表示数据“1” *//* 等待数据1的高电平结束 */
{
while
(DHT11_Dout_IN()==1);|
temp=()uint8_t(0x01<<(7-)i);//把第7-i位置1,MSB先行 }
else
// x us后为低电平表示数据“0” &
{
temp=()uint8_t~(0x01<<(7-)i);//把第7-i位置0,MSB先行 }
}
return
; temp}
/*
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
*/
dht11_read
uint8_t (*DHT11_Data_TypeDef )DHT11_Data// int j=0;//循环次数
{
// u8 buf[5];//存储数据
/*输出模式*/
DHT11_Mode_Out_PP
();/*主机拉低*/
;
DHT11_Dout_0/*延时18ms*/
DHT11_DELAY_MS
(18);/*总线拉高 主机延时30us*/
;
DHT11_Dout_1DHT11_DELAY_US
(30);//延时30us /*主机设为输入 判断从机响应信号*/
DHT11_Mode_IPU
();/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if
(DHT11_Dout_IN()==)Bit_RESET/*轮询直到从机发出 的80us 低电平 响应信号结束*/
{
while
(DHT11_Dout_IN()==)Bit_RESET;/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while
(DHT11_Dout_IN()==)Bit_SET;// for(j=0;j<5;j++)
// {
// buf[j]=DHT11_ReadByte();
//
// }
/*开始接收数据*/
=
DHT11_Data->humi_intDHT11_ReadByte ();=
DHT11_Data->humi_deciDHT11_ReadByte ();=
DHT11_Data->temp_intDHT11_ReadByte ();=
DHT11_Data->temp_deciDHT11_ReadByte ();=
DHT11_Data->check_sumDHT11_ReadByte ();/*读取结束,引脚改为输出模式*/
DHT11_Mode_Out_PP
();/*主机拉高*/
;
DHT11_Dout_1if
(==DHT11_Data->check_sum + DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int) DHT11_Data->temp_deci//进行校验,检查读取的数据是否正确return
; SUCCESSelse
return
; ERROR}
else
return
; ERROR}
/********************************************************************************
* @file main.c
* @author 浅白杨
* @version V_N.0
* @date 2021年5月24日
* @brief dht11温湿度传感器测试实验
******************************************************************************/
main.c
include
#"stm32f10x.h" include
#"systick.h" include
#"dht11.h" include
#"usart.h" include
#"delay.h" include
#"stdio.h" void
HMISends (char* )buf1;void
HMISendb ()u8 buf;void
HMISends (char* )buf1//字符串发送函数 =
{
u8 i0;while
(1)if
{
([buf1]i!=0)USART_SendData
{
(,USART1[buf1]i);//发送一个字节 while
(USART_GetFlagStatus(,USART1)USART_FLAG_TXE==)RESET}{;//等待发送结束++
i;}
else
return
; }
}
void
HMISendb ()u8 k//字节发送函数 ;
{
u8 ifor
(=i0;<i3;++i)if
{
(!=k0)USART_SendData
{
(,USART1)k;//发送一个字节 while
(USART_GetFlagStatus(,USART1)USART_FLAG_TXE==)RESET}{;//等待发送结束}
else
return
; }
}
void
HMISendstart (void)Delay_ms
{
(200);HMISendb
(0xff);Delay_ms
(200);}
int
main (void);
{
DHT11_Data_TypeDef DHT11_DataSysTick_Init
();USART1_Config
();//初始化串口1NVIC_Configuration
();//中断printf
("\r\n***野火STM32 dht11 温湿度传感器实验***\r\n");/*初始化DTT11的引脚*/
(
DHT11_Init );HMISendstart
();HMISendb
(0xff);while
(1)//调用dht11_read()函数
{
if
((dht11_read &) DHT11_Data== ) SUCCESSprintf
{
("\r\n读取DHT11成功!\r\n\r\n湿度为%d.%d %RH ,温度为 %d.%d℃ \r\n",.\
DHT11_Data,humi_int.DHT11_Data,humi_deci.DHT11_Data,temp_int.DHT11_Data)temp_deci;HMISendstart
();//串口HMI显示 printf
{
("n0.val=%d",.DHT11_Data)temp_int;HMISendb
(0xff);printf
("n2.val=%d",.DHT11_Data)temp_deci;HMISendb
(0xff);printf
("n1.val=%d",.DHT11_Data)humi_int;HMISendb
(0xff);printf
("n3.val=%d",.DHT11_Data)humi_deci;HMISendb
(0xff);}
}
else
printf
{
("Read DHT11 ERROR!\r\n");}
Delay_ms
(2000);}
}
上位机设计的串口屏界面:
关于串口屏注意事项:
串口屏中的一个数字控件只能显示一个数字类型,想要显示小数点后面的数字需要再添加一个数字控件负责显示小数部分;
串口屏之间的控件会被覆盖,注意间距即可。
在C语言中想要打印%,则写成%d%%即可;
\r\n:回车换行,如果不回车之间换行,则在第二行不会从头开始写
DHT11在测温度时不精确,可以换成温度传感器DS18B20来测温度。
**人不能两次踏进同一条河流
作者能力水平有限,文章难免存在错误和纰漏,请大佬不吝赐教,非常欢迎大家与小白杨进行技术交流,一起学习技术。**
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)