DHT11温湿度传感器,经过封装,易于移植的DHT11驱动程序,stm32,HAL库版本,添加低功耗设计

本文讲述了作者在毕业设计中遇到的DHT11传感器移植问题,发现现有的STM32HAL库代码混乱且移植困难。作者通过创建一个易于移植的驱动程序,采用模块化和面向对象的方法,解决了IO配置、延时函数和低功耗问题,提供了清晰的使用示例。
摘要由CSDN通过智能技术生成

这几天做毕业设计,要用到DHT11温湿度传感器,本来想着多一事不如少一事,随便找了几篇文章,对比了一下就随便找了个文章的代码尝试使用,结果不尽人意,试了好几个都移植失败,在移植过程中发现,目前网上的Dht11使用stm32hal库版本编写的代码都太过混乱,不易于移植,有的在读取函数中添加串口打印函数,有的全程使用原生的GPIOx,GPIO_PIN_x,有的胡乱命名,有的使用whlie循环实现微秒级延时,可是大家使用的MCU主频不一致,总之移植起来非常不方便。

尝试几个无果后,决定自己动手,丰衣足食,在参考了一些还不错的代码的逻辑后,编写了这个DHT11的驱动程序。借鉴了HAL库采用的模块化编写方式,以及面向对象的思维,我希望能写一个易于移植,易于使用的DHT11驱动程序。

我将IO口,引脚号,模块状态,数据缓冲区,微秒级延时函数这些必要的特性封装到了一个结构体当中,使用init函数进行初始化,这样在移植时就首先避免了之前遇到的全程使用原生的GPIOx,GPIO_PIN_x导致移植时添加工作量。

我将微秒级延时函数以函数指针的形式传入到dht11的句柄中,这样就避免了有人使用while循环,有人使用定时器,有人主频72MHZ,有人主频64MHZ等等千奇百怪的微秒级延时函数实现方式。移植时只要传入一个指向void delayUs(uint16_t us)的函数指针,具体的实现根据实际情况来。同时驱动内部的毫秒级延时函数也是基于微秒级延时函数。我是通过定时器来实现的微秒级延时,毕竟定时器不受中断影响,且最准确,具体实现如下

void delay_us(uint16_t us)
{
	TIM1->CNT=0;
	while(TIM1->CNT<us);
}

需要注意的是如果要使用我这种延时,需要将一个计数周期设为1us

同时我将读取函数和其他程序逻辑进行分离,避免之前遇到的在读取函数中添加串口打印函数这种影响读取效率的事情发生,读取时只需要传入指向保存温度,湿度数据的指针,如果需要对数据进行其他处理,可以在离开读取函数后进行,确保模块化设计

由于毕设和低功耗有关,就了解了一些常见低功耗做法,所以提供了一些反初始化的函数,反初始化会将数据引脚置为模拟态,模拟态下几乎没有电流,可以避免引脚悬空,大大降低功耗。DHT11的供电电流:测量 0.3mA 待机 60μA,如果在使用时,配合模块的VCC引脚的关闭,就可以大大降低待机时的功耗。在毕设中我就计划使用一个三极管开关电路和一个MCU引脚控制所有外接模块的VCC,毕竟大多数模块都没有低功耗设计,且不需要长期开启。休眠后只留下一个外部中断引脚,其他的全部置为模拟态,预计可以将平均电流从几百毫安降低到100uA以下

网上有很多有关DHT11的使用说明,我就不赘述这些原理了,主打一个易于移植,拿上来,看一下就能使用

驱动程序的头文件如下

#ifndef __DHT11_H
#define __DHT11_H

#include "main.h"
#include "stm32f1xx_hal.h"

#define DHT11_CLOCK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()

#define DHT11_IO_INPUT 0
#define DHT11_IO_OUTPUT 1
#define DHT11_IO_ANALOG 2

typedef enum
{
	DHT11_ON,
	DHT11_OFF,
	DHT11_ERROR,
	FAULT
} Dht11State;

typedef struct Dht11Typedef
{
	GPIO_TypeDef *IO;
	GPIO_InitTypeDef DataIO;
	Dht11State state;
	uint8_t dta[5];
	void (*DelayUs)(uint16_t);
} Dht11Typedef;

