打印

错误爱上我——关于嵌入式程序错误处理的框架性的探讨

[复制链接]
2318|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dongshan|  楼主 | 2008-4-21 13:46 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
“程序悠悠,错误多多,改啊又改错。容错处理,程序框架,几人能看透。。。”,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

相关帖子

沙发
dongshan|  楼主 | 2008-4-21 13:47 | 只看该作者

忘记传工程文件。

使用特权

评论回复
板凳
dld2| | 2008-4-21 14:36 | 只看该作者

为什么没有人顶。支持一下!

使用特权

评论回复
地板
sheenhero| | 2008-4-21 15:43 | 只看该作者

不错

使用特权

评论回复
5
yewuyi| | 2008-4-21 16:51 | 只看该作者

呵呵,用goto也能捣鼓出来~~

使用特权

评论回复
6
HWM| | 2008-4-21 17:27 | 只看该作者

看看我的出错处理四步骤


      1)将一个完整的算法归为一个模块(一个子程序或函数)
   2)在模块中的出错处理点用return ErrCode;返回(ErrCode 不等于零)
   3)正常返回点为return 0;
      4)返回后若有错则用专门的出错处理模块来处理出错信息

使用特权

评论回复
7
hqgboy| | 2008-4-21 18:05 | 只看该作者

ding....

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

79

主题

1143

帖子

7

粉丝