其于Keil C51的断言实现

lbing7 2006-12-04 10:13:46
在PC上的开发的时候,大伙都能方便地使用断言(assert)来尽可能快地让自己找到BUG,在调试的时候不用那么痛苦,至少能知道问题出在什么地方.不用怎么死都不知道.但是51下开发就没那么爽了.在Keil C51里,虽然其提供了了标准库里的assert.h.但是它也像标准库里一样采用printf();来进行输出.在51里printf默认的打印设备是串口.想必在绝大多数的51应用里是没用到串口的,如果只为了这个应用就去扩一个串口也太搞笑了.因此Keil C51提供的这个assert就成了花瓶.
附一:Keil C51中的断言原码:
#ifndef __ASSERT_H__
#define __ASSERT_H__

#undef assert

#ifndef __ASSERT_INC
#include <stdio.h> /* prototype for 'printf' */
#define __ASSERT_INC
#endif

#ifndef NDEBUG

#define assert(expr) \
if (expr) { ; } \
else {\
printf("Assert failed: " #expr " (file %s line %d)\n", __FILE__, (int) __LINE__ );\
while (1);\
}
#else
#define assert(expr)
#endif

#endif
分析Keil C51中assert成摆设的原理最大的问题不外乎它没选对输出对象,没有像PC一样选中了一个像显示器这样的必用的设备,却选了一个可用可不用的串口.跟据偶这五个多月来的51开发经验(就当它是吧.)51下的开发唯一不离的就是它的I\O口,如果谁敢说做51开发不用I\O的话.除非是做片子的.要么就是吃饱了撑着的.

偶这样想的:
1) 如果条件出了问题可以把I/O的值改变,以标识说是状态也好,特征也行,反正就这么个东西.同样您可以用万用表来测一下各I/O口的值,以至可以在执行的时候找到出错的地方.同时禁止MCU去响应任何的中断请求,以防在中断里会改变I/O的值.
2) 如果条件出了问题我的机器就没必要继续工作下去了,就停在出问题的地方.如果这个时候是通过软件仿真就能直接看到PC停在什么地方,使哥哥们可以直接找到问题所在.(后来偶一看Keil C51也采用和偶一样的做法,这是个巧合了).
3) 哥哥们可以任选I/O口对其的任意位进行设置.
附二:偶的断言
1. #ifndef NODEBUG

2. #define true 1
3. /*为循环提供一个宏*/

4. #ifndef _IN_LASSERT_H_
5. #include <reg51.h> /*用到标准51里的,EA中断使能位*/
6. #define _IN_LASSERT_H_
7. #endif
8. /*
9. 宏名:lassert(Condition,SetPort,SetBit)
10. Condition:为传入条件
11. SetPort:若出错,产生反应的端口
12. SetBit: 若出错,产生反应的端口及对应位,SetBit <= 7;51的每个I/O只有八位.照顾一下.

13. 功能:此为从写标准库的宏,专为适应51的特点.
14. */
15. #define lassert(Condition,SetPort,SetBit) \
16. { \
if (!Condition) \
{ \
EA = 0; \
SetPort &= (0x01 << SetBit); \
while (true); \
} \
17. }

18. #else

19. /*将宏屏蔽,我们聪明的编译器会优化掉它可能产生的代码.*/
20. #define lassert(Condition,SetPort,SetBit)\
21. { \
; \
22. }

23. #endif

24. #endif

注:16句内的:EA = 1;允许MCU去响应中断, EA = 0;MCU不响应任何中断;
相信各位哥哥都看着相当的简单.
下面给出Keil C51的反汇编代码.
#include <reg51.h>
#include "myassert.h"

int main()
{
int a = 1;
lassert(a != 1,P1,0);/*P1口0位置1*/
return 0;
}

