首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

计时器之PWM信号

上一篇文章中,我们提到时基单元是计时器的基础, 它提供了基本的计数功能。此外,STM32还为每个计时器提供了四个输出比较通道(或者输入捕获通道,两者是复用的关系, 同一时间只能使用一种),通过与时基单元的功能组合,可以实现很多功能。

本文关注输出比较通道,通过合理的配置寄存器,我们将利用计时器实现PWM输出的功能,最终将其应用到LED灯亮度的控制上。

1. PWM简介

PWM全称Pulse Width Modulation,就是通过对脉冲宽度进行调制,以获得期望的波形。说的直白一点它就是一个方波,只是涉及到的三个参数可以调整罢了。

一个理想的方波只有两个电平,描述它我们只需要三个参数:频率、幅值、占空比。频率很好理解,就是一组高低电平每秒钟出现的次数。 幅值则是高低电平所对应的具体电压,但我们通常并不关心它的具体数值。因为MCU产生的PWM信号通常只是一个控制信号, 其后面还需要再接一个功率电路才可以驱动具体的设备。占空比是指一个周期中高电平时间的百分比。 比如说对于一个50kHz的PWM信号,其周期为20μs,占空比40%就表示在一个周期中有8μs的时间是高电平,12μs的低电平时间。

PWM信号的一个典型应用就是有刷电机的驱动。我们知道在有刷电机的正负两极加上一个电压,电机就会转动,而且转速与电压值成正比关系。 反接电极,电机就会反转。驱动电机转动时所需要的功率是比较大的,远不是MCU能够承受的起的,所以不可能直接用MCU驱动。为了能够控制电机的转速和转动方向, 我们会在电机与MCU之间用MOS管搭一个H电桥,由MCU输出的信号控制各个MOS管导通或者关断,以达到改变电机两极的电压和极性的目的。 需要注意的是,不能把同一侧的上下两个桥臂同时导通了,这样会引起短路。

电机驱动里的H电桥就是一个强电部分,负责系统的大功率输出。MCU则是系统的弱电部分,负责系统的逻辑控制。两部分是隔离的分别供电的, MCU输出的控制信号通过光耦等隔离措施后,施加到MOS管的门极电路就可以控制强电部分。通常我们给H电桥的控制信号就是PWM信号,H电桥就相当于一个功率放大器, 把PWM信号转换成不同的电压施加在电机两极。

在固定频率的条件下,通过调节PWM信号的占空比可以起到调整电机两端电压的效果。在采样控制中,有一个重要的原理就是, "冲量相等但形状不同的窄脉冲施加在同一个惯性环节上,所产生的效果基本相同"。如果我们对其冲击响应做傅里叶分析的话会发现,它们在低频段的响应基本一致, 只在高频特性上有些差异。所以理论上我们可以用PWM信号近似任何波形。电机在电气中是一个电感器件,具有一定惯性作用。只要PWM信号频率足够高, 就相当于一个直流到直流转换电路,所以调节占空比可以调整电机两端的电压,也就可以调节电机转速。

2. 计时器的输出比较通道

计时器的输出比较通道为我们提供了一个高效地获取指定频率和占空比的PWM波的方法。当然输出比较通道不仅限于产生一个PWM波, 它还有一些其它的工作模式,我们在这里进行详细的介绍。

计时器的输出比较通道和输入捕获通道共用一组寄存器,所以每个通道同一时间只能工作在一种状态下,具体可以通过CCMR寄存器的CCxS位做出选择。 下图1为计时器的输出通道结构示意图,STM32中每个计时器都有四个通道,每个通道都有一个CCR寄存器(Capture/Compare Register), 用于存储来自输入通道的捕获值,或者是输出通道的比较值。它也是一个影子寄存器结构,我们对CCR的访问实际上是对预装寄存器的访问, 而具体影响输出通道行为的则是影子寄存器。在每个更新事件发生时,把预装寄存器中的值写入影子寄存器中。

图1 输出比较通道结构图

