野火STM32_HAL库版课程笔记-DWT应用与DHT11温湿度传感器

前置介绍

DHT11 温湿度传感器
温湿度传感器模块 DHT11 外观
DHT11 模块简介

DHT11 温湿度模块可以检测环境相对湿度和温度, 内部包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接, 使用单总线与主控通信,仅需使用一个 I/O 口

DHT11 使用说明 & 模块接口
DHT11 程序流程 (通信流程)
  1. DHT11 使用单线双向的串行通行方式
  2. 数据格式为: 16bit 湿度数据 (8bit 整数, 8bit 小数), 16bit 温度数据 (8bit 整数, 8bit 小数), 8bit 校验和数据. 共 40bit, 高位先出
  3. 通信时序为: 建立连接, 数据接收
    1. 建立连接 (主机发送起始信号):
      1. 上电后等待越过不稳定状态
      2. 由主机输出拉低数据线, 时间要大于 18ms
      3. 主机端口改为输入状态
    1. 建立连接 (从机响应信号):
      1. 从机接收到信号后, 拉低数据线代表应答
      2. 再拉高数据线表示 连接建立成功
    1. 数据接收:
      1. 根据低电平后的高电平时间来决定接收数据的种类
数据接收这里可以看到:

其通过判断低电平后的高电平时间来决定接收数据的种类, 在低电平后的高电平时间是 us 级别的, 所以这里需要用 DWT 来精确计量,

同时这里也可以使用不同的逻辑判断数据种类.

并不需要去判断其高电平时间是否为指定的时间长度, 只需要判断高电平来临这一刻往后的中间数的延迟时间 (比如: 40us) 后是否还是高电平, 如果是, 那就说明其发送为 位数据"1", 如果不是, 那就是 位数据"0"

同时, 需要注意, 每次采集间隔需大于 2 秒

如何实现该程序:

项目配置

复制上一节 "DWT基础应用与获取程序运行时间Debug练习" 项目以直接使用模板开始本节 DWT 应用

添加串口部分
  1. 找到之前的 串口发送之发送字符串与printf函数 项目
  2. 找到项目 .\Core\Inc 和 .\Core\Src 下的 usart.h 和 usart.h
  3. 在本节项目中 .\User\ 下创建 usart 文件夹 (用于存放串口模块代码)
  4. 将刚刚找到的两个文件拷贝到本节项目的 .\User\usart/ 下
  1. (可选) 因为这里的串口是板级支持的, 所以可以将两个文件前加 "bsp_" 前缀
修改本节项目名称
  1. 点击 Manage Project Items 打开配置界面
  2. 双击 Project Targets 下的列, 将其改为本节项目名 "DWT_DHT11"
添加 usart.c
添加 HAL 库文件

因为创建模板库的时候, 采取的是渐进式的引入方案,

所以这里我们用到了 usart 就需要引入对应的 HAL 库文件.

  1. Groups 选择 STM32F1xx_HAL_Driver, Files 列下面点击 "Add Files..."
  2. 转到 .\Libraries\STM32F1xx_HAL_Driver\Src\Legacy 文件夹中
  3. 找到 "stm32f1xx_hal_usart.c" "stm32f1xx_hal_dma.c" "stm32f1xx_hal_uart.c" 三个文件, 添加

这时, 在左侧即可看到对应文件被添加到项目中

对 usart 文件进行改造
  1. 删除 CubeMX 自动生成的注释及不相关的内容

  2. 增加 printf 重定向内容

    /**

    • @brief 重定向 printf 的输出到串口
    • @param ch: 要发送的字符
    • @param f: 文件指针 (标准库要求的参数, 一般不使用)
    • @retval 返回发送的字符
      */
      int fputc(int ch, FILE *f)
      {
      HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
      return ch;
      }
  3. 在 h 文件中 引入 "main.h"

  4. 在 main.h 中引入 <stdio.h> 以使用 printf

解决 usart 的 Error_Handler() 报错问题

设置完成后会看到 MX_USART1_UART_Init() 中 Error_Handler() 报错

这是因为 HAL 库手动建模板的时候, 并没有错误的处理,

可以到之前的项目中的 main.c 中找到 Erro_Hanlder 复制添加到 mian.c 中

复制代码
/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
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 */
}

然后在 main.h 中添加该函数的声明

