4. PWM输出原理:定时器计数器、比较寄存器、占空比与频率的计算

好,咱们进入第四章。PWM,脉宽调制,这玩意儿在嵌入式里太常用了。调个灯亮度、控制电机转速、给舵机发信号,都离不开它。

很多人刚开始学的时候,觉得PWM很玄乎。其实说白了,它就是让单片机的一个引脚,按照你设定的节奏,快速地在高电平和低电平之间切换。你想想看,如果切换得足够快,人眼或者电机就反应不过来,看到的就不是闪烁,而是平均效果——比如灯变暗了。

那这个“节奏”和“高电平持续的时间”怎么精确控制?答案就在定时器/计数器和比较寄存器里。我当年第一次用PWM驱动直流电机,结果电机嗡嗡叫就是不转,后来才发现是频率没算对,跟电机本身的机械谐振频率撞上了。嗯,从那以后,我对PWM的寄存器配置就格外小心。

4.1 定时器/计数器:PWM的“心跳”

PWM的核心,是一个不断递增(或递减)的计数器。这个计数器,就是我们常说的定时器/计数器模块。它就像一个不知疲倦的秒表,从0开始,每个时钟脉冲就加1,一直加到我们设定的最大值,然后清零重新开始。

这个最大值,通常由一个叫自动重装载寄存器(ARR)或者周期寄存器(PER)的东西决定。不同的单片机叫法不一样,但功能一样。

核心概念:

  • 计数时钟(CK_CNT):定时器工作的节拍。通常由系统时钟经过预分频器得到。
  • 计数周期(ARR/PER):计数器从0跑到这个值,然后归零。这个值决定了PWM的频率。
  • 计数模式:最常用的是向上计数模式(0→ARR),也有向下和中央对齐模式。

举个例子。假设你的定时器时钟是1MHz,也就是1秒跑1,000,000个脉冲。你把ARR设为999,那么计数器从0跑到999,一共是1000个脉冲。跑完一轮需要的时间就是 1000 / 1,000,000 = 0.001秒,也就是1ms。这个1ms,就是PWM的周期。频率就是周期的倒数,1kHz。

我的小技巧:在配置定时器时钟时,我习惯先算好预分频系数,让计数时钟频率是一个整数,比如1MHz、100kHz。这样后面算频率和占空比的时候,心算都能搞定,不容易出错。

4.2 比较寄存器:决定“高”多久

有了计数器这个“秒表”,我们还需要一个“闹钟”来决定什么时候切换电平。这个“闹钟”就是比较寄存器(CCR),或者叫捕获/比较寄存器。

它的工作原理很简单:

  1. 计数器从0开始向上跑。
  2. 当计数器的值 小于 CCR的值时,PWM引脚输出高电平(或者低电平,取决于极性设置)。
  3. 当计数器的值 大于等于 CCR的值时,PWM引脚输出低电平(或者高电平)。
  4. 当计数器跑到ARR的值,归零,重新开始。

你看,CCR的值直接决定了高电平持续的时间。这个时间占整个周期的比例,就是占空比。

我曾经踩过的坑:有一次我设置CCR等于ARR,心想这不就是100%占空比吗?结果输出一直是低电平。后来查手册才发现,很多单片机在CCR等于ARR时,比较器不会产生匹配事件,导致输出状态不翻转。正确的做法是,想要100%占空比,应该把CCR设为ARR+1,或者直接强制输出高电平。不同芯片细节不同,一定要看数据手册!

4.3 占空比与频率的计算:纸上谈兵不如实战

好了,理论讲完,咱们来点实际的。计算PWM的频率和占空比,其实就两个公式。

频率计算

PWM的频率由定时器的时钟频率和ARR寄存器共同决定。

PWM频率 = 定时器时钟频率 / (ARR + 1)

为什么是ARR+1?因为计数器是从0跑到ARR,一共跑了ARR+1个脉冲。

占空比计算

