单片机定时器的复用【科普】

2020-01-26 13:06发布

本帖最后由 julian 于 2013-9-13 15:53 编辑

    一个单片机系统的设计经常会用到多种不同目的和用途的定时,例如系统需要输出一个指示“心跳正常”的秒闪信号,间隔0.5s;按键检测时临时需要约20ms的消抖;蜂鸣器需要发声延时;用户菜单选择时可能需要对应的发光管或LCD点阵(字段)闪烁;通讯时需要设定应答超时判别,等等。是不是要抱怨一个单片机上的若干个定时器不够用了?其实,用一个定时器资源就可以搞定所有的这一切定时要求。
    首先,选定一个你喜欢的定时器,按所需应用的定时精度要求设定其定时中断频率。一般人机界面的定时精度为ms级就足够了,所以可以设定定时中断时间间隔为1ms,5ms或10ms;例如我的选择:
// TPM2 overflow interrupt service routine
// Interrupt at every 1ms
void interrupt 14 TPM2_Overflow_ISR(void){  
        TPM2SC_TOF = 0;          //reset interrupt flag   
        msTimeoutCount++;        //1ms increment
}
变量msTimeoutCount是一个16位word型的静态变量,在中断服务程序中简单地对它递增,无需考虑溢出。如果你的中断时间间隔为Nms,则在中断中对其递增的方法为“msTimeoutCount += N”。它在程序模块的前面被声明,为了提高中断服务程序的效率,其被定位在直接寻址区:
// Following data are declared in the direct addressing area
//for fast access (address < 0x100)
#pragma DATA_SEG SHORT MY_ZEROPAGE   // direct addressing data segment
volatile word msTimeoutCount;
然后写一段独立的定时判别函数。这个函数有两个入口参数:特定定时实例的一个定时变量指针和所需的定时时间长度。若定时时间长度为0,则定时过程被复位,实际上是当前的定时计数器值(msTimeoutCount)被复制到定时实例的一个定时变量中。返回值为0则表明定时时间未到,0xff则为一次定时时间到并自动开始下一次的定时过程。具体代码如下:
// Check for timeout occurance
// Input *timer - pointer of timer counter
// timeOutVal - timeout value, 0=forced timer update
// Return 0 - no timeout yet
// 0xff - timeout occured and timer updated
byte TimeOutChk(word *timer, word timeOutVal){
word shadow, diff;
TPM2SC_TOIE = 0;        
针对8位机必须禁止定时中断,16位机以上则无需如此
shadow = msTimeoutCount;        
将当前时间计数值复制一份以作后需
TPM2SC_TOIE = 1;
对应上面的中断禁止,现在开放中断
  if (timeOutVal= =0) {
//复位定时过程
*timer = shadow;
return(0);
}        else {
diff = shadow - *timer;         //计算定时时间间隔
if (diff>=timeOutVal) {          //定时时间到
*timer += timeOutVal;                //更新定时变量,开始下一次定时过程        return(0xff);                // 返回时间到标志
} else {
return(0);         //定时时间未到
}
}
}
剩下的就看具体应用的需要而开辟特定的定时实例了。每一个实例必须有一个word型的变量作为定时跟踪变量。
例如产生500ms的定时(msCount变量在模块前面已经定义):
void main(void) {
...
TimeOutChk(&msCount, 0);         //复位初始化定时实例
...
while(1) {
Clock();
KeyScan();
...
}
}
//=============================================================// Keep the system clock running //=============================================================void Clock(void) {
if (TimeOutChk(&msCount, 500)==0)
return;          //wait for 0.5 second time ou
runFlag.halfSec = !runFlag.halfSec;
dispCodeBuff[2] = 0x80;
dispCodeBuff[3] = 0x80;
if (runFlag.halfSec) {
return;
}
second++;
if (second==30) {         //sync soft clock with RTC value   
RTC_Read()
}
if (second>59) {
second = 0;
minute++;
if (minute>59) {
minute = 0;
hour++;
if (hour>23)
hour = 0;
}
}
runFlag.clkDisp = 1;
}
//按键扫描时的消抖延时实现, keyDebounce在模块前面为局部静态变量定义
//============================================================= //Scaning key input
//=============================================================void KeyScan(void) {
byte keyInput;
keyInput = (PTFD^0xff) &
0b00011111;
switch (keyState) {
case 0:   //idle
if (keyInput) {    //possible key input detected
runFlag.keyCon = 0;     //continuous key strike not allowed by default        TimeOutChk(&keyDebounce, 0);    //reset debounce timer          keyState = 1;
}
break;
case 1:   //down debouncing
if (TimeOutChk(&keyDebounce, 50)) {    //50ms debounce timeout
if (keyInput) {
KeyFifoAdd(keyInput);              TimeOutChk(&keyDebounce, 0);         //!复位定时准备实现按键持续按下时的连续激发功能
keyState = 2;    //key is holding
} else {
keyState = 0;    //debounce check failed
}
break;
case 2:   //hold
if (keyInput==0) {     //possible key release detected          TimeOutChk(&keyDebounce, 0);
keyState = 4;
} else {         
if (runFlag.keyCon) {   //continuous key strike allowed
if (TimeOutChk(&keyDebounce, 500)) {        //持续按下时间达0.5s
KeyFifoAdd(keyInput);
TimeOutChk(&keyDebounce, 0); //准备后续每隔0.1s激发一个按键值
keyState = 3;    //invoke key continuous strike
}
}
}
break;
case 3:   //continuous strike
if (keyInput==0) {     //possible key release detected         TimeOutChk(&keyDebounce, 0);
keyState = 4;
} else {
if (TimeOutChk(&keyDebounce, 100)) {         //每隔0.1s激发一个按键值KeyFifoAdd(keyInput);
TimeOutChk(&keyDebounce,0);
}
}
break;
case 4:   //up debouncing
if (TimeOutChk(&keyDebounce, 50)) {    //50ms debounce timeout
if (keyInput) {
keyState = 2;    //key is still holding
} else {
keyState = 0;    //confirm released
}
}
break;
default:
keyState = 0;
}
}
所以理论上只要你有足够多的内存作为定时跟踪变量,你就可以实现任意多个定时实例,无论什么时间和什么地点。当然上面的定时程序有一个局限,就是一次最大的定时时间为65535ms。如果要实现更长时间的定时,可以用一个实例产生1s(或更长)的定时基准,然后参照函数TimeOutChk另外写一个例如TimeOutChkSec,按1s的分辨率最多实现65535s的定时。                                                                               
键盘用了FIFO,那按键处理程序是不是另外写一个函数从FIFO中取键处理?
首先判断这个FIFO是不是为空,如不为空则根据当前的状态(如果存在一键多用的情况)进行相应的按键处理??
采用状态机实现按键检测是最可靠最有效的方法。同时在单片机设计中实现多任务的并发和协调,状态机起着不可或缺的作用。
对于按键处理,部分代码如下:
#define KEY_FIFO_LEN 4
byte keyFifo[KEY_FIFO_LEN], keyPut, keyGet;
//=============================================================
// Add a key into FIFO
//=============================================================
void KeyFifoAdd(byte code)
{
   keyFifo[keyPut++] = code;
   keyPut &= (KEY_FIFO_LEN-1);
}
//=============================================================
// Fetch a key from FIFO
//=============================================================
byte KeyFifoGet(void)
{
   byte tmp;
   tmp = keyFifo[keyGet++];
   keyGet &= (KEY_FIFO_LEN-1);
   return(tmp);
}
//=============================================================
// Do key function for primary task
//==============================================================
void KeyFuncMain(void)
{
   byte keyCode;
   if (keyPut==keyGet) return;
   keyCode = KeyFifoGet();
   switch (keyCode) {
      case KEY_CH1_CTL:
         RELAY1_CTL = !RELAY1_CTL;
         if (RELAY1_CTL)
            dispCodeBuff[4] |= 0x10;
         else
            dispCodeBuff[4] &= (0x10^0xff);
         SetBeep(200);
         break;
      case KEY_CH2_CTL:
         RELAY2_CTL = !RELAY2_CTL;
         if (RELAY2_CTL)
            dispCodeBuff[4] |= 0x08;
         else
            dispCodeBuff[4] &= (0x08^0xff);
         SetBeep(200);
         break;
      case KEY_SET:
         SetBeep(50);
         TimeOutChk(&menuTimeout, 0);
         menuId = 0;
         MainTaskEntry = SetupEnable;
         MenuTaskEntry = ClockSetup;
         dispCodeBuff[4] = 0x01;
         break;
      default:
         return;
   }
}
状态机是解决很多问题的法宝
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。