//一个单片机系统的设计经常会用到多种不同目的和用图的定时,例如系统需要输出 //一个指示“心跳正常”的秒闪信号,间隔0.5s;按键检测时临时需要约20ms的消抖; //蜂鸣器需要发声延时;用户菜单选择时可能需要对应的发光管或LCD点阵(字段) //闪烁;通讯时需要设定应答超时判别,等等。是不是要抱怨一个单片机上的若干个 //定时器不够用了?其实,用一个定时器资源就可以搞定所有的这一切定时要求。
//1)首先,选定一个你喜欢的定时器,按所需应用的定时精度要求设定其定时中断频 //率。一般人机界面的定时精度为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 out
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的定时。
//采用状态机实现按键检测是最可靠最有效的方法。同时在单片机设计中实现多任务 //的并发和协调,状态机起着不可或缺的作用。对于按键处理,部分代码如下: #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; } } |