复制代码
void Error_Handler(void);
在 main.c 中引入 usart.h
复制代码
#include "usart/bsp_usart.h"
删除来自上一个项目的代码

将 main.c 中的 main 函数改为如下

删除 while 中的内容, 调用串口初始化函数.

复制代码
int main(void)
{

    HAL_Init();             // 初始化 HAL 库

    SystemClock_Config();   // 配置系统时钟, 设置为 72MHz 

    DWT_Init();             // 启动 DWT 计数器, 用于精确测量程序运行时间

    MX_USART1_UART_Init();

    while (1)
    {

    }
}
创建添加 DHT11 模组相关文件
  1. 在 项目/User/ 文件夹下创建 dht11 文件夹

  2. 在 dht11 文件夹下创建 "bsp_dht11.h" 和 "bsp_dht11.c" 文件

  3. 为 .h 和 .c 文件添加 基础部分

    #ifndef BSP_DHT11_H
    #define BSP_DHT11_H

    /* Includes ------------------------------------------------------------------*/
    #include "main.h"

    #endif /* BSP_DHT11_H */

    /**

    • @file bsp_dht11.c
    • @brief 温湿度传感器底层函数接口
      */

    #include "dht11/bsp_dht11.h"

DHT11 驱动代码
1. 完成 IO 引脚的初始化

默认配置成推挽输出

按之前的方式为使用 CubeMX 进行配置, 但这里不使用 CubeMX 直接用 bsp_led.c 中的 LED_GPIO_Config 将其修改后直接使用

需要将其改为 DHT11 使用的 GPIO 引脚和对应 IO 组

2. 配置对应引脚的宏定义
复制代码
#ifndef __BSP_DHT11_H__
#define __BSP_DHT11_H__

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* 定义 DHT11_DATA 连接的 GPIO 端口, 用户只需要修改下面的代码即可改变受控制的 DHT11_DATA 引脚 */
#define DHT11_DATA_Pin        GPIO_PIN_12           /* GPIO 端口 */
#define DHT11_DATA_GPIO_Port  GPIOB                 /* 对应引脚 */

#endif /* __BSP_DHT11_H__ */
3. 修改 IO 引脚的初始化中的配置与注释
复制代码
#include "dht11/bsp_dht11.h"

 /**
 * @brief 初始化 DHT11 的 GPIO 引脚
 */
void DHT11_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* 使能GPIOB端口时钟 */
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = DHT11_DATA_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出
  GPIO_InitStruct.Pull = GPIO_NOPULL;             // 不上拉不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 低速输出
  HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);

  /* 初始化 DHT11 数据引脚 */
  HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
}
4. 增加 DHT11_SetGPIOMode 函数
复制代码
 /**
 * @brief 配置 DHT11 数据引脚的工作模式
 * @param mode 引脚模式, 如输入, 输出等, 使用 HAL 库中的 GPIO_MODE_XXX 宏
 * @param pull 上下拉配置, 使用 HAL 库中的 GPIO_PULL_XXX 宏
 */
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = DHT11_DATA_Pin;
  GPIO_InitStruct.Mode = mode;                    // 推挽输出
  GPIO_InitStruct.Pull = pull;                    // 不上拉不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 低速输出
  HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);  // 初始化 GPIO
}
5. 增加 DHT11 数据读取函数
复制代码
/**
 * @brief  读取DHT11一个字节数据
 * @retval 返回8位数据(1字节)
 */
uint8_t DHT11_ReadByte(void)
{
    uint8_t value = 0;

    for (uint8_t i = 0; i < 8; i++)
    {
        // 等待信号线变高,表示开始传输第i位数据
        while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET);

        DWT_DelayUs(40);  // 延时40微秒,判断数据位是0还是1

        // 如果40us后线仍然为高,表示该位为1
        if (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
        {
            value |= (1 << (7 - i));  // 把对应位设置为1

            // 等待信号线变低,准备接收下一位
            while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET);
        }
        // 如果40us后线为低,则该位为0,直接继续下一位
    }

    return value;
}

