1 目的说明
实现定时器最简单的溢出中断,结合我手头的开发板,使得位于P10的LED灯,以2HZ的速度不断闪烁。这样的实验还是非常亲切的,让我想起了第一次在51上实现了这样的代码,自己第一次在CC2430上实现,依然非常激动。
2 使用方法概述
需要使用定时器的中断,需要知道如何操作才可以产生这个中断请求。数据手册中提到需要两个条件,第一IEN1.T1EN需要置位,第二TIMIF.OVFIM需要置位。代码中使用modulo、模式,使用该模式可以改变定时器溢出的频率。
3 代码总览
先来看看所有的代码,然后再分步解释。
#include"hal.h"
voidTimer1_Init();
voidmain(){
SET_MAIN_CLOCK_SOURCE(CRYSTAL);
IO_DIR_PORT_PIN(1,0,IO_OUT);
Timer1_Init();
while(1){
}
}
voidTimer1_Init(){
TIMER1_INIT();
T1CTL=0x0c;
T1CC0L=0x24;
T1CC0H=0xF4;
TIMER1_ENABLE_OVERFLOW_INT(TRUE);
INT_ENABLE(INUM_T1,INT_ON);
INT_GLOBAL_ENABLE(INT_ON);
TIMER1_RUN(TRUE);
}
#pragmavector=T1_VECTOR
__interruptvoidTimer1_ISR(void)
{
if(T1CTL&0x10){
P1_0=!P1_0;
T1CTL&=~0x10;
}
}
4 主函数说明
SET_MAIN_CLOCK_SOURCE(CRYSTAL);
IO_DIR_PORT_PIN(1,0,IO_OUT);
Timer1_Init();
操作CC2430之前,先指定系统时钟,这是一个好习惯。由于定时器时钟和系统时钟频率有关,所以必须要设定好系统的时钟。在SET_MAIN_CLOCK_SOURCE()在这个动作宏中,把系统时钟设定为32MHz。(该宏前面的文章已经提到,不多做说明)
请注意定时器的时钟频率 默认为16MHz,而不是32MHz。
请注意CLKCON的5:3位, 该3位组成了一个定时器时钟的分频器,该参数决定了定时器的时钟频率。在定时器1的相关操作中还有定时器时钟的分频系数设置,那是定时器1特有的,这里的定时器分频参数是分频了定时器1,3,4的时钟。相见数据手册或下图:
为了操作IO口,定义LED相关的IO口为输出。IO_DIR_PORT_PIN()的相关操作如下面的代码所示:
#defineIO_DIR_PORT_PIN(port,pin,dir)\
do{\
if(dir==IO_OUT)\
P##port##DIR|=(0x01<<(pin));\
else\
P##port##DIR&=~(0x01<<(pin));\
}while(0)
该宏操作了PXDIR寄存器,定义了IO口的方向。
5 定时器初始化操作
TIMER1_INIT()把定时器1的寄存器全部复位。具体的代码如下:
#defineTIMER1_INIT()\
do{\
T1CTL=0x00;\
T1CCTL0=0x00;\
T1CCTL1=0x00;\
T1CCTL2=0x00;\
TIMIF&=~0x40;\
}while(0)
从这个代码中也可以看出定时器1的操作和哪些寄存器有关。具体的定义可以查看数据手册,这里不多做说明。
6 设定定时器中断频率
操作代码如下
T1CTL=0x0c;
T1CC0L=0x24;
T1CC0H=0xF4;
由于CC2430的运行速度比较快,所以需要对定时器1进行分频。由于T1CTL在前面的函数中已经被全部复位,所以可以舒服的操作T1CTL寄存器。在这里把系统时钟设定为128分频,定时器T1的运行速度只有125K。这个速度对于0.5闪烁来说,还是非常快的。
接着设定T1CC0寄存器。这个寄存器还是非常特殊的。请注意,T1CC0在modulo模式和up-down模式中,始终作为定时器T1计数的最大值。数据手册上说定时器1有3个比较匹配中断,其实这个和AVR的定时器1有的两个比较匹配时一样的,因为CC2430没有一个专用寄存器储存计数的最大值,那么定时器1的比较通道0就“牺牲”了比较通道的作用。所以要产生两路频率指定的PWM波的时候,T1CC0作为最大值决定PWM的频率,而T1CC1和T1CC2决定PWM的相位。
下面再讲讲计数值的计算方法。我是从分频的角度思考的,写出这个等式:
Ftimer/(N*T1CC0) = Fdesi。
其中Ftimer为定时器的运行时钟,此处为16,000,000Hz;N为分频系数,此处为128;T1CC0为定时器的计数值;Fdesi为期望溢出频率,此处为2Hz。带入这个等式可以计算出T1CC0的值为62500,写成16进制为F424。如果计算出来的结果大于65536,那么只能进一步降低定时器的运行频率,在这里只能调整Ftimer了。
7 使能该使能的内容
TIMER1_ENABLE_OVERFLOW_INT(TRUE);
INT_ENABLE(INUM_T1,INT_ON);
INT_GLOBAL_ENABLE(INT_ON);
开篇的时候就说了需要操作哪两个寄存器——第一IEN1.T1EN,第二TIMIF.OVFIM。操作时分别使用了以下两个宏。具体的代码如下:
#defineTIMER1_ENABLE_OVERFLOW_INT(val)\
(TIMIF=(val)?TIMIF|0x40:TIMIF&~0x40)
#defineINT_ENABLE(inum,on)\
do{\
if(inum==INUM_RFERR){RFERRIE=on;}\
elseif(inum==INUM_ADC){ADCIE=on;}\
elseif(inum==INUM_URX0){URX0IE=on;}\
elseif(inum==INUM_URX1){URX1IE=on;}\
elseif(inum==INUM_ENC){ENCIE=on;}\
elseif(inum==INUM_ST){STIE=on;}\
elseif(inum==INUM_P2INT){(on)?(IEN2|=0x02):(IEN2&=~0x02);}\
elseif(inum==INUM_UTX0){(on)?(IEN2|=0x04):(IEN2&=~0x04);}\
elseif(inum==INUM_DMA){DMAIE=on;}\
elseif(inum==INUM_T1){T1IE=on;}\
elseif(inum==INUM_T2){T2IE=on;}\
elseif(inum==INUM_T3){T3IE=on;}\
elseif(inum==INUM_T4){T4IE=on;}\
elseif(inum==INUM_P0INT){P0IE=on;}\
elseif(inum==INUM_UTX1){(on)?(IEN2|=0x08):(IEN2&=~0x08);}\
elseif(inum==INUM_P1INT){(on)?(IEN2|=0x10):(IEN2&=~0x10);}\
elseif(inum==INUM_RF){(on)?(IEN2|=0x01):(IEN2&=~0x01);}\
elseif(inum==INUM_WDT){(on)?(IEN2|=0x20):(IEN2&=~0x20);}\
}
while(0)
最后还要操作一个“总”中断,这个是51中断的老大——全局中断EA。有点基础的一定知道这个东西,代码如下:
#defineINT_GLOBAL_ENABLE(on)EA=(!!on)
(不知道为什么来个双重否定????)
8 启动定时器T1
准备好所有的初始化代码之后,才开始启动定时器。在启动定时器就是选择工作方式(也搞不明白为什么CC2430不来一个定时器启动相关的寄存器),把工作方式定义为modulo模式。具体的代码如下:
#defineTIMER1_RUN(value)(T1CTL=(value)?T1CTL|0x02:T1CTL&~0x03)
由于默认的初始值为00(从第0位开始),把第1位置位就相当于选择了modulo模式,清零保留默认模式。
9 定时器中断
#pragmavector=T1_VECTOR
__interruptvoidTimer1_ISR(void)
{
if(T1CTL&0x10){
P1_0=!P1_0;
T1CTL&=~0x10;
}
}
所有的中断都有固定的写法,这个大家必须牢牢记住。先使用伪命令定义中断入口地址:#pragma vector=T1_VECTOR,然后定义函数名称__interrupt void Timer1_ISR(void)。通过检测OVFIM(T1CTL 第4位)来判断是否发生了溢出中断,最后通过软件清除中断标志位。(但是我发现不清除中断标志位同样可以再次进入中断,最后查看数据手册,如何清除这个中断标志位还和同时处理多个中断的关系有关,在这里也不多做解释)。
总结
在这里使用了定时器T1的modulo模式,进行了定时器溢出的相关操作,在这里需要掌握的是定时器溢出频率的计算和中断服务函数的书写。后面还会讲定时器比较匹配的相关内容,如果有兴趣的话,请关注。