其于Keil C51的断言实现
在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不允许贴图,不然我能把更多的测试信息弄出来.