/**
 * @brief  读取DHT11传感器数据
 * @param  data 指向存放温湿度数据的结构体指针
 * @retval HAL_OK 成功,HAL_ERROR 失败
 */
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data)
{
    uint8_t retry = 0;

    // 1. 主机拉低总线,发送起始信号(至少18ms)
    DHT11_SetGPIOMode(GPIO_MODE_OUTPUT_PP, GPIO_NOPULL);     // 设置为推挽输出
    HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_RESET);
    DWT_DelayMs(20);  // 保持低电平20ms,通知DHT11开始传输
    HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
    DWT_DelayUs(30); 	// 延时30微秒

    // 2. 设置引脚为输入,等待DHT11响应信号
    DHT11_SetGPIOMode(GPIO_MODE_INPUT, GPIO_PULLUP);

    // 3. 等待DHT11拉低响应信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时无响应,读取失败
        DWT_DelayUs(1);
    }

    // 等待DHT11拉高信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时,读取失败
        DWT_DelayUs(1);
    }

    // 等待DHT11再次拉低信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时,读取失败
        DWT_DelayUs(1);
    }

    // 4. 读取5字节数据(湿度整数、小数,温度整数、小数,校验和)
    data->humi_int   = DHT11_ReadByte();
    data->humi_deci  = DHT11_ReadByte();
    data->temp_int   = DHT11_ReadByte();
    data->temp_deci  = DHT11_ReadByte();
    data->check_sum  = DHT11_ReadByte();

    // 5. 校验数据是否正确
    uint8_t sum = data->humi_int + data->humi_deci + data->temp_int + data->temp_deci;
    return (sum == data->check_sum) ? HAL_OK : HAL_ERROR;
}
DHT11 应用层代码
1. 项目\ 目录下, 创建 App 文件夹. App 文件夹下创建 dht11 文件夹, 分别创建 app_dht11.h , app_dht11.c
复制代码
项目
└── App
    └── dht11
        ├── app_dht11.h
        └── app_dht11.c
2. 在 Keil 中, 添加 组 App, 和 app_dht11.c
3. 在 "魔术棒" 中添加头文件目录
4. 编写应用层 DHT11 (APP)
复制代码
#ifndef __APP_DHT11_H
#define __APP_DHT11_H

#include "main.h"


/* 函数声明 */
void Dht11_ReadAndPrint(void);

#endif /* __APP_DHT11_H */

/**
  * @file       app_dht11.c
  * @brief      读取DHT11温湿度传感器实验应用层函数接口
  */

#include "dht11/app_dht11.h"
#include "dht11/bsp_dht11.h" 

static DHT11_DATA_TYPEDEF dht11_data = {0};

/**
  * @brief  读取DHT11传感器数据并打印结果
  * @param  无
  * @retval 读取成功返回1,失败返回0
  */
void Dht11_ReadAndPrint(void)
{
    if(DHT11_ReadData(&dht11_data) == HAL_OK)
    {
        printf("当前数据传输校验正确: ");

        if(dht11_data.humi_deci & 0x80) // 湿度负数判断(一般DHT11无负湿度,保留)
        {
            printf("湿度为 -%d.%d %%RH, ", dht11_data.humi_int, dht11_data.humi_deci);
        }
        else
        {
            printf("湿度为 %d.%d %%RH, ", dht11_data.humi_int, dht11_data.humi_deci);
        }

        if(dht11_data.temp_deci & 0x80) // 温度负数判断
        {
            printf("温度为 -%d.%d ℃ \r\n", dht11_data.temp_int, dht11_data.temp_deci);
        }
        else
        {
            printf("温度为 %d.%d ℃ \r\n", dht11_data.temp_int, dht11_data.temp_deci);
        }
    }
    else
    {
        printf("读取DHT11数据错误! \r\n"); 
    }
}
5. 在 main.c 中添加对应头文件引用
复制代码
/**
  * @file       main.c
  * @brief      DWT应用与DHT11温湿度传感器
  */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "led/bsp_led.h"
#include "dwt/bsp_dwt.h"
#include "usart/bsp_usart.h"
#include "dht11/bsp_dht11.h"
#include "dht11/app_dht11.h"

代码部分

复制代码
#ifndef __MAIN_H
#define __MAIN_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
#include <stdio.h>  // 使用 printf
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
void Error_Handler(void);

#endif /* __MAIN_H */

/**
  * @file       main.c
  * @brief      DWT应用与DHT11温湿度传感器
  */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "led/bsp_led.h"
#include "dwt/bsp_dwt.h"
#include "usart/bsp_usart.h"
#include "dht11/bsp_dht11.h"
#include "dht11/app_dht11.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/

uint64_t begin = 0;     // 记录计时起始值
uint64_t end = 0;       // 记录计时结束值
uint64_t duration = 0;  // 记录程序运行所花的时间 (单位: DWT 计数器周期)

