目录

让汇编象C一样强壮

在大多数的单片机中,汇编是不可缺少的一种技能。他是最底层的一种语言,最直接的一种控制方式,也是最麻烦的、最容易出错的一种重复性工作。

汇编的出错大多在内存单元的分配。因为一般来说,你不能没有限制的定义静态变量,那么,一些动态的变量在没有办法的情况下,定义为相同的地址(不同的引用名字)。在不同的子程序中使用时,可能会把他们混淆(同时使用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。