输出比较,体现在CCR寄存器与时基单元的计数器CNT的比较上。根据CCR与CNT之间的关系,当CNT ≥ CCRx时我们说在通道x上发生了一次匹配事件。 根据寄存器CCMR的OCxM位的值,参考逻辑OCxREF将会对应发生变化,具体的工作方式参见下表1。参考逻辑OCxREF及其经过一个非门的信号一起在一个选择器中, 由CCER的CCxP位决定,最终输出到引脚OCx上的电平与OCxREF之间是正逻辑关系还是反逻辑关系。CCxE位则决定了OCxREF的逻辑是否会输出到引脚OCx上。

表1 不同输出比较模式下的参考逻辑OCxREF动作

OCxM 说明
000b Frozen, 计数器CNT与比较寄存器CCR之间的比较值对输出通道引脚没有影响。
001b 当发生匹配事件时,参考逻辑OCxREF将处于激活状态(active)
010b 当匹配事件发生时,参考逻辑OCxREF将处于失活状态(inactive)
011b 在此模式下,发生匹配事件时,翻转参考逻辑OCxREF
100b 参考逻辑OCxREF被强制处于失活状态
101b 参考逻辑OCxREF被强制处于激活状态
110b PWM模式1,计时器向上计数时,当CNT < CCRx时通道x处于激活状态(OCxREF=1),否则处于失活状态(OCxREF=0); 计时器向下计数时,当CNT > CCRx时通道x处于失活状态(OCxREF = 0),否则处于激活状态(OCxREF = 1)。
111b PWM模式2,参考逻辑的状态与PWM模式1相反。

此外,在CCMR寄存器中还有一个OCxPE的字段,用于开启或者关闭CCRx的预装模式。如果OCxPE=1,那么对CCRx的修改都是对其预装寄存器的修改,只有等到更新事件发生时, 才会被写入影子寄存器。如果OCxPE=0,那么对CCRx的修改都会直接写到其影子寄存器中,立即生效。

3. 计时器的PWM配置

根据第2节中关于输出通道的介绍,我们知道当CCMR的OCxM位为110b或者111b时,响应的输出通道就会工作在PWM模式下,配置CCER寄存器的CCxE位打开响应通道的输出功能, 我们就可以通过引脚OCx得到输出。所生成的PWM信号频率由时基单元的自动装载寄存器ARR决定,占空比则有对应通道的CCRx寄存器控制。 修改CCER的CCxP的值可以控制输出信号的极性。

在手册中还提到了生成的PWM信号有边沿对齐模式和中间对齐模式两种。这两种模式,并不是有OCxM=110b或者OCxM=111b决定的,它们是由时基单元的计时模式决定的。 时基单元有三种计数模式:向上计数、向下计数、中间对齐计数。其中向上计数和向下计数模式,在一个计数周期中只有一次匹配事件,所以对应的是边沿对齐模式, 而中间对齐计数在一个计数周期中有两次匹配事件,输出的PWM信号将变化两次,因而会产生中间对齐的PWM信号。而OCxM的取值,只是决定了匹配事件前后输出引脚的极性而已。

此外,STM32的TIM1和TIM8是两个高级一点的计时器,它们的前三个通道都对应有两个输出引脚OCx和OCxN,能够生成一对互补的PWM信号,即当OCx输出高电平时,OCxN输出低电平。

它就很适合用来驱动一个H电桥,将同侧的上下两个桥臂同时接入一对互补的PWM信号,理想情况下就不会存在上下桥同时导通,而使系统短路的情况。 而实际的MOS管都不可能理想,电子器件开关都需要时间的,虽然我们给了一对互补的PWM信号,但存在一个器件还没有及时关闭,另一个就已经打开了的情况。 为了解决类似的问题,会在高低电平切换的时刻添加一段死区时间,在这段时间里,互补的两个PWM信号将输出相同的电平。那么我们可以利用死区时间保证上下桥臂都处于关断的状态, 才打开某一个桥臂,就不会有段路的情况发生了。

因此,在TIM1和TIM8中还额外有一个寄存器BDTR,用于配置计时器的死区时间。

4. 通过PWM调整LED灯的亮度

除了控制电机,我们还可以用它来控制LED灯的亮度。 我们可以通过配置时基单元产生一个频率在60Hz以上的PWM信号。 因为人眼所能识别的极限频率就是60Hz左右,这样就不能察觉LED闪烁了。配置ARR值为255,我们用一个8位数字描述占空比,也就是灯光的亮度。 在探索者开发板用的是三个单色的LED, 实际上有一种三色LED,我们可以用一个计时器的三个通道来调节每个颜色的亮度,这样就可以用它来组合出各种颜色。

