STM32之PWM波

说起PWM波,做过智能小车的人肯定都很清楚了,其实他就是一种脉宽调制。在智能小车上,我们一般用PWM波来控制小车的速度,通过控制方波中高低电平的比例,来达到控制小车转速的目的。而32的芯片他提供了专门的PWM波输出通道,我们只需要进行相应的配置就可以调用32的PWM波的输出,今天我们就用定时器3的通道2产生PWM波来控制LED灯的亮度,以实现呼吸灯的效果。

我先来讲一下32的PWM波输出的大概工作原理。首先我们还是需要用定时器,(其实用的是计数器,因为定时器的本质就是计数器)假如说我们让计数器从0开始计数,一直计到100,然后我们在0到100中间再设定一个数,假如说我们设定为30,那么当计数器中的计数值小于30的时候,PWM输出的引脚会输出一个电平(具体是高电平还是低电平,是可以设置的),然后如果计数器的值大于了30,那么PWM波的输出引脚会输出和刚才相反的电平,然后计数到100之后,再返回到0,重新开始下一轮计数,然后我们通过控制中间这个数值(就是刚才那个例子中的30),我们就可以控制PWM波的占空比了,然后通过控制计数的最大值(就是刚才那个例子中100)就可以控制PWM波输出的周期了。这就是32中PWM波输出的基本原理。

今天我们还是用到了LED和定时器,按照老套路还是先建两个空白的文件,分别命名为time.c和time.h,然后根据前两篇博客的步骤给添加到工程中去,具体步骤在这我就不再赘述了。这次我们把所有的初始化函数都放在time.c文件里面,也就是说不用再建led.c和led.h文件了。
首先我们对定时器3进行基本的配置,代码如下

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//注意结构体的声明必须在函数的开头
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period=arr;
TIM_TimeBaseStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);

这些代码和上篇博客中的一样,我就不再多讲,不懂的可以去翻看上篇博客(利用定时器中断控制LED)。

然后我们来配置定时器3通道2的PWM输出的工作方式。代码如下

    //配置TIM3 Channel2 PWM
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
    TIM_OC2Init(TIM3,&TIM_OCInitStructure);//注意此处的函数名字以及函数的入口参数

大家看,这还是结构体,32里面基本所有的配置都是使用的结构体。我们首先来看这个结构体中的第一个成员变量 TIM_OCMode,这个变量是设置PWM的工作模式,关于PWM的工作模式有好几种,如下

#define TIM_OCMode_Timing                  ((uint16_t)0x0000)
#define TIM_OCMode_Active                  ((uint16_t)0x0010)
#define TIM_OCMode_Inactive                ((uint16_t)0x0020)
#define TIM_OCMode_Toggle                  ((uint16_t)0x0030)
#define TIM_OCMode_PWM1                    ((uint16_t)0x0060)
#define TIM_OCMode_PWM2                    ((uint16_t)0x0070)

我们这里选择模式2,其他的模式我也不清楚,我知道模式2和模式1的区别就是,他俩的有效电平是相反的,关于有效电平的概念我一会就会提到。

关于PWM输出
PWM Mode 1:
向上计数:TIMx_CNT<TIMx_CCR1 active ;TIMx_CNT>=TIMx_CCR1 inactive
向下计数:TIMx_CNT>TIMx_CCR1 inactive ;TIMx_CNT<=TIMx_CCR1 active
PWM Mode 2:
向上计数:TIMx_CNT<TIMx_CCR1 inactive ;TIMx_CNT>=TIMx_CCR1 active
向下计数:TIMx_CNT>TIMx_CCR1 active ;TIMx_CNT<=TIMx_CCR1 inactive

这里我们选择模式2,那么代码就是

TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;

然后这个结构体的第二个成员就是设置输出极性,也就是有效电平。假如说我们设置输出极性(有效电平)是高电平,对于模式2来说,当计数器的值小于你设定的阈值(上面那个例子中的30)的时候,PWM波对应的输出引脚就会输出无效电平,也就是低电平,当计数器的值大于你设定的阈值的时候,PWM波就会输出有效电平,也就是高电平;而对于模式1来说,则正好相反,当计数器的值小于你设定的阈值(上面那个例子中的30)的时候,PWM波对应的输出引脚就会输出有效电平,也就是高电平,当计数器的值大于你设定的阈值的时候,PWM波就会输出无效电平,也就是低电平;
这里我们选择输出极性(有效电平)为高电平,代码如下

TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;

然后这个结构体的第三个成员就是选择输出状态是使能或者不使能,我们选择使能,代码如下

TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;

到这就把PWM波的工作方式配置完了,然后就是把这些成员利用下面这个函数给导入到相应的寄存器中,代码如下

TIM_OC2Init(TIM3,&TIM_OCInitStructure);//注意此处的函数名字以及函数的入口参数

在这我们需要注意一下这个函数的名字TIM_OC2Init,它里面的2就代表着配置的是定时器的第二个通道,然后第一个参数就代表着配置的是定时器3,第二个参数就是刚才配置的结构体,总的来说就是配置了定时器3的第二个通道,如果要用到定时器3的其他通道,则把这个函数名字中的2改成对应的通道数就行。
定时器配置完了,接下来我们就要开始配置这次所用到的GPIO口了,代码如下

