2. 硬件平台介绍:常用MCU的PWM外设资源
做PWM输出,说白了就是让硬件帮你干活。你写软件告诉它「我要一个20ms周期、占空比5%的波形」,它自己就把事办了。但前提是——你得选对硬件,还得知道怎么翻数据手册。
我刚开始学PWM时,上来就对着寄存器猛啃,结果发现不同MCU的定时器结构差异挺大。后来我养成了一个习惯:拿到一块新板子,第一件事就是打开数据手册的定时器章节,先看它有几个通道、支持什么模式。嗯,今天我们就聊聊这个。
2.1 主流MCU的PWM外设概览
市面上能跑PWM的MCU太多了,但最常用的无非两类:STM32和Arduino(AVR)。我个人的项目里,80%的PWM应用都用这两家搞定。下面这张表帮你快速建立认知:
| MCU系列 | 典型型号 | 定时器数量 | PWM通道数 | 分辨率 | 特色功能 |
|---|---|---|---|---|---|
| STM32F1 | STM32F103C8T6 | 7个(TIM1~TIM7) | 最多16路 | 16位 | 互补输出、刹车功能 |
| STM32F4 | STM32F407VGT6 | 14个 | 最多30路 | 16位 | 高级定时器、死区插入 |
| Arduino Uno | ATmega328P | 3个(Timer0/1/2) | 6路 | 8位 | 快速PWM、相位修正PWM |
| Arduino Mega | ATmega2560 | 6个 | 15路 | 8位/16位 | 多通道独立输出 |
关键认知:STM32的定时器是「通用型」的,一个定时器可以同时输出多路不同占空比的PWM;而Arduino的定时器往往和系统时钟、delay()函数共用,改PWM频率时要注意别把系统时间搞乱。
2.2 STM32定时器家族:从基本到高级
STM32的定时器分三个层级,我当年第一次看数据手册时差点被绕晕。其实说白了就三类:
- 基本定时器(TIM6/TIM7):只能计时,不能输出PWM。用来做延时或触发DAC。
- 通用定时器(TIM2~TIM5):最常用。每个定时器有4个独立通道,可以输出4路PWM。支持输入捕获、输出比较。
- 高级定时器(TIM1/TIM8):功能最全。除了通用定时器的功能,还支持互补输出、死区插入、刹车输入——做电机控制必备。
我在做无刷电机驱动时,就踩过高级定时器的坑。当时想输出一对互补PWM,结果忘了配置死区时间,上下管直接导通短路。嗯,从那以后我每次配高级定时器,都会先检查死区寄存器。
2.3 Arduino的PWM:简单但有限制
Arduino的PWM上手确实快。你写一句 analogWrite(9, 128); 就能在9号脚输出50%占空比的方波。但它的限制也很明显:
- 分辨率只有8位:占空比只能从0到255,精度远不如STM32的16位。
- 频率固定:Arduino Uno的PWM默认频率约490Hz(5、6号脚约980Hz)。想改频率?得直接操作寄存器。
- 定时器冲突:Timer0被系统用来做millis()和delay(),你改了它的分频系数,delay(1000)可能就变成delay(2000)了。
我的建议:如果你只是控制LED亮度或舵机角度,Arduino完全够用。但如果你要做高精度电机控制或音频输出,还是老老实实上STM32吧。
2.4 如何高效阅读数据手册的定时器章节
很多新手拿到数据手册就从头读到尾,结果看到第200页还没找到PWM配置。我个人的方法是「三看三不看」:
- 先看目录,找到定时器章节。 STM32的数据手册里,定时器通常在「Timers」或「General-purpose timers」部分。
- 看框图。 每个定时器章节开头都有一张功能框图。你花5分钟看懂这张图,比读10页文字都管用。
- 看寄存器描述。 重点看TIMx_CCMR1、TIMx_CCER、TIMx_ARR、TIMx_CCR这几个寄存器。
- 不看应用笔记里的重复内容。 数据手册只讲「是什么」,应用笔记才讲「怎么做」。
- 不看电气特性表。 除非你在做低功耗或高频设计。
- 不看封装信息。 那是画PCB时才用的。
我曾经犯过的错:拿到STM32F103的数据手册,直接跳到PWM章节开始配寄存器。结果配了半天发现——我用的型号是STM32F030,定时器结构完全不同。所以第一步一定是确认你的具体型号,然后下载对应的参考手册(Reference Manual),不是数据手册(Datasheet)。
2.5 定时器核心寄存器速查
不管用哪家MCU,PWM配置都绕不开下面这几个寄存器。我整理了一张速查表,你调试时可以直接对照:
| 寄存器 | 全称 | 作用 | STM32示例 | AVR示例 |
|---|---|---|---|---|
| ARR / TOP | Auto-Reload Register | 设定PWM周期 | TIM2->ARR = 999 | OCR1A = 19999 |
| CCR / OCR | Capture/Compare Register | 设定占空比 | TIM2->CCR1 = 500 | OCR1B = 1500 |
| PSC | Prescaler | 分频,降低计数频率 | TIM2->PSC = 71 | (通过CSR寄存器设置) |
| CCMR | Capture/Compare Mode Register | 配置通道为PWM模式 | TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TCCR1A |= (1 << WGM11) |
| CCER | Capture/Compare Enable Register | 使能PWM输出 | TIM2->CCER |= TIM_CCER_CC1E | TCCR1A |= (1 << COM1A1) |
记住这个公式:PWM频率 = 定时器时钟 / (PSC + 1) / (ARR + 1)。你调频率时,先改PSC做粗调,再改ARR做细调。占空比 = CCR / (ARR + 1) × 100%。
2.6 一张图看懂PWM配置流程
下面这张SVG图,是我自己画的一个PWM配置决策树。你照着这个流程走,基本不会漏步骤:
2.7 实战:5分钟定位PWM寄存器
假设你拿到一块STM32F103C8T6,想用TIM2的通道1输出PWM。我教你一个快速定位法:
- 打开参考手册(RM0008),翻到目录找到「16 General-purpose timers (TIM2 to TIM5)」。
- 看16.3节「TIMx functional description」,里面有时钟树和通道框图。
- 翻到16.4节「TIMx registers」,找到TIM2的基地址(0x40000000)。
- 定位CCMR1寄存器(偏移0x18),配置OC1M位为110(PWM模式1)。
- 定位CCER寄存器(偏移0x20),使能CC1E位。
- 配置ARR和CCR1,搞定。
小技巧:我习惯在数据手册的PDF里给常用寄存器页加书签。比如TIM2的ARR页标为「TIM2_ARR」,下次找起来不用翻目录。另外,ST官方有定时器配置工具STM32CubeMX,你可以在图形界面里配好,然后直接生成初始化代码——但前提是你得理解背后每个寄存器的作用,不然出了问题还是不会调。