ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32定时器PWM输出实战:CubeMX配置、参数计算与踩坑排查

STM32定时器PWM输出实战:CubeMX配置、参数计算与踩坑排查 简介一套面向STM32初学者的PWM输出实验例程演示通过定时器产生可调频率与占空比的脉宽调制信号适合电机调速、LED亮度调节等嵌入式控制场景。压缩包共131个文件、约2.31MB包含34个头文件、33个C源文件以及Keil工程配置、编译脚本、链接脚本和调试信息.uvprojx/.uvoptx可打开完整工程.axf/.hex可直接烧录验证。代码基于STM32F10x标准外设库覆盖定时器预分频器、自动重载值、PWM1/PWM2模式、通道极性与死区时间等知识点并附有stm32f10x_tim.c、stm32f10x_rcc.c等驱动源码便于理解从寄存器配置到波形输出的完整链路。工程结构清晰适合对照示例学习定时器初始化、PWM通道配置及中断/DMA处理方式也可作为后续电机控制或信号生成项目的基础模板。已有799人学习下载对刚接触STM32 PWM开发的读者是一份可直接运行的优质参考。 做嵌入式这行PWM 绕不开。不管是舵机控制、LED 呼吸灯、电机调速还是无刷电调的油门信号本质上都是在和 PWM 打交道。我之前基于 STM32F103C8T6 做项目时第一个认真折腾的外设就是 TIM1 的 PWM 输出当时跟着例程点亮一颗 LED 的呼吸效果那种“定时器还能这么玩”的感觉至今印象很深。这篇文章就把我在 stm32 基本例程 PWM 输出实验里摸索到的东西从原理、配置、参数计算到踩坑排查完整梳理一遍给刚入门或者被 PWM 卡住的朋友做个参考。1. PWM输出实验的前置认知1.1 PWM 是什么用“调光台灯”理解占空比PWM 的全称是脉冲宽度调制说人话就是把一个方波信号里高电平的时间比例拉长或缩短从而改变输出的“平均等效电压”。你可以把它想象成一个调光台灯不同的是台灯是靠旋钮改变电阻而 PWM 是靠改变开关的导通时间。举个例子假设一个方波周期是 10ms高电平 5ms、低电平 5ms那占空比就是 50%。如果高电平 8ms、低电平 2ms占空比就是 80%。因为人眼和许多负载的响应速度跟不上这个开关频率所以看到/感受到的就是一个等效的“平均效果”。LED 在 80% 占空比下就会比 50% 占空比更亮电机在 80% 占空比下转速也会更高。在 STM32 上PWM 输出不是靠软件延时去翻转引脚的那是软件 PWM精度差还占 CPU而是由定时器硬件在后台自动完成计数器一直在跑中间有比较寄存器做阈值判断一旦计数值到阈值输出引脚就自动翻转。整个过程 CPU 不需要干预只需要在初始化时配好参数之后想改占空比就改一下寄存器非常省心。1.2 定时器选择为什么 F103C8T6 上常用 TIM1STM32F103C8T6 这颗芯片虽然便宜但外设相当齐全。它自带 1 个高级控制定时器 TIM1、3 个通用定时器 TIM2~TIM4外加一个基本定时器 TIM6。其中TIM1高级定时器支持 PWM 互补输出、死区插入、刹车输入适合电机控制、逆变器这类需要双路反相波形的场景。TIM2/TIM3/TIM4通用定时器也能输出 PWM但引脚上没有互补通道功能相对简单。网上搜“stm32f103c8t6 tim1 pwm输出”会发现很多例程都选 TIM1 通道 1PA8。原因很简单PA8 这个引脚不需要重映射直接就是 TIM1_CH1 的复用输出对新手来说少踩一层“引脚映射”的坑。而如果选 TIM2/TIM3你得仔细查手册确认通道和引脚的对应关系比如 TIM2_CH1 是 PA0、TIM3_CH1 是 PA6稍不留神就会把信号接错引脚结果看不到波形还以为是配置错了。我的建议是做基本 PWM 实验优先挑不用重映射的引脚比如 PA8 的 TIM1_CH1 或者 PA0 的 TIM2_CH1先让波形出来再去研究引脚重映射和通道切换。跑通了流程后面理解 PWMDMA、互补输出这些进阶玩法就顺理成章。2. 环境准备与 CubeMX 初始化配置2.1 开发环境选型HAL 库还是标准库现在学 STM32 基本是两套路子标准外设库和 HAL 库。标准库就是以前的老工程师们用的固件库结构清晰但 ST 官方已经停止维护新项目的代码资源基本都在往 HAL 库迁移。HAL 库配合 STM32CubeMX 图形化工具点几下鼠标就能把时钟树、引脚复用、外设参数全部配好再生成一个 MDK 工程。很多老教程还在用标准库那是因为历史遗留资源多。如果你刚起步我建议直接上 CubeMX HAL 库省去手动建工程的痛苦尤其是“stm32固件库模板下载搭建”那种折腾现在真的没必要自己搞。Keil MDK 还是要装的因为代码要编译、要下载调试CubeMX 负责生成Keil 负责改代码和烧录。2.2 CubeMX 配置 PWM 的完整步骤打开 CubeMX新建工程搜索 STM32F103C8T6选中后开始配置。整个流程可以分四步走。第一步配置时钟树。在 “System Core → RCC” 里把 HSE外部高速时钟设为 Crystal/Ceramic Resonator然后在 “Clock Configuration” 页面把系统时钟调到 72MHz。如果板子上没有 8M 晶振那就把 HSE 关掉直接用内部 HSI 8MHz 时钟倍频到 72MHz也能跑。这个细节做小项目时影响不大但串口波特率、PWM 频率的精度都会受影响有外部晶振尽量用外部晶振。第二步选引脚。在芯片图上找到 PA8鼠标左键点击选择 TIM1_CH1。此时 PA8 就会被标记为复用功能引脚。CubeMX 会自动帮你把 GPIO 的模式设成复用推挽输出。第三步配置 TIM1 参数。在左侧 “Timers → TIM1” 里找到 “PWM Generation CH1”勾选 Enable。下面会出现一组参数Prescaler (PSC)预分频值Counter Mode计数模式选 Up向上计数即可Counter Period (ARR)自动重装载值Auto-reload preload建议选 Enable这样更新 ARR 时会同步生效避免毛刺Pulse比较值可以顺手设一个初始占空比。Polarity 选 High高电平有效即可选 Low 会导致占空比反向后面会讲到。第四步在 “Project Manager” 里设置工程名、保存路径IDE 选 MDK-ARM V5然后点击 “GENERATE CODE” 生成工程。到这里CubeMX 的活就干完了。有一点要提醒CubeMX 生成的代码是一个框架它不会帮你直接启动 PWM 输出需要自己在用户代码区调用启动函数下面会详细说。3. 参数计算与核心代码实现3.1 PSC 和 ARR 怎么算从 72MHz 到实际频率PWM 输出频率由三个数决定系统主频、预分频值 PSC、自动重装载值 ARR。公式是PWM 频率 主频 / (PSC 1) / (ARR 1)为什么都要加 1因为 PSC 和 ARR 都是从 0 开始计数的。PSC 71 意味着 72 分频计数器时钟是 72MHz / 72 1MHzARR 999 意味着计数器数 1000 个脉冲才溢出一次所以 PWM 频率 1MHz / 1000 1kHz。这个“1”是最容易出错的地方很多人配完频率不对基本都是忘了这茬。那占空比怎么控制靠比较寄存器 CCR在 CubeMX 里叫 Pulse。计数器从 0 往上数还没到 CCR 时输出一种电平到 CCR 时翻转输出。CCR 250 时占空比就是 25%CCR 500 就是 50%CCR 750 就是 75%。举个例子你要做一个 1kHz、50% 占空比的 PWM最稳妥的配法就是 PSC 71ARR 999Pulse 500。这个组合在整个 F103 系列里非常经典记不住公式时直接套这个参数。3.2 HAL 库 PWM 初始化代码逐段拆解CubeMX 生成的代码里和 PWM 相关的核心函数就三个HAL_TIM_PWM_Init(htim1); HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1);第一个函数把 TIM1 的预分频、计数模式、自动重装载值全部写入硬件寄存器第二个函数配置输出比较通道的模式、极性、比较值第三个函数启动 PWM 输出。这里有一个几乎所有新手都会踩的坑使用高级定时器 TIM1/TIM8 时光调用HAL_TIM_PWM_Start()是不够的。高级定时器里有一个“主输出使能”位MOEMain Output Enable它默认是关闭的。如果不把 MOE 打开哪怕你所有寄存器都配好了引脚上也不会有波形输出。所以使用 TIM1 时必须额外加一行__HAL_TIM_MOE_ENABLE(htim1);或者调用HAL_TIM_PWM_Start()之后再强制使能主输出。CubeMX 生成的代码里不会自动帮你调用这个函数这是 stm32f103c8t6 tim1 pwm 输出实验里最典型的“没输出”原因之一。完整的主函数初始化代码大概是这样/* 初始化 TIM1 PWM */ HAL_TIM_PWM_Init(htim1); /* 配置通道1为PWM模式比较值先设500 */ sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 500; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); /* 启动PWM输出 */ HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); __HAL_TIM_MOE_ENABLE(htim1);注意Pulse最大值不要超过 ARR否则占空比一直是 100%很容易忽略。3.3 占空比动态调整从呼吸灯到舵机控制PWM 输出跑通之后最直观的实验就是呼吸灯。思路很简单在 while 循环里让比较值从小到大、再从大到小循环变化每次延时 10ms 左右uint16_t pulse 0; int8_t direction 1; while (1) { __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pulse); // 修改占空比 HAL_Delay(10); pulse direction; if (pulse 1000) { direction -1; } else if (pulse 0) { direction 1; } }__HAL_TIM_SET_COMPARE这个宏会直接修改 CCR 寄存器占空比立即生效。实测下来LED 的亮度变化非常平滑肉眼能看到明显的“呼——吸——呼——吸”效果这就是硬件 PWM 和软件 PWM 的最大区别整个过程几乎不占 CPU 时间。如果你手头有舵机可以换个玩法。SG90 舵机的控制信号是 50Hz 的 PWM周期 20ms脉宽 0.5ms~2.5ms 对应 0°~180°。把 PSC 设成 71ARR 设成 19999这样 PWM 频率正好是 50Hz。CCR 500 时脉宽是 0.5msCCR 2500 时脉宽是 2.5ms中间线性对应角度。注意舵机一定要和 STM32 共地否则信号参考电位不一致舵机会乱抖甚至不动。4. 常见问题与排查技巧实录4.1 PWM 完全没有输出先按顺序查这三处我在实验中最常遇到的状况是代码编译下载都成功了LED 就是不亮示波器一量 PA8 也没有波形。这时候不要慌按照下面顺序排查第一查定时器时钟有没有打开。在 CubeMX 生成的代码里HAL_TIM_PWM_Init()内部会自动打开 TIM1 的时钟但如果你的工程是在老标准库基础上改的忘了RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE)那就直接白搭。第二查 GPIO 复用功能。PA8 如果没被配置为复用推挽输出而是默认的浮空输入那 PWM 信号根本不会从引脚出来。在 CubeMX 里选完 TIM1_CH1 后它会自动配好但手动改代码时很容易忽略。第三查高级定时器的 MOE 主输出使能。这上面已经说过TIM1/TIM8 必须做__HAL_TIM_MOE_ENABLE(htim1)才真正有输出。这一点坑了无数人包括我在内。4.2 占空比反了、频率不对多半是极性和除法忘记 1如果你观察到 LED 亮度“反着调”Pulse 设 250 时特别亮设 750 时反而暗那多半是极性配置反了。把OCPolarity从TIM_OCPOLARITY_HIGH改成TIM_OCPOLARITY_LOW占空比的定义就反过来了。CubeMX 的 PWM Generation 设置里有 PWM Mode 和 Polarity 两个下拉框一定要留意极性选的是 High 还是 Low。频率不对的问题几乎都是 PSC 和 ARR 忘记加 1。比如说你想要 10kHz然后 PSC 配 71、ARR 配 71心想 72MHz / 72 / 72 13.8kHz 也不对算出来的实际频率和预期差一大截。老老实实按 “/ (PSC1) / (ARR1)” 去算再用示波器验证一遍波形就能对上了。4.3 引脚冲突JTAG 占用了你的 PWM 引脚还有一个很隐蔽的坑STM32 默认开了 JTAG 调试功能而 JTAG 的四个引脚是 PA13、PA14、PA15 和 PB3、PB4。如果你想把 PA15 用作定时器通道输出会发现怎么配置都不生效因为 JTAG 优先占用了这个引脚。解决办法是禁用 JTAG、保留 SWD。在代码里调用__HAL_AFIO_REMAP_SWJ_DISABLE();或者用 CubeMX 的System Core → SYS → Debug设置为 Serial Wire这样 JTAG 相关引脚就会被释放出来PWM 输出才正常。平时调试用 ST-Link 的 SWD 接口就够了完全不用 JTAG。4.4 疑难杂症速查表现象可能原因解决办法PWM 无输出定时器时钟未开启检查 RCC 时钟使能CubeMX 工程重点看HAL_TIM_PWM_Init是否执行PWM 无输出高级定时器 MOE 未使能加__HAL_TIM_MOE_ENABLE(htim1);PWM 无输出GPIO 复用功能未配置确认 PA8 已设为 AF_PP 复用推挽模式频率与设定不符PSC/ARR 未加 1用公式 主频 / (PSC1) / (ARR1) 反推占空比反向极性配置错误把 OCPolarity 从 HIGH 改为 LOW引脚复用不了JTAG 占用禁用 JTAG保留 SWD舵机乱抖信号地没共地舵机 GND 与 STM32 GND 相连小占空比时毛刺明显ARR 的 preload 未开启在 CubeMX 中使能 Auto-reload preload5. 实验扩展PWM DMA 的几个有趣玩法当你把基本 PWM 输出跑顺之后可以试着把 DMA 引进来——这也是网上经常提到“stm32f103 pwm dma”的原因。PWM 加 DMA 的本质是用 DMA 自动把内存里的数据搬运到 CCR 寄存器从而不需要 CPU 逐次修改占空比就能实现一组预设波形的高速输出。举个例子如果你用 TIM2_CH2PA1和 TIM2_CH4PA3分别控制两个 LED想要让它们同时做呼吸效果可以准备两个正弦表数组启动 DMA 循环搬运两个通道的占空比会自动按表变化而 CPU 完全被解放出来可以去做其他事情比如处理按键、刷新 OLED 屏幕。这个玩法在电机控制、音频播放、多路彩灯控制里非常实用。不过在配置 DMA 时要特别注意一点DMA 搬运的目标地址是htim2.Instance-CCR2或htim2.Instance-CCR4不是htim2本身而且数据宽度要选 Half Word16 位正好对应 CCR 寄存器否则搬运的数据会和寄存器宽度不匹配占空比更新就会错乱。我还试过用两个通道的 PWM 做“虚拟 DAC”把多路 PWM 经过简单的 RC 低通滤波就能输出一个直流电压占空比不同电压不同再用 DMA 正弦表就能输出音频信号。虽然不是正经 DAC但做一些简单的音调播放、LED 渐变特效成本低且效果完全够用。6. 写在最后实验之外的一点体会PWM 输出实验虽然排在 stm32 基本例程的前几课但它几乎是理解 STM32 定时器机制的钥匙。通过这个实验你会把 PSC、ARR、CCR 这三个寄存器的关系彻底搞清楚会理解什么叫做硬件自动输出、CPU 零参与也会意识到一个高级定时器里还有 MOE 这种“总开关”存在。我在实际做项目时的体会是不要只满足于让波形出来多去折腾一下“如果改变 ARR 会怎样”“如果极性反过来会怎样”“如果把时基改成定时器同步会怎样”。这些基础问题的答案会在你做电机控制、舵机云台、PWM 调光电源时全部用得上。最后一个小技巧买一个几十块钱的逻辑分析仪调试 PWM 时直接抓波形比对着寄存器猜半天高效太多。示波器更好但逻辑分析仪对学生党来说性价比已经很高了。波形是工程师的“眼睛”眼见为实这句话放在 PWM 学习上一点不假。本文还有配套的精品资源点击获取
返回列表