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配置。我个人的方法是「三看三不看」:

  1. 先看目录,找到定时器章节。 STM32的数据手册里,定时器通常在「Timers」或「General-purpose timers」部分。
  2. 看框图。 每个定时器章节开头都有一张功能框图。你花5分钟看懂这张图,比读10页文字都管用。
  3. 看寄存器描述。 重点看TIMx_CCMR1、TIMx_CCER、TIMx_ARR、TIMx_CCR这几个寄存器。
  4. 不看应用笔记里的重复内容。 数据手册只讲「是什么」,应用笔记才讲「怎么做」。
  5. 不看电气特性表。 除非你在做低功耗或高频设计。
  6. 不看封装信息。 那是画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配置决策树。你照着这个流程走,基本不会漏步骤:

PWM配置决策流程图 PWM配置决策流程图 选择定时器 配置时钟分频(PSC) 设置自动重装值(ARR) 选择PWM模式(CCMR) 设置占空比(CCR) 使能输出(CCER) 注意事项 1. 先配PSC再配ARR 2. 占空比不能超过ARR 3. 高级定时器需配死区 常见错误 忘记使能定时器时钟 GPIO未配置为复用功能 PSC写0导致频率过高

2.7 实战:5分钟定位PWM寄存器

假设你拿到一块STM32F103C8T6,想用TIM2的通道1输出PWM。我教你一个快速定位法:

  1. 打开参考手册(RM0008),翻到目录找到「16 General-purpose timers (TIM2 to TIM5)」。
  2. 看16.3节「TIMx functional description」,里面有时钟树和通道框图。
  3. 翻到16.4节「TIMx registers」,找到TIM2的基地址(0x40000000)。
  4. 定位CCMR1寄存器(偏移0x18),配置OC1M位为110(PWM模式1)。
  5. 定位CCER寄存器(偏移0x20),使能CC1E位。
  6. 配置ARR和CCR1,搞定。

小技巧:我习惯在数据手册的PDF里给常用寄存器页加书签。比如TIM2的ARR页标为「TIM2_ARR」,下次找起来不用翻目录。另外,ST官方有定时器配置工具STM32CubeMX,你可以在图形界面里配好,然后直接生成初始化代码——但前提是你得理解背后每个寄存器的作用,不然出了问题还是不会调。