uint64_t task_us = 0;   // 记录程序运行所花的时间 (单位: DWT 计数器周期)

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/* Private functions ---------------------------------------------------------*/

int main(void)
{
  
  HAL_Init();             // 初始化 HAL 库

  SystemClock_Config();   // 配置系统时钟, 设置为 72MHz 

  MX_USART1_UART_Init();  // 初始化串口, 用于打印调试信息
  
  DWT_Init();             // 启动 DWT 计数器, 用于精确测量程序运行时间

  DHT11_GPIO_Config();    // 初始化 DHT11 传感器相关 GPIO 引脚
  
  DWT_DelayS(1);          // 启动前先延时1秒, 确保 DHT11 完成上电初始化 (建议上电后等待至少1秒) 

  while (1)
  {
    /* DHT11 温湿度传感器读取与打印任务 */
    Dht11_ReadAndPrint(); // 读取传感器数据并通过串口打印出来
    
    DWT_DelayS(2);        // 延时2秒后再读取 (DHT11每2秒更新一次数据, 不能太快读取)
  }
}

/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow : 
  *            System Clock source            = PLL (HSE)
  *            SYSCLK(Hz)                     = 72000000
  *            HCLK(Hz)                       = 72000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 2
  *            APB2 Prescaler                 = 1
  *            HSE Frequency(Hz)              = 8000000
  *            HSE PREDIV1                    = 1
  *            PLLMUL                         = 9
  *            Flash Latency(WS)              = 2
  * @param  None
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_ClkInitTypeDef clkinitstruct = {0};
  RCC_OscInitTypeDef oscinitstruct = {0};
  
  /* Enable HSE Oscillator and activate PLL with HSE as source */
  oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;
  oscinitstruct.HSEState        = RCC_HSE_ON;
  oscinitstruct.HSEPredivValue  = RCC_HSE_PREDIV_DIV1;
  oscinitstruct.PLL.PLLState    = RCC_PLL_ON;
  oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;
  oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  {
    /* Initialization Error */
    while(1);
  }

  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
     clocks dividers */
  clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
  if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  {
    /* Initialization Error */
    while(1);
  }
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
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 */
}

#ifndef __BSP_DWT_H 
#define __BSP_DWT_H

/* Includes ------------------------------------------------------------------*/
#include "main.h"
// 引用 main.h 是因为需要用到很多的 HAL 库和定义. 所以也建议后续添加的每一个 .h 文件中都要引入 main.h 

/* 
   在 Cortex_M 里面有一个外设叫 DWT (Data Watchpoint and Trace), 该外设有一个32位的寄存器叫 CYCCNT, 它是一个向上的计数器, 记录的使内核时钟运行的个数,
   假设内核频率为 72M, 内核跳一次的时间大概为 1/72M = 14ns (最长能记录的时间为: 60s = 2 的 32 次方 / 72 000 000) . 当 CYCCNT 溢出之后, 会清0重新开始向上计数
   使能 CYCCNT 计数的操作步骤:
   
   1. 先使能 DWT 外设设备, 这个由另外内核调试寄存器 DEMCR 的 位 24 控制, 写 1 使能
   2. 使能 CYCCNT 寄存器之前, 先清0
   3. 使能 CYCCNT 计数器, 这个由 DWT_CTRL (代码上宏定义为 DWT_CR) 的位0控制, 写 1 使能
   4. CYCCNT 和 us 互相转换: 1 / SystemCoreCLock * CYCCNT (S) = 1000 / SystemCoreClock * CYCCNT (MS) = 1000000 / SystemCoreClock * CYCCNT (US) = (X)US
*/

/* DWT时间戳相关寄存器定义 */
#define DEMCR               *(uint32_t *) (0xE000EDFC)
#define DWT_CTRL            *(uint32_t *) (0xE0001000)
#define DWT_CYCCNT          *(uint32_t *) (0xE0001004)

#define DEMCR_TRCENA           (1<<24)
#define DWT_CTRL_CYCCNTENA     (1<<0)

void DWT_Init(void);
uint32_t DWT_GetTick(void);
uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency);

void DWT_DelayUs(uint32_t time);
void DWT_DelayMs(uint32_t time);
void DWT_DelayS(uint32_t time);


#endif /* __BSP_DWT_H */