GPIO_InitTypeDef GPIO_InitStructure;//注意结构体声明必须放在函数的开头
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

程序的第一行同样是定义一个结构体,然后第二行是开启GPIOB的时钟,然后第三行是开启复用功能的时钟,关于复用功能我简单说一下。我们今天想实现的是用定时器3的通道2来控制LED的亮度,在我使用的开发板上,今天用到的LED是接在PB5上,然而定时器3的通道2所产生的的PWM波却不是通过PB5引脚来输出的,所以说我们就需要开启32的复用功能,我么可以把定时器3的通道2 的PWM波输出给映射到PB5引脚上,这就可以实现让PWM波从PB5引脚上输出了,(重映射的引脚不是随意的,只能映射到固定的引脚)关于复用功能具体的大家可以百度查询。
然后程序的第四行就是配置GPIO口的工作模式,我们这里选择复用推挽输出模式。然后程序的第五、六、七行就不多说了。

这些都配置完之后,我们要再调用一个函数,如下

    //把TIM3部分重映射到PB5
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);

这个函数就是实现了把定时器3给重映射到了PB5引脚上。

然后我们用到了定时器3,所以就需要开启定时器3的时钟,代码如下

    //使能TIM3时钟
    TIM_Cmd(TIM3,ENABLE);

我们今天没有用到定时器中断,所以就不用再使能定时器中断的时钟了。
到此为止,我们time.c文件的程序都已经写好了,完整代码如下

#include "time.h"
void time3_PWM_init(u16 arr,u16 psc)
{
    //配置TIM3
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//注意结构体的声明必须在函数的开头
    TIM_OCInitTypeDef TIM_OCInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period=arr;
    TIM_TimeBaseStructure.TIM_Prescaler=psc;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);

    //配置TIM3 Channel2 PWM

    TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
    TIM_OC2Init(TIM3,&TIM_OCInitStructure);//注意此处的函数名字以及函数的入口参数

    //配置GPIO口,并且设置成复用功能

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);

    //把TIM3部分重映射到PB5
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);

    //使能TIM3时钟
    TIM_Cmd(TIM3,ENABLE);
}


接下来开始写time.h文件,还是只需要把time.c文件中所定义的函数给声明一下就行,代码如下

#ifndef __TIME_H
#define __TIME_H

#include "sys.h"

void time3_PWM_init(u16 arr,u16 psc);


#endif

然后我们就开始写main.c文件中的程序了。先把全部代码贴出来

#include "sys.h"
#include "delay.h"
#include "time.h"

int main(void)
{
    u16 led0pwm=0;
    u8 dir=1;
    delay_init();
    time3_PWM_init(899,0);
    while(1)
    {
        delay_ms(10);
        if(dir)
            led0pwm++;
        else if(!dir)
            led0pwm--;
        if(led0pwm>=300)
            dir=0;
        else if(led0pwm==0)
            dir=1;
        TIM_SetCompare2(TIM3,led0pwm);
    }
}


在main.c文件的刚开始我们还是先写需要调用的头文件,因为我们今天主函数里面用到了延时函数,所以要添加头文件”delay.h”,调用的头文件如下

#include "sys.h"
#include "delay.h"
#include "time.h"

然后我们在主函数中定义了两个变量:led0pwm和dir,其中led0pwm是当做PWM波输出的那个阈值(就是前面举的例子中的那个30)的,然后变量dir是用来控制led0pwm的增长方向,以便实现呼吸灯的效果。然后我们还用到了另外一个重要的函数

TIM_SetCompare2(TIM3,led0pwm);

这个函数有两个入口参数,第一个参数就是选择对哪个定时器进行操作,第二个参数就是设置PWM波输出的那个阈值,我们在主函数中每隔10ms对led0pwm改变一次数值,也就是PWM波的那个阈值一直的在变化(每隔10ms变化一次),而每一个阈值对应的LED的发光亮度都会不一样,对于我们今天设定的工作方式来说,阈值越大,LED亮度越大,阈值越小,LED亮度越小。(因为我们刚才配置的PWM波工作模式为模式2,然后输出有效电平是高电平,而对于本人使用的开发板来说,给PB5引脚高电平,LED熄灭,给PB5引脚低电平,LED点亮,所以说当计数器的值小于led0pwm的时候,PWM波输出为无效电平,也就是低电平,这个时候LED点亮,而当计数器的值大于led0pwm的时候,PWM波输出有效电平,也就是高电平,这个时候LED熄灭,所以说我们的led0pwm的值越小,PWM波输出低电平的时间就越短,LED的亮度就越小,led0pwm的值越大,PWM波输出低电平的时间就越长,LED的亮度就越大)
综上所述,一直改变led0pwm的值,就可以一直改变LED的亮度,LED就会实现从暗到亮,然后从亮到暗,然后再从暗到亮,这样一直循环下去,就实现了呼吸灯的效果。
至此,本次用定时器3输出PWM波控制LED灯的实验就结束了,把代码烧录到开发板中,就可以看到正点原子精英版的开发板上DS0的亮度一直的在变化。
注意主函数中的那一条延时语句 delay_ms(10) 不能省略,否则我们将看不到LED的亮度变化,即我们不能让led0pwm的值变化的太快。

猜你喜欢

转载自blog.csdn.net/qq_36554582/article/details/81239628