基于stm32的多功能时钟2——DHT11测量温湿度
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于stm32的多功能时钟2——DHT11测量温湿度相关的知识,希望对你有一定的参考价值。
参考技术A 亲爱的读者们,我又回来了~上一章中,我带着大家实现了时钟显示和按键调整的功能。在这一章中,我将利用DHT11温湿度传感器,来测量环境温度和湿度。
DHT11温湿度传感器是数字式的,包括1个电阻式感湿元件和1个NTC测温元件,内部自带AD转换功能,采用单总线,具有响应快、抗干扰能力强、性价比高等特点。该模块总共4个引脚,其中两个是电源引脚VCC和GND,一个是数据引脚,还有一个为空引脚。
目前流行的数据传输总线有II2C总线,SPI总线,单总线等,而DHT11则采用单总线传输数据。单总线,顾名思义,就是采用单根信号线,即可传输时钟,又能传输数据,而且数据传输是双向的,从而有主机和从机之别。在这里,stm32作为核心控制器,所以是主机,而DHT11为从机。 采用单总线进行数据传输,我们需要查看数据手册的时序图。
总线空闲状态为高电平,主机把总线拉低等待 DHT11 响应,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号。DHT11 接收到主机的开始信号后, 等待主机开始信号结束,然后发送 80us 低电平响应信号.主机发送开始信号结束后,延时等待 20-40us 后, 读取 DHT11 的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。
根据时序图,单片机需要先将总线拉低至少18ms,然后拉高总线20~40us,此时主机的开始信号结束,检测DHT11的响应信号。如果检测到低电平,则DHT11响应,并且低电平时间维持80us,然后DHT11拉高总线80us。此时DHT11准备传输数据,传输的数据间隙为50us低电平,传输的数据通过高电平的时间长短来区分"0"和"1"。数据传输完毕,DHT11将总线拉低50us,最后主机再拉高总线。
(1)编写延时函数
由于DHT11的时序比较严格,需要毫秒级别和微妙级别的延时。这里我们采用Systick去做延时。在之前按键扫描函数里也用到延时的,在此我叙述一下。
我们需要配置系统时钟,然后把Systick设置成72,这样就能产生1us时间基准,其次编写Systick中断处理函数,让变量自减,从而达到延时的效果,最后编写延时函数,也就是对自减的变量赋初始值。
__IO uint32_t TimingDelay;
/*配置SysTick函数*/
void systick_init(void)
/*配置Systick重载值,系统时钟为72MHz*/
/*设置72,中断时间:72*(1/72000000) = 1us*/
if(SysTick_Config(72)==1) //若SysTick_Config函数返回产生中断信号,返回值为0
while(1); //SysTick_Config函数返回值为1,则等待
/*时间变量自减函数*/
void TimingDelay_Decrement(void)
if(TimingDelay!=0x00)
TimingDelay--;
/*SysTick中断处理函数*/
void SysTick_Handler(void)
TimingDelay_Decrement();
/*延时函数,时间基准为1ms*/
void delay_ms(__IO uint32_t nTime)
TimingDelay = nTime*1000;
while(TimingDelay!=0);
/*延时函数,时间基准为1us*/
void delay_us(__IO uint32_t nTime)
TimingDelay = nTime;
while(TimingDelay!=0);
(2)配置相应的GPIO口作为单总线数据端
/*配置DHT11数据引脚,设置成浮空输入模式*/
void dht11_gpio_portIn(void)
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置DHT11数据引脚,设置成推挽输出模式*/
void dht11_gpio_portOut(void)
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
由于DHT11采用单总线通信协议,所以数据传输是双向的,所以分别将数据端口设置成浮空输入模式和推挽输出模式。并且将数据口的输入和输出定义成宏定义的形式。
#define DHT11_OUT_H GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define DHT11_OUT_L GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define DHT11_IN GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4)
(3)根据DHT11时序图,编写时序函数
现在,我们开始编写总线的驱动函数,根据时序图,主机发送命令启动转换,接着,等待DHT11转换响应并且输出数据,最后读取数据。
/*启动总线函数*/
void dht11_reset(void)
dht11_gpio_portOut(); //设置成输出模式
DHT11_OUT_L; //主机将总线拉低至少18ms
delay_ms(18);
DHT11_OUT_H; //主机拉高保持20~40us
delay_us(30);
dht11_gpio_portIn(); //设置成输入模式,等待DHT11响应
/*DHT11响应函数*/
u8 dht11_scan(void)
return DHT11_IN; //返回值为DHT11的响应信号
实时监控DHT11的数据线,直至其产生出低电平,表示DHT11响应主机请求,开始传输数据。
/*DHT11读取位函数*/
u8 dht11_read_bit(void)
while(DHT11_IN==RESET); //传输数据位前存在50us低电平
delay_us(40); //根据高电平的时间长短决定电平是1还是0
if(DHT11_IN==SET) //"0"电平持续时间为26~28us,"1"电平持续时间为70us
while(DHT11_IN==SET);
return 1; //若检测到高电平,返回值为1
else
return 0; //若检测到高电平,返回值为0
/*DHT11读取字节函数*/
//注:数据最高位先传输
u8 dht11_read_byte(void)
u8 i,dat = 0x00;
for(i=0; i<8; i++)
dat = dat<<1;
dat = dat|dht11_read_bit();//将串行数据读取出来
return dat;
当DHT11响应后,就开始通过单总线传输数据,在读取位函数里,通过高电平的时间长短来判断输出的是'1'还是'0',在读取字节函数里,调用读取位函数,将传输的每8位整合出字节,并读取出来。
我们查阅DHT11数据手册,得知数据传输的结构(依次顺序):湿度整数部分(1字节)、湿度小数部分(1字节)、温度整数部分(1字节)、温度小数部分(1字节)、校验和(1字节)。这里,其实就是一个简单的通信协议。校验和就是源数据所有字节之和的低8位,确保传输数据的正确与稳定。
/*DHT11读取数据函数*/
u8 dht11_read_data(void)
u8 i;
dht11_reset();
if(dht11_scan()==RESET) //DHT11发出响应信号
while(DHT11_IN==RESET); //DHT11拉低总线80us
while(DHT11_IN!=RESET); //DHT11拉高总线80us
for(i=0; i<5; i++)
buffer[i] = dht11_read_byte();
while(DHT11_IN==RESET); //发送完最后1bit数据后,等待50us低电平结束
dht11_gpio_portOut();
DHT11_OUT_H; //主机拉高总线
if(buffer[0]+buffer[1]+buffer[2]+buffer[3]==buffer[4])
return 1; //校验成功
else
return 0; //校验失败
else
return 0; //DHT11未发出响应信号
在读取字节里,先等待DHT11响应,然后开始接收数据,并且连续读取5次,存放在事先定义好的数组里,主机发出结束信号,最后对读取的数据进行校验。
(4)测量显示温湿度
主函数调用DHT11读数据函数,并调用lcd显示函数,将温度和湿度显示出来即可。
if(dht11_read_data()==1)//读取数据,前提是DHT11响应,并且数据校验成功
humidity = buffer[0];//buffer[0]存放的是湿度整数部分
temperature = buffer[2];//buffer[2]存放的是温度整数部分
lcd_display_string(2,0,"温度");
lcd_display_num_m(2,32,temperature/10);
lcd_display_num_m(2,40,temperature%10);
lcd_display_string(4,0,“湿度”);
lcd_display_num_m(4,32,humidity/10);
lcd_display_num_m(4,40,humidity%10);
至此,通过本章的讲解,我相信,大家应该对于DHT11模块有了大致的了解,当然,如果你只是看看文章的话,可能效果不怎么理想。这些东西,都需要亲自动手的,正所谓"实践出真知"。所以,你可以在网上买开发板,再买一些模块,不一定要和我的一样。当然,想要挑战自己的话,可以买块最小系统板,这样,外围的硬件电路可以由自己搭建(功能自定义),灵活性强的同时,也锻炼了自己的动手能力。如果你是会设计PCB的大佬,那更好,估计你的水平,就浏览一下我的文章即可。
STM32实例——基于STM32开发板实现传感数据采集-DHT11温湿度采集
STM32开发板实现传感数据采集-DHT11温湿度采集
一、前言
本项目是基于STM32开发板的温湿度采集,传感器采用DHT11温湿度传感器,软件采用keil5等。本项目采用ARM结构中最为代表的Cortex-M4系列的芯片,选用STM32F407ZGT6开发板进行项目开发。传感器将采集到的数据传输到STM32(MCU)主控进行数据处理,最后通过串口打印出来。
二、硬软件准备
1、硬件准备
- STM32F407ZGT6
- DHT11温湿度传感器
2、软件准备
keil5:
官方链接:http://www.keil.com/demo/eval/arm.htm
STM32f407固件库:
官方链接:http://www.keil.com/dd2/pack
STM32CudeMx
官方链接:http://www.st.com/web/en/catalog/tools/PF259242
STM32CudeMx的f407软件包:
官方链接:http://www.st.com/web/en/catalog/tools/PF259243
3、项目实施
1、keil5安装
安装请参照链接:keil5安装链接
2、STM32CudeMx安装
安装请参照链接:STM32CudeMx安装
3、BSP工程项目创建
①打开STM32CudeMX
②点击创建工程(ACCESS TO MCU SELECTOR)
③搜索STM32F407ZG,双击绿色区域
④点击Categor→System Core →GPIO,选择PF9和PF10,都选择为GPIO_OutPut方式
⑤对PF9和PF10的GPIO进行具体配置
⑥配置RCC时钟
⑦配置系统时钟
⑧这里以串口1为例 我们可以选择串口的模式(异步,同步,半双工) 串口接收中断
a)点击USATR1
b)设置MODE为异步通信(Asynchronous)
c)基础参数:波特率为115200 Bits/s。传输数据长度为8 Bit。奇偶检验无,停止位1 接收和发送都使能
d)GPIO引脚设置 USART1_RX/USART_TX
e) NVIC Settings 一栏使能接收中断
⑨配置STM32F407ZGT6的时钟树,由于是外部8M的晶振,所以得出一下的时钟树
a)选择外部时钟HSE 8MHz
b)PLL锁相环倍频168倍
c)系统时钟来源选择为PLL
d)设置APB1分频器为 /4
⑩建立工程
4. BSP工程项目
①用keil5打开此工程
②点击option(魔法棒),然后进行主频配置,修改为8.0或者12.0,然后重新打开该工程进行检查,最后进行编译。
③在keil5上面创建SYSTEM和HARDWAVE两个文件夹
④回到创建开始的STM32_DHT11工程目录,添加这两个文件夹,复制库文件里面SYSTEM和HARDWAVE两个文件夹到STM32_DHT11工程目录下。(文件具体看末尾)
⑤回到keil5里面,继续点击那个文件管理,然后根据对应的文件夹添加文件,把SYSTEM和HARDWAVE两个文件夹里面的文件都添加进去。
⑥配置头文件路径,选择为第4步已经复制的两个文件夹(SYSTEM和HARDWAVE)的路径添加进去。
⑦编程代码
将资源中的main.c、uart.c和uart.h的代码复制替换工程中的相应的代码然后就可以编译了。
main.c代码如下:
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "dht11.h"
void SystemClock_Config(void);
int main(void)
u8 t=0;
u8 temperature;
u8 humidity;
int times;
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
delay_init(168);
SystemClock_Config();
DHT11_Init();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);
while (1)
if(t%10==0)//?100ms????
DHT11_Read_Data(&temperature,&humidity);
printf("2018A14122 WuXiaoXian\\r\\n");
printf("Tem:%d\\r\\n",temperature);
printf("Hum:%d\\r\\n",humidity);
printf("\\r\\n\\n");
delay_ms(100);
t++;
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
RCC_OscInitTypeDef RCC_OscInitStruct = 0;
RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
Error_Handler();
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
Error_Handler();
void Error_Handler(void)
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
/* USER CODE END Error_Handler_Debug */
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\\r\\n", file, line) */
/* USER CODE END 6 */
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
uart.c
#include "usart.h"
#include "stdio.h"
/* USER CODE BEGIN 0 */
uint8_t USART_RX_BUF[USART_REC_LEN];
uint16_t USART_RX_STA=0; //??????
uint8_t aRxBuffer[RXBUFFERSIZE];//HAL??????????
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
/* USART1 init function */
int fputc(int ch, FILE *f)
HAL_UART_Transmit(&huart1,(uint8_t *)&ch, 1, 0XFFFF);
return ch;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
if(huart->Instance==USART1)//?????1
if((USART_RX_STA&0x8000)==0)//?????
if(USART_RX_STA&0x4000) //????0x0d
if(aRxBuffer[0]!= 0x0a)
USART_RX_STA=0; //????,????
else
USART_RX_STA|=0x8000; //?????
else //????0x0D
if(aRxBuffer[0] == 0x0d)
USART_RX_STA|=0x4000;
else
USART_RX_BUF[USART_RX_STA&0x3FFF]=aRxBuffer[0];
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))
USART_RX_STA=0; //??????,??????
void MX_USART1_UART_Init(void)
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
Error_Handler();
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
GPIO_InitTypeDef GPIO_InitStruct = 0;
if(uartHandle->Instance==USART1)
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
if(uartHandle->Instance==USART1)
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
uart.h
/**
******************************************************************************
* @file usart.h
* @brief This file contains all the function prototypes for
* the usart.c file
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__
#ifdef __cplusplus
extern "C"
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
#define USART_REC_LEN 500
#define RXBUFFERSIZE 1
extern uint8_t USART_RX_BUF[USART_REC_LEN];
extern uint16_t USART_RX_STA;
extern UART_HandleTypeDef UART1_Handler; //UART??
extern uint8_t aRxBuffer[RXBUFFERSIZE];//HAL??????????
/* USER CODE END Includes */
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_USART1_UART_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
#endif
#endif /* __USART_H__ */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
⑧编译下载
完成上述步骤后打开串口助手查看即可
总结
本项目是STM32中比较简单的实验了,STM32总体来说编译运行以及代码烧录那几个步骤熟悉掌握后再看这篇文章会收获比较多,这篇文章只是讲解了如何运用别人的代码,具体的原理并没有深入讲解,若是想要深入学习STM32单片机的话这些建议自行去深入了解一下代码是如何编写的。
以上是关于基于stm32的多功能时钟2——DHT11测量温湿度的主要内容,如果未能解决你的问题,请参考以下文章
基于STM32大棚DHT11温湿度监测的Proteus仿真 (代码+仿真+原理图+PCB+参考报告)
STM32实例——基于STM32开发板实现传感数据采集-DHT11温湿度采集