野火STM32_HAL库版课程笔记-DWT应用与DHT11温湿度传感器
前置介绍DHT11 温湿度传感器温湿度传感器模块 DHT11 外观DHT11 模块简介DHT11 温湿度模块可以检测环境相对湿度和温度 内部包括一个电容式感湿元件和一个 NTC 测温元件并与一个高性能 8 位单片机相连接 使用单总线与主控通信仅需使用一个 I/O 口DHT11 使用说明 模块接口DHT11 程序流程 (通信流程)DHT11 使用单线双向的串行通行方式数据格式为: 16bit 湿度数据 (8bit 整数, 8bit 小数), 16bit 温度数据 (8bit 整数, 8bit 小数), 8bit 校验和数据. 共 40bit, 高位先出通信时序为: 建立连接, 数据接收建立连接 (主机发送起始信号):上电后等待越过不稳定状态由主机输出拉低数据线, 时间要大于 18ms主机端口改为输入状态建立连接 (从机响应信号):从机接收到信号后, 拉低数据线代表应答再拉高数据线表示连接建立成功数据接收:根据低电平后的高电平时间来决定接收数据的种类数据接收这里可以看到:其通过判断低电平后的高电平时间来决定接收数据的种类, 在低电平后的高电平时间是 us 级别的, 所以这里需要用 DWT 来精确计量,同时这里也可以使用不同的逻辑判断数据种类.并不需要去判断其高电平时间是否为指定的时间长度, 只需要判断高电平来临这一刻往后的中间数的延迟时间 (比如: 40us) 后是否还是高电平, 如果是, 那就说明其发送为 位数据1, 如果不是, 那就是 位数据0同时, 需要注意, 每次采集间隔需大于 2 秒如何实现该程序:项目配置复制上一节 DWT基础应用与获取程序运行时间Debug练习 项目以直接使用模板开始本节 DWT 应用添加串口部分找到之前的 串口发送之发送字符串与printf函数 项目找到项目 .\Core\Inc 和 .\Core\Src 下的 usart.h 和 usart.h在本节项目中 .\User\ 下创建 usart 文件夹 (用于存放串口模块代码)将刚刚找到的两个文件拷贝到本节项目的 .\User\usart/ 下(可选) 因为这里的串口是板级支持的, 所以可以将两个文件前加 bsp_ 前缀修改本节项目名称点击 Manage Project Items 打开配置界面双击 Project Targets 下的列, 将其改为本节项目名 DWT_DHT11添加 usart.c添加 HAL 库文件因为创建模板库的时候, 采取的是渐进式的引入方案,所以这里我们用到了 usart 就需要引入对应的 HAL 库文件.Groups 选择 STM32F1xx_HAL_Driver, Files 列下面点击 Add Files...转到 .\Libraries\STM32F1xx_HAL_Driver\Src\Legacy 文件夹中找到 stm32f1xx_hal_usart.c stm32f1xx_hal_dma.c stm32f1xx_hal_uart.c 三个文件, 添加这时, 在左侧即可看到对应文件被添加到项目中对 usart 文件进行改造删除 CubeMX 自动生成的注释及不相关的内容增加 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; }在 h 文件中 引入 main.h在 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 模组相关文件在 项目/User/ 文件夹下创建 dht11 文件夹在 dht11 文件夹下创建 bsp_dht11.h 和 bsp_dht11.c 文件为 .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.hDHT11 驱动代码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字节数据湿度整数、小数温度整数、小数校验和 >DHT11 应用层代码1. 项目\ 目录下, 创建 App 文件夹. App 文件夹下创建 dht11 文件夹, 分别创建 app_dht11.h , app_dht11.c项目 └── App └── dht11 ├── app_dht11.h └── app_dht11.c2. 在 Keil 中, 添加 组 App, 和 app_dht11.c3. 在 魔术棒 中添加头文件目录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 (124) #define DWT_CTRL_CYCCNTENA (10) 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-InstanceUSART1) { /* 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-InstanceUSART1) { /* 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字节数据湿度整数、小数温度整数、小数校验和 >硬件连接程序现象使用串口工具打开串口, 查看输出的数据, 可以看到温湿度数据读取成功并且正确按照指定格式进行输出.向温湿度传感器模块吹气或用手握住, 可以看到其温湿度发生明显变化.