本文引用地址:https://www.eepw.com.cn/article/201611/322212.htm以下是一个键盘扫描的例子,这里假设tick = 20 ms, ScanKeyboard()函数控制口线的输出扫描,并检测输入转换为键码,利用每个state之间20 ms的间隔去抖动。
enum EnumKey {
EnumKey_NoKey =0,
…
};
struct StructKey {
intkeyValue;
boolkeyPressed;
} ;
struct StructKeyProcess key;
void ProcessKey() { (*states[state])(); }
void state0() { }
void state1() { key.keyPressed = false; state++; }
void state2() { if (ScanKey() != EnumKey_NoKey) state++; }//next state if a key pressed
void state3()
{//debouncing state
key.keyValue = ScanKey();
if (key.keyValue == EnumKey_NoKey)
state--;
else {
key.keyPressed = true;
state++;
}
}
void state4() {if (ScanKey() == EnumKey_NoKey) state++; }//next state if the key released
void state5() {ScanKey() == EnumKey_NoKey? state = 1 : state--; }
上面的键盘处理过程显然比通常使用标志去抖的程序简洁清晰,而且没有软件延时去抖的困扰。以此类推,各个任务都可以划分成一个个的state,每个state实际上占用不多的处理时间。某些任务可以划分成若干个子任务,每个子任务再划分成若干个状态。
(题外话:对于常数类型,建议使用enum分类组织,避免使用大量#define定义常数)
对于一些完全不能分割,必须独占的任务来说,比如我以前一个低成本应用中红外遥控器的软件解码任务,这时只能牺牲其他的任务了。两种做法:一种是关闭中断,完全的独占;
void RunTaskN()
{
Disable_Interrupt;
…
Enable_Interrupt;
}
第二种,允许定时中断发生,保证某些时基register得以更新;
void Timer_Interrupt()
{
SetTimer();
Enable_Timer_Interrupt;
UpdateTimingRegisters();
if (watchDogCounter = 0) {
ResetStack();
for (i=0; i
(*tasks[i])();
while (1) IDLE;
}
else
watchDogCounter--;
}
只要watchDogCounter不为0,那么中断正常返回到中断点,继续执行先前被中断的任务,否则,复位stack,重新进行任务循环。这种状况下,中断处理过程极短,对独占任务的影响也有限。
中断驱动多任务配合状态机的使用,我相信这是mcu下无os系统较好的设计结构。对于绝大多数mcu程序设计来说,可以极大的减轻程序结构的安排,无需过多的考虑各个任务之间的时间安排,而且可以让程序简洁易懂。缺点是,程序员必须花费一定的时间考虑如何切分任务。
下面是一段用C改写的CD Player中检测disc是否存在的伪代码,用以展示这种结构的设计技巧,原源代码为Z8 mcu汇编,基于Sony的DSP, Servo and RF处理芯片,通过送出命令字来控制主轴/滑板/聚焦/寻迹电机,并读取状态以及CD的sub Q码。这个处理任务只是一个大任务下用state machine切开的一个二级子任务,tick = 20 ms。
state1() { InitializeMotor(); state++; }
state2() {
if (innerSwitch != ON) {
SendCommand(EnumCommand_SlidingMotorBackward);
timeout = MILLISECOND(10000);
state++;//滑板电机向内运动,直至触及最内开关。
}
else
state +=2;
}
state3() {
if ((--timeout) == 0) {//note: some C compliers do not support (--timeout) ==
SendCommand(EnumCommand_SlidingMotorStop)
systemErrorCode = EnumErrorCode_InnerSwitch;
state = 0;// 10 s超时错误,
}
else {
if (innerSwitch == ON) {
SendCommand(EnumCommand _SlidingMotorStop)
timeout = MILLISECOND(200);// 200ms电机停止时间
state++;
}
}
}
state4() { if ((--timeout) == 0) state++; }//等待电机完全停止
state5() {
SendCommand(EnumCommand_SlidingMotorForward);
timeout = MILLISECOND(2000);
state++;
}//滑板电机向外运动,脱离inner switch
state6() {
if ((--timeout) == 0) {
SendCommand(EnumCommand_SlidingMotorStop)
systemErrorCode = EnumErrorCode_InnerSwitch;
state = 0;// 2 s超时错误,
}
else {
if (innerSwitch == OFF) {
SendCommand(EnumCommand_SlidingMotorStop)
timeout = MILLISECOND(200);// 200ms电机停止时间
state++;
}
}
}
state7() { state4(); }
state8() { LaserOn(); state++; retryCounter = 3;}//打开激光器
state9() {
SendCommand(FocusUp);
state++;
timeout = MILLISECOND(2000);
}//光头上举,检测聚焦过零3次,判断cd是否存在
state10() {
if (FocusCrossZero){
systemStatus.Disc = EnumStatus_DiscExist;
SendCommand(EnumCommand_AutoFocusOn);//有cd,打开自动聚焦。
state = 0;//本任务结束。
playProcess.state = 1;//启动play任务
}
else if ((--timeout) == 0) {
SendCommand(EnumCommand_ FocusClose);//光头聚焦复位
if ((--retryCounter) == 0) {
systemStatus.Disc = EnumStatus_Nodisc;//无盘
displayProcess.state = EnumDisplayState_NoDisc;//显示闪烁的无盘
LaserOff();
state = 0;//任务停止
}
else
state--;//再试
}
}
stateStop() {
SendCommand(EnumCommand_SlidingMotorStop);
SendCommand(EnumCommand_FocusClose);
state = 0;
}
评论