STM32F103:利用通用定时器输出PWM

通用定时器  —-输出
    1,输出一个PWM
    2,检测脉冲宽度
    
    1》PWM—脉冲宽度调制
        占空比:高电平占整个周期的百分比

    2》PWM作用:调节灯的亮度,声音的大小,速度的快慢—-平均电压值
        

什么是PWM信号?

        PWM,英文名Pulse Width Modulation,是脉冲宽度调制(记住这个名词)缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比,例如方波的占空比就是50%.

    PWM脉冲宽度调制,实际上就是脉冲信号,但是这个脉冲信号的高/低电平在一个周期所占时间可以调节。

占空比:

        是指高电平在一个周期之内所占的时间比率,方波的占空比为50%,占空比为0.5,说明正电平所占时间为0.5个周期。若信号的周期为T,每周期高电平时间为t1,低电平时间为t2,T=t1+t2,则占空比D=t1/T。

        

         当我们思考pwm给外设供电的时候,可以从能量的角度去思考。高电平*其持续的时间 = 提供的能量,所以占空比越大,提供的能量就越大,此时外设工作的动能就越大。

        但是,我们在思考pwm的时候,必须要考虑周期。如下图:

请问:这两个pwm提供的总能量都一样,其占空比也一样。但是它们对外设的影响有什么区别?

        它俩的周期不一样,虽然对外设提供的总能量大小一样,但是对外设提供能量的频率却不一样。一个是隔一小段时间提供一小段能量,而另外一个却是,持续的将能量提供完。不同的能量供应导致外设的工作状态就会有所不同。

        这也就是为什么,对LED灯提供PWM供电。当占空比变大时,LED灯就会变亮,当占空比变小时,LED灯就会变暗。因为,此时LED是隔一小段时间亮一小段,增大占空比,就是增大能量的供给。由于电能转换为光能,电能供给增大,光的亮度就增大。注意:我们人眼对光识别得频率为50hz,PWM的频率要大于我们人眼识别频率,要不然会出现闪烁现象。

片上外设PWM模块的好处是什么?

        如果MCU没有内置pwm模块,一般我们用GPIO输出高/低电平来模拟PWM。当我需要输出pwm时,就进入中断,用GPIO来模拟输出PWM信号,但是,由于在中断中,那么此时MCU就只能执行这个中断,而不能做其他事情,MCU的资源就被占用了。

        内置的片上外设pwm模块是用单独的硬件模块去自动生成输出pwm波形,而不是利用MCU去控制GPIO模拟的pwm。所以此时MCU的资源并没有被占用。如下图:

通用定时器

想要使用PWM,我们需要使用通用定时器来对PWM的脉宽进行调制。

        向上/向下自动装载计数器:就是中央对齐模式。

        4个独立通道:PWM模块有4个输出通道,不同的通道在不同的引脚上,它们互不干扰,可以各自输出不同频率、不同占空比的PWM波形

 

         注意:通道TIMx的4路通道,如果某一个通道用作输出,那么该通道就不可以用作输入。用作输入,就不可以用作输出。所以,图中看似有输入、输出一共8路通道,实际上,只有4路。

捕获/比较寄存器(CCR):(CCR寄存器是16位的)

        一开始捕获/比较寄存器(CCR)输出的是低电平(高电平),然后我们给捕获/比较寄存器中赋一个数值。当16为计数器(CNT)中的值计数超过捕获/比较寄存器中的数值的时候,捕获/比较寄存器就会输出一个高电平(低电平)。当计数器计数溢出后,重新开始计数,计数器的数值又低于捕获/比较寄存器中的数值了,那么此时捕获/比较寄存器的输出就会恢复原来的状态,即输出一个低电平(高电平)。然后,如此反复。这个实际上就是PWM波的形成过程。如下图:

有效/无效电平:有效/无效电平是我们根据外部的外设来设置的,有效电平就是可以使外部的外设正常工作的电平,无效电平就是不能使外部的外设正常工作的电平。