/**
  * @file  bsp_dwt.c
  * @brief 使用内核定时器函数接口
  */
  
#include "dwt/bsp_dwt.h"

/**
 * @brief   初始化DWT计数器s
 * @param   无
 * @retval  无
 * @note    使用延时函数前, 必须调用本函数
 */
void DWT_Init(void)
{
  /* 使能 DWT 外设 */
  DEMCR |= (uint32_t)DEMCR_TRCENA;

  /* DWT CYCNT 寄存器计数清0 */
  DWT_CYCCNT = (uint32_t)0U;      // 使能 CYCCNT 寄存器之前, 先清0

  /* 使能 Cortex-M DWT CYCCNT 寄存器 */
  DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;
}

/**
 * @brief   读取当前时间戳
 * @param   无
 * @retval  当前时间戳, 即 DWT_CYCCNT 寄存器的值
 */
uint32_t DWT_GetTick(void)
{
  return ((uint32_t)DWT_CYCCNT);
}

/**
 * @brief   节拍数转化时间间隔 (微秒单位)
 * @param   tick: 需要转换的节拍数
 * @param   frequency: 内核时钟频率
 * @retval  当前时间戳 (微秒单位)
 */
uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency)
{
  return (uint32_t)(1000000.0 / frequency * tick);
}

/**
 * @brief   DWT计数器实现精确延时, 32位计数器
 * @param   time: 延迟长度, 单位: us
 * @retval  无
 */
void DWT_DelayUs(uint32_t time)
{
  /* 将微秒转化为对应的时钟计数值 */
  uint32_t tick_duration = time * (SystemCoreClock / 1000000);
  uint32_t tick_start = DWT_GetTick();  /* 刚进入时的计数器值 */

  while(DWT_GetTick() - tick_start < tick_duration);
}

/**
 * @brief   DWT计数器实现精确延时, 32位计数器
 * @param   time: 延迟长度, 单位: ms
 */
void DWT_DelayMs(uint32_t time)
{
  for (uint32_t i = 0; i < time; i++)
  {
    DWT_DelayUs(1000);
  }
}

/**
 * @brief   DWT计数器实现精确延时, 32位计数器
 * @param   time: 延迟长度, 单位: s
 */
void DWT_DelayS(uint32_t time)
{
  for (uint32_t i = 0; i < time; i++)
  {
    DWT_DelayMs(1000);
  }
}

#ifndef __BSP_LED_H__
#define __BSP_LED_H__

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* ----------------------------- LED 引脚定义 ----------------------------- */
// 红灯
#define LED_R_Pin GPIO_PIN_1
// 绿灯
#define LED_G_Pin GPIO_PIN_2
// 蓝灯
#define LED_B_Pin GPIO_PIN_3
// 所有灯都连接在 GPIOA
#define LED_Port GPIOA

/* ----------------------------- 函数声明 ----------------------------- */
void LED_GPIO_Config(void);

/* ----------------------------- LED控制宏 ----------------------------- */

// 红灯控制
#define LED_R_ON()              HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_RESET);   // 点亮红灯
#define LED_R_OFF()             HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_SET);     // 熄灭红灯
#define LED_R_TOGGLE()          HAL_GPIO_TogglePin(LED_Port, LED_R_Pin);                  // 翻转红灯状态

// 绿灯控制
#define LED_G_ON()              HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_RESET);   // 点亮绿灯
#define LED_G_OFF()             HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_SET);     // 熄灭绿灯
#define LED_G_TOGGLE()          HAL_GPIO_TogglePin(LED_Port, LED_G_Pin);                  // 翻转绿灯状态

// 蓝灯控制
#define LED_B_ON()              HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_RESET);   // 点亮蓝灯
#define LED_B_OFF()             HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_SET);     // 熄灭蓝灯
#define LED_B_TOGGLE()          HAL_GPIO_TogglePin(LED_Port, LED_B_Pin);                  // 翻转蓝灯状态

/* ----------------------------- LED 组合控制宏 ----------------------------- */

// 三色灯全亮
#define LED_RGB_ALL_ON()        LED_R_ON(); LED_G_ON(); LED_B_ON()

// 三色灯全灭
#define LED_RGB_ALL_OFF()       LED_R_OFF(); LED_G_OFF(); LED_B_OFF()

// 仅亮红灯
#define LED_R_ON_ONLY()         LED_R_ON(); LED_G_OFF(); LED_B_OFF()