extern Dht11Typedef hdht111;
void Dht11_init(Dht11Typedef *hdht11, GPIO_TypeDef *IO, uint32_t Pin, void (*DelayUs)(uint16_t));
void Dht11_deinit(Dht11Typedef *hdht11);
void Dht11_read_data(Dht11Typedef *hdht11, double *temp, double *humi);
Dht11State Dht11_Check(Dht11Typedef *hdht11);

#endif

驱动文件本体如下

#include "dht11.h"

Dht11Typedef hdht111 = {0};

static uint8_t Dht11_dataVerification(Dht11Typedef *hdht11);
static void dht11_delay_ms(Dht11Typedef *hdht11, uint16_t ms);
static void Dht11_DataIO_init(Dht11Typedef *hdht11, uint8_t mode);
static uint8_t Dht11_read_bit(Dht11Typedef *hdht11);
static uint8_t Dht11_read_byte(Dht11Typedef *hdht11);
static void Dht11_IO_deinit(Dht11Typedef *hdht11);
static void Dht11_Reset(Dht11Typedef *hdht11);

/**
 * @brief  输出模式下,控制引脚输出
 * @param  hdht11 : DHT11 handle
 * @param  PinState : 引脚输出状态
 * @retval None
 */
#define Dht11_DataPinWrite(hdht11, PinState)                               \
	do                                                                     \
	{                                                                      \
		HAL_GPIO_WritePin((hdht11)->IO, (hdht11)->DataIO.Pin, (PinState)); \
	} while (0);

/**
 * @brief  输入模式下,读取引脚输入状态
 * @param  hdht11 : DHT11 handle
 * @retval  PinState
 */
#define Dht11_DataPinRead(hdht11) HAL_GPIO_ReadPin((hdht11)->IO, (hdht11)->DataIO.Pin)

/**
 * @brief  用于实现毫秒级延时
 * @note   该函数利用传入DHT11句柄中的微秒级延时函数进行延时,请确保DelayUs函数准确
 * @param  hdht11 : DHT11 handle
 * @retval None
 */
static void dht11_delay_ms(Dht11Typedef *hdht11, uint16_t ms)
{
	uint16_t i = 0;
	for (i = 0; i < ms; i++)
	{
		hdht11->DelayUs(1000);
	}
}

/**
 * @brief  用于Dht11数据引脚的初始化,输出模式,输入模式,模拟模式
 * @param  hdht11 : DHT11 handle
 * @param  mode :输出模式:DHT11_IO_OUTPUT,输入模式:DHT11_IO_INPUT,模拟模式:DHT11_IO_ANALOG
 * @retval None
 */
static void Dht11_DataIO_init(Dht11Typedef *hdht11, uint8_t mode)
{
	if (mode == DHT11_IO_OUTPUT)
	{
		Dht11_DataPinWrite(hdht11, GPIO_PIN_SET);
		hdht11->DataIO.Mode = GPIO_MODE_OUTPUT_PP;
		hdht11->DataIO.Pull = GPIO_NOPULL;
		hdht11->DataIO.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(hdht11->IO, &hdht11->DataIO);
	}
	else if (mode == DHT11_IO_INPUT)
	{
		hdht11->DataIO.Mode = GPIO_MODE_INPUT;
		hdht11->DataIO.Pull = GPIO_PULLUP;
		hdht11->DataIO.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(hdht11->IO, &hdht11->DataIO);
	}
	else if (mode == DHT11_IO_ANALOG)
	{
		hdht11->DataIO.Mode = GPIO_MODE_ANALOG;
		hdht11->DataIO.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(hdht11->IO, &hdht11->DataIO);
	}
}

/**
 * @brief  用于DHT11数据校验
 * @param  hdht11 : DHT11 handle
 * @retval 0:数据错误
 * @retval 1:数据正确
 */
static uint8_t Dht11_dataVerification(Dht11Typedef *hdht11)
{
	return hdht11->dta[0] + hdht11->dta[1] + hdht11->dta[2] + hdht11->dta[3] == hdht11->dta[4];
}

/**
 * @brief	用于Dht11的初始化,包括指定数据引脚,指定微秒级延时实现方法,IO时钟使能
 * @note	该函数会延时1秒用于确保DHT11启功稳定,请在dht11.h中指定使能的时钟
 * @param	hdht11 : DHT11 handle
 * @param	IO:数据引脚使用的IO口
 * @param	Pin:数据引脚使用的引脚号
 * @param 	DelayUs :指向微秒级延时函数的函数指针
 * @retval	None
 */
