TDxxxV1.0
__SelectBANK _captrueZero_arg0 MOV _captrueZero_arg0, A ;End push arg....
call _captrueZero ;end of function call L451:
.stabn 0x44,0,587,L452-_procADC L452:
jmp L431
而对系统寄存器的位判断则允许有下面三种方式: 一是用上面提到的专用函数:
if(_bTest1(&INTRQ,4)) {
_bCLR(&INTRQ,4);
//FT0IRQ)
//FT0IRQ=0;
T0INT(); }
一是用系统定义的位名称:
if(FT0IRQ) {
//(_bTest1(&INTRQ,4))
//FT0IRQ=0;
_bCLR(&INTRQ,4); T0INT();
}
还有就是用位运算的方法:
if(INTRQ&0x10) { _bCLR(&INTRQ,4);
//FT0IRQ //FT0IRQ=0;
T0INT(); }
但是,遗憾的是上面3种方式产生代码的效率并不一样。在编程过程中比较你会发现第三种方式产生的代码最短,所以建议使用。
当然,它们的运行效果是一样的,都能起到分支判断的作用。
九、 内嵌汇编
C语言和别的语言一样,虽然具有非常多的优点,但也免不了存在一些局限性。有的时候,对硬件操作的功能无法实现,或者实现的代码所花费的时间空间代价太大。这时,我们还是不得不考虑使用汇编,毕竟其具有高效率和紧贴硬件的特性。正出于这样的考虑,SN8 C支持在C程序中内嵌汇编。
46
TDxxxV1.0
9.1 如何内嵌汇编
和一般的C语言一样,SN8 C 提供专门的关键字__asm(两个下划线)用于在C的源代码内嵌入汇编。
__asm关键词有以下两种用法:
__asm(“code\\n”)
__asm { asm_text }
这两种方式都可以在C中内嵌汇编,在C 中是作为一条语句来处理,当它处在顶层时(也就是说没有被任何函数所包含)就可以内嵌任意的汇编码到输出的汇编码中。
在asm_text中的Macros和其他的C指示性语句在预处理中不被处理,除非选择cpp_noskip_asmblock编译选项。cpp_noskip_asmblock选项会对插入到asm_text 中的cpp指示性语句和Macros进行处理。在asm_text 中的C和C++ 类型的注释仍然会被忽略。在asm_text里面,不能带有右括符(i.e., “)”),除非这个符号是字符串附带的。
我们来看看在C中嵌入汇编的例子:
if(step == ONE_PRESS_CLOCK_KEY_C) { //调节时钟时时间闪烁 disp_blink ^= 0x03; //闪烁小时位
disp_blink &=0x03; __asm {
mov mov
a,0x01 _led_dp,a
//两点常亮
};
//led_dp = 1;
}
我们来看看其产生的汇编码,看看内嵌汇编语句是怎么被处理的:
L125:
__SelectBANK _step MOV A, (_step+0)
SUB A, #0x10 JNZ L93 L128:
__SelectBANK _disp_blink MOV A, #3
XOR _disp_blink, A L129:
;__SelectBANK _disp_blink MOV A, #3
AND _disp_blink, A
47
TDxxxV1.0
L130: mov L131: mov L132: L133:
jmp L94
a,0x01 _led_dp,a
我们看到内嵌的汇编被原样转换嵌入生成的汇编码当中(L130,L131),那么其运行的结果自然就是我们所需要的了。
9.2 内嵌汇编时变量的传递
我们知道,在汇编当中与在C当中定义变量的方法是不同的。但是,我们在内嵌汇编当中也需要对由C定义的变量进行操作。该如何来处理这样的一个矛盾呢?
如下C程序代码:
void func(void) {
int x;
__asm { …
;; 存取局部变量 x. … }
return; }
经编译的结果:
C:\\sn8cc>sn8cc +w prog.c SN8CCWARN@(`prog.c' 3): source_warning: local `int x' is not referenced prog.c: 0 error(s), 1 warning(s)
上例中, 因为sn8cc认为变量x没有用到(sn8cc不检查内嵌式汇编码), +w编译选项会让sn8cc报告警告讯息。且不会为变量x配置空间。这样内嵌式组语码内所存取的位置是错误的。
为了解决这样的问题,SN8 C 专门引入“#pragma ref id”预处理指令,上述问题可用“#pragma ref id”通知sn8cc, 变数x是有用到的:
void func(void) {
int x;
#pragma ref x __asm { …
;; 存取局部变量 x. … }
return; }
48
TDxxxV1.0
这样sn8cc才不会报告警告讯息。
而我们还会面对的一种情况是在内嵌汇编当中要对全局变量进行读写操作,这又是一个什么情况呢?
假如我们直接应用C定义的名称,如:
Unsigned int Ver1; Void func(void) { }
编译则系统会提示出错!警告用户没有Ver1这样一个变量!这时候很奇怪了,明明有定义这样一个变量,可是系统就是提示Error!
其实,这就是由于在编译当中,编译器会对内嵌汇编进行原样转换所引发的错误。我们来看前面嵌入汇编的例子中的全局变量在产生的.Asm档中,对全局变量是怎么转换的。
C源程序中对全局变量的定义如下:
union flagWord2 {
unsigned int flagByte; struct bitdefine2 {
unsigned bit0:1; unsigned bit1:1; unsigned bit2:6;
…
__asm{ Mov … }; …
ver1,#0x5a;
}flagBit;
}led_dp;
unsigned int door_cnt; unsigned int door_cnt1; unsigned int door_cnt2;
而这些定义在生成的汇编中被转换成如下形式:
.stabs \
.stabs \.stabs \.stabs \
原来它们将每一全局变量的前面都增加了一个“_”来标识它们!
那么我们就可以找到解决的方法了,我们在嵌入的汇编中,如果要读写全局变量,就在它们的前面加上一个“_”,其实我们提到的例子中的led_dp就是这样子的!回过头去看看这条语
49
TDxxxV1.0
句:
mov _led_dp,a
上面的led_dp就是一个全局变量,而在内嵌汇编码中被增加了一条下划线。
需要提醒用户的是,变量转换的语法并不属于程序员掌握的范畴,这里也没有详细介绍,使用当中尽量少用这方面的转换,以免引起程序查错上的麻烦。这些应用必须是对生成.asm档有一定的了解才行。
十、SN8 C程序的结构组织
在程序的结构上,C同样与汇编有很大的区别。在汇编程序中,大多的程序员喜欢应用跳转,在程序中会安排许多的跳转指令,以此来控制程序的流程。而C则反对在程序中进行跳转,因为这样会破坏C 的模块特性,影响其结构化编程语言的优势。而单片机的C语言可以说应该介于他们之间。
10.1 主函数与子函数
C程序都会有且只有一个main()函数,并且将它作为整个程序的入口。这是一个非常重要的函数,我们都称之为主函数。而相对于主函数而言完成相对模块功能的直接或间接被主函数调用的这些功能函数,我们一般都把它称之为子函数。在C 当中,main()函数在整个的程序流程当中起着主控作用。
我们来看看怎么样来构建一个程序才能使其完全符合我们的需求呢?
我们是针对单片机的C语言,因此实时性是对程序一个非常普遍的要求,也是不同于运行于通用计算机上的C程序的特别的要求,我们的程序必须在最快的情况下,检测到外部条件的改变,在最短的时间内作出正确的响应,并且响应的同时又不能落下任何外部的请求。这样才能使我们的系统适用瞬息万变的环境,并发挥优异的性能,这才是我们需要的系统。
怎么样用好我们的芯片有限的处理能力,来完成如此复杂的任务? 对于整个程序而言,安排好main()函数对其他子函数的调用起着至关重要的作用。我们知道,大多任务我们没法在1毫秒内完成,有的甚至需要很长时间的延时,而在很短的时间内环境有可能发生改变,外部可能产生很多个请求。怎么样让程序既能完成任务(响应)又能及时接受外部请求及时处理?面对这样的情况我们就很自然会想起问题的优先分配,和任务的分时处理。当然我们没必要去开发一个操作系统来管理他们,我们只要调整我们的程序架构就可以做到这一点。
我们把所有的任务都分别用一个标志位来标示他们是不是完成,对于完成的任务会产生什么样新的需求也用一个标志来标识它。而对于正在进行的任务我们每次只去完成应该完成的一部分,将这些正在完成的任务轮换进行处理直到完成。改换这一思想是实现我们的编程方案的关键。我们来看看我们熟悉的按键扫描吧,传统上我们会按下面的方法来扫描判断按键:
50