// 仅亮绿灯
#define LED_G_ON_ONLY()         LED_R_OFF(); LED_G_ON(); LED_B_OFF()

// 仅亮蓝灯
#define LED_B_ON_ONLY()         LED_R_OFF(); LED_G_OFF(); LED_B_ON()

#endif /* __BSP_LED_H__ */

/**
  * @file  bsp_led.c
  * @brief LED灯函数接口
  */

#include "led/bsp_led.h"

 /**
 * @brief 初始化控制LED的GPIO引脚
 * @note  配置为推挽输出, 默认全灭
 */
void LED_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* 使能GPIOA端口时钟 */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = LED_R_Pin|LED_G_Pin|LED_B_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出
  GPIO_InitStruct.Pull = GPIO_NOPULL;             // 不上拉不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 低速输出
  HAL_GPIO_Init(LED_Port, &GPIO_InitStruct);

  /* 初始化时关闭所有LED (低电平点亮, 默认全设为高电平) */
  HAL_GPIO_WritePin(LED_Port, LED_R_Pin|LED_G_Pin|LED_B_Pin, GPIO_PIN_SET);
}

#ifndef __BSP_USART_H__
#define __BSP_USART_H__

/* Includes ------------------------------------------------------------------*/
#include "main.h"

extern UART_HandleTypeDef huart1;

void MX_USART1_UART_Init(void);


#endif /* __BSP_USART_H__ */

/**
  * @file  bsp_usart.c
  * @brief 初始化串口并重定向printf函数到usart端口
  */
  
#include "usart/bsp_usart.h"

UART_HandleTypeDef huart1;

/* USART1 init function */

/**
 * @brief  初始化 USART1 (波特率 115200, 8N1, 双向通信)
 * @note   可用于 printf 重定向输出
 * @retval 无
 */
void MX_USART1_UART_Init(void)
{
  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();
  }
}

/**
 * @brief  USART1 外设底层初始化 (由 HAL_UART_Init 自动调用)
 * @param  uartHandle: UART 句柄
 * @retval 无
 */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
    /* 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_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

/**
 * @brief  UART MSP 反初始化 (释放资源)
 * @param  uartHandle: UART 句柄
 * @retval 无
 */
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
    /* 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);
  }
}

/**
  * @brief  重定向 printf 的输出到串口
  * @param  ch: 要发送的字符
  * @param  f:  文件指针 (标准库要求的参数, 一般不使用)
  * @retval 返回发送的字符
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

#ifndef __BSP_DHT11_H__
#define __BSP_DHT11_H__

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* 定义 DHT11_DATA 连接的 GPIO 端口, 用户只需要修改下面的代码即可改变受控制的 DHT11_DATA 引脚 */

// DHT11_DATA
#define DHT11_DATA_Pin        GPIO_PIN_12           /* GPIO 端口 */
#define DHT11_DATA_GPIO_Port  GPIOB                 /* 对应引脚 */

/* 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;

/* 函数声明 */
void DHT11_GPIO_Config(void);
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull);
uint8_t DHT11_ReadByte(void);
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data);

#endif /* __BSP_DHT11_H__ */

/**
  * @file  bsp_dht11.c
  * @brief 温湿度传感器底层函数接口
  */
  
#include "dht11/bsp_dht11.h"
#include "dwt/bsp_dwt.h"

 /**
 * @brief 初始化 DHT11 的 GPIO 引脚
 */
void DHT11_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* 使能GPIOB端口时钟 */
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = DHT11_DATA_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出
  GPIO_InitStruct.Pull = GPIO_NOPULL;             // 不上拉不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 低速输出
  HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);

  /* 初始化 DHT11 数据引脚 */
  HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
  
}

 /**
 * @brief 配置 DHT11 数据引脚的工作模式
 * @param mode 引脚模式, 如输入, 输出等, 使用 HAL 库中的 GPIO_MODE_XXX 宏
 * @param pull 上下拉配置, 使用 HAL 库中的 GPIO_PULL_XXX 宏
 */
void DHT11_SetGPIOMode(uint32_t mode, uint32_t pull)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */
  GPIO_InitStruct.Pin = DHT11_DATA_Pin;
  GPIO_InitStruct.Mode = mode;                    // 推挽输出
  GPIO_InitStruct.Pull = pull;                    // 不上拉不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 低速输出
  HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);  // 初始化 GPIO
}

