PWM输出实验详细示例

目录

PWM输出实验

PWM的工作原理(以向上计数为例)

PWM与AFIO引脚重映射综合实验

库函数使用说明

PWM配置的基础知识点

GPIO端口功能重映射

时钟分割的含义

PWM的两个比较模式是什么?

预加载寄存器(TIMx_CCMR1)的作用

PWM输出极性是什么?

PWM配置注意事项

占空比怎么设置?

库函数配置步骤

第一步:使能GPIO/AFIO/TIM的外设总线时钟

第二步:配置引脚功能的重映射

第三步:配置定时器的基本属性

第四步:配置PWM输出模式

第五步:配置预加载寄存器

第六步:正式使能TIM功能

综合代码示例

Main.c

Led.c

Led.h

Timer.c

Timer.h

运行结果


PWM输出实验

PWM的工作原理(以向上计数为例)

 

CCRX寄存器中的值是用来和计数器中的值比较的,当CNT计数器中的值等于CCRX寄存器时,通用定时器输出信号电平翻转。最终当计数器计数至MAX(ARR中的值)时,(还可以触发“更新中断”)立刻返回0并且重新进行向上计数。

PWM与AFIO引脚重映射综合实验

库函数使用说明

库函数名称

功能

RCC_APB1PeriphClockCmd() / RCC_APB2PeriphClockCmd()

APB总线外设时钟使能

GPIO_Init()

GPIO初始化函数

GPIO_PinRemapConfig()

GPIO端口重映射函数

TIM_ARRPreloadConfig()

使能或者失能 TIMx 在 ARR 上的预装载寄存器

 

TIM_TimeBaseInit()

初始化定时器

TIM_OCInit()

初始化比较参数

TIM_Cmd()

定时器使能

TIM_SetCompare2()

不断改变比较值CCRx,达到不同的占空比效果

PWM配置的基础知识点

GPIO端口功能重映射

首先我们必须要清楚我们要将TIM功能重映射到那个引脚:

我们从AFIO的GPIO复用功能重映射了解到,当我们使用“TIM3部分部分重映射”功能时,我们可以将TIM3的TIM3_CH3引脚映射至PB5引脚上,用它去驱动LED0显示PWM的功能。

此时,应该注意以下几点:

① 一定要把GPIO输出模式配置为“复用推挽输出“,根据下图所示,只有配置成复用,才可以将其他非IO功能的信号通过这个GPIO引脚输出,只有配置成推挽,才可以不依赖于外界电平去向外输出高低电平,而且推挽输出符合TIM3功能的输出需求。

② 配置GPIO引脚的重映射功能

意义与作用:TIM3这个功能本身不属于PB5,但是我们查表可以得知“TIM3可以将功能重映射到PB5引脚上“,当我们将TIM3功能重映射到PB5引脚之前,我们必须使能AFIO时钟和配置重映射寄存器。

GPIO_InitTypeDef GPIO_InitStructure;  
  
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);    //使能定时器3时钟  
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟  
  
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5      
  
  //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2  
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出  
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO  

时钟分割的含义

我们配置TIM时,会调用此函数:TIM_TimeBaseInit();

其中它的结构体参数会配置“TIM_ClockDivision “属性。

先了解一下TIMER3的为经过PSC分频的时钟是如何获得的:

我们这个实验使用来自APB1总线的时钟(AHB时钟频率是72MHz),我们注意到AHB->APB1分频器->APB1中如果APB1预分频器的分频系数是1那么TIMXCLK的时钟频率为“TIMCLK=APB1=AHB”,如果APB1预分频器的分频系数为N(N不为1),那么TIMXCLK的时钟频率为“TIMCLK=2xAPB1=2xAHB/N”。

但是当我们调用ST公司提供的初始化时钟源库函数时,APB1分频器默认的分频系数是2,因此最终TIMXCLK=2xAPB1=2xAHB/2=AHB。

这里的时钟分割代表的就是APB1的预分频系数。

PWM的两个比较模式是什么?

预加载寄存器(TIMx_CCMR1)的作用

当我们在运行过程中更新ARR的值,如果预加载寄存器中的OC1PE被置1,那么ARR值会在下个周期被放入计数器中;如果被置0,那么ARR值会立刻加载到计数器中。

PWM输出极性是什么?

PWM配置注意事项

我们既然把PWM信号输出至PB5引脚上,那么我们就不需要像往常一样配置完GPIO引脚属性再赋给PB5特定的电平。因为PB5现在不是不同的IO引脚,它的状态由它接收到的PWM输出电平所决定。

占空比怎么设置?

我们在timer.c中只定义了如下部分:

TIM_OC_InitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 选择PWM输出模式1——当计数器值小于比较值时输出有效电平;当计数器值大于比较值时输出无效电平  
TIM_OC_InitStructure.TIM_OutputState = TIM_OutputState_Enable; // PWM输出模式使能  
TIM_OC_InitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 配置有效电平——高电平有效  
TIM_OC2Init(TIM3, &TIM_OC_InitStructure); // 配置PWM输出比较的属性  

这段代码只阐述了“如何设置TIMER3的2端口的PWM输出属性(PWM模式,有效电平,使能或失能)“,我们并没有设置”比较值”,因此此时默认的占空比是100%。

我们需要在配置完定时器的PWM后,再对CCRx比较值进行设置,代码如下:

TIM_SetCompare2(TIM3, CompareValue);  // 用于修改CCRx的值进而修改PWM占空比

我这里是在main函数中配置的,用于不断修改CCRx比较值产生亮度逐渐变化的LED灯效果。同样我们也可以修改ARR的值用于调整PWM周期,相应库函数如下:

TIM_SetAutoreload(TIM3, NEW_ARR); // 用于修改ARR的值进而修改PWM周期  

库函数配置步骤

第一步:使能GPIO/AFIO/TIM的外设总线时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟  
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟  

第二步:配置引脚功能的重映射

GPIO_InitTypeDef GPIO_InitStructure;  
PIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射  TIM3_CH2->PB5      
  
  //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2  
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出  
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO

  

第三步:配置定时器的基本属性

  //初始化TIM3  
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值  
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值   
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim  
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式  
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

  

第四步:配置PWM输出模式

TIM_OC_InitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 选择PWM输出模式1——当计数器值小于比较值时输出有效电平;当计数器值大于比较值时输出无效电平  
TIM_OC_InitStructure.TIM_OutputState = TIM_OutputState_Enable; // PWM输出模式使能  
TIM_OC_InitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 配置有效电平——高电平有效  
TIM_OC3Init(TIM3, &TIM_OC_InitStructure); // 配置PWM输出比较的属性  

第五步:配置预加载寄存器

TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能比较值预加载寄存器  
TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能预加载值寄存器  

TIM_OC3PreloadConfig()

用于配置”用于预加载CCRx值”的寄存器

TIM_ARRPreloadConfig()

用于配置“用于预加载ARR值“的寄存器

第六步:正式使能TIM功能

 

我们在寄存器工作流程框图中可以看到,等我们配置完定时器的所有属性之后我们才可以正式的使能TIM功能。

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

综合代码示例

Main.c

#include "led.h"  
#include "sys.h"  
#include "timer.h"  
  
 int main(void)  
 {        
    u16 CompareValue=0;  
    u8 Dir=1;       
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //设置NVIC中断分组2:2位抢占优先级,2位响应优先级  
    LED_InitConfig();                //LED端口初始化  
    TIMER_InitConfig(899,0);     //不分频。PWM频率=72000000/900=80Khz  
       
    while(1)  
    {  
        if(Dir == 1)   
        {  
            CompareValue++;  
        }  
        else   
        {  
            CompareValue--;  
        }  
          
        if(CompareValue >= 300)  
        {  
            Dir = 0;  
        }  
        else  
        {  
            Dir = 1;  
        }  
          
        TIM_SetCompare2(TIM3, CompareValue);  
    }      
 }  

Led.c

#include "led.h"  
#include "stm32f10x.h"  
  
void LED_InitConfig()  
{  
    GPIO_InitTypeDef GPIO_InitStructure;  
      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); // 使能LED1的外设时钟  
      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOE, &GPIO_InitStructure); // 配置LED1的输出属性  
      
    GPIO_SetBits(GPIOE, GPIO_Pin_5); // 配置初始引脚输出电平为低电平  
}  

Led.h

#ifndef _LED_H  
#define _LED_H  
  
#include "sys.h"  
  
void LED_InitConfig();  
  
#define LED1 PEout(5)  
  
#endif  

Timer.c

#include "timer.h"  
#include "led.h"  
#include "stm32f10x.h"  
#include "delay.h"  
  
void TIMER_InitConfig(u16 ARR, u16 PR)  
{  
    GPIO_InitTypeDef GPIO_InitStructure;  
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  
    TIM_OCInitTypeDef TIM_OC_InitStructure;  
    NVIC_InitTypeDef NVIC_InitStructure;  
      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); // 使能GPIOB的外设时钟  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3的外设时钟  
      
    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的引脚属性  
      
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); // 引脚重映射配置  
      
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB到TIMxCLK之间没有分频  
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式  
    TIM_TimeBaseInitStructure.TIM_Period = ARR; // 0x1388 = 5000  
    TIM_TimeBaseInitStructure.TIM_Prescaler = PR; // 0x1C20 = 7200  
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); // 周期长度为1s  
      
    TIM_OC_InitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 选择PWM输出模式1——当计数器值小于比较值时输出有效电平;当计数器值大于比较值时输出无效电平  
    TIM_OC_InitStructure.TIM_OutputState = TIM_OutputState_Enable; // PWM输出模式使能  
    TIM_OC_InitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 配置有效电平——高电平有效  
    TIM_OC2Init(TIM3, &TIM_OC_InitStructure); // 配置PWM输出比较的属性  
      
    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能比较值预加载寄存器  
    TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能预加载值寄存器  
      
    TIM_Cmd(TIM3, ENABLE); // TIM3使能  
      
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  
    NVIC_Init(&NVIC_InitStructure); // 配置NVIC嵌入式中断向量优先级  
      
    TIM_ITConfig(TIM3, TIM_IT_CC2, ENABLE); // 定时器具体中断模式配置  
}  
  
void TIM3_IRQHandler()  
{  
    delay_init();  
    if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET)  
    {  
        delay_ms(500);  
        LED1 = !LED1;  
    }  
    TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);  
}  

Timer.h

#ifndef _TIMER_H  
#define _TIMER_H  
  
#include "sys.h"  
  
void TIMER_InitConfig(u16 ARR, u16 PR);  
  
#endif  

运行结果

LEDx状态

状态改变位置

LED1亮灭交替

PWM比较中断

LED0亮度随着占空比的变化而变化

PWM输出电平

由于我用了delay_ms(500)作为延迟,因此你可以清楚的看到LED1由于比较中断状态交替变化,但是由于PWM比较值逐渐+1变化缓慢,因此你需要一段时间来仔细观察LED0的亮度变化。

猜你喜欢

转载自blog.csdn.net/weixin_45590473/article/details/108062177