未加NODEBUG编译选项时:
4: int main()
5: {
6: int a = 1;
7: lassert(a != 1,P1,0); /*在这展开了*/
C:0x0003 7401 MOV A,#0x01
C:0x0005 7004 JNZ C:000B
C:0x0007 7F01 MOV R7,#0x01
C:0x0009 8002 SJMP C:000D
C:0x000B 7F00 MOV R7,#0x00
C:0x000D EF MOV A,R7
C:0x000E 6401 XRL A,#0x01
C:0x0010 6007 JZ C:0019
C:0x0012 C2AF CLR EA(0xA8.7)
C:0x0014 539001 ANL P1(0x90),#0x01
C:0x0017 80FE SJMP C:0017/*while (ture);被编译成这句,这编译器还是可以哈*/
8: return 0; /*由于是旧标准的编译器,处理这个return是显得有点蠢*/
C:0x0019 E4 CLR A
C:0x001A FE MOV R6,A
C:0x001B FF MOV R7,A
9: }
加入NODEBUG编译选项时
4: int main()
5: {
6: int a = 1;
C:0x000F 750800 MOV 0x08,#0x00
C:0x0012 750901 MOV 0x09,#0x01
7: lassert(a != 1,P1,0); /*挺漂亮的,直接优化掉了*/
8: return 0;
C:0x0015 E4 CLR A
C:0x0016 FE MOV R6,A
C:0x0017 FF MOV R7,A
9: }
另:
可以把其它不相关的I/O口给屏蔽了,以防搞不清楚正确的输出位置.
如加一句:P0 &= P1 &= P2 &= P3 &= 0x00;
后记:
一、 现在这种端口置位的方法太简单了,一个I/O只能有八种状态,只能在小型的程序,或小范围内应用.除了用置位的方法之外,还可以用端口输出值的办法这样一个端口就能输出256种状态.这样将大大丰富了表求的状态,不过也太来了读状态的问题,可以自已制一个显示电路,用三个译码器(或一个MCU)读端口的输入,通过三个LED二极管.显示出相应的数值来.(不知道传说中的PCDEBUG卡用的是不是差不多的原理,应该更加复杂吧).
二、 小兵刚刚开始进入这块地方,十分想让大伙把偶的不足之处都晾出来.如果大伙那有其它的方案我们也一起来聊聊好不好不一定是51的,其它的嵌入式方向的都行?,另:如果大伙有更好的关于硬件上的开发,调试经验.能不能和小兵聊聊?谢谢
<PS> CSDN不允许贴图,不然我能把更多的测试信息弄出来.
...全文
1951 18 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
Rundstedt 2007-02-04
  • 打赏
  • 举报
回复
Keil里面的printf/scanf是调用putch/getch来实现的,如果不想用串口,用其他设备如I/O、LCD(如果有的话),很简单,把putch/getch重写一遍即可,这两个函数的原始程序,在LIB那个文件夹已经存在了。
江南一书生 2007-01-31
  • 打赏
  • 举报
回复
现在这种端口置位的方法太简单了,一个I/O只能有八种状态,只能在小型的程序,或小范围内应用.除了用置位的方法之外,还可以用端口输出值的办法这样一个端口就能输出256种状态.这样将大大丰富了表求的状态,不过也太来了读状态的问题,可以自已制一个显示电路,用三个译码器(或一个MCU)读端口的输入,通过三个LED二极管.显示出相应的数值来.(不知道传说中的PCDEBUG卡用的是不是差不多的原理,应该更加复杂吧).
=====================================================================================
简单问题复杂化了吧,与其这么麻烦还是用串口了
Jim_King_2000 2007-01-25
  • 打赏
  • 举报
回复
to ibing7:
我用的是philips公司的51,用串口做isp的.下载完毕之后,再用串口调试.等到正式发布的时候,max232芯片可以不焊.至于assert,你可以追焊电阻.但我用串口可以打印出一些运行时信息,比如程序运行到哪一步,变量的值,中断的类型等等.调试像usb这样比较复杂一点的东西的时候,我觉得串口是一个很好的工具.我只是觉得这个方法是我的一些小经验.在此提出来给大家分享而已.
lbing7 2007-01-25
  • 打赏
  • 举报