void Dht11_init(Dht11Typedef *hdht11, GPIO_TypeDef *IO, uint32_t Pin, void (*DelayUs)(uint16_t))
{
	hdht11->IO = IO;
	hdht11->DataIO.Pin = Pin;
	hdht11->DelayUs = DelayUs;
	hdht11->state = DHT11_ON;

	DHT11_CLOCK_ENABLE();
	Dht11_DataIO_init(hdht11, DHT11_IO_OUTPUT);
	dht11_delay_ms(hdht11, 1000);
	Dht11_Check(hdht11);
}

/**
 * @brief	用于Dht11的反初始化,主要改变模块状态,反初始化数据引脚
 * @param	hdht11 : DHT11 handle
 * @retval	None
 */
void Dht11_deinit(Dht11Typedef *hdht11)
{
	Dht11_IO_deinit(hdht11);
	hdht11->state = DHT11_OFF;
}

/**
 * @brief	用于Dht11的数据引脚反初始化,将引脚变为模拟态
 * @note	引脚为模拟态可降低功耗
 * @param	hdht11 : DHT11 handle
 * @retval	None
 */
static void Dht11_IO_deinit(Dht11Typedef *hdht11)
{
	Dht11_DataIO_init(hdht11, DHT11_IO_ANALOG);
}

/**
 * @brief	用于Dht11的读取一个bit
 * @param	hdht11 : DHT11 handle
 * @retval	1:bit=1
 * @retval	0:bit=0
 */
static uint8_t Dht11_read_bit(Dht11Typedef *hdht11)
{
	uint8_t re = 0;
	while (Dht11_DataPinRead(hdht11) && re < 110) // 等待变为低电平
	{
		re++;
		hdht11->DelayUs(1);
	}
	re = 0;
	while (!Dht11_DataPinRead(hdht11) && re < 110) // 等待变高电平
	{
		re++;
		hdht11->DelayUs(1);
	}
	hdht11->DelayUs(40); // 等待40us
	if (Dht11_DataPinRead(hdht11))
		return 1;
	else
		return 0;
}

/**
 * @brief	用于Dht11的读取一个byte
 * @param	hdht11 : DHT11 handle
 * @retval	dat
 */
static uint8_t Dht11_read_byte(Dht11Typedef *hdht11)
{
	uint8_t i, dat;
	dat = 0;
	for (i = 0; i < 8; i++)
	{
		dat <<= 1;
		dat |= Dht11_read_bit(hdht11);
	}
	return dat;
}

/**
 * @brief	用于Dht11的读取温湿度
 * @param	hdht11 : DHT11 handle
 * @param	temp:保存温度数据的指针
 * @param	humi:保存湿度数据的指针
 * @retval	None
 */
void Dht11_read_data(Dht11Typedef *hdht11, double *temp, double *humi)
{
	uint8_t i;
	Dht11_Reset(hdht11);
	if (Dht11_Check(hdht11) == DHT11_ON)
	{
		for (i = 0; i < 5; i++)
		{
			hdht11->dta[i] = Dht11_read_byte(hdht11);
		}
		if (Dht11_dataVerification(hdht11))
		{
			*humi = (double)hdht11->dta[0] + (double)hdht11->dta[1] / 100;
			*temp = (double)hdht11->dta[2] + (double)hdht11->dta[3] / 100;
		}
	}
}

/**
 * @brief	用于Dht11的检查状态
 * @param	hdht11 : DHT11 handle
 * @retval	DHT11_OFF:未开启DHT11的数据引脚
 * @retval	DHT11_ON:DHT11正常开启
 * @retval	DHT11_ERROR:DHT11异常
 */
Dht11State Dht11_Check(Dht11Typedef *hdht11)
{
	if (hdht11->state == DHT11_OFF)
	{
		return DHT11_OFF;
	}
	else
	{
		uint8_t re = 0;
		Dht11_DataIO_init(hdht11, DHT11_IO_INPUT);	  // 设置为输入
		while (Dht11_DataPinRead(hdht11) && re < 100) // DHT11会拉低40~80us
		{
			re++;
			hdht11->DelayUs(1);
		};
		if (re >= 100)
			return DHT11_ERROR;
		else
			re = 0;
		while (!Dht11_DataPinRead(hdht11) && re < 100) // DHT11拉低后会再次拉高40~80us
		{
			re++;
			hdht11->DelayUs(1);
		};
		if (re >= 100)
			return DHT11_ERROR;
		return DHT11_ON;
	}
}