PWM波的输出有4种方式:(我们设置低电平为有效电平,ARR=100,CRR=60)

        1.   PWM1递增方式:

                

           CNT计数器递增方式计数(从0开始递增计数),我们的有效电平是低电平。 一开始CNT计数器中的数值小于CRR的数值60,所以输出有效电平——低电平。当CNT计数器中的数值大于CRR的数值60的时候,输出无效电平——高电平。然后,当CNT计数器计数溢出的时候,计数器重新开始计数,此时CNT计数器中的数值小于CRR的数值60,此时又输出了有效电平——低电平。反复如此。

        2.   PWM1递减方式:

                

          CNT计数器递减方式计数(从设定值开始递减计数到0),我们的有效电平是低电平。 一开始CNT计数器中的数值大于CRR的数值60,所以输出无效电平——高电平。当CNT计数器中的数值小于CRR的数值60的时候,输出有效电平——低电平。然后,当CNT计数器计数到0的时候,计数器重新开始计数,此时CNT计数器中的数值大于CRR的数值60,此时又输出了无效电平——高电平。反复如此。  

          3.   PWM2递增方式:

         CNT计数器递增方式计数(从0开始递增计数),我们的有效电平是低电平。 但是与PWM1的不一样,CNT计数器中的数值小于CRR的数值60,输出的是无效电平。而CNT计数器中的数值大于CRR的数值60,输出的是有效电平。所以,一开始CNT计数器中的数值小于CRR的数值60,所以输出无效电平——高电平。当CNT计数器中的数值大于CRR的数值60的时候,输出有效电平——低电平。然后,当CNT计数器计数溢出的时候,计数器重新开始计数,此时CNT计数器中的数值小于CRR的数值60,此时又输出了无效电平——高电平。反复如此。

        4.   PWM2递减方式:

          CNT计数器递减方式计数(从设定值开始递减计数到0),我们的有效电平是低电平。 但是与PWM1的不一样,CNT计数器中的数值大于CRR的数值60,输出的是有效电平。而CNT计数器中的数值大于CRR的数值60,输出的是无效电平。所以,一开始CNT计数器中的数值大于CRR的数值60,所以输出有效电平——低电平。当CNT计数器中的数值小于CRR的数值60的时候,输出无效电平——高电平。然后,当CNT计数器计数到0的时候,计数器重新开始计数,此时CNT计数器中的数值大于CRR的数值60,此时又输出了有效电平——低电平。反复如此。

注意:输出的高低电平,是由捕获/比较寄存器后面的输出控制单元输出的。

影响PWM波形的因素:

        1.模式PWM1或PWM2

        2.计数方式:向上计数、向下计数、中央对齐计数

        3.CCR寄存器中的数值(改变CRR寄存器中设置的数值,就可以改变PWM波形的占空比)

        4.ARR寄存器中的数值(改变ARR寄存器中设置的数值,就可以改变PWM波形的周期/频率

        5.有效电平的设置

        6.PSC预分频器

重映射:

        如果,我们的原理图上面,包含TIM3模块的引脚被其他外设占用了,我们就只能使用重映射,将TIM3的功能映射到其他引脚上面,使用其他引脚来驱动设备。

         现在,我们想要使用PC6~PC8来驱动LED灯进行呼吸灯,需要使用到TIM3的CH1~CH3通道。但是TIM3的CH1~CH3通道却是在PA6  PA7  PB0引脚上面的复用功能。所以我们需要使用重映射,将TIM3的CH1~CH3通道功能映射到PC6~PC8上面。如下图:

 

         我们使用完全重映射就可以将TIM3的CH1~CH3通道,映射到PC6~PC8上面。这样,PC6~PC8引脚就可以使用IM3的CH1~CH3功能了。

 

预装载寄存器—-影子寄存器:

         我们在图中便可以看到影子寄存器,图中红框框住的黑色阴影就是影子寄存器。其它寄存器没有阴影,说明其他寄存器没有影子寄存器。

        这个寄存器是实际存在的寄存器,但是是你不可以直接操作的寄存器。实际上真正参加工作的寄存器,是影子寄存器而不是我们设置的ARR、CCR寄存器,ARR、CCR寄存器只是相当于一个缓冲寄存器。我们把数值设置到ARR、CCR寄存器中,在当前周期完成后(即:计数器计数溢出后),ARR、CCR寄存器中的数值才会传给它们各自的影子寄存器。在进数值行比较和数值修改时,真正起作用和参与数值比较等一系列工作的是影子寄存器,而不是ARR、CCR寄存器。   

        注意:当你需要改变周期值和比较值时,影子寄存器不会当即发生改变,会在当前周期完成后(即:计数器计数溢出后),它才会被改变。也就是说,需要等当前周期完成后,我们修改的值才会起作用。

编程步骤:

        1,打开时钟—-GPIOC,TIM3,AFIO 
        2,初始化GPIOC
            —-GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8
            —-复用推挽输出
            —-速度—50MHZ
        3,完全重映射TIM3—-将GPIOC6,7,8的功能给TIM3控制
        4,初始化定时器3
            —-ARR的值:255
            —-预分频器的值:1-1
            —-计数方式:向上 
        5,输出通道初始化
            —-通道1,2,3
            —-模式:PWM1
            —-有效电平:低电平
            —-捕获比较寄存器的值:0
        6,使能ARR的预装载寄存器
        7,使能通道1,2,3的预装载寄存器
        8,使能定时器3 

一些问题:

        1.为什么在初始化的时候,CCR的值设置为0?

                因为,在CNT计数器计数方式为向上计数,且在PWM1模式下。此时CCR的值如果设置为0的话,那么CNT计数器的值会一直大于0,这样的话,通道输出的一直就是无效电平——高电平,那么此时不会产生PWM波形(因为电平一直是某种状态)。当我们想要TIM输出有效电平的时候或者说我们想要输出PWM的时候,我们再来将CRR的值进行改变,此时PWM波形就会产生了。也就是说,当我们想要输出PWM波的时候,再去产生PWM波。下图是设置TIM1~TIM3通道的CCR寄存器的库函数:

 

有可能我们会在程序运行的过程中临时改变CRR中的值,那么就用这个函数来改变。

下图是设置ARR寄存器中数值的库函数:

 有可能我们会在程序运行的过程中临时改变ARR中的值,那么就用这个函数来改变。

注意:下图中的库函数是改变CNT计数器中的值而不是ARR中的值

        2.为什么PSC预分频器的值要写成1-1?

        

        因为它内部的值会自动加1,所以如果我们设置1分频,那么它内部就会自动+1,变成2分频。所以,我们这里设置为0,那么0+1= 1,即1分频。

编写程序:

void TIM3_PWM_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    TIM_OCInitTypeDef  TIM_OCInitStruct;
    // 1,打开时钟—-GPIOC,TIM3,AFIO ,注意:用到复用功能,就需要打开复用功能的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOC,ENABLE);

    // 2,初始化GPIOC
    GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed  =GPIO_Speed_50MHz;
    GPIO_Init(GPIOC,&GPIO_InitStruct);

    // 3,完全重映射TIM3—-将GPIOC6,7,8的功能给TIM3控制
    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

    // 4,初始化定时器3
    TIM_TimeBaseInitStruct.TIM_CounterMode   =TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Period        =255;        //ARR寄存器的值为255
    TIM_TimeBaseInitStruct.TIM_Prescaler     =1-1;        //1分频
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);

    // 5,输出通道初始化
    TIM_OCInitStruct.TIM_OCMode        =TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OCPolarity    =TIM_OCPolarity_Low;
    TIM_OCInitStruct.TIM_OutputState   =TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse         =0;
    TIM_OC1Init(TIM3,&TIM_OCInitStruct);
    TIM_OC2Init(TIM3,&TIM_OCInitStruct);
    TIM_OC3Init(TIM3,&TIM_OCInitStruct);

    //  6,使能ARR的预装载寄存器
    TIM_ARRPreloadConfig(TIM3,ENABLE);

    // 7,使能通道1,2,3的预装载寄存器
    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);

    // 8,使能定时器3 
    TIM_Cmd(TIM3,ENABLE);
}

