前言
上篇文章简单地应用SysTick定时器来点灯,这次通过TIMER定时器来点灯。
一、定时器原理
stm32的常规定时器可分为三类:基本定时器、通用定时器和高级定时器。
1.基本定时器(TIM1、TIM8):连接在APB1,主要功能是生成定时中断。
2.通用定时器(TIM2、TIM3、TIM4、TIM5):连接在APB1,除了定时中断外,还有输入捕获的重要功能。
3.高级定时器(TIM6、TIM7):连接在APB2,具备通用定时器的全部功能,并额外具有死区插入、刹车功能、自动输出等功能。
下图是Stm32的时钟树
从图中可以看出,定时器的时钟不是直接来源于 APB2 或 APB1,而是来自于输入为 APB2 或 APB1 器的一个倍频器。
当 APB预分频系数为1 时, 定时器的时钟频率等于APB1的时钟频率 ,这个倍频器不起作用
当 APB预分频系数不为1 时,这个倍频器起作用 。假定 AHB = 36 MHZ,由于APB1 最大就是36MHZ,所以 APB1 预分频系数可以为任意合理值。
通用定时器挂载在低速外设总线APB1上,其时钟来源于输入为 APB1的一个倍频器,只要APB1的时钟分频数不为1,TIMx的时钟频率就会为APB1时钟频率的2倍,即72MHz 。
基本定时原理:
时钟信号CK_PSC经过预分频器PSC分频后,计数器对分频后的时钟信号CK_CNT进行计数,每一个上升沿触发一次计数,技术到目标值后触发更新中断UI,并自动重载计数值。同时产生的更新时间U可以触发内部其他电路的工作。
注意PSC预分频器和自动重装载寄存器都是有两个的,一个是本体,另一个是它的影子寄存器(或缓冲寄存器)。
对于PSC预分频器:
它本体是用来读写的,而影子寄存器才是用来修改分频系数的。这是因为更改分频系数会导致前后的时钟频率不一致,如果在计数的中途立刻修改计数的频率,那么这一个计数周期就会混乱。所以用影子寄存器来控制分频系数,当你修改分频系数时,只是修改了供读写的预分频器,而真正起作用的影子寄存器只有在计完一个计数周期后才会修改时钟频率。
自动重装载寄存器:
同理,为了防止计数到一半突然修改重载值导致计数混乱,也是由它的影子寄存器来实现重载值的修改。
二、工程部分
采用通用定时器来实现定时功能,这里选用TIM2。
定时器频率 =CK_PSC/(PSC+1)/(ARR+1)
CK_PSC是分频前时钟频率,PSC分频系数,ARR自动重载计数周期值。
如果我们想定时1ms的话,可以让分频系数为71,计数周期为1000,这样定时器的频率就是72MHZ/(71+1)/(1000)=1000。然后计数1次,周期就为(1/1000)*1=1ms。当然,也可以选择其他的组合。
GeneralTIM.h,主要功能是初始化TIM2定时器,并设定计数周期为1ms。
#include "GeneralTIM.h"
/**
* 函数功能: 通用定时器 TIMx,x[2,3,4,5]中断优先级配置
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void GENERAL_TIMx_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 设置中断组为0 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
/* 设置中断来源 */
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQ;
/* 设置主优先级为 0 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
/* 设置抢占优先级为3 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
/*定时器使能 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/*
* TIM_Period / Auto Reload Register(ARR) = 1000 TIM_Prescaler--71
* 中断周期为 = 1/(72MHZ /72) * 1000 = 1ms
*
* TIMxCLK/CK_PSC --> TIMxCNT --> TIM_Period(ARR) --> 中断 且TIMxCNT重置为0重新计数
*/
/*
* 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
* TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
* 另外三个成员是通用定时器和高级定时器才有.
*-----------------------------------------------------------------------------
*typedef struct
*{ TIM_Prescaler 都有
* TIM_CounterMode TIMx,x[6,7]没有,其他都有
* TIM_Period 都有
* TIM_ClockDivision TIMx,x[6,7]没有,其他都有
* TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
*}TIM_TimeBaseInitTypeDef;
*-----------------------------------------------------------------------------
*/
void GENERAL_TIMx_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 开启TIMx_CLK,x[2,3,4,5],即内部时钟CK_INT=72M */
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK, ENABLE);
/* 通用定时器 TIMx,x[2,3,4,5]中断优先级配置 */
GENERAL_TIMx_NVIC_Configuration();
/* 自动重装载寄存器周的值(计数值) */
TIM_TimeBaseStructure.TIM_Period=1000;
/* 累计 TIM_Period个频率后产生一个更新或者中断
时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M */
TIM_TimeBaseStructure.TIM_Prescaler= 71;
/* 时钟分频因子 ,没有用到,不用管 */
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
/* 计数器计数模式,基本定时器TIM6和TIM7只能向上计数,没有计数模式的设置,不用管 */
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
/* 重复计数器的值,通用定时器没有,不用管 */
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
/* 初始化定时器TIMx, x[2,3,4,5] */
TIM_TimeBaseInit(GENERAL_TIMx, &TIM_TimeBaseStructure);
/* 清除计数器中断标志位 */
TIM_ClearITPendingBit(GENERAL_TIMx, TIM_IT_Update);
/* 开启计数器中断 */
TIM_ITConfig(GENERAL_TIMx,TIM_IT_Update,ENABLE);
/* 使能计数器: */
TIM_Cmd(GENERAL_TIMx, ENABLE);
}
主要的宏定义:
#define GENERAL_TIMx TIM2
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM2
#define GENERAL_TIM_IRQ TIM2_IRQn
然后是TIM2的中断函数TIM2_IRQHandler(在"stm32f10x_it.c"里面)
void TIM2_IRQHandler (void)
{
if ( TIM_GetITStatus(GENERAL_TIMx,TIM_IT_Update) != RESET )
{
timer_count++;
TIM_ClearITPendingBit(GENERAL_TIMx , TIM_IT_Update);
}
}
最后主函数设置计数到500(对应500ms)翻转一次LED灯的状态即可。