|
此内容由EEWORLD论坛网友骑IC看MCU原创,如需转载或用于商业用途需征得作者同意并注明出处
定时器,作为MCU最重要也是最基本的功能集成在每一个MCU中。随着MCU功能的日渐强大,定时器的功能也越来越强大,因此配置和使用起来也就比较麻烦,下面我们针对MSP430的Timer模块进行详细讲解,配合多种可以直接使用的例程,方便用户直接移植和深入理解。
首先,普及一下定时器知识:本质上就是一个计数器,可以由用户自定义计数的值,同时到达计数值后可以执行相应的动作,因此可以时间周期性动作,采集捕捉动作等,用于实时控制及多种功能实现。
MSP430 MCU中有三种定时器:Timer_A Timer_B Timer_D.
首先 Timer_D定时器基本没有集成在MCU中,很少很少使用,是一个高分辨率的定时器,因此我们不做讲解(内部寄存器和使用与Timer_A/B很是相似,如果有用户用到,可以直接参考,如果有人需要可以留言,我会开贴再次单独讲解Timer_D)。
因此本次内容主要讲解一下Timer_A和Timer_B定时/计数器。首先先说一下MSP430 MCU内部Timer的主要功能:
主要就这三种功能,用户在使用过程中也就是这三种功能,然后Timer有一些自己的特点:
那么为什么MSP430会有两种定时器呢:A和B,有什么区别呢:
什么?不懂?没事,等我讲解完Timer所有功能后你就了解了,如果有疑问请接着往后看,最后在讲解完之后,我会重新详细分析一下Timer_A和Timer_B的区别。
首先先了解一下Timer_A的内部结构图,从下图可以看到,内部的定时器位数是16位,同时支持TAxCLK,ACLK,SMCLK,INCLK作为时钟源,后可以进行进一步的分频使用,同时拥有7个比较输出模块,可用于输出PWM波(注意 一个Timer只能输出6个PWM波,因为有一路计数器需要作为base使用:在可以调节PWM频率和占空比的情况下)
内部结构图决定的是模块的实现方式,因此可以清晰地看出实现的原理和寄存器中每个位的作用,因此最好养成观看内部结构图的习惯,这样有利于高效地调试自己的程序,当然现在刚开始看起来有点懵,不太理解也比较正常,但当学习完整个Timer后,对比着程序来看就会恍然大悟。
好啦,下面开始进入Timer地核心内容区域:使用模式。
简单来说就是如何使用它,怎么使用,有几种使用方法。对于MSP430 Timer_A定时器来说,总共有四种工作模式,如下图所示,由寄存器TAxCLT中MC位决定,如下图所示:
在使用这种模式下,中断在什么时候产生呢?这时候就要弄清楚一个问题:Timer_A模块 有两种中断:TAxIFG(定时器中断)和TAxCCRx(捕捉/比较中断),TAxIFG时定时器的基本中断,一个定时器模块只有一个,就是当内部计数到设定值后就会产生,TAxCCRx时输入/比较中断,其实内部就一个定时器在计数,但是有不同通道的几个值可以设定,也就是TAxCCRx寄存器,因此当相对应的通道设定了值之后,则会触发相应的中断。
触发中断的时间如下图所示:
TAxIFG是在计数完CCR0后会产生的,TAxCCR0中断时在计数完CCR0-1后产生的,两者相差一个时钟位。不过在使用过程中,基本可以忽略了,只是在理论上了解一下更好。
那么还有一个问题:当前的计数值是在寄存器TAxR(看内部结构框图可以发现)中的,TAxCCR0只是设定一个值,那么当模式从Stop开始到Up模式时,TAxR的值不一定为0,那么设定就会出现两个情况:
因此这个时候就会出现两种情况,然而你在设定时如果不去检查TAxR的值,那么可能对于第一个周期,就会出现不同的结果。为了避免这个情况,建议在设定CCR0值的时候,将MC设置成Stop模式(TI建议),其实这时候不仅仅应该设置成Stop模式,最重要的是要将TACLR设置成1(注意这个位是TAxR清除位,直接设置成1后会自动清0,去读的话 也都是0)。
这种模式下,仅有TAxIFG中断会用到,因为没有设定CCR寄存器,也不需要这个寄存器,产生中断的时刻如下图所示:
在完成0xFFFF计数后才会产生中断。那么当工作在Continuous模式下 CCRx就不能使用了吗?
NO,也可以使用,而且可以根据客户的需求来定义不同占空比 不同形状的波形,如下图所示,使用两个CCRx时可以产生的中断(加上TAxIFG中断会更多,灵活使用):
上图可以看到,在CCR0和CCR1处都会产生中断,同时TAxIFG也是存在的。这种模式下在设定CCRx值时就仅有一种情况了,因为TAxR一直在计数,因此设定CCRx值时并不影响计数,只是看什么时候会产证中断而已,如果当前计数到10000,你设定10001,那么下一个时钟就会中断,如果当前10000,你设定9999,那么需要等下一次TAxR值等于9999时才能产生中断了,与Up模式不同的时,设定CCRx的值,不会改变或影响TAxR的值。
触发中断的时间如下图所示:
TAxIFG会在计数到0(计完1)后触发,CCIFG会在计数完CCR0-1后触发。
那么改变CCRx值时是什么状况呢?
因此此时设定CCRx值时,推荐和Up模式下保持一致。
上图显示的是在Up/down模式下CCRx使用方式,当然 这个也是PWM的输出方法,由CCR0通道作为base计数,用于调节PWM的频率,CCRx则用来调节占空比,而且还支持多种输出模式。这个模式的区别,后面会详细讲解。
到此处位置,Timer_A的四种工作模式,其实就三种(除去Stop)已经全部讲解完成,本质上也就是每个定时器模块内有一个计数器,一直在计数,然后有一些CCRx寄存器,可用于用户设定相应的值,当计数到这个值时产生相应的中断,去执行相应的操作。那么如何使用描述的这些功能呢? 我认为需要了解五块内容,会和你的使用息息相关:
中断方式,寄存器,输入捕捉,输出比较(PWM),例程。
下面按照这种方法来进行讲解(比较乱,但是我认为这种方式比较合理,先搞清楚定时器原理及模式,在进一步细看寄存器,中断实现方式,在面向使用去了解输入捕捉和输出比较,最后进行例程的学习)。
在细看完上面的讲解后,你可能会有个印象,Timer_A模块有两种中断:TAxIFG和TAxCCRx中断,那么中断向量如何呢?(中断向量是中断的入口)
如果拆开看有8个中断:TAxIFG,CCR0IFG-CCR6IFG,每个都分配一个中断向量? 太浪费了吧! 因此MSP430只分配了两个中断向量:
TAxCCR0自己单独一个中断向量,名字叫做:TIMER0_A0 (TimerA0),这个不需要记,去看工程下的cmd文件即可。
剩下的TAxIFG和TAxCCRx共同享用一个中断向量:TIMER0_A1 (TimerA0),因此你会发现每个定时器仅有两个中断向量,也就够使用了,那么如何用呢?
对于CCR0中断:
// Timer0_A0 interrupt service routine
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR (void)
{
P1OUT ^= BIT0;
}
对于其他中断,进入中断后需要进行中断类型判断:
// Timer0_A1 Interrupt Vector (TAIV) handler
#pragma vector=TIMER0_A1_VECTOR
__interrupt void TIMER0_A1_ISR(void)
{
switch(__even_in_range(TA0IV, TA0IV_TAIFG))
{
case TA0IV_NONE: break; // No interrupt
case TA0IV_TACCR1: break; // CCR1 not used
case TA0IV_TACCR2: break; // CCR2 not used
case TA0IV_TACCR3: break; // reserved
case TA0IV_TACCR4: break; // reserved
case TA0IV_TACCR5: break; // reserved
case TA0IV_TACCR6: break; // reserved
case TA0IV_TAIFG: // overflow
P1OUT ^= BIT0;
break;
default: break;
}
}
注意:并不是所有的Timer_A都有6个CCR寄存器,具体有几个看每个芯片的datasheet。
先看一下总共有多少寄存器:18个寄存器,但是只有六类寄存器,其他都是相同的。
给出一个再Up模式下不同模式的波形图:
模式1(001) : CCR1中断时直接置位。
模式2(010) : CCR1中断时反转,CCR0中断时复位。
模式3(011) : CCR1中断时置位,CCR0中断时复位。
模式4(100) : CCR1中断时反转。
模式5(101) : CCR1中断时复位。
模式6 (110): CCR1中断时反转,CCR0中断时置位。
模式7 (111): CCR1中断时复位,CCR0中断时置位。
首先看,什么叫输入捕捉,用在什么地方:主要用于测量脉冲数量,监控外部脉冲等功能,其实最主要的就是连接增量式编码器来获取固定时间内的脉冲数量从而计算电机的转速,这个是最最常用的功能。
如下是MSP430 输入捕捉功能的工作流程图:
那么如何设定输入捕捉呢?
这个主要再TAxCCTLn寄存器,首先你需要将CAP设置成1,代表使用输入捕捉模式,其次根据你的硬件连接选择A或者B通道,然后设定同步位,捕捉类型等,最后依旧是要设定最基本的定时器功能,因为定时器要工作,开始计数嘛!
当然设置完这些后输入捕捉就可以工作了,但是捕捉到信号之后怎么办呢? 需要开中断嘛,这时进中断,将这个时刻计数器的值给记下来,用于脉冲频率等的测量。
后续如何使用此项功能,在后面会给出详细的Example Code.
输出比较最常用的就是输出PWM波了,这个电机控制,LED控制等中非常常用,原理上前面已经描述清除,就是靠Base计数器和CCRx计数器,当计数到CCRx寄存器时会进行相应的动作,从而实现PWM功能的输出,通过CCR0调节PWM频率,CCRx调节占空比。
好啦,上面讲了一大堆最最主要的还是程序应该怎么写,怎么使用每个功能,下面给出几种常用的程序。(例程使用MSP430FR5969,别问为什么 主要因为刚好有这个板子。。。其实MSP430 内部Timer_A模块都是一样的,因此程序基本通用,可能仅仅有一些寄存器不一样或者功能有点区别而已,总的来说大同小异。)
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; // Stop WDT
// Configure GPIO
P1DIR |= BIT0;
P1OUT |= BIT0;
// Disable the GPIO power-on default high-impedance mode to activate
// previously configured port settings
PM5CTL0 &= ~LOCKLPM5;
TA0CCTL0 = CCIE; // TACCR0 interrupt enabled
TA0CCR0 = 20000; // SMCLK default 1MHz,
// Set 20000 : 20000/1000000 = 20ms
TA0CTL = TASSEL__SMCLK | MC__UP; // SMCLK, UP mode
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupt
__no_operation(); // For debugger
}
// Timer0_A0 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR (void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) Timer0_A0_ISR (void)
#else
#error Compiler not supported!
#endif
{
P1OUT ^= BIT0;
}
#include "msp430.h"
#define NUMBER_TIMER_CAPTURES 20
volatile unsigned int timerAcaptureValues[NUMBER_TIMER_CAPTURES];
unsigned int timerAcapturePointer = 0;
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
// Configure GPIO
P1OUT &= ~0x01; // Clear P1.0 output
P1DIR |= 0x01; // Set P1.0 to output direction
// Disable the GPIO power-on default high-impedance mode to activate
// previously configured port settings
PM5CTL0 &= ~LOCKLPM5;
// Clock System Setup
CSCTL0_H = CSKEY >> 8; // Unlock CS registers
CSCTL2 &= ~SELA_7;
CSCTL2 |= SELA__VLOCLK; // Select ACLK=VLOCLK
CSCTL0_H = 0x00; // Lock CS module (use byte mode to upper byte)
__delay_cycles(1000); // Allow clock system to settle
// Timer0_A3 Setup
TA0CCTL2 = CM_1 | CCIS_1 | SCS | CAP | CCIE;
// Capture rising edge,
// Use CCI2B=ACLK,
// Synchronous capture,
// Enable capture mode,
// Enable capture interrupt
TA0CTL = TASSEL__SMCLK | MC__CONTINUOUS; // Use SMCLK as clock source,
// Start timer in continuous mode
__bis_SR_register(LPM0_bits | GIE);
__no_operation();
}
// Timer0_A3 CC1-4, TA Interrupt Handler
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A1_VECTOR
__interrupt void Timer0_A1_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_A1_VECTOR))) Timer0_A1_ISR (void)
#else
#error Compiler not supported!
#endif
{
switch (__even_in_range(TA0IV, TA0IV_TAIFG)) {
case TA0IV_TA0CCR1:
break;
case TA0IV_TA0CCR2:
timerAcaptureValues[timerAcapturePointer++] = TA0CCR2;
if (timerAcapturePointer >= 20) {
while (1) {
P1OUT ^= 0x01; // Toggle P1.0 (LED)
__delay_cycles(100000);
}
}
break;
case TA0IV_TA0IFG:
break;
default:
break;
}
}
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW | WDTHOLD; // Stop WDT
// Configure GPIO
P1DIR |= BIT0 | BIT1; // P1.0 and P1.1 output
P1SEL0 |= BIT0 | BIT1; // P1.0 and P1.1 options select
// Disable the GPIO power-on default high-impedance mode to activate
// previously configured port settings
PM5CTL0 &= ~LOCKLPM5;
CSCTL0_H = CSKEY >> 8; // Unlock CS registers
CSCTL1 = DCOFSEL_6; // Set DCO = 8MHz
CSCTL2 = SELA__VLOCLK | SELS__DCOCLK | SELM__DCOCLK;// Set ACLK=VLO SMCLK=DCO
CSCTL3 = DIVA__8 | DIVS__8 | DIVM__8; // Set all dividers
CSCTL0_H = 0; // Lock CS registers
// Configure Timer0_A
TA0CCR0 = 1000-1; // PWM Period 1KHz
TA0CCTL1 = OUTMOD_7; // CCR1 reset/set
TA0CCR1 = 750; // CCR1 PWM duty cycle
TA0CCTL2 = OUTMOD_7; // CCR2 reset/set
TA0CCR2 = 250; // CCR2 PWM duty cycle
TA0CTL = TASSEL__SMCLK | MC__UP | TACLR; // SMCLK, up mode, clear TAR
__bis_SR_register(LPM0_bits); // Enter LPM0
__no_operation(); // For debugger
}
先看一下结构图的区别:B可以成组,因此多了TBCLGRP,其次可以设置成8,10,12,16位模式,因此多了CNTL位,没有SCCI位,故CCI不能到EQU模块。
先说第一条:B可以设定成8,10,12,16位模式,也就是说计数的最大值不同,这个在TBxCTL中你会发现多了CNT位(12-11),用于选择定时器位数:
其次没有SCCI,因此也就没有这个设定位了。
最后就是Group,看下面寄存器截图:
这个功能就是在输出比较模式下,这几个通道可以同时成组的去更新数据,用于多个输出比较形成同步的功能。