驱动LED灯时我们分析过原理图, 知道LED0连接的PF9引脚可以用作TIM14的通道1。我们定义函数led_pwmio_init()配置引脚工作模式:

        void led_pwmio_init(void) {
            RCC->AHB1ENR.bits.gpiof = 1;
            // 功能选择, TIM14的通道1
            GPIOF->AFR.bits.pin9 = 0x09;
            GPIOF->MODER.bits.pin9 = GPIO_Mode_Af;
            GPIOF->OTYPER.bits.pin9 = GPIO_OType_PP;
            GPIOF->PUPDR.bits.pin9 = GPIO_Pull_Up;
            GPIOF->OSPEEDR.bits.pin9 = GPIO_OSpeed_Very_High;
        }

我们在函数led_pwm_init()中配置计时器,我们先打开TIM14的驱动时钟,然后配置其时基单元,设定计数周期和计数方向。 接着通过两个辅助函数timer_set_ccmr()和timer_set_ccer()设定TIM14的通道1使其输出PWM。最后写寄存器CCR1设定占空比为0,并开启计数器。

        void led_pwm_init(void) {
            RCC->APB1ENR.bits.tim14 = 1;
        
            TIM14->CR1.bits.DIR = TIM_COUNT_DIR_UP;
            TIM14->PSC = 4999;
            TIM14->ARR = 255;
            TIM14->CR1.bits.ARPE = 1;
        
            union timer_chanel_mode cfg;
            cfg.oc.OCxM = TIM_OCMode_PWM2;
            cfg.oc.OCxPE = 1;
            union timer_chanel_en cen;
            cen.bits.CCxE = 1;
            cen.bits.CCxNE = 0;
            cen.bits.CCxP = 0;
            cen.bits.CCxNP = 0;
        
            timer_set_ccmr(TIM14, 1, cfg);
            timer_set_ccer(TIM14, 1, cen);
        
            TIM14->CCR1 = 0;
            TIM14->CR1.bits.CEN = 1;
        }
        void timer_set_ccmr(timer_regs_t * tim, uint8 c,
            union timer_chanel_mode cfg) {
            switch (c) {
            case 1:
                tim->CCMR1.bytes.b13 = cfg.byte;
                break;
            case 2:
                tim->CCMR1.bytes.b24 = cfg.byte;
                break;
            case 3:
                tim->CCMR2.bytes.b13 = cfg.byte;
                break;
            case 4:
                tim->CCMR2.bytes.b24 = cfg.byte;
                break;
            default:
                tim->CCMR1.all = 0;
                tim->CCMR2.all = 0;
                break;
            }
        }
        void timer_set_ccer(timer_regs_t * tim, uint8 c,
            union timer_chanel_en cen) {
            uint16 tmp = tim->CCER.all;
            c = 4 * (c - 1);
            tmp &= ~(0x000F << c);
            tmp |= (cen.all << c);
            tim->CCER.all = tmp;
        }
最后,我们就可以通过函数led_set_bright()设定灯光的强弱:
        void led_set_bright(uint8 r) {
            TIM14->CCR1 = r;
        }

5. 总结

在STM32中,每个计时器都有四个通道,可以工作在输入捕获模式或者输出比较模式下,但同一时间只能工作在一个模式下。在输出比较模式下, 比较的对象是通道的CCRx寄存器与时基单元的计数器,在不同的工作模式下可以控制通道的输出引脚产生不同的电平,这点可以通过CCMR的CCxM位做到。 PWM工作模式本质上是一种输出比较模式,只是最终输出引脚的电平控制方式有点特别罢了。为了满足输出互补的PWM信号的需求, STM32提供了两个高级一点的计时器TIM1和TIM8,它们的前三个通道都有两个输出引脚分别为OCx和OCxN,我们可以调整BDTR来控制死区时间。

最后,我们列出了控制LED灯亮度的关键代码。实际上如果将一个三色灯接到一个计时器的三个通道上,我们可以通过三路PWM组合出任意的颜色。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1