目录
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的亮度变化。