在大多数的单片机中,汇编是不可缺少的一种技能。他是最底层的一种语言,最直接的一种控制方式,也是最麻烦的、最容易出错的一种重复性工作。
汇编的出错大多在内存单元的分配。因为一般来说,你不能没有限制的定义静态变量,那么,一些动态的变量在没有办法的情况下,定义为相同的地址(不同的引用名字)。在不同的子程序中使用时,可能会把他们混淆(同时使用2个不同名字的同一个地址单元)。
而在 C 中,不需要你考虑变量的分配,尤其是动态变量的分配。另外,在汇编中,调用子函数时,你可能经常记不起需要调用的参数是那些,或者是入口参数应该存在什么单元中。
我们在这个时候,就需要学习 C 的一些技巧,来让我们的汇编更加容易编写、更加稳定,就是一般说的“强壮”。
首先,考虑内存定义的问题 。。。。。。。养成良好的习惯!
区分变量:
- 静态(程序中保存全局数据的单元。)
- 通用(固定的用于函数调用,传递参数,保存返回值。通常可以用通用寄存器。)
- 动态(子函数中内部使用,退出函数时丢弃的数据。使用靠近堆栈的高端内存单元。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
;**
Org Ram
;———————————————————
; Static. << Public >> 静态
Total Ds 8 ; 静态:剩余量.
Ucode Ds 8 ; 用户编号。
;———————————————————
; General Register Defined. 通用,十分重要的地方。
; 宏定义8个通用寄存器。十分固定的用于函数调用。决不做其他的用途。
Byte Xx,Xa,Loop,Ex,Tmp0,Tmp1,Tmp2,Tmp3
; 宏定义,同一个地址取不同的 3 组名字,每次只能使用同一组的名字调用。
Same Xx,Sour,Extadd ; X 临时保存 / 源地址 / 外部地址
Same Xa,Dest,Ramadd ; A 临时保存 / 目的地址 / 内部地址
;———————————————————
; Temp Data Group. 动态
Temp Ds 16
Q0 Equ Temp+8
;**
|
接着,定义函数调用的规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
;**
; Lcall:通用调用函数,宏。最底层函数,使用 X/A/Loop/Ex。
; 此例为使用Motorola汇编。十分重要的地方。用 Motorola 芯片是因为稳定、小巧、强大(看看手机)。
;**
Lcall Macro
Ifnc “\5”,”” ; 有第5个参数。出错。
Fail “Error, Parameter too long in LCALL.”
Endif
; Ifnc “\5”,”” ; 有第5个参数。存入Ex。
; Lda \5 ; 目前尚没有使用过。
; Sta Ex
; Endif
;——————————————————————–
Ifnc “\4”,”” ; 有第4个参数。存入Loop。
Ifc “\4”,”X” ; 第4个参数是X。
Stx Loop
Else
Ifc “\4”,”A” ; 第4个参数是A。
Sta Loop
Else ; 第4个参数不是A。
Sta Ex
Lda \4
Sta Loop
Lda Ex
Endif
Endif
Endif
;——————————————————————–
Ifnc “\3”,”” ; 有第3个参数。存入A。
Ifnc “\3”,”A” ; 第3个参数不是A。
Ifc “\3”,”X” ; 第3个参数是X。
Txa
Else
Lda \3
Endif
Endif ; 第3个参数是A。不操作。
Endif
;——————————————————————–
Ifnc “\2”,”” ; 有第2个参数。存入X。
Ifnc “\2”,”X” ; 第2个参数不是X。
Ifc “\2”,”A” ; 是A。
Ifnc “\3”,”” ; 有第3个参数。
Fail “Error, A Cannot Input Here in LCALL.”
Else ; 是A。但没有第3个参数。
Tax
Endif
Else
Ldx \2
Endif
Endif ; 第2个参数是X。不操作。
Endif
;——————————————————————–
Jsr \1 ; 调用第1参数函数。
Endm
;**
|
使用规则调用函数。
现在,让我们看看函数调用是什么样子的。参数小于等于 2 个时,使用 X/A 对系统通用寄存器。
1
|
Lcall Iszero,#Total,#8 ; Total 放入 X;8 放入 A。
|
这样,Iszero 函数也很好写。因为规则总是把第一个参数放入 X,第二个放入 A。第三个放入 Loop。第四个放入 Ex。处理时,次序很整齐,就不会出错了。再看一个。参数大于 2 个时,使用 Xx/Xa 对寄存器。
1
|
Lcall Copy,#Ic,#Total,#8
|
在 Lcall 中,首先第一句使用一个宏(XA2add)。则第一个参数放入 Xx,其实也就是 Sour(源地址)。第二个参数放入 Xa,其实也就是 Dest(目的地址)。XA2add 定义如下。
1
2
3
4
|
Xa2Add Macro
Stx Xx
Sta Xa
Endm
|
我的所有函数都是遵循这个原则。示例如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Lcall I.Read,Ex,#Ic,#8
Lcall Iszero,#Ic,#8
Ldx #No_D-Msg
Bcc Xgas1 ; 为全0,转移。
Lcall Isbcd,#Ic,#8
Ldx #Ic_E-Msg
Bcs Xgas ; 不是 BCD 码,出错处理。
Lcall Clear,#Temp,#8
Lcall I.Write,Ex,#Temp,#8 ; 此处大量使用动态单元。
Lcall I.Read,Ex,#Temp,#8
Lcall I.Read,Ex,#Temp,#8
Ldx #No_C-Msg
Bih Xgas
Lcall Iszero,#Temp,#8
Ldx #Ez_E-Msg
Bcs Xgas
|
大家看了,应该还可以吧。起码比较好看。容易看懂。不大会出错了。特别的是,从此,你的子函数可以做成标准的库了 !!!!!
当然,有钱的话,可以买 C 编译器。不过,一半的编译器有很多调用的限制,如 C68 只能传递 2 个参数,多余的参数需要自己先放在固定的单元,还是需要自己去记参数的位置。
我没有买,还因为他要$1100。太贵了。且不是我想象的 C。