一、定时器
(一)定时器简介
STM32定时器是STM32微控制器中的重要块,用于生成精确的时间基准。它可以用于测量时间间隔、产生脉冲、实现定时中断等功能。通过配置寄存器,用户可以灵活地控制定时器的工作模式和参数,实现各种实时控制和定时任务。
(二)定时器类型
1、常见定时器
- SysTick定时器:
SysTick系统时钟位于Cortex-M3内核,是一个24位的递减计数器,主要用于:
1)精确延时,在多任务操作系统中为系统提供时间基准(时基);
2)任务切换,为每个任务分配时间片。
- WatchDog定时器:
1)作用:当微控制器受到外部干扰或程序中出现不可预知的逻辑故障导致应用程序脱离正常的执行流程时(俗称程序跑飞),在一定的时间间隔内使系统复位,回到初始状态;
2)看门狗设计是用来监视MCU程序运行状态的,是确保系统可靠稳定运行的一种有效措施。
WatchDog又可以分为独立看门狗和窗口看门狗,区别如下:
独立看门狗:采用独立时钟,不受其他时钟和总线影响,可在停机和待机模式下工作。适用于独立于主程序之外、能够独立工作且对时间精度要求不高的场合。
窗口看门狗:计数值有一个上限时间和下限时间,低于下限时间或高于上限时间都会触发MCU复位。适用于精确计时窗口起作用的应用程序。
- 基本定时器(下方重点介绍):TIM6和TIM7。
- 通用定时器(下方重点介绍):TIM2、TIM3、TIM4和TIM5。
- 高级定时器(下方重点介绍):TIM1和TIM8。
2、定时器的主要功能
- 计数:脉冲计数,使用使用微控制器内部的外部时钟(PCLK)来计数,是对固定周期的脉冲信号计数。
- 定时:时间控制,通过对微控制器内部的时钟脉冲进行计数实现定时功能。
- 输入捕获:对输入信号进行捕获,实现对脉冲的频率测量,可用于对外部输入信号脉冲宽度的测量,比如测量电机转速。
- 输出比较:将计数器计数值和设定值进行比较,根据比较结果输出不同电平,用于控制输出波形,比如直流电机的调速。
3、常规定时器
STM32定时器分类比较表
(1)基本定时器
简介:
STM32有2个基本定时器TIM6和TIM7,可用作: 通用的16位计数或者 产生DAC触发信号,基本定时器的计数模式只有向上计数模式。
(2)通用定时器
简介:
TIM2、TIM3、TIM4、TIM5为STM32的4个独立的16位通用定时器,具有定时、测量输入信号的脉冲长度(输入捕获)、输出所需波形(输出比较、产生PWM、单脉冲输出等)等功能。如下图为通用定时器内部结构框图。
STM32F103系列微控制器的定时器功能十分强大,内部结构也比较复杂,STM32通用定时器TIMx(x=2,3,4,5)主要由时钟源、时钟单元、捕获和比较通道等构成,核心是可编程预分频驱动的16位自动装载计数器。通过对通用定时器内部结构进一步简化得到如下图所示的内部简化图:
通用定时器相关参数:
1)通用定时器的时钟源:
如下图为通用定时器框图中有关时钟源的部分:
当定时器使用内部时钟时,定时器的时钟源统称为TIMxCLK。虽然在系统默认的配置中,TIMxCLK的时钟频率都是72MHz,但其时钟来源并不相同。
- 定时器TIM2~TIM7挂接在APB1上
- 定时器TIM1和TIM8挂接在APB2上
若外部晶振的频率为8MHz,则系统默认的时钟频率为72MHz
- APB1预分频器的分频系数设置为2,则PCLK1=36MHz;
- APB2预分频系数设置为1,则PCLK2=72MHz,TIM1和TIM8的时钟频率TIMxCLK=72MHz;
- Cortex系统时钟由AHB时钟(HCLK)8分频得到,即SysTick的频率为9MHz。
2)预分频器PSC:
如图为预分频器时序图:
简介:
PSC参数代表预分频器(Prescaler)。预分频器用于将定时器的时钟频率分频,以便获得所需的计数频率。在定时器工作时,计数器将以预分频器所设定的频率递增,直到达到设定的自动重载值(ARR,Auto-Reload Register)为止,然后重新开始计数。
限制:可以以1~65535之间的任意数值对时钟源CK_PSC的时钟频率进行分频,输出CK_CNT脉冲供计数器CNT进行计数。
计数器计数频率计算公式:CK_CNT = CK_PSC / (PSC + 1)
3)计数器CNT:
如图为计数器时序图:
简介:
- TIMxCNT是一个16位的寄存器,计数范围为1~65535,可以向上计数、向下计数或向下向上双向计数。
- 要得到想要的计数值,需要对输入时钟频率进行分频。
- 当计数值达到设定值时,便产生溢出事件,溢出时产生中断或DMA请求,然后再由自动装载寄存器进行重新加载或更新。
- 计数器溢出中断属于软件中断,执行相应的定时器中断服务程序。、
计数器溢出频率计算公式:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
4)自动装载寄存器ARR
定时器的定时时间主要取决于定时周期和预分频因子,这里ARR+1是因为计数器都是从0开始计数的。计算公式为:
定时时间=(ARR+1)×(预分频值PSC+1)/输入时钟频率 或者
T=(TIM_Period +1)*(TIM_Prescaler +1)/TIMxCLK
例如下方实践中要求延时2秒闪烁及定时器设为2s,假设系统时钟为72MHz,通用定时器时钟TIMxCLK为72MHz,设置如下:
预分频系数PSC=6000-1; ARR=4000-1;
则,定时时间=4000×36000/72000000=2s。
(3)高级定时器
简介:TIM1和TIM8是STM32的2个16位的高级定时器,高级定时器相比基本定时器、通用定时器,功能更为强大
功能: 高级定时器除了通用定时器的功能外,还支持更高级的特性,如相位锁定回路(PLL)、编码器接口、三角波生成等。
用途场景: 适用于需要更高级、复杂计时功能的应用,例如音频处理、电机控制、编码器接口等。重复计数器、死区生成、互补输出、刹车输入等功能。
(三)定时器配置
1、定时器标准外设库接口函数
简介:
STM32F103系列微控制器的定时器库函数存放在STM32标准外设库的stm32f10x_tim.c和stm32f10x_tim.h文件中,stm32f10x_tim.h头文件中声明了定时器有关的库函数以及相关的宏定义、结构体等。由于STM32的定时器功能较为强大,其库函数也较多,如下图所示:
TIM_TimeBaseInit()库函数
用于定时器TIMx的参数初始化,函数原型:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
其中
(1)typedef struct(第二行)是被定义在定义在stm32f10x_tim.h头文件中。
(2)uint16_t TIM_Prescaler(第四行),TIMx预分频寄存器TIMx_PSC的值,数值上等于TIMx计数器TIMxCNT的预分频系数减1
(3)uint8_t TIM_RepetitionCounter(第八行),为TIM1和TIM8高级定时器所特有。
(4)TIM_CounterMode(第五行):TIMx计数器TIMxCNT的计数模式分为向上计数和向下计数以及三种中央对其模式计数。
- TIM_CounterMode_Up:向上计数模式,从0递增,计到TIMx_ARR计数器的自动装载值,并产生计数溢出脉冲
- TIM_CounterMode_Down:向下计数模式,从TIMx_ARR计数器的自动装载值递减,计到0,并产生计数溢出脉冲
- TIM_CounterMode_CenterAligned1:中央对齐模式1计数器交替地向上和向下计数,输出比较中断标志位,只在计数器向下计数时被设置;
- TIM_CounterMode_CenterAligned2:中央对齐模式2计数器交替地向上和向下计数,输出比较中断标志位,只在计数器向上计数时被设置;
- TIM_CounterMode_CenterAligned3:中央对齐模式3计数器交替地向上和向下计数,输出比较中断标志位,在计数器向下和向上计数时均可被设置。
(5) uint16_t TIM_Period(第六行); 下一个更新事件时装入自动重装载寄存器TIMx_ARR的周期值,数值上等于TIMx计数器TIMxCNT的计数周期减1。
- TIM_Period是一个16位无符号整型数据,取值范围为0~65535。
- 定时器定时时间主要取决于预分频因子TIM_Prescaler和定时周期TIM_Period
T=(TIM_Prescaler+1)*(TIM_Period+1)/TIMxCLK
- TIMxCLK为定时器TIMx的时钟频率,STM32F103的TIMxCLK默认设置为72MHz。
(6) uint16_t TIM_ClockDivision(第七行); 与TIMx_CR寄存器中bit[9:8]的CKD[1:0]对应,用于计数器工作模式时滤除高频干扰,有如下选择:
- “00”——采样频率基准fDTS=定时器输入频率fCK_INT;
- “01”——fDTS=fCK_INT/2;
- “10”——fDTS=fCK_INT/4。
2、定时器标准外设库配置
(1)步骤一:TIM2时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
(2)步骤二:初始化定时器参数,设置自动重装值,分频系数,计数方式
定时器初始化使用TIM_TimeBaseInit()函数,如下所示:
void TIM_TimeBaseInit(TIM_TypeDef*TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- 第一个参数TIMx是具体定时器,x可为2~4
- 第二个参数为TimeBaseInitStruct结构体指针。
下面以初始化通用定时器TIM2为例,进行以下配置:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIM初始化类型结构体变量
TIM_TimeBaseStructure.TIM_Period = (36000-1); //设置自动重载计数周期值
TIM_TimeBaseStructure.TIM_Prescaler = (4000-1); //设置分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置为向上计数方式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化定时器TIM2
TIM_Cmd(TIM2,ENABLE); //使能TIM2定时器
(3)步骤三:设置TIM2允许更新中断
为了避免在初始化定时器时进入中断,需要在初始化过程中清除中断标志。中断在使用前必须先使能再使用,如使能定时器TIM2的更新模式中断,则调用库函数TIM_ITConfig(),函数原型为:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
其中:
- 第一个参数TIMx是具体定时器,x可为2~4。
- 第二个参数用来指明所使能定时器的中断类型。
- 第三个参数为TIMx中断的状态,该参数可取值为ENABLE(使能)或者DISABLE(失能)。
(4)步骤四:TIM2中断优先级设置
如图为定时中断基本结构
结合先前学习的中断编程,参考博客:中断编程入门-CSDN博客
对TIM2中断做如下所示的配置:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
(5)步骤五:使能TIM2
使用TIM_Cmd()函数使能相应的定时器,其函数原型为:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) ;
其中:
- 第一个参数TIMx是具体定时器,x可为2~4
- 第二个参数为TIMx的状态,该参数可取值为ENABLE(使能)或者DISABLE(失能)
例如,使能定时器TIM2的更新中断,配置如下:
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
(6)步骤六:编写中断服务函数
STM32标准外设库函数中用来读取中断状态的库函数为:
ITStatus TIM_GetITStatus (TIM_TypeDef* TIMx, uint16_t) ;
清除定时器相应中断标志位的函数为:
void TIM_ClearITPendingBit (TIM_TypeDef* TIMx, uint16_t TIM_IT) ;
例如想要通过定时器设定某时间后GPIOA5引脚的灯反转,则中断服务函数如下所示:
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)((1- GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5))));
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
因此我们可以得到一个TIM2中断服务函数的模板以便以后使用时可以直接填入。
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
二、PWM
(一)PWM简介
PWM基本结构
1、介绍及其功能
PWM(Pulse Width Modulation,脉冲宽度调制)是一种利用脉冲宽度即占空比实现对模拟信号进行控制的技术,即是对模拟信号电平进行数字表示的方法。
广泛应用于电力电子技术中,比如PWM控制技术在逆变电路中的应用;PWM还应用于直流电机调速,如变频空调的交直流变频调速,除实现调速外,还具有节能等特性。
下图是一个周期为10ms(频率为100Hz)的PWM波形图
2、占空比
占空比(Duty Cycle),是指在一个周期内,高电平时间占整个信号周期的百分比,即高电平时间与周期的比值: 占空比=Tp/T。
(二)PWM的工作原理
1、产生PWM的定时器:
- 在STM32的定时器中除了TIM6和TIM7,其他定时器都可以用来产生PWM输出;
- 高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出;
- 通用定时器能同时产生多达4路的PWM输出
2、产生PWM的原因:
- STM32中每个定时器有4个输入通道:TIMx_CH1~TIMx_CH4;
- 每个通道对应1个捕获/比较寄存器TIMx_CRRx,将寄存器值和计数器值相比较,通过比较结果输出高低电平,从而得到PWM信号;
- 脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
3、PWM的工作原理解释
- 在PWM的一个周期内,定时器从0开始向上计数,在0-t1时间段,定时器计数器TIMx_CNT值小于TIMx_CCRx值,输出低电平;
- 在t1-t2时间段,定时器计数器TIMx_CNT值大于TIMx_CCRx值,输出高电平;
- 当定时器计数器的值TIMx_CNT达到ARR时,定时器溢出,重新从0开始向上计数,如此循环。
(三)PWM标准外设库输出配置过程
(1)步骤一:配置PWM输出通道,开启TIM2时钟
STM32规定了具体的引脚作为PWM输出引脚,选择不同的引脚时,还必须使用重定向功能(Remap)。
定时器TIM1的引脚复用功能映像:
定时器TIM2的引脚复用功能映像:
定时器TIM3的引脚复用功能映像:
- 使用定时器3的通道2(TIM2_CH1)作为PWM的输出引脚;
- 对PA0引脚进行配置,配置PA0为复用推挽输出模式(GPIO_Mode_AF_PP)代码如下所示:
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO重映射*/
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
(2)步骤二:初始化定时器TIM2
设置预分频系数PSC、自动重载计数值ARR、计数模式、时钟分割等,代码如下所示。下方实验中要求呼吸灯的周期为1~2s,且设置为向上计数法则如下配置。
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
(3)步骤三:设置TIM2_CH1的PWM模式,使能TIM2的CH1输出
如下配置TIM2_CH1的PWM模式使能TIM2的CH1能够正常输出,配置情况如下所示:
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
(4)步骤四:使能定时器TIM2
最后使能TIM2,定时器开始运行:
TIM_Cmd(TIM2, ENABLE);
三、实际应用
(一)关于定时器的题目
1、题目要求
使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。
2、代码
(1)Timer.c
#include "stm32f10x.h" // Device header
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
(2)LED.c
#include "stm32f10x.h" // Device header
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
(3)main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
#include "LED.h"
int main(void)
{
LED_Init();
Timer_Init();
while (1)
{
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)((1- GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5))));
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
3、实物效果
定时器控制LED
(二)关于PWM的题目
1、题目要求
采用定时器PWM模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整占空比变化到一个满意效果;使用Keil虚拟示波器,观察 PWM输出波形。
2、代码
(1)PWM.c
#include "stm32f10x.h" // Device header
/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO重映射*/
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}
(2)main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "PWM.h"
uint8_t i; //定义for循环的变量
int main(void)
{
/*模块初始化*/
PWM_Init(); //PWM初始化
while (1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(15); //延时500ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(15); //延时500ms
}
}
}
3、Keil波形图
4、实物效果
呼吸灯
四、总结
学习完定时器相关内容,就像掌握了一个闹钟。通过自己设定时间,当时间走完则自动运行预先选择做出的行动,为以后的单片机开发提供了一个新的思路。在学习了PWM之后,发现原来点灯也变得有趣起来了,可以通过赋予脉冲使得灯逐亮逐灭,为后续学习舵机等外设打好了基础。