当前位置: 首页 > news >正文

STM32定时器的PWM模式,从捕获/比较寄存器开始理解

你有没有想过,一个定时器的PWM输出,说到底就是比较两个寄存器的值?先看代码:

TIM_HandleTypeDef htim2; TIM_OC_InitTypeDef sConfigOC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999; HAL_TIM_PWM_Init(&htim2); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

这段代码配置完,PA0引脚上会输出一个1kHz、占空比50%的方波。Prescaler=71把72MHz的系统时钟先除以72,得到1MHz的计数时钟。Period=999意味着计数器从0走到999正好1000个计数步——1MHz / 1000 = 1kHz,没错。Pulse=500就是高电平持续500步,占空比50%。

一步步来,先搞清楚PWM的底层是怎么运作的。

核心硬件:一个计数器CNT,一个自动重装载寄存器ARR,一个捕获/比较寄存器CCR。

CNT在每个时钟脉冲后加1,从0累加到ARR的值,然后溢出回0,如此循环。CCR里存的是比较阈值——硬件比较器在每一个时钟周期里比较CNT和CCR的大小。在PWM1模式下,CNT < CCR时输出有效电平,CNT >= CCR时输出无效电平。

所以方才那个配置里,CNT从0走到499时输出高,从500走到999时输出低。下一周期CNT回到0,又重新开始。周期由ARR决定,占空比由CCR决定。这两个寄存器的组合把PWM的频率和占空比完全解耦了——改频率只动ARR,改占空比只动CCR,互不影响。

一个有意思的地方是,CCR可以在运行时随时写入新值。这意味着占空比可以动态调整,而且在修改的瞬间生效。做呼吸灯的话只需要在主循环里逐次改变Pulse的值:

uint16_t brightness = 0; int8_t direction = 1; while (1) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, brightness); brightness += direction; if (brightness >= 999) direction = -1; if (brightness == 0) direction = 1; HAL_Delay(2); }

每次循环做一次寄存器写入,LED的亮度就从暗到亮再回到暗。整个过程没有中断参与,完全是硬件在背后默默地做比较。CPU每秒要跑成百上千次其他任务,而PWM的精度丝毫不受影响——这就是硬件外设存在的意义。

深入一步看。STM32的一个定时器里,多个通道共享同一个ARR,但每个通道拥有独立的CCR。这意味着我们可以用同一个定时器输出多路频率相同、占空比各自独立的PWM。驱动四路舵机或者一组RGB LED时,这个设计特别实用——频率统一由ARR决定,每路的脉宽单独调。

不妨这样考虑:如果反过来,固定CCR、去读CNT的值,捕获/比较单元就成了输入捕获——测量外部信号的脉宽或者周期。同一个寄存器块,方向颠倒一下,功能完全不同。PWM输出也好,输入捕获也好,一个定时器可以同时工作在多种模式下,关键是理解它的工作机制。

HAL库里有一个宏值得注意:

#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \ (*(__IO uint32_t *)&((__HANDLE__)->Instance->CCR1) \ + (__CHANNEL__)) = (__COMPARE__)

本质上就是一次对CCR寄存器的内存写入。没有函数调用栈的开销,没有参数校验,没有状态检查。在控制BLDC电机或者做数字电源这类对响应时间有要求的场景里,用这个宏直接写寄存器比调用HAL_TIM_PWM_SetPulseAnd duty要快得多。一层层函数调用下去,最终做的是同一件事——往CCR写值。

更深入一点:高级定时器TIM1和TIM8支持互补PWM输出和可编程死区插入。做H桥驱动时,上下管绝对不能同时导通,死区时间就是上下管切换之间那个"两路都关断"的安全窗口。STM32的硬件死区发生器能精确到几个时钟周期,比自己用软件延时去折腾可靠得多。

从捕获/比较寄存器的视角去理解PWM,整个机制其实很简洁的。频率由ARR控制,占空比由CCR决定,动态修改CCR就能实时调脉宽。无论用HAL库、LL库还是直接操作寄存器,底层逻辑没有区别。换一个系列的MCU,只要理解了比较器的工作方式,上手只是换个寄存器名字的事。

http://www.jsqmd.com/news/1144564/

相关文章:

  • 百考通智能降重为原创保驾护航
  • 个人微信API二次开发,还在用低效的JSON格式,难道没弄懂底层的Protobuf序列化吗?
  • SCI论文引言不会写?五段式逻辑+AI提示词,轻松写出高质量Introduction
  • ComfyUI ControlNet Aux技术深度解析:构建AI图像生成的控制中枢
  • 如何为TouchDesigner配置MediaPipe虚拟摄像头:Spout与Syphon完整指南
  • 网盘直链技术革新:突破传统下载架构的智能解决方案
  • Web 路径安全:避免 `../` 目录遍历攻击的 3 种服务器端校验方案
  • 千问8元新客立减券完整使用教程,新用户福利,千问新用户专属220372(7.7)
  • 企业级GlusterFS Dashboard部署指南:高可用与安全最佳实践
  • JSP 用户管理模块 3 大常见安全漏洞与修复:SQL注入、XSS与权限校验
  • Parsec VDD高性能虚拟显示器驱动:Windows游戏串流与远程办公终极指南
  • 如何5分钟掌握GBFR-Logs:碧蓝幻想Relink最强DPS统计工具完全指南
  • Seedance2.5本地AI生图视频部署:硬件要求与稳定性测试指南
  • 豆包Claw仿冒事件:AI工具信任劫持与恶意行为深度解析
  • Barlow字体终极指南:为什么这款开源字体能彻底改变你的设计工作流?
  • 酷狗音乐API终极指南:解决VIP歌曲获取与版本兼容性问题
  • 高压数字隔离技术:ISOM8710与PIC18F26J13的工程实践
  • 8款免费Illustrator自动化脚本:让创意设计告别重复劳动的智能神器
  • QRazyBox终极指南:免费修复无法扫描的二维码
  • Visual C++运行库终极指南:一次安装解决所有DLL缺失问题
  • 医疗金融千级KOS矩阵短视频违禁词批量检测方案与合规工具测评
  • 5个核心技巧让你高效使用Poppins多语言字体:几何无衬线字体的完整指南
  • MySQL解析器深度定制实战:HINT注入与执行计划劫持的工程实现
  • PIC18LF25K40与PAM8904构建智能音频报警系统
  • Claude Sonnet 5 如何加速 L5 级无人驾驶研发:大语言模型驱动的自动驾驶范式革命
  • PDF 元数据属性修改实战:3 种工具批量清除 Word 遗留标题,解决显示名不符问题
  • FixMatch 与 UDA 实战:CIFAR-10 仅用 4000 标签实现 94.5% 精度的半监督图像分类
  • Paperxie|告别论文双重检测卡稿!分类型降重降 AIGC 一站式解决方案
  • macOS 14.5 虚拟机性能优化:4项配置调整提升 Windows 11 主机流畅度 30%
  • A3910与PIC18LF46K22电机驱动方案详解