MSP-EXP430G2ET定时器学习笔记
写在前面:我只能说,这块开发板真的不错,虽然比不过32,但是还是有它独特的优点的,虽然有关资料少了些,但是真要是认真学,还真不是太难。为了能更好的理解定时器,我做一张有关定时器的思维导图,希望能帮助大家更好的认识定时器。
一,定时器的介绍
在所有单片机中,定时器都具有极其重要的作用。因为单片机 CPU 是单线程的,所有任务必须按照顺序执行(除非有中断发生)。像是之前闪烁 LED 程序中的延时就是用 CPU 完成的,这相当于让 CPU 什么也不干,只是掰着手指头数秒,这无疑是对 CPU 的极大浪费。定时器的功能就像是一个可以定时的闹钟,可以帮 CPU 完成延时等重复的劳动,把 CPU 解放出来去完成更重要的工作,如运算、控制等。可以说有了定时器, CPU 才真正成为了一个完整的自由人。MSP430G2 系列单片机的定时器叫做 Timer_A 模块。具体到 MSP430G2553 这个型号,内部有 2 个 Timer_A 定时器,分别叫做 Timer0_A3 和 Timer1_A3,或简称 TA0 和 TA1。 在MSP430 系列定时器的命名规则中, Timer 后面的数字表示定时器的编号;而下划线后面的 A 或B 表示定时器的类型; A 后面的数字 3 表示定时器中包含 3 个比较/捕获单元。
Timer_A 定时器是带有中断功能的,当定时器计数值到达设定的值时,将自动产生一个定时器中断信号。我们可以利用这个功能方便地制造一个周期性的中断,这样就可以每隔一段事件执行一个指令。利用这个中断也可以进行延时,并且这样的延时是不需要消耗 CPU 资源的。Timer_A 定时器除了完成计数以外,还“附赠”了一个重要的功能模块,叫做比较/捕获单元。顾名思义,这个单元有捕获和比较这两个功能。捕获功能的作用是可以自动捕捉一个外部信号的电平变化,并记录下该变化发生的时刻。例如一个周期性的方波,可以通过捕获单元分别记录两次上升沿(电平由低变高)或是下降沿(电平由高变低)的时刻,并将二者相减来计算出波形的周期。而比较功能最大的作用是可以自动产生脉宽调制波形(PWM) , 目前各种数字控制信号中很多都是通过 PWM 波来实现的,利用比较功能可以在不消耗 CPU 资源的情况下产生多路 PWM波形。 但需要注意的是,在同一时刻下,一个比较/捕获单元中只能在比较和捕获功能中选择一个进行使用,两个功能是无法同时使用的。
二,定时器的功能
1.计数功能
(1)计数原理介绍
其实定时就是跟时间有关,时间也是又数字一个一个累积出来的,而Timer_A 的核心是一个 16 位的计数器,其最大计数值是 2 的 16 次方,即从 0 到 65535 之间计数。 在每个定时器的时钟周期, 计数器的值会自动加 1(或减 1,取决于计数模式) , 我们可以通过配置寄存器来选择定时器的时钟周期。 计数器的计数值存在一个名为 TAR 的寄存器当中。 默认状态下, 当计数器的值达到 65535之后,计数器会自动回到 0 并重新开始计数——这叫做计数器溢出。当计数器溢出时,定时器可以产生一个溢出中断, Timer_A 的溢出中断标志位 TAIFG 会被置为 1。 这时如果我们开启了定时器中断使能位 TAIE 以及全局中断使能位,则 MSP430 会自动进入到定时器溢出中断服务函数中。从计数器的工作过程可以看出,只要定时器的时钟源保持精准,那么我们就可以通过计数器来完成精确的时间控制。计数器的值乘以时钟周期就等于真正的时间间隔。 1s=10^-3ms=10^-6um=10^-9nm
(2)计数器的模式
计数器共有 4 种工作模式,通过 TACTL 寄存器中的 MCx 位可以配置,其中 MCx=00 为停止,另外 3 种模式分别是:连续计数模式(continuous mode)、向上计数模式(up mode)、向上/向下计数模式(up/down mode)。
1)停止模式
设置MCx=00,用于定时器的暂停,并不发生复位,所有寄存器现行类容在停止模式结束后可用。当定时器暂停后重新计数时,计数器将从暂停前的计数方向计数。
2)连续计数模式(continuous mode)
设置 MCx=10,计数器将工作在连续计数模式下。在此模式下, TAR 寄存器将从 0 到 65535连续增加,到 65535 后则清零重新计数。在连续计数模式下,定时器的周期仅由时钟源的频率决定,频率越高,则越快计数到 65535,定时器周期越短。
TACTL |= MC_2; //选择模式——连续计数
3) 向上计数模式(up mode)
设置 MCx=01,计数器将工作在向上计数模式。与连续计数模式不同的是, 向上计数模式时计数器的溢出值是由 TACCR0 寄存器设定的,计数器达到设定值后将自动清零。例如将TACCR0 设为 40959,则 TAR 的值只能在 0 到 40959 之间计数。在向上计数模式下,定时器周期不仅与时钟源有关,还与 TACCR0 的设定值有关。
TACTL |= MC_2; //选择模式——向上计数 TACCR0 = 10000;
4) 向上/向下计数模式(up/down mode)
设置 MCx=11,计数器将工作在向上/向下计数模式。与向上计数不同的是,当计数器到达TACCR0 的设定值之后,计数器不清零,而是从递增变成递减,直到计数器的值回到 0。在向上/向下计数模式下,定时器的周期是向上计数模式的两倍。
TACTL |= MC_3; //选择模式——向上/下计数 TACCR0 = 10000;
(2)定时器中断功能
从上述 3 中工作模式的描述中可以看到,计数器工作时有 2 个关键节点,分别是计数达到CCR0 和 65535。与之对应的,定时器可以产生 2 个中断,分别是 CCR0 中断和溢出中断,它们对应的中断标志位分别是 CC0IFG 和 TA0IFG。
1) CCR0 中断
当计数器的值达到 CCR0 时,定时器会产生 CCR0 中断。 CCR0 中断是定时器所有中断中优先级最高的,并且单独拥有一个中断向量。而 CCR1 和 CCR2 与定时器溢出中断共用一个中断向量。 CCR0 中断的使能位 CCIE 和标志位 CCIFG 都在 TACCTL0 寄存器中,其中断向量名是TIMER0_A0_VECTOR。要使用 CCR0 中断,首先要使能 CCIE 使能位,然后最关键的一步就是设置 CCR0 寄存器的值。 无论是在向上计数模式还是向上/向下计数模式, 当计数器的值到达 CCR0 时 CCIFG 都会被置位。 当进入 CCR0 的中断服务函数以后, CCIFG 标志位会自动复位,不需要手动设置。下面是一个 CCR0 中断的示例程序:
int main(void) { ... TACCTL0 = CCIE; // CCR0 interrupt enabled TACCR0 = 1000; // Set CCR0 value ... } // Timer A0 CCR0 interrupt service routine #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A(void) { ... }
2) 定时器溢出中断
除了 CCR0 中断之外, 计数器还可以产生一个溢出中断。 需要注意的是溢出中断发生的时刻并不是 TAR 的计数值等于溢出值的时刻,而是 TAR 的值返回到 0 的时刻。例如在向上计数模式下, CCR0 中断信号产生的时刻是 TAR 等于 CCR0 的时刻,而溢出中断产生的时刻是 TAR 溢出后返回 0 的时刻,二者相差了一个时钟周期。溢出中断的使能位 TAIE 和标志位 TAIFG 都在 TACTL 寄存器中。溢出中断与CCR1、 CCR2中断共同分享一个中断向量,向量名是 TIMER0_A1_VECTOR。 那么如果同时开启了溢出中断和CCR1/CCR2 中断, 如何区分究竟是哪一个中断触发了中断向量呢? 在定时器中有一个寄存器TAIV, 它的第 1-3 位包含了中断源的信息。 在中断服务函数中读取 TAIV 寄存器的值可以判断究竟是哪一个中断信号触发了中断。对 TAIV 的读/写操作都会使当前优先级最高的一个中断标志位自动复位。如果此时还有其他中断在等待,那么该中断会在当前中断结束后执行。例如当前 TACCR1 和 TACCR2 标志位都被置位,那么读写 TAIV 之后, TACCR1 的标志位会被自动复位,但 TACCR2 不会。当前中断服务函数执行完之后会直接进入 TACCR2 的中断。
下面是一个定时器溢出中断的示例程序。与 CCR0 中断相比,主要是多了读取 TAIV 来判断中断源的语句。
int main(void) { ... TACTL = TASSEL_2 + MC_2 + TAIE; // smclk、contmode、中断 ... } // 计时器中断向量(TA0IV)处理程序 #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer_A(void) { switch( TA0IV ) { case 2: break; // CCR1未使用 case 4: break; // CCR2未使用 case 10: P1OUT |= 0x01; break;// 溢出 } }
三,计数器使用的程序流程
1) 选择定时器的时钟来源(TASSELx)及分频值(IDx),常用的时钟来源有 ACLK 和SMCLK,此外也可以用外部时钟输入到 TACLK 引脚作为时钟源。
//内部时钟 TACTL |= TASSEL_1;//定时器时钟源选择-辅助时钟ACLK TACTL |= ID_0;//定时器A输入分配器 0
//外部时钟 TACLK |= TASSEL_2;//定时器时钟源选择-子系统时钟SMCLK TACLK |= ID_0;//定时器A输入分配器 0
2) 确定定时器工作模式(MCx),即连续计数、向上计数、向上/向下计数,见上图。
TACTL |= MC_1; //选择模式——向上计数
3) 根据定时器工作模式确定使用哪个中断,并进行初始化。如果是 CCR0 中断需要打开使能位并设置 CCR0 的值;如果是溢出中断只要打开使能位即可。
TACCTL0 |= CCIE;//CCR0中断 TACCTL1 |= CCIE;//CCR1 TACCTL2 |= CCIE;//CCR2
4) 编写对应的中断服务函数。
#pragma vector = TIMER0_A0_VECTOR __interrupt void Time0(void) { P1OUT ^= 0x41; }
根据上面步骤就可以写一个简单的定时器中断程序了
实验:LED灯一秒的闪烁变换。
#include "MSP430G2553.h" int main( void ) { // Stop watchdog timer to prevent time out reset WDTCTL = WDTPW + WDTHOLD; //时钟设置 BCSCTL1=CALBC1_1MHZ;//将DCO设置为1MHZ DCOCTL=CALDCO_1MHZ; BCSCTL3 = LFXT1S_2;//ACLK设置为VLO // BCSCTL2 |= DIVA_2;//设置为2分频 //方向寄存器指向 P1DIR |= 0x41; P2DIR |= 0x22; P2DIR |= 0x08; //TACLK设置 TACLK |= TASSEL_1; TACTL |= TASSEL_1;//定时器时钟源选择-辅助时钟 TACTL |= ID_0;//定时器A输入分配器 0 TACTL |= MC_1; //选择模式——向上计数 TACTL |= TACLR; //计数器清除 TACTL |= TAIE;//计数器A计数器中断启用 // TACCTL0 |= CCIE;//CCR0中断 TACCTL1 |= CCIE;//CCR1 TACCTL2 |= CCIE;//CCR2 TACCR0 = 10000; TACCR1 = 10000; TACCR2 = 10000; _EINT();//使能总中断 LPM3;//进入低功耗模式3 } #pragma vector = TIMER0_A0_VECTOR __interrupt void Time0(void) { P1OUT ^= 0x41; } #pragma vector = TIMER0_A1_VECTOR __interrupt void Time1(void) { switch(TAIV) { case 2 : P2OUT ^= 0x02; break; case 4 : P2OUT ^= 0x08; break; case 10 : P2OUT ^= 0x20; break; } }
下一个帖子更新Timer_A 定时器的比较/捕获模块的使用和pwm调整灯光亮度。