/**
 * @brief	用于Dht11的复位
 * @param	hdht11 : DHT11 handle
 * @retval	None
 */
void Dht11_Reset(Dht11Typedef *hdht11)
{
	Dht11_DataIO_init(hdht11, DHT11_IO_OUTPUT); // 设置为输出
	Dht11_DataPinWrite(hdht11, GPIO_PIN_RESET); // 低电平
	dht11_delay_ms(hdht11, 20);					// 拉低至少18ms
	Dht11_DataPinWrite(hdht11, GPIO_PIN_RESET); // 高电平
	hdht11->DelayUs(60);						// 主机拉高20~40us
}

由于我毕设是跑系统的,所有这里展示两个使用案例,一个是我毕设的,另一个是由AI生成的裸机使用例程,这个AI生成的能否正常使用有待商榷,我没有实验验证,但大体不会有误

先是我自己的代码片段

//主函数完成外设初始化后,对延时函数,DHT11的初始化代码片段

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_TIM1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	delay_init();
	Dht11_init(&hdht111,GPIOB,GPIO_PIN_10,delay_us);
  OLED_Init();

//与DHT11相关的任务片段
void DHT11_handle(void *argument)
{
  /* USER CODE BEGIN DHT11_handle */
  OledInfo dht11_data = {0};
  double temp=0,humi=0;
  dht11_data.Line=3;
  /* Infinite loop */
  for (;;)
  {
    Dht11_read_data(&hdht111,&temp,&humi);
    sprintf(dht11_data.info,"H:%.2f T:%.2f",humi,temp);
    osMessageQueuePut(Oled_QueueHandle, &dht11_data, 0, osWaitForever);
    osDelay(1000);
  }
  /* USER CODE END DHT11_handle */
}

//与显示有关的代码片段
void Oled_handle(void *argument)
{
  /* USER CODE BEGIN Oled_handle */
  OledInfo oledinfo={0};
  char clearLine[17]="                ";
  /* Infinite loop */
  for (;;)
  {
    if(osMessageQueueGet(Oled_QueueHandle, &oledinfo, 0, osWaitForever)==osOK)
    {
      OLED_ShowString(oledinfo.Line,1,clearLine);
      OLED_ShowString(oledinfo.Line,1,oledinfo.info);
    }
  }
  /* USER CODE END Oled_handle */
}

这是效果图

再是由AI生成的裸机使用案例,目测无误

#include "main.h"
#include "dht11.h"
#include "usart.h"
#include <stdio.h>

void delay_us(uint16_t us);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    Dht11Typedef hdht11 = {0};
    Dht11_init(&hdht11, GPIOA, GPIO_PIN_0, delay_us);

    double temperature, humidity;

    while (1)
    {
        Dht11_read_data(&hdht11, &temperature, &humidity);

        char buffer[50];
        sprintf(buffer, "Temperature: %.2f C, Humidity: %.2f%%\r\n", temperature, humidity);
        HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);

        HAL_Delay(2000); // 每隔2秒钟读取一次数据
    }
}

