ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32 学习日志:PWM 驱动直流电机、DWT 精确延时、DHT11 单总线通信与 Keil 工程模板

STM32 学习日志:PWM 驱动直流电机、DWT 精确延时、DHT11 单总线通信与 Keil 工程模板 STM32 学习日志PWM 驱动直流电机、DWT 精确延时、DHT11 单总线通信与 Keil 工程模板学习日期2026 年 8 月 19 日今天主要学习了 PWM 控制 TB6612FNG 有刷直流电机、DWT 微秒延时、DHT11 温湿度传感器的单总线通信以及手动建立 Keil 工程模板的方法。一、PWM 基础知识PWM 即脉宽调制通过控制一个方波中“高电平持续时间”的比例来控制平均输出效果。对于 STM32 定时器PWM 频率 定时器计数时钟 / [(PSC 1) × (ARR 1)] 占空比 CCR / (ARR 1) 占空比分辨率 1 / (ARR 1)其中PSC预分频器决定定时器计数速度。 ARR自动重装载值决定一个 PWM 周期需要计多少次。 CCR比较寄存器决定高电平持续到第几次计数。 CNT定时器当前正在计数的值。可以简单记忆PSC 控制计数速度。 ARR 决定 PWM 周期。 CCR 决定高电平时间也就是占空比。例如PSC 71 ARR 99 定时器时钟 72MHz则PWM 频率 72MHz / 72 / 100 10kHz如果CCR 20占空比约为20 / 100 20%如果CCR 80占空比约为80 / 100 80%注意代码中直接写20、80作为 PWM 速度值的前提是ARR 99。如果 ARR 不是 99CCR 的范围也必须跟着改变。二、TB6612FNG 电机驱动模块STM32 的 GPIO 引脚只能输出很小的电流不能直接驱动直流电机。因此需要 TB6612FNG 这种电机驱动模块。TB6612FNG 可以理解为电机的“功率放大器”STM32 负责发出控制命令。 TB6612FNG 负责使用 VM 电源给电机提供实际电流。本次电机 1 使用的核心连接如下STM32 引脚TB6612FNG 引脚作用PB0PWMAPWM 调速信号PA4AIN1电机方向控制PA5AIN2电机方向控制PA7STBY驱动芯片使能KEY1用户按键切换快慢KEY2用户按键切换正反转需要重点记住PWMA控制电机速度。 AIN1、AIN2控制电机方向。 STBY控制驱动芯片是否工作。 AO1、AO2连接电机两根线。 VM电机电源。 VCC驱动芯片逻辑电源。 GNDSTM32、模块、电机电源必须共地。三、为什么 PWM 极性通常设置为 HighTB6612FNG 的 PWMA/PWMB 是高电平有效。CubeMX 中设置CH Polarity High表示PWM 高电平驱动器允许输出。 PWM 低电平驱动器停止输出或进入制动状态。这样逻辑最直观CCR 越大 → 高电平时间越长 → 电机获得驱动的时间越长 → 电机转速越快注意PWM 极性不决定电机正反转。PWM控制速度。 AIN1/AIN2控制方向。四、TB6612FNG 电机控制程序1. 先启动 PWM再使能电机驱动芯片在/* USER CODE BEGIN 2 */中加入HAL_TIM_PWM_Start(htim3,TIM_CHANNEL_3);HAL_GPIO_WritePin(STBY_GPIO_Port,STBY_Pin,GPIO_PIN_SET);含义第一句让 TIM3_CH3 开始从 PB0 输出 PWM。 第二句把 STBY 拉高让 TB6612FNG 退出待机并开始工作。如果 STBY 没有拉高即使 PWM 和方向引脚都正确电机也不会转动。2. 编写电机方向与速度控制函数voidMotor_Set(uint8_tdir,uint16_tspeed){__HAL_TIM_SET_COMPARE(htim3,TIM_CHANNEL_3,speed);if(dir0){HAL_GPIO_WritePin(AIN1_GPIO_Port,AIN1_Pin,GPIO_PIN_SET);HAL_GPIO_WritePin(AIN2_GPIO_Port,AIN2_Pin,GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(AIN1_GPIO_Port,AIN1_Pin,GPIO_PIN_RESET);HAL_GPIO_WritePin(AIN2_GPIO_Port,AIN2_Pin,GPIO_PIN_SET);}}这个函数有两个参数dir方向。 speedPWM 比较值也就是 CCR 值。方向逻辑AIN1AIN2电机状态高低正转低高反转如果实际电机方向和程序描述相反通常只是电机两根线接反了。可以交换 AO1/AO2 的电机接线或者交换程序中正反转逻辑。3. 定义速度和状态变量#defineSPEED_LOW20#defineSPEED_HIGH80uint8_tspeed_mode0;uint8_tdir_mode0;含义SPEED_LOW低速 PWM 值。 SPEED_HIGH高速 PWM 值。 speed_mode 0低速。 speed_mode 1高速。 dir_mode 0正转。 dir_mode 1反转。4. 按键控制速度和方向if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)GPIO_PIN_SET){speed_mode!speed_mode;uint16_tspeed(speed_mode0)?SPEED_LOW:SPEED_HIGH;Motor_Set(dir_mode,speed);while(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)GPIO_PIN_SET);}!speed_mode的作用是切换 0 和 10 变 1。 1 变 0。三目运算符(speed_mode0)?SPEED_LOW:SPEED_HIGH;意思是如果当前是低速模式speed 取 SPEED_LOW。 否则 speed 取 SPEED_HIGH。while等待松手的作用是防止按住按键时不断切换状态。实际使用时还应加入约 10ms 到 20ms 的消抖延时否则机械按键可能一次按下被识别多次。五、DWT 精确延时DWT 是 Cortex-M 内核自带的高速计数器全称为Data Watchpoint and Trace 数据观察点与跟踪单元初学阶段最常用的是DWT_CYCCNTCPU 周期计数器。STM32F103 常用主频为 72MHz1 秒 72,000,000 个时钟周期。 1 微秒 72 个时钟周期。因此 DWT 可以实现微秒级延时DWT_DelayUs(10);DWT_DelayMs(100);DWT_DelayS(1);DWT 初始化的核心逻辑DEMCR|(uint32_t)DEMCR_TRCENA;DWT_CYCCNT0;DWT_CTRL|(uint32_t)DWT_CTRL_CYCCNTENA;含义DEMCR_TRCENA打开内核调试与跟踪功能。 DWT_CYCCNT 0先将周期计数器清零。 DWT_CTRL_CYCCNTENA让周期计数器开始计数。DWT 适合DHT11 单总线时序。 超声波模块微秒延时。 测量一段程序的运行时间。 精确软件延时。注意DWT 延时属于忙等待延时期间 CPU 一直在循环等待不适合长时间延时。短延时DWT_DelayUs()。 较长延时HAL_Delay()、定时器中断或非阻塞计时。六、DHT11 单总线通信流程DHT11 使用一根 DATA 数据线进行双向通信。空闲状态下DATA 由上拉电阻保持高电平。完整流程1. DHT11 上电后等待至少 1 秒。 2. STM32 将 DATA 配置为输出。 3. STM32 拉低 DATA 至少 18ms。 4. STM32 释放 DATA并改为输入。 5. DHT11 拉低约 83us表示收到请求。 6. DHT11 拉高约 87us表示准备发送数据。 7. DHT11 发送 40 位数据。 8. STM32 读取数据并校验。DHT11 一次发送 5 个字节湿度整数 湿度小数 温度整数 温度小数 校验和校验和计算校验和 湿度整数 湿度小数 温度整数 温度小数如果计算结果和接收到的第五个字节相同说明数据正确。DHT11 每一位数据的判断方式先低电平约 50us。 高电平约 26us表示 0。 高电平约 70us表示 1。所以程序会在高电平开始后约 40us 判断40us 后仍是高电平这一位是 1。 40us 后已经变低这一位是 0。读取 DHT11 时等待电平变化必须加入超时判断。否则 DHT11 未接好或没有供电时程序可能永久卡在while循环中。七、GPIO 配置结构体的理解DHT11 中经常需要让一个 GPIO 在输出和输入之间切换GPIO_InitTypeDef GPIO_InitStruct{0};这句含义是创建一个 GPIO 配置变量。 并把结构体中所有成员先初始化为 0。后续填写GPIO_InitStruct.PinDHT11_DATA_Pin;GPIO_InitStruct.Modemode;GPIO_InitStruct.Pullpull;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(DHT11_DATA_GPIO_Port,GPIO_InitStruct);可以理解为先准备一张 GPIO 配置表。 填写要配置哪个引脚、输入还是输出、是否上拉、输出速度。 最后调用 HAL_GPIO_Init()将配置真正写入 STM32。八、Keil 工程目录作用手动创建 STM32 工程时常见目录如下文件夹作用Doc项目文档、接线图、模块说明、readme 等LibrariesCMSIS 核心库和 HAL 驱动库ProjectKeil 工程文件、编译输出文件等User用户编写的 main.c、外设驱动、BSP 文件等常见 User 文件作用文件作用main.c主函数入口主要应用逻辑main.hmain.c 对应头文件放函数声明和全局定义stm32f1xx_hal_conf.hHAL 外设模块开关配置stm32f1xx_it.c中断服务函数实现stm32f1xx_it.h中断函数声明system_stm32f1xx.c系统初始化、时钟变量更新九、手动建立 Keil 工程模板步骤第一步建立 Doc、Libraries、Project、User 四个文件夹。 第二步复制 CMSIS、STM32F1xx_HAL_Driver 到 Libraries。 第三步复制 main.c、main.h、stm32f1xx_hal_conf.h、 stm32f1xx_it.c、stm32f1xx_it.h、system_stm32f1xx.c 到 User。 第四步在 Keil 中新建工程选择正确的 STM32 芯片。 第五步建立分组 STARTUP、CMSIS、STM32F1xx_HAL_Driver、USER、DOC。 第六步分别把启动文件、system 文件、HAL 源文件、 User 源文件和文档添加到对应分组。 第七步在魔术棒中设置芯片、时钟、输出 HEX、 宏定义、头文件路径和下载调试器。 第八步编写 main.c编译验证。常用宏定义USE_HAL_DRIVER STM32F103xB十、新增模块文件时的注意事项在已有工程中加入一个新模块例如 DHT11、OLED、DWT 或电机模块时1. 在 User 文件夹建立模块目录例如 dht11。 2. 放入该模块的 .c 和 .h 文件。 3. 在 Keil 的 USER 分组中添加对应的 .c 文件。 4. 在使用该模块的源文件中包含对应 .h 文件。 5. 确认 HAL 配置文件已打开所需外设模块。 6. 重新编译查看错误和警告。今天还遇到了一个典型问题MX_USART1_UART_Init();函数虽然已经在串口源文件中实现但main.c没有包含串口头文件导致编译器提示function MX_USART1_UART_Init declared implicitly解决方法是在main.c加入#includeusart/bsp_usart.h这说明.c文件负责实现函数.h文件负责声明函数。使用某模块时不能只把.c文件加入 Keil还要在需要调用的位置包含对应的.h文件。十一、今日总结今天学习的内容可以概括为PWM 中ARR 决定周期CCR 决定占空比。 TB6612FNG 中PWMA 控制速度 AIN1/AIN2 控制方向 STBY 必须拉高才能工作。 DWT 是内核自带的高速周期计数器 适合微秒延时和程序运行时间测量。 DHT11 使用单总线通信 主机低电平至少 18ms 发起请求 传感器回应后发送 40 位温湿度数据。 Keil 工程中 模块的 .c 文件要加入工程 模块的 .h 文件要在使用处包含。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进