//通过PWM的占空比来控制三个小灯的不同亮度
void PWM_CompareValue(uint32_t R,uint32_t G,uint32_t B)
{
    TIM_SetCompare1(TIM3, B);        //设置通道1中CCR寄存器中的数值,即改变通道1输出PWM波的占空比
    TIM_SetCompare2(TIM3, G);
    TIM_SetCompare3(TIM3, R);
}

int main(void)
{
    RCC_ConfigTo72M();//将系统时钟配置成72MHZ 
    Systick_Config(72);
    TIM3_PWM_Config();        /*注意:由于PWM的配置需要用到完全重映射,可能会导致与串口配置产生冲突,所以如果用到串口,我们需要将TIM3的配置写在串口配置之前,否则可能无法产生PWM波形*/

    while(1){

        PWM_CompareValue(136, 49, 121);        /*不同的占空比,LED的亮度不同,通道1的CRR寄存器的值设置为136,通道2的CRR寄存器的值设置为49,通道3的CRR寄存器的值设置为121*/

    }
}

        注意:在输出PWM波形的过程中,如果此时发送某个中断,那么中断执行的时间不能长,也不要在中断中加延时。否则当执行完中断后,回到main中的PWM输出,此时PWM无法正常输出。

呼吸灯代码

uint8_t red=0;
uint8_t blue=0;

int main(void)
{
    RCC_ConfigTo72M();//将系统时钟配置成72MHZ 
    Systick_Config(72);
    TIM3_PWM_Config();

    while(1){

        if(redflag==0){
            red++;
            blue++;
            if(red==255){

                redflag=1;

            }

        }else{

            red–;
            blue–;
            if(red<10){
                redflag=0;

            }

        }
        

        PWM_CompareValue(red, 0, blue);
        Systick_NmsDelay(10);        //延时10ms
        

    }
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F103:利用通用定时器输出PWM

发表评论