TDxxxV1.0
char office[10];
};
定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为class;另一个为字符数组,数组名为office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量class或存放字符数组office。
联合在硬件编程中的应用最大的好处在于对存储空间的控制,我们来看看下面的这段汇编代码:
.DATA
flag7 ds 1 …
micro_on_off_f equ flag7.0 grill_on_off_f equ flag7.1 motor_on_off_f equ flag7.2 uv_on_off_f equ flag7.3
… .CODE
…
b0bset motor_on_off_f
…
motor_on_off_f motor_port motor_on_off_f motor_port
;关炉灯
b0bts1 b0bclr b0bts0 b0bset b0bts0
grill_on_off_f
;关烧烤 ;关闭所有负载
;没有回到原来位置电机继续转
jmp $+3
b0bclr grill_port … clr flag7
这段代码中定义了一个变量flag7,占一个Byte。同时又对Flag7 的前面4个位进行定义。因为汇编对硬件操作的灵活性,所以能很容易的对一个Byte进行整体操作,也很容易对这个Byte 的每个位进行操作。我们用struct定义位域的方法,在C 中也能对位进行单独的操作而不用通过与或等运算。我曾想用下面的方法来定义:
Unsigned int Flag7;
Struct bitFlag{ Unsigned bit0:1; Unsigned bit1:1;
Unsigned bit2:1; Unsigned bit3:1; Unsigned null:4;
}pFlag7;
我想用一个结构体指针指向Flag7,但是我发现并不能达到我的需求目的,那么该怎么来
36
TDxxxV1.0
实现这上面的汇编所需实现的功能呢?当然,如果细想的话会有很多种方法,其中定义联合体就是一个很好的方法。我们在C中为了能对位进行直接的操作,一般都会定义一个以下形式的结构体:
Struct bitDefine{ Unsigned bit0:1; Unsigned bit1:1;
Unsigned bit2:1; Unsigned bit3:1; Unsigned bit4:1; Unsigned bit5:1; Unsigned bit6:1; Unsigned bit7:1;
};
那我们就可以利用这个定义来声明一个结构体放到一个联合体里,让它与一个int型变量共存。如:
union flagWord
{
unsigned int flagByte; bitdefine flagBit;
};
我们就可以通过union来对相同地址的Byte和Bit进行操作。 对于上面的这些功能我们得到下面的C程序: FlagWord flag7;
Flag7.flagBit.bit2 = 1;
;没有回到原来位置电机继续转
…
If(flag7.flagBit.bit2) motor_port =1; Else motor_port = 0; ;关炉灯 …
If(!flag7.flagBit.bit1) grill_port = 0;
;关烧烤
Else …
Flag7.flagByte = 0; ;关闭所有负载 这段C程序可以实现上面汇编一模一样的功能
同样的,将一个Long型数据与两个int型数据的结构体组成Union,如:
union longtype
{ };
37
unsigned long longV; struct inttype {
unsigned int int_l,int_h; }intV;
TDxxxV1.0
通过定义longType 的实例,我们就可以既对一个long型数据进行操作,又可以对数据的高低字节进行操作,而不用通过与或运算或移位就可以实现。这在我们的8-bit单片机的编程中,会用的比较频繁。这种的实现既兼顾了C的方便特性,又能有汇编的灵活性。这大概是union的最大优势了。
七、 中断
在实时性程序中,中断功能是一个非常重要的功能,中断的实现状况会直接影响程序中的计时计数,更可能影响程序功能的正确实现。因此如何来实现中断是一个很有价值的问题。那么在SN8 C中又如何来实现中断?
7.1 中断函数的定义
在芯片资源当中,大多是通过向量列表跳转的方法来实现程序的中断。在标准C中,所有的功能都是通过函数来实现,但是都有固定的中断函数,如int86()等,它们也都指向一个固定的中断向量,如0x80等系统固定的资源。
在8-bit Sonix 芯片当中,都有一个中断向量0x08,我们来看看汇编的中断程序实现:
.CODE org 00h
jmp Main_ST
org 08H jmp int_ser
…
int_ser: int_ser0:
b0xch a,accbuf mov a,pflag b0mov pflagbuf,a nop
fp00ien
int_ser10: b0bts1
jmp int_ser11 b0bts0 fp00irq jmp int_ser20 ;p00中断 b0bts1
ft0ien
38
int_ser11:
TDxxxV1.0
jmp int_ser19 b0bts0 ft0irq jmp int_ser40
;t0中断
int_ser19: jmp int_ser9 int_ser20:
b0bclr
fp00irq
;activation
;p00中断
jmp int_ser9 int_ser40:
b0bclr b0bset
ft0irq t0int t0c,#64h
;T0中断
;activation
mov_ int_ser9:
b0mov a,pflagbuf mov pflag,a b0xch
a,accbuf
reti …
上面的这段程序完成了从0x08的地址跳转并且根据设定的优先顺序判断中断类别(int_ser10—int_ser19),然后进行处理(int_ser20—int_ser40)的任务。其中中断开始和结束分别进行了寄存器的数据的Push和pop实现。
在SN8 C中,通过一个特殊函数来完成相同功能,这是一个SN8 C专有的函数,用关键字__interrupt 来声明。__interrupt关键字(以两个底线开头)指示函数是要作中断向量的处理函数。中断向量函数是一个无参数, 也无返回值的特殊函数。
其声明方式如下:
__interrupt MyHandler () { .... }
我们来看一个SN8 C的中断函数: __interrupt ints (void) //中断程序入口 ;1ms {
if(INTRQ&0x10) t0ints();
else if(INTRQ&0x01) p00ints(); //过零点中断
}
void t0ints(void) {
_bCLR(&INTRQ,4); t_loop_f = 1;
if(int_f) ++cnt11;
T0C+=t0int_val+1; _bCLR(&INTRQ,4); }
void p00ints(void) {
//有外部中断触发开始计时 继电器开关的时间 //T0中断数值重装
//20ms
39
TDxxxV1.0
_bCLR(&INTRQ,0); int_f = 1;
if(cnt11 >= 18) {
cnt11 = 0;
flag2.flagByte = 0xffU; ++t_ms2; if(t_ms2 == 50) { }
t_ms2= 0;
flag10.flagByte = 0xffU;
} }
我们看到__interrupt 函数非常简单,只是按顺序判断中断的类别,调用相应的处理函数。这里与汇编的区别就是没有0x08 的跳转和对寄存器的push和pop。原来中断向量进入点会备份所有寄存器,中断向量结束前, 前述备份的项目均会被还原。这些都是由编译器内部完成的。
事实上,这正是C语言的优势所在,只有跨过了硬件关联才使得C 有了跨平台特性!
用户若有其它额外的备份需求, 需要自行撰写inline assemble ( __asm(“asmcode”)或__asm { … } ),来完成或修改 sn8cc_macro.asm 中的 __pushInterruptSavedRegs 与 __popInterruptSavedRegs 。这里不对此进行详述!
7.2 中断过程的分析
中断产生后,系统都完成了哪些动作,对于单片机程序编写者来讲还是需要进了解才能使编写的程序达到最佳的效率。
② ① ④
③
图7-1 中断的过程
上图所描绘的是一个中断的过程。程序在主循环中运行到①的时候,中断条件成立,系统产生中断,此时,系统将会去执行中断程序。而为了能从中断中正确返回,在进入中断程序之前,系统会对当前的状态(ACC,PC等等)进行保存。然后,在②处程序运行进入中断程序,
40