/**
 * @brief  读取DHT11一个字节数据
 * @retval 返回8位数据(1字节)
 */
uint8_t DHT11_ReadByte(void)
{
    uint8_t value = 0;

    for (uint8_t i = 0; i < 8; i++)
    {
        // 等待信号线变高,表示开始传输第i位数据
        while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET);

        DWT_DelayUs(40);  // 延时40微秒,判断数据位是0还是1

        // 如果40us后线仍然为高,表示该位为1
        if (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
        {
            value |= (1 << (7 - i));  // 把对应位设置为1

            // 等待信号线变低,准备接收下一位
            while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET);
        }
        // 如果40us后线为低,则该位为0,直接继续下一位
    }

    return value;
}

/**
 * @brief  读取DHT11传感器数据
 * @param  data 指向存放温湿度数据的结构体指针
 * @retval HAL_OK 成功,HAL_ERROR 失败
 */
HAL_StatusTypeDef DHT11_ReadData(DHT11_DATA_TYPEDEF *data)
{
    uint8_t retry = 0;

    // 1. 主机拉低总线,发送起始信号(至少18ms)
    DHT11_SetGPIOMode(GPIO_MODE_OUTPUT_PP, GPIO_NOPULL);     // 设置为推挽输出
    HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_RESET);
    DWT_DelayMs(20);  // 保持低电平20ms,通知DHT11开始传输
    HAL_GPIO_WritePin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin, GPIO_PIN_SET);
    DWT_DelayUs(30); 	// 延时30微秒

    // 2. 设置引脚为输入,等待DHT11响应信号
    DHT11_SetGPIOMode(GPIO_MODE_INPUT, GPIO_PULLUP);

    // 3. 等待DHT11拉低响应信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时无响应,读取失败
        DWT_DelayUs(1);
    }

    // 等待DHT11拉高信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_RESET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时,读取失败
        DWT_DelayUs(1);
    }

    // 等待DHT11再次拉低信号(最大等待100us)
    retry = 0;
    while (HAL_GPIO_ReadPin(DHT11_DATA_GPIO_Port, DHT11_DATA_Pin) == GPIO_PIN_SET)
    {
        if (++retry > 100)
            return HAL_ERROR;    // 超时,读取失败
        DWT_DelayUs(1);
    }

    // 4. 读取5字节数据(湿度整数、小数,温度整数、小数,校验和)
    data->humi_int   = DHT11_ReadByte();
    data->humi_deci  = DHT11_ReadByte();
    data->temp_int   = DHT11_ReadByte();
    data->temp_deci  = DHT11_ReadByte();
    data->check_sum  = DHT11_ReadByte();

    // 5. 校验数据是否正确
    uint8_t sum = data->humi_int + data->humi_deci + data->temp_int + data->temp_deci;
    return (sum == data->check_sum) ? HAL_OK : HAL_ERROR;
}

硬件连接

程序现象

使用串口工具打开串口, 查看输出的数据, 可以看到温湿度数据读取成功并且正确按照指定格式进行输出.

向温湿度传感器模块吹气或用手握住, 可以看到其温湿度发生明显变化.

相关推荐
hmbbcsm8 小时前
关于transformors库的学习笔记
笔记·学习
xqqxqxxq8 小时前
Java AI智能P图工具技术笔记
java·人工智能·笔记
无人装备硬件开发爱好者9 小时前
STM32G474 + 1.32 寸 OLED(128×96)俄罗斯方块游戏实现指南
stm32·嵌入式硬件·游戏
FakeOccupational9 小时前
【电路笔记 PCB】Altium Designer : AD20信号完整性(Signal Integrity)分析+单线路传输分析+串扰分析(暂记)
笔记
三佛科技-134163842129 小时前
SM2850P无电感离线稳压器 5V输出 典型应用电路分析(管脚、关键设计要点)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
潜创微科技10 小时前
IT6636+USB 协同芯片 3 进 1 出 HDMI2.1 KVM 切换器一体化方案
嵌入式硬件·音视频
dqsh0610 小时前
关于STM32G474芯片有规律的自动重启的问题
stm32·单片机·嵌入式硬件·系统重启·原因解析
闪闪发亮的小星星10 小时前
链路预算相关
笔记
霍霍的袁11 小时前
【初识C语言】预处理笔记(预处理指令、宏定义等)
c语言·笔记·visualstudio