“程序悠悠,错误多多,改啊又改错。容错处理,程序框架,几人能看透。。。”,dongshan 哼着歌,摇摆着脑袋向阿姨家走去。最近有些忙,很久没到阿姨家串门,不知众表哥怎么样?这不,带上些小礼品,来看看各位表兄弟。阿姨家不是别人家,无论你什么时候去,那里总有一张椅子在等着你,这让人很感动。 二十一世纪最贵的是什么?人才。编写代码最怕的是什么?废话——错误!当然,此处的“错误”并不是指我们人为编码的错误,而是指在我们的嵌入式系统中,经常会有不确定的意外事件发生,这些并不是我们想看到的。比如,你正在等待一个IIC器件的应答,可是那个IIC器件就是“不理你”,又或者,你用了一个字节类型的标志位变量mode,并且指定它为0时,表示为自动模式;为1时,表示为手动模式。其它的值都是非法的,可是某一天,你会惊讶的发现,有人竟然动了你的奶酪,此变量的值变成了其它的值。 我所说的这些均属于意外的事件,它们可能是由于器件的损坏,或者是干扰所致。故我们在编码时,要“防范性”编程。所谓的“防范性”,就是你可以随便的意淫,但是要作最坏的打算,这与GMD的“宁可错杀三千,不可放过一个”有异曲同工之妙。于是,我们针对以上两种情况,可能作如下处理(用C伪码表示): 1. IIC无应答的错误处理 …… //此处代码省略一万行^D^ while(IIC无应答) { 计时; If(计时)100) //防止永远无应答 { break; } } …… //此处代码省略一万行 2.变量被意外的改变的错误处理 ……. //此处代码省略一万行 If(mode==0) { 自动模式处理; } Else if(mode==1) { 手动模式处理; } Else { //此处是我们不希望看到的,但是事情已发生了,只有人为的指定一个合法的值哦。 mode=0; //嗯,此乃错误处理呢 }
…… //此处代码省略一万行
“火星人都会”,有的表兄弟不屑地说。嗯,不错,dongshan学着紫霞仙子的语气,幽幽地道: “你们的表弟dongshan是个大笨蛋,他会哆哆嗦嗦地讲个没完,但你们只预测到了开头,却没预料到结尾。。。”。 这种写法,当然可以。但是随着程序的疯长,及容错设计的增多,到后来以致防错代码遍及程序每个角落,这样阅读,修改,维护起来十分吃力。有没有像C++中的try…catch那样东东,使正常代码与错理处理代码分别开来呢?而这正是我今天想探讨的内容。 一个C++的防错代码可能如下: try { …… throw 异常; …… } catch(异常) { 异常处理; } catch(…) { 异常处理; }
经过一番折腾,终于发现,c中的setjmp不就可以用作c++中的try…catch吗?c中 longjmp不就可以用作c++中的throw吗?setjmp,longjmp的用法请参见C语言手册,在此并不作说明,表弟我可以对着月亮发誓,并不是因为懒,而是因为我清楚的知道,我再怎么浪费口舌也比不上参考手册来的明了。故在此直接应用。但在作具体的应用之前,首先对我的系统作个假设: 假设我的MCU是51系列,其程序主循环中有三个模块,一个是IIC模块IIC_DO,一个是SPI模块SPI_DO,一个是显示模块DISP。它们均可能发生意外的错误,有的错误是不可恢复的,如:IIC器件损坏。有的错误是可恢复的,如:显示状态变量值非法。不同的错误码类型处理的方法也不同,对于不可恢复的错误,必须封锁相关的模块,而对于可恢复的错误,应在修复错误后重新运行。在此假设IIC,SPI模块发生的是可修复错误码,DISP发生的是不可修复错误码。为了便于观察结果,每个模块的实际功能仅是在串口输出“XXX was called”的提示信息。
于是乎,给出一个完整的程序框架,具体代码如下(keil中已调试通过):
#include <REG52.H> #include<setjmp.h> #include <stdio.h>
#define IIC_NOACK 1 #define SPI_ERR 2 #define DISP_ERR 3
void TriggerErr( int t); //触发一个错误 void ErrHandle(int t); //整个系统错误处理函数,与正常代码区别开来。
void InitSys(void); /*系统初始化模块 ,此处令其初始化串口,用于调试 ,当然你可以加入任何其它的初始化代码*/ void IIC_DO(void); //IIC通信模块 void SPI_DO(void); //SPI通信模块 void DISP(void); //显示模块
jmp_buf env;
bit bIsIICErr, bIsSPIErr,bIsDispErr; /*因为错误是不可预测的,故用此标志来模拟某些模块出错,实际应用并不需要这些标志变量*/ bit bLock0=0,bLock1=0,bLock2=0; /*0:模块正常执行, 1:模块被封锁,用于处理不可恢复的错误*/ main() { int flag;
InitSys(); //初始化系统
bIsIICErr=1; //运气真是太背了,三个系统模块均会出现错误! bIsSPIErr=1; bIsDispErr=1;
while(1) { flag=setjmp(env); if(0==flag) { if(!bLock0){IIC_DO();} //IIC通信 if(!bLock1){SPI_DO();} //SPI通信 if(!bLock2){DISP(); } //显示模块 } else { ErrHandle(flag); //此处是整个系统的错误处理代码 } }
}
void InitSys(void) { SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */ TMOD |= 0x20; /* TMOD: timer 1, mode 2, 8-bit reload */ TH1 = 221; /* TH1: reload value for 1200 baud @ 16MHz */ TR1 = 1; /* TR1: timer 1 run */ TI = 1; /* TI: set TI to send first char of UART */ }
void TriggerErr(int t) { longjmp(env,t); }
void ErrHandle(int t) { switch(t) { case IIC_NOACK: printf ("IIC ERR WAS HANDLED
"); bIsIICErr=0; //经过错误处理后,错误已被修复,故恢复执行 break; case SPI_ERR: printf ("SPI ERR WAS HANDLED
"); bIsSPIErr=0; //此句用于模拟错误码被恢复,经过错误处理后,错误已被修复,故恢复执行 break; case DISP_ERR: printf ("DISP ERR WAS HANDLED
"); bLock2=1; //此处对发生不可恢复的错误的模块作屏蔽处理. break; }
}
void IIC_DO(void) { printf ("IIC_DO was called
"); if(bIsIICErr) //模拟出错 { printf ("IIC ERR OCURRED
"); TriggerErr(IIC_NOACK); } } void SPI_DO(void) { printf ("SPI_DO was called
"); if(bIsSPIErr) //模拟出错 { printf ("SPI ERR OCURRED
"); TriggerErr(SPI_ERR); } } void DISP(void) { printf ("DISP was called
"); if(bIsDispErr) //模拟出错 { printf ("DISP ERR OCURRED
"); TriggerErr(DISP_ERR); } } 如此来来,我们就把所有的错误处理放到了ErrHandle中。编译,链接,调试,全速运行,停止,以上动作一气呵成,打开串口0窗口,发现了吗?结果如下: IIC_DO was called //IIC模块被调用 IIC ERR OCURRED //IIC模块发生了错误 IIC ERR WAS HANDLED //错误被修复 IIC_DO was called //重新运行 SPI_DO was called IIC ERR OCURRED SPI ERR WAS HANDLED IIC_DO was called SPI_DO was called DISP was called DISP ERR OCURRED DISP ERR WAS HANDLED //此错误是不可修复的,故封锁了DISP模块,以后不再被调用 IIC_DO was called SPI_DO was called IIC_DO was called SPI_DO was called IIC_DO was called SPI_DO was called IIC_DO was called SPI_DO was called IIC_DO was called
以上是表弟我对错误处理的一点思考,不妥之处请各位拍砖!夜已深,是该睡觉了。 “快不快乐我无所谓,只想找个女人来陪。。。”dongshan**地哼着的歌曲,姗姗而去,远远望去,留下的仅是一个猥琐的背影。。。 “神经”,背后不知谁骂了一句。
附:完整的工程文件。Keil c51 v8.08 |