占空比就是高电平时间占整个周期的百分比。

占空比 = (CCR + 1) / (ARR + 1) * 100%

这里加1的原因跟上面一样,计数器是从0开始的。不过有些芯片的计数器是从1开始计数的,公式会略有不同,务必以手册为准。

实战案例:

假设我们要产生一个50Hz的舵机控制信号,占空比要求是7.5%(中间位置)。

  • 第一步:选择定时器时钟。我选1MHz,这样好算。
  • 第二步:计算ARR。根据公式:50 = 1,000,000 / (ARR + 1) → ARR + 1 = 20,000 → ARR = 19,999。
  • 第三步:计算CCR。占空比7.5%,所以 CCR + 1 = 20,000 * 7.5% = 1,500 → CCR = 1,499。

配置好ARR和CCR,PWM输出就搞定了。是不是很简单?

为了让你更直观地理解这个过程,我画了一张图,把计数器、比较器和输出波形的关系展示出来。

PWM生成原理:计数器与比较器 PWM生成原理:计数器与比较器 计数器值 (CNT) ARR CCR 时间 (t) PWM输出 (OCx) 高电平时间 (由CCR决定) 周期 (由ARR决定) 关键说明: • 计数器从0开始递增,到达ARR后归零,形成锯齿波。 • 当计数器值 < CCR时,输出高电平;当计数器值 ≥ CCR时,输出低电平。 • 改变ARR可以调整PWM频率,改变CCR可以调整占空比。

4.4 寄存器配置实战:以STM32为例

光说不练假把式。咱们拿最常见的STM32来走一遍流程。假设我们要用定时器TIM2的通道1输出PWM,配置成上面那个50Hz、7.5%占空比的例子。

// 假设定时器时钟已经配置为1MHz
// 1. 设置自动重装载寄存器 (ARR)
TIM2->ARR = 19999;  // 周期 = (19999+1) / 1MHz = 20ms = 50Hz

// 2. 设置捕获/比较寄存器 (CCR1)
TIM2->CCR1 = 1499;  // 占空比 = (1499+1) / (19999+1) = 7.5%

// 3. 设置TIM2的CCMR1寄存器,配置通道1为PWM模式1
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;  // 配置为PWM模式1
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;  // 使能预装载

// 4. 设置CCER寄存器,使能输出并选择极性
TIM2->CCER |= TIM_CCER_CC1E;  // 使能通道1输出,高电平有效

// 5. 设置CR1寄存器,使能计数器
TIM2->CR1 |= TIM_CR1_CEN;  // 启动定时器

你看,核心就是配置ARR和CCR。其他寄存器都是辅助的,用来选择模式、使能输出、设置极性。我个人习惯在配置PWM时,先把ARR和CCR算好写在纸上,然后再去填寄存器。这样思路清晰,不容易乱。

避坑指南:我曾经在配置CCMR1时,忘了设置OC1PE位(预装载使能)。结果在运行时修改CCR的值,输出波形会立刻变化,导致产生不完整的脉冲,电机跟着抖了一下。所以,如果你需要在运行中动态调整占空比,一定要使能预装载功能,让CCR的值在下一个周期开始时才生效。

4.5 不同模式下的PWM

除了最常用的向上计数PWM模式1,还有模式2、中央对齐模式等。我把它们的区别整理了一下。

模式 计数方式 输出电平逻辑 典型应用
PWM模式1 (向上计数) 0 → ARR CNT < CCR 时有效电平 最常用,电机、LED调光
PWM模式2 (向上计数) 0 → ARR CNT > CCR 时有效电平 与模式1极性相反,特殊场合
中央对齐模式 0 → ARR → 0 向上时CNT<CCR有效,向下时CNT>CCR有效 需要对称波形的场合,如三相逆变

中央对齐模式产生的PWM波形是对称的,谐波分量更少,在电机控制里用得比较多。不过它的频率是向上计数模式的一半,因为计数器跑一个来回才算一个周期。