回复
是的,我在开发的时候我也用串口调试,当然是运行期和没有硬件仿真的情况下...

呵呵

lbing7 2007-01-24
  • 打赏
  • 举报
回复
Jim_King_2000() ( ) 信誉:100 Blog 2007-01-24 13:49:08 得分: 0

我一般用51都会把串口当调试口用,把调试信息dump到pc上.等到正式发布的时候再把它禁用。所以我觉得用assert没有什么不妥。至于lz的方法,就必须使用示波器了。如果用串口的话就不需要示波器。

=====================================
用示波器是可以,其实用万用表就搞定了,再简单点,可以用几个LED+小电阻排八个,直接就出来了
没必要上那么强的家伙

用串口,一般都得做个电平转换,这个不是所有的系统都有的.
有些时候甚至得为实验和最终产品做两套板子,而且,有时实验时没有的问题到了产品的板子上却出现了....

一件事,为什么要分两件来做呢?
lbing7 2007-01-24
  • 打赏
  • 举报
回复
muzixiaoli(mutou) ( ) 信誉:100 Blog 2007-1-22 22:34:57 得分: 0

KEIL的调试功能那么强大。一定要用ASSERT吗?

==========================================
有些运行期的事,不是三下五除二的仿真就能找出问题的
Jim_King_2000 2007-01-24
  • 打赏
  • 举报
回复
我一般用51都会把串口当调试口用,把调试信息dump到pc上.等到正式发布的时候再把它禁用。所以我觉得用assert没有什么不妥。至于lz的方法,就必须使用示波器了。如果用串口的话就不需要示波器。
muzixiaoli 2007-01-22
  • 打赏
  • 举报
回复
KEIL的调试功能那么强大。一定要用ASSERT吗?
lbing7 2006-12-29
  • 打赏
  • 举报
回复
51的资源有限,当然能省则省

但是,在调试期还是有这些小技巧的好

少进入泥潭,心情好点.
xiaohuzi1997 2006-12-28
  • 打赏
  • 举报
回复
我从来不在51里面加入assert,也不加入奇偶校验,能省则省!
hyg2008 2006-12-23
  • 打赏
  • 举报
回复
mark
lican990602 2006-12-06
  • 打赏
  • 举报
回复
while(1){p1.0=~p1.0}设断点用示波器测p1.0来进行调试.不过看输出结果还是得用串口.很少用assert()
lbing7 2006-12-06
  • 打赏
  • 举报
回复
楼上,其实就采用你的那个思路的.

只不过,如果代码里多处需要参数查看的时候,从BEAT版到发布版.是很郁闷的去一个一个删

有些时候,还不一定能找着在什么地方,系统集成的时候.

如果用ASSERT的话,那么可以在编译的时候,NODEBUG一个参数,把所有的宏都干掉.

是不是好多了?
seedundersnow 2006-12-05
  • 打赏
  • 举报
回复
把简单问题搞复杂了吧?
lbing7 2006-12-05
  • 打赏
  • 举报
回复
终于有拍砖的了,欢迎详细点的.
rogerfhl 2006-12-04
  • 打赏
  • 举报
回复
先帮顶了~
Leaveye 2006-12-04
  • 打赏
  • 举报
回复
挺好。

assert 之所以有用,是因为能及时得到错误的自动检查报告。
这就需要输出设备了,就单片机系统来讲,它的输出象你说的,就是几个 IO 脚,不加外部元件很难看到它的变化。
而这就依赖于它的外部硬件电路了,对不同的应用开发,这个是截然不同的。
所以。。不可避免的,任何一个 assert 实现在这里都有片面性的。
所以。实现一个你够用的就好了。

我记得以前是专门做了一个捕捉的函数,
测试的时候,在此函数尾的 while 1 处断点,而此函数的输入,包括字符串和数值,就体现了错误发现的位置。

仅供参考。
laiwusheng 2006-12-04
  • 打赏
  • 举报
回复
mark

27,514

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 单片机/工控
社区管理员
  • 单片机/工控社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