总之使用方法就是定义一个DHT11句柄,选好自己要用的引脚,实现微秒级延时,然后据此使用Dht11_init()函数初始化它,读取时使用Dht11_read_data按照要求传入参数就可以便捷读取

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于度湿度一体的传感器DHT11 以下是在51单片机上测试成功的代码 #include<at89x52.h> #include<intrins.h>//加上这句下面的 _nop_();就能用 bit xianshiqiehuan; // sbit dht11_dat=P1^6; //开发板用 sbit dht11_dat=P2^0; //使用版用 unsigned char c,count, dht11temp,dht11dat; unsigned char dht11value[5]; unsigned int x,y,z; unsigned char code dat[]={ 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,}; delay() { unsigned char a; for(a=200;a>0;a--); } display(unsigned char x) //使用版用 { P0=dat[(x0)/10];//十位 P2_3=0; delay(); P2_3=1; P0=dat[(x0)];//个位 P2_2=0; delay(); P2_2=1; } /*display(unsigned char x) //开发板用 { P0=dat[(x0)/10];//十位 P1_2=0; delay(); P1_2=1; P0=dat[(x0)];//个位 P1_3=0; delay(); P1_3=1; } */ delay_1s() { unsigned int i=50000; while(i--); } delay_10us() //10us { _nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); } void delayms(unsigned char x) //1ms单位延时程序 { unsigned char j; while(x--) { for(j=0;j<123;j++){;} } } read_dht11() { unsigned char i; dht11_dat=1; _nop_(); //起始 dht11_dat=0;//拉低总线 delayms(18);//手册要求大于18ms dht11_dat=1;//拉高总线等待dht11回应 while(dht11_dat); // 等待dht11回应 若有回应 dht11_dat=0;往下执行 while(!dht11_dat);//回应后dht11将总线拉低80us,过后又将总线拉高,进入下一步 while(dht11_dat); //拉高80us 又变低,往下执行 进入50us延时 for(i=0;i<24;i++) { while(!dht11_dat);//50us过后...... dht11_dat=1;往下执行 delay_10us();delay_10us();delay_10us();//延时30us,查看总线是高是低, dht11temp=0; //先默认为0处理 if(dht11_dat) dht11temp=1; //1处理 dht11dat=dht11dat<<1; //必须先移动再或 若先或再移本次数据就移动了 dht11dat=dht11dat|dht11temp; dht11value[i/8]=dht11dat; while(dht11_dat);//如果处理的是1,30us过后总线还是1,那就在此等待总线变为0进入下一个50us低电平,不然会重复进行0处理 } } main() { delay_1s(); //要求上电等1秒,让dht11稳定 EA=1;//开放中断 TMOD=0x01;//设T0 为16位计数方式 ET0=1;//定时0中断允许 TR0=1;//开启TR0 while(1) { if(!xianshiqiehuan) //显示度 display(dht11value[2]) ; else {display(dht11value[0]) ; //显示湿度 P0=0x92&0x7f; //千位显 S.代表湿度 P2_5=0; delay(); P2_5=1; } } } dingshi() interrupt 1 //定时器0服务程序 { TH0=0; TL0=0; count++; if(count==55){count=0;read_dht11();xianshiqiehuan=~xianshiqiehuan; } //在切换显示时采集,以防中断采样带来的显示闪烁 }
您好!对于使用STM32F1HAL驱动DHT11湿度传感器,您可以按照以下步骤进行操作: 1. 首先,您需要确保已经正确连接了DHT11传感器到STM32F1开发板上。将DHT11的VCC引脚连接到STM32F1的3.3V电源引脚,将GND引脚连接到STM32F1的地引脚,将DHT11的数据引脚连接到STM32F1的GPIO引脚。 2. 在STM32CubeMX中配置相关的GPIO引脚。选择合适的GPIO引脚作为DHT11传感器的数据引脚,并设置为输入模式。 3. 在代码中初始化相关的GPIO引脚,以及使用HAL库中的延时函数。 4. 编写代码读取DHT11传感器的数据。您可以使用GPIO读取数据引脚的电平变化来读取湿度数据。具体的读取过程可以参考DHT11的通信协议。 下面是一个简单的示例代码,用于读取DHT11传感器的湿度数据: ```c #include "stm32f1xx_hal.h" #define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 void delay_us(uint32_t us) { us *= (SystemCoreClock / 1000000) / 9; while (us--) { __NOP(); } } void DHT11_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = DHT11_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct); } void DHT11_Read(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] = {0}; uint8_t i, j; // 主机拉低总线,持续18ms以上 HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); delay_us(20000); // 主机释放总线,并等待DHT11响应 HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); delay_us(30); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET) { // 等待DHT11拉低总线,持续80us while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET); // 等待DHT11拉高总线,持续80us while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET); // 接收湿度数据 for (i = 0; i < 5; i++) { for (j = 0; j < 8; j++) { // 等待DHT11拉低总线,持续50us while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET); // 计算数据位 delay_us(30); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) { data[i] |= (1 << (7 - j)); } // 等待DHT11拉高总线,持续26-28us或70us while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET); } } // 校验数据 if ((data[0] + data[1] + data[2] + data[3]) == data[4]) { *humidity = data[0]; *temperature = data[2]; } } } int main(void) { uint8_t humidity, temperature; HAL_Init(); DHT11_Init(); while (1) { DHT11_Read(&humidity, &temperature); // 处理湿度数据 // ... } } ``` 请注意,这只是一个简单的示例代码,您可能需要根据实际情况进行适当的修改和调整。此外,为了保证准确性,您可能需要添加适当的校验和错误处理机制。 希望这能帮助到您!如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值