
这篇文章是 STM32C542R 开发实战系列的第 4 篇。前面咱们把工程模板、GPIO 输出和串口通信都跑通了今天直接上 PWM。做嵌入式的朋友对 PWM 应该不陌生电机调速、舵机角度控制、LED 呼吸灯、蜂鸣器音调、Buck 电源的输出调节都离不开这一路方波。它不复杂但真要改频率、改占空比的时候很多新手会在定时器分频、自动重载和比较值三者关系上绕一圈才想明白。STM32C542R 属于 ST 的 C5 系列Cortex-M33 内核定位上比老的 F0 系列性能强不少定时器资源也更完整。C542R 的 R 后缀代表 LQFP64 封装片内定时器覆盖基础 PWM 到高级控制场景。这篇会从 CubeMX 配置讲起把 PWM 的启动方式、频率计算公式、占空比修改方法和实测验证流程完整过一遍。看完之后你能完成一个最小可用的 PWM 输出工程并且知道在运行时怎么平滑修改频率和占空比。下面直接进入正题。1. PWM 与 STM32C542R 定时器资源概述PWM 的全称是 Pulse Width Modulation脉冲宽度调制。要理解 PWM先记住一句话频率由定时器溢出周期决定占空比由比较值决定。定时器每计到一个设定值输出电平翻转一次通过改变这个设定值相对整个计数周期的时间位置就能拉宽或收窄高电平的宽度也就是改变占空比。STM32C542R 内部的定时器分为几类。基础定时器只做定时通用定时器可以输出 PWM高级定时器还支持互补输出、死区插入和刹车输入。做电机控制时互补 PWM 加死区是刚需做简单的舵机或 LED 调光通用定时器就够了。实际工程里优先按外设需求选定时器而不是哪个通道顺手就用哪个。从开发流程角度STM32C542R 的 PWM 配置建议直接用 STM32CubeMX 生成 HAL 库代码。手动翻寄存器当然也能写但初始化阶段用图形化工具更快后续你只需要在应用层修改 PSC、ARR、CCR 这三个寄存器。这篇文章的所有示例都以 CubeMX HAL 库为基础工程从上次的串口工程派生。2. 核心能力速览能力项说明内核Arm Cortex-M33主频以具体型号参考手册为准定时器资源通用定时器与高级定时器多路 PWM 通道PWM 输出方式单通道输出、多通道输出、互补 PWM高级定时器频率控制通过 PSC 分频与 ARR 自动重载值联合调整占空比控制通过 CCR 比较值调整配置工具STM32CubeMX HAL 库启动方式HAL_TIM_PWM_Start 启动定时器 PWM 模式是否支持运行时修改支持修改 ARR 和 CCR 可动态改变频率与占空比典型应用电机调速、舵机控制、LED 调光、蜂鸣器驱动、触发 ADC 采样验证工具示波器或逻辑分析仪这一节里的重点是PWM 频率和占空比都只是定时器计数配置的结果不涉及中断或 CPU 持续输出。定时器一旦启动波形由硬件产生CPU 可以去做别的事这也是 PWM 相比纯 GPIO 翻转的一个重要优势。3. 适用场景与使用边界PWM 能做的场景很多但不同场景对频率和占空比精度的要求差异很大。LED 调光或呼吸灯频率通常 1kHz 到 10kHz占空比 0% 到 100%要求不高通用定时器轻松驱动。舵机控制周期固定 20ms也就是 50Hz高电平在 0.5ms 到 2.5ms 之间对应不同舵机角度。这里关注的是高电平时间绝对值而不是占了周期的百分之几。电机调速直流有刷电机一般 10kHz 到 20kHz无感 BLDC 或 PMSM 控制需要高频 PWM 和同步 ADC 采样许多场景会用到定时器触发 ADC这在后面单独讲。Buck 电源调节通过改变占空比调整输出电压。这里要特别注意频率选择影响电感电流纹波和开关损耗频率越高纹波越小但 MOS 管开关损耗增大片内定时器精度需要足够。蜂鸣器音调占空比通常固定 50%频率变化决定音高。这类应用频率精度越高音准越好。边界在哪里首先PWM 输出是数字电平不能直接驱动电机、加热丝这类功率负载中间必须有 MOS 管、栅极驱动或专用驱动芯片。其次频率过高时输出边沿会变差因为 GPIO 的压摆率、外部线路寄生电容都会影响波形质量。再有就是互补 PWM 做电机控制时上下管导通之间必须插入死区否则直通短路风险极高这部分要在高级定时器配置里处理好。合规方面如果 PWM 用于电机拖动、电源变换或音频驱动调试时要注意高压隔离和短路保护。使用开发板做验证时先低压小电流跑通逻辑再接入真实功率负载。不要在不确认引脚电平匹配的情况下直接驱动外部模块避免损坏板载 GPIO。4. 环境准备与 CubeMX 工程配置4.1 环境清单在开始之前确认以下内容已经准备好STM32CubeMX用于图形化生成初始化工程建议使用较新版本以便正确识别 STM32C5 系列器件。STM32CubeIDE 或其他支持 HAL 库的编译环境。STM32C542R 对应开发板和数据手册用于确认引脚复用表。示波器或逻辑分析仪用于观察 PWM 波形。串口工具方便程序运行时打印调试信息继续沿用前面工程里的串口输出。4.2 新建工程并配置时钟打开 STM32CubeMX新建工程在 MCU 选择器里搜索 STM32C542R选中对应封装的芯片开始初始化配置。时钟部分先按系统默认 PLL 配置跑起来。定时器 PWM 的时基最终来自定时器时钟通常经过 APB 定时器时钟倍频之后得到。务必打开 Clock Configuration 页面确认 TIM 所在的 APB 总线时钟是多少后面计算 PSC 和 ARR 会用到这个数值。4.3 配置定时器 PWM 模式这里以 TIM3 的通道 1 为例引脚通常可以映射到 PA6但不同封装和复用表可能有差异最终以 CubeMX 自动分配的引脚为准。操作步骤在左侧 Pinout Configuration 中找到 Timers展开 TIM3。勾选 Internal Clock让定时器使用内部时基时钟。在 Channel1 处选择 PWM Generation CH1。打开 Parameter Settings配置以下关键参数Prescaler分频值。Counter ModeUp向上计数模式。Counter PeriodARR 值。Auto-reload preloadEnable使能自动重载预装载避免运行时修改 ARR 产生瞬间异常波形。PulseCCR 值也就是初始占空比对应的比较值。CH PolarityHigh输出极性为高电平有效。这里先给一组能直接跑的初始参数。假设定时器时钟为 250MHz需要输出 1kHz PWM占空比 50%那么 PSC 设为 249ARR 设为 999Pulse 设为 500。验证一下定时器分频后计数频率为 250MHz / (249 1) 1MHz再经过 ARR 1 1000 个计数周期输出频率就是 1MHz / 1000 1kHz。占空比等于 CCR 除以 ARR1也就是 500 / 1000正好 50%。注意CubeMX 里 Counter Period 字段实际对应 ARR 值Pulse 字段对应 CCR 值。如果之前没有接触过这两个名称很容易搞混。4.4 GPIO 复用确认配置完定时器通道后CubeMX 会自动把对应的 GPIO 引脚切换到复用功能模式。在 GPIO 设置里可以看到引脚模式为 Alternate Function复用编号对应 TIM3_CH1。这个环节不需要手写 GPIO 初始化HAL 生成的代码会自动完成。如果手动修改了引脚映射一定要检查原理图确认引脚没有被其他功能占用。比如同一个引脚既有串口功能又有定时器通道配置后 CubMX 会提示冲突这时候要根据实际硬件选择保留哪一路。4.5 生成工程配置完成后在 Project Manager 设置工程名称、编译工具链和最小生成栈大小然后点击生成代码。生成后的工程里会自动包含tim.c、gpio.c、main.c定时器初始化和引脚配置都不用再手写了。5. 生成代码与 PWM 启动打开生成的工程在main.c的main函数中定时器初始化函数MX_TIM3_Init已经被调用。要使 PWM 真正输出到引脚还需要在应用代码里启动定时器的 PWM 模式。找到main函数中的用户代码区域添加如下代码HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);启动之后PA6 引脚就会持续输出 1kHz、50% 占空比的 PWM 波形。频率和占空比由硬件自动维持CPU 不再参与波形的生成。如果使用的是高级定时器 TIM1还需要额外启动互补输出或者使用HAL_TIM_PWM_Start时指定对应通道。同样的逻辑也适用于其他通道比如 TIM3_CH2只需要把通道参数换成TIM_CHANNEL_2即可。启动 PWM 之后可以用示波器探头观察输出引脚正常应该看到固定频率的方波。看到的波形如果上下沿不够陡优先检查探头的接地方式使用接地弹簧比长地线夹的振铃更小波形也更干净。6. 修改频率与占空比的几种实现方式实际项目中PWM 参数一般不会固定不变。电机要调速舵机要转动LED 要呼吸这些都需要在运行过程中修改频率或占空比。修改方法归结起来就是操作 ARR 和 CCR 两个寄存器。6.1 修改占空比占空比的修改最简单。HAL 库提供了直接设置比较值的宏__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 750);设置之后CCR 变成 750ARR 仍是 999占空比变为 75%。这个宏最终操作的是定时器的 CCR1 寄存器不会影响 PWM 频率。如果要按百分比设置占空比可以封装一个函数void set_duty_percent(TIM_HandleTypeDef *htim, uint32_t channel, uint8_t percent) { if (percent 100) { percent 100; } uint32_t arr __HAL_TIM_GET_AUTORELOAD(htim); uint32_t compare (uint32_t)arr * percent / 100; __HAL_TIM_SET_COMPARE(htim, channel, compare); }这个函数先读取当前 ARR 值再按百分比计算 CCR。注意percent做了限幅处理避免传入超过 100 的值导致比较值大于周期输出恒为高电平的情况发生。6.2 修改频率修改频率本质是修改定时器的溢出周期也就是 ARR。比如把 ARR 从 999 改成 499频率就从 1kHz 变为 2kHz。修改 ARR 之后当前周期未必立即结束新的周期值会等到更新事件发生后才生效。CubeMX 里如果开启了 Auto-reload preload这层保护由硬件完成。代码如下__HAL_TIM_SET_AUTORELOAD(htim3, 499); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 250);这里有一个容易踩的坑修改 ARR 的同时CCR 也要同步调整否则占空比会跟着变。比如原本 ARR999CCR500占空比 50%ARR 改成 499 后CCR 还是 500比较值已经大于周期输出直接饱和成恒高电平。正确的做法是按照新频率重新计算 CCR。6.3 频率和占空比同时修改有些场景需要频率和占空比同时改变比如电机启动阶段的频率爬升。封装一个综合设置函数会更稳妥void set_pwm_freq(TIM_HandleTypeDef *htim, uint32_t channel, uint32_t freq_hz, uint32_t timer_clock_hz) { uint16_t psc 0; uint16_t arr 0; if (freq_hz 0) { return; } psc 0; arr (timer_clock_hz / (freq_hz * (psc 1))) - 1; __HAL_TIM_SET_PRESCALER(htim, psc); __HAL_TIM_SET_AUTORELOAD(htim, arr); }这个函数通过修改 ARR 改变频率。更精细的做法是同时调整 PSC 和 ARR保证一个比较大的 ARR 值让占空比调节精度更高。但 PSC 修改后要等更新事件才会生效实际驱动中需要留意时序。6.4 清除更新标志某些场景下修改 ARR 后需要立即产生更新事件让新值马上生效。此时可以手动操作__HAL_TIM_SET_AUTORELOAD(htim3, 499); __HAL_TIM_GENERATE_UPDATE(htim3, TIM_EVENTSOURCE_UPDATE);但要注意强制产生更新事件会把当前计数清零如果 PWM 正在控制电机这个动作可能导致瞬间的相位跳变。对频率缓慢变化的场景等一个自然周期结束通常更安全。7. 功能测试与效果验证PWM 配置完成、代码编译下载之后需要按步骤验证。不要只看现象要量化检查频率和占空比是否和理论值一致。7.1 测试准备硬件连接示波器 CH1 探头接 PA6接地接 GND。开发板通过 ST-Link 供电确认复位无误。打开串口助手观察程序运行后的打印信息。7.2 验证步骤先验证固定参数输出。下载一个空程序只启动 PWM不修改任何参数。示波器上应该看到频率1kHz误差在示波器精度范围内。占空比50%。波形上下沿清晰无明显振铃。然后验证占空比修改。调用__HAL_TIM_SET_COMPARE把 CCR 改到 750观察占空比变为 75%。再把 CCR 改成 250占空比变为 25%。这个过程频率应保持不变。接着验证频率修改。调用__HAL_TIM_SET_AUTORELOAD把 ARR 改成 499同时把 CCR 改成 250观察频率变为 2kHz占空比仍为 50%。用串口把计算出的频率和实时 CCR、ARR 打印出来方便排查。比如printf(ARR%lu, CCR%lu, freq_hz%lu\n, __HAL_TIM_GET_AUTORELOAD(htim3), __HAL_TIM_GET_COMPARE(htim3, TIM_CHANNEL_1), current_freq);如果频率测量值和理论值偏差过大优先检查 CubeMX 里定时器时钟的配置看看 PLL 倍频之后的实际时基是多少。这是最容易出错的地方。8. PWM 与其他外设的联动触发 ADC 采样单独输出 PWM 往往只是第一步。很多电机控制场景需要把 PWM 和 ADC 采样联动起来比如在 PWM 中心对齐的特定时间点采样电流。STM32 定时器可以产生触发事件把 ADC 的启动和 PWM 周期绑定这样采样点能精确卡在 PWM 的某个相位上。CubeMX 里的配置思路是在 TIM 的 TRGO 参数中把 Update Event 作为触发输出然后在 ADC 配置中选择 Trigger 为 Timer x Trigger Out event。这样 ADC 会按照定时器更新事件启动转换而不用 CPU 干预。如果需要采样多个通道可以配合 DMA 把 ADC 结果搬到内存里。这样做的好处是采样时机非常稳定。尤其是当前主流无刷电机控制里PWM 在中心对齐模式下电流纹波在计数周期中点附近最小这个时刻采样出来的电流值最接近真实相电流。如果只是靠 Main 循环里随机读 ADC采到的值可能落在开关噪声最大的区间结果不稳定。需要说明的是PWM 触发 ADC 采样重点在于定时器触发事件和 ADC 触发源的匹配不同系列配置入口稍有差异。如果开发阶段的波形看不到预期采样动作可以先用示波器观察定时器 TRGO 引脚信号确认触发事件确实产生了。9. 资源占用与性能观察PWM 输出本身不占 CPU这一点对实时系统很重要。定时器启动后波形生成交给硬件CPU 的负载只在修改参数时短暂增加。如果程序每秒更新一次占空比对 CPU 占用率的影响可以忽略不计。真正需要关注的是以下几点定时器时钟误差。PLL 配置不准会导致 PWM 频率整体偏移。建议用一个外部频率计或示波器测频功能校准。GPIO 翻转速率。高速 PWM 下GPIO 的压摆率设置会影响边沿。CubeMX 的 GPIO 速度默认值一般够用但如果你要输出超高频 PWM可以尝试把 GPIO Speed 调高波形边沿会更陡峭。中断对计数值的影响。PWM 不依赖中断但如果你额外开启了定时器中断做频率动态调整中断处理时间过长会在波形上留下毛刺。尽量把中断处理函数做短或者把重计算工作放在主循环里。修改频率的时机。在电机运行中大幅修改 ARR可能出现一个异常波形周期。对严格的应用可以在特定相位点更新参数或者暂时停止 PWM、改完参数再启动。整体结论是频率和占空比修改操作本身开销极低资源占用问题主要出在配置不合理、中断处理不及时或外部驱动电路上而不是 PWM 外设本身。10. 常见问题与排查方法问题现象可能原因排查方式解决方案引脚没有波形定时器 PWM 未启动检查是否调用 HAL_TIM_PWM_Start在应用层启动对应通道引脚复用冲突同一引脚被多个外设占用查看 CubeMX 引脚分配更换引脚或调整外设分配频率和理论值不一致PSC 或 APB 定时器时钟配置错误核对时钟树确认定时器时基时钟按实际时基重新计算 PSC 和 ARR修改占空比后输出异常CCR 大于 ARR串口打印比较值和周期值限制百分比不超过 100修改 ARR 后占空比变化CCR 未同步调整示波器观察波形检查寄存器值修改 ARR 时同步重设 CCR波形振铃严重探头接地方式不当更换接地弹簧缩短接地回路电机驱动时有噪声PWM 频率偏低或死区不足听噪声用示波器观察互补 PWM调整频率检查死区时间输出恒为高或恒为低极性配置错误检查 CH Polarity改为 High 或 Low 后重新验证其中占空比修改不生效是比较常见的。原因是__HAL_TIM_SET_COMPARE修改的是影子寄存器若预装载未开启可能写入后要等下一个更新事件才装载。CubeMX 里建议开启 Auto-reload preload同时 CCR 预装载也要保持默认使能。另一个高频问题是引脚复用冲突。同一颗芯片内部同一个 GPIO 引脚的多个复用功能是互斥的。CubeMX 里如果配置冲突会直接报错但如果你手动改过寄存器或使用旧工程可能就会出现引脚波形完全没有输出的情况。排查时先回到 CubeMX 检查引脚分配图再看原理图确认引脚没有外接其他负载。11. 最佳实践与使用建议从工程化的角度给几条实操建议。第一个建议是先小参数跑通再改复杂逻辑。第一次实验使用 1kHz 和 50% 占空比这样的保守参数确认示波器波形正确后再尝试动态修改频率和占空比。不要一上来就接电机先把波形验证过了再接负载。第二个建议是参数计算要保留在代码注释中。PSC、ARR、CCR 这三个值的来龙去脉过几天再看可能就想不起来了。注释里写明定时器时基时钟 250MHz、目标频率 1kHz、PSC249、ARR999、CCR500后续维护会轻松很多。第三个建议是把 PWM 参数设置封装成独立模块。不要在主循环里到处写__HAL_TIM_SET_COMPARE而是提供一个pwm_control.c统一管理频率、占空比、刹车、死区等操作。这样换定时器、换通道时只需改底层配置上层逻辑不变。第四个建议是批量调试多个通道时先每路独立测试再组合使用。多路 PWM 同时输出的问题往往是振铃、串扰或相移逐路验证可以快速定位是哪一路接线或配置有问题。第五个建议涉及合规。PWM 驱动舵机和电机时注意供电电压和开发板的电平匹配。大功率电机必须通过驱动板隔离开发板只输出信号不要直接接电机电源。音频相关应用要确认喇叭阻抗和功率匹配避免长时间满占空比输出烧毁器件。涉及声音、图像、人体相关的应用还要遵守隐私和版权相关的法规不要拿他人肖像、声音做未授权的合成或传播。12. 运行日志与最终验证最后再补充一个可落地的验证流程。把一个 PWM 周期内的 ARR、CCR 和计算出的实际频率打印出来配合示波器值对比确定无误后再做一次 5 分钟长跑测试观察波形漂移和芯片温度。串口周期性打印示例while (1) { printf(ARR%lu CCR%lu\n, __HAL_TIM_GET_AUTORELOAD(htim3), __HAL_TIM_GET_COMPARE(htim3, TIM_CHANNEL_1)); HAL_Delay(1000); }如果你观察到打印值正常但示波器波形不对那问题更可能在硬件链路比如引脚连错、外部负载过重、示波器探头衰减档位不对。如果打印值和示波器值都对得上说明 PWM 链路已经跑通可以继续做动态频率修改、互补 PWM 或触发 ADC 采样这些进阶功能了。STM32C542R 的 PWM 本质上就是一个定时器按预设节奏翻转引脚的过程。掌握 PSC、ARR、CCR 三者关系之后剩下的就是多测试、多对比波形。建议把这篇里的 CubeMX 配置步骤和代码封装直接存下来后续做电机控制或电源调节时可以在这个基础上继续扩展。