1定时器基础
(1) 概念
概念还是需要读参考手册获取。
可编程通用定时器TIMx[x=2,3,4,5]的主要部分是一个16位计数器(CNT)和与其相关的自动装载(APP)寄存器。这个计数器可以向上计数、向下计数或者向上向下双向计数。计数器由预分频器的时钟输出CK_CNT驱动,计数器的时钟CK_CNT由 预分频器TIMx_PSC分频得到。
计数器从计数开始到计数溢出的时间被称为定时器的时基。以下3个单元决定时基:
- 计数器寄存器 (TIMx_CNT)。
- 预分频器寄存器 (TIMx_PSC)。[ 发生更新事件更新TIMx_PSC的值 ]
- 自动装载寄存器 (TIMx_ARR)。[ 发生更新事件更新TIMx_ARR的值 ]
更新(UEV)事件是指在允许更新TIMx_UDIS=0的情况下,某个事件(如计数器溢出)发生时影子寄存器(ARR、PSC、CCRx)的值会发生改变,用于产生下一个新的时基。
(2) 定时器中断涉及的寄存器
- TIMx_CR1: 提供“APP自动重载”、“计数方式”、“中断更新源”、“更新事件”、“计数器使能”等配置。
- TIMx_DIER: 中断允许设置(UIE).
- TIMx_PSC: 时钟分频配置,更新事件发生时其值为程序赋予的新值。
- TIMx_ARR: 在CR1.APRE=1时更新事件发生时其值为程序赋予的新值。
- TIMx_CNT: 可不用对其设置,则按照计数方式单周期内计数TIMx_ARR次。如在向上计数模式中,CNT从0计数到TIMx_ARR。
- TIMx_SR: 在中断函数内软件清0。
- NVIC->ISERx: 开启定时器模块的中断。
- RCC-> APBxENR: 开启定时器时钟。
STM32F10XXX外设时钟在默认下都是关闭的,而要使某模块工作必须要有时钟,所以必须开启定时器的时钟。另外,NVIC所管理的每个模块的中断也是关闭的,在使用对应模块的中断时也需开启中断。
(3) 定时器更新事件与中断请求
定时器经初始化后,计数器根据时钟信号(CK_CNT)开始按照设定的模式计数,计数器计数溢出/下溢时可能发生两件事情。
- 在允许更新事件TIMx_CR1.UDIS=0的状态下可由“计数器溢出/下溢”产生更新事件,此时TIMx_PSC的值被更新,在TIMx_CR1.ARPE=1时TIMx_ARR的值被更新。
- 在允许更新中断(TIMx_DIER.UIE = 1) 的状态下,TIMx_CR1.URS=1产生中断请求,如果在NVIC中打开了TIMx定时器模块中断,中断就会发生。
定时器的中断和更新事件都还可以通过其它的方式(“设置TIMx_EGR.UG位”、“从模式控制”)实现。
(4) NVIC中断发生的条件
- 中断开启。
- 中断请求。
- 中断执行。
- 一般还需要清除中断标记位。
模块如定时器(TIMx)发生中断申请,并且在NVIC中TIMx中断被开启就可以发生中断,从而调用对应的中断函数,在中断函数内一般需要将中断标记清0.
(5) 定时器中断实现步骤
根据定时器工作的基本原理得到定时器中断实现的基本步骤如下:
- 开启定时器模块时钟 (RCC->APB1ENR)。
- 开启定时器中断(NVIC->ISERx, NVIC->ISPRx, NVIC->IPRx)。
- 初始化定时器参数(TIMx_PSC, TIMx_CR1, TIMx_APP)。
- 允许定时器中断更新(TIMx_DIER)。
- (中断优先级设置)。
- 编写定时器中断服务函数。
2 定时器中断编程流程
(1) 初始化
[1] NVIC初始化
默认情况下,外设的中断都是关闭的。在使用中断前需要配置NVIC使能对应的中断。关于NVIC寄存器的讲解需要看《PM5006 Cortex-M3技术参考手册》的” Core Peripherals ”部分。NVIC相关寄存器的“位” (《PM5006 Cortex-M3技术参考手册》 Table4.1)与中断向量表的“位置号” (《RM008 stm32f10xx参考手册》表54~55,Page 132)相对应,这些中断和中断号(向量表)被定义在MDK的<stm32f10x.h>中。如NVIC->ISER[0]|= 1 << 28表示使能TIM2中断。
对于TIM2的NVIC配置如下:
- TIM2中断使能:NVIC->ISER[0]|= 1 << 28。
- 默认TIM2中断优先级。
[2] 定时器初始化
时钟使能(RCC->APBxENR)
TIMx[x=2, 3, 4,5]定时器都挂在APB1总线上,语句RCC->APB1ENR |= 0x01 << (x-2)对应TIMx时钟使能。
设置预分频和自动重载值(TIMx_PSC, TIMx_APP)
分频得CK_CNT
APB1 总线上的时钟信号在进入定时器之后第一件事情是被预分频器TIMx_PSC分频输出CK_CNT来驱动计数器计数。程序中随时都可以更改TIMx_PSC的值,但是TIMx_PSC是有缓冲的,只有发生了更新事件时新的预分频值才会生效。
计数器的时钟频率CK_CNT等于fCK_PSC/(PSC[15:0]+1)。 fCK_PSC为进入到定时器的时钟频率。如时钟频率fCK_PSC为72MHz时,当给TIMx->PSC = (7200 - 1)时,CK_CNT = 72000KHz / 7200 = 10KHz,这就表示计数器计数一次花的时间值为0.1ms。
重载值
自动装载寄存器TIMx_ARR是预先装载的。根据在TIMX_CR1 寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被永久地或在每次的更新事件UEV 时传送到影子寄存器。当计数器达到溢出条件(向下计数时的下溢条件)并当TIMX_CR1 寄存器中的UDIS 位等于0 时,产生更新事件。
若现在要产生一个5Hz(0.2s)的定时周期,则需要语句TIMx->APP= 2000 – 1 (TIMx_CR1的CMS[1:0] = 00)。
综上:预分频寄存器TIMx->PSC配置得到计数一次的时间周期,再联合自动装载寄存器TIMx->APP配置得到定时周期。
控制寄存器配置(TIMx_CR1)
- Bit 9:8, CKD[1:0]配置数字滤波器采样频率基于定时器时钟CK_CNT的分频倍数。
- Bit 7, 设置TIMx_ARR自动载入,TIMx_ARPE = 1.
- Bit 6:5, 选择边沿模式CMS[1:0] = 00.
- Bit 4, 计数器从0开始向上计数DIR =0.
- Bit 3, 在发生更新事件时计数器不停止OPM = 0.
- Bit 2, 在更新中断允许的情况下,计数溢出产生中断URS = 1.
- Bit 1, 允许更新UDIS = 0.
- Bit 0, 允许计数器计数CEN = 1.
综上配置CR1寄存器的语句为:TIMx->CR1 =0x0085.
使能更新中断
DMA/中断使能寄存器(TIMx_DIER) 有一位为UIE,只有这一位为1才能产生更新中断。对应的语句为:TIMx_DIER |=0x0001;。
(2) 中断服务函数
[1] 中断服务函数名
中断服务函数的名字是在 MDK 中事先有定义的。在以Cortex-M3为内核的STM32F10XXX芯片编程中,这些中断函数都被MDK定义在“startup_stm32f10x_hd.s”中。其中通用定时器的中断服务函数名为TIMx_IRQHandler。
[2] 中断服务函数格式
void BlockName_IRQHandler( void ) { //contents TIMx->SR= 0xfffe; } |
无参数,无返回值。
BlockName
为每个中断的名字,具体可以到“
startup_stm32f10x_hd.s
”查找。
3 TIM2定时中断流水灯闪烁代码
以通用定时器TIM2为例,实现一个定时中断,代替使流水灯闪烁的延迟函数。
(0) NVIC初始化
/*
* @function: 通过配置NVIC->ISER[x]寄存器开启IRQChannel中断
* @arg: IRQChannel为中断号,x是ISER寄存器下标*/
void
NVIC_Init(unsigned char IRQChannel, unsigned char x)
{
NVIC->ISER[x] |= 1 << IRQChannel;
}
(1) 定时器中断初始化
/*
* @function 定时器TIMx初始化
* @arg: TIMx可以是TIM2,…,TIM4,x为APB1ENR寄存器使能对应TIMx的位值*/
void
TIMx_Init(TIM_TypeDef *TIMx, unsigned char x)
{
switch(x){
case 0:
//开启TIM2时钟
RCC->APB1ENR |= 0x01;
//配置TIM2参数
TIM2->PSC = 7200 - 1; //CK_CNT=72Mhz / 7200 = 10KHz,0.1ms
TIM2->ARR = 2000 - 1; //时基=0.1ms * 2000 = 0.2s
TIM2->CR1 = 0x0085; //APP重载,计数溢出时产生中断,计数器使能
TIMx->DIER = 0x0001; //允许中断更新
break;
case 1:
RCC->APB1ENR |= 0x01 << 1;
break;
case 2:
break;
default:
break;
}
}
(2) TIM2中断服务函数内容
//TIM2中断服务函数
void TIM2_IRQHandler(void)
{
//在TIM2->APRE = 1时下列语句有效
//TIM2->ARR = 10000 - 1;
//软件清TIM2中断更新标记
TIM2->SR = 0;
i++;
}
当TIM2->APRE = 1时,可以每次进入中断函数后更改APP的值以产生新的时基。
(3) 主函数
int main(void)
{
NVIC_Init(0x1c, 0);
TIMx_Init(TIM2, 0);
LedInit();
while(1) {
if(5 == i){
LedLight();
}else if(10 == i){
i = 0;
LedDieOut();
}
}
}
下载程序到开发板子中,重新给开发板上电后。Led以1s的周期闪烁。如果去掉中断服务函数中的TIM2->ARR =10000 – 1语句的注释,则定时器的时基为1s,led以5s的周期闪烁。令人开心的是,这次的程序没有经过调试就运行成功了。MEIZU_X2在拍照模式下按住开机键和音量减还可以截图。
电脑已老配定时器中断led闪烁
4 附加
决定定时器是否要循环计数的控制器为TIMx->CR1.OPM位,此位为1时,在发生下次更新事件时,计数器停止计数。可以在中断函数中用TIMx->CNT = 0(或者=m);TIMx->CR1 = 0x0001来重新使能定时器,让定时器按照设定方式重新计数。
Practical Note Over.