C 和汇编混合编程
有两个原因决定了用 C
机的一些特殊指令操作在标准的 C 语言语法中没有直接对应的描述,例如 PIC 单片机的清
看门狗指令“clrwdt”和休眠指令“sleep”;单片机系统强调的是控制的实时性,为了实现这
一要求,有时必须用汇编指令实现部分代码以提高程序运行的效率。这样,一个项目中就会
出现 C 和汇编混合编程的情形,我们在此讨论一些混合编程的基本方法和技巧。
11.9.1
在
汇编指令,PICC 提供了一个类似于函数的语句:
asm(“clrwdt”);
双引号中可以编写任何一条 PIC 的标准汇编指令。例如:
for (;;) {
}
//休眠
//空操作延时
例 11-8
如果需要编写一段连续的汇编指令,PICC 支持另外一种语法描述:用“#asm”开始汇
编指令段,用“#endasm”结束。例如下面的一段嵌入汇编指令实现了将
RAM 全部清零:
#asm
#endasm
例 11-9
11.9.2
C 语言中定义的全局或静态变量寻址是最容易的,因为这些变量的地址已知且固定。按
C 语言的语法标准,所有 C 中定义的符号在编译后将自动在前面添加一下划线符“_”,因
此,若要在汇编指令中寻址 C 语言定义的各类变量,一定要在变量前加上一“_”符号,我
们在上面例 11-9 中已经体现了这一变量引用的法则,因为 FSR 和 INDF 等所有特殊寄存器
是以 C 语言语法定义的,因此汇编中需要对其寻址时前面必须添加下划线。
对于 C 语言中用户自定义的全局变量,用行内汇编指令寻址时也同样必须加上“_”
下面的例 11-10 说明了具体的引用方法:
volatile unsigned char tmp;
void Test(void)
{
clrf
#endasm
}
}
//测试程序
//开始行内汇编
//选择 bank0
//设定初值
//tmp=0x10
//结束行内汇编
//开始 C 语言程序
例 11-10
上面的例子说明了汇编指令中寻址 C 语言所定义变量的基本方法。PICC 在编译处理嵌
入的行内汇编指令时将会原封不动地把这些指令复制成最后的机器码。所有对 C 编译器所
作的优化设定对这些行内汇编指令而言将不起任何作用。编程员必须自己负责编写最高效的
汇编代码,同时处理变量所在的 bank 设定。对于定义在其它 bank 中的变量,还必须在汇编
指令中加以明确指示,见例 11-11 的代码范例。
volatile bank1 unsigned char tmpBank1;
volatile bank2 unsigned char tmpBank2;
volatile bank3 unsigned char tmpBank3;
void Test(void)
{
bcf
bsf
movlw
movwf
bsf
bcf
movlw
//测试程序
//开始行内汇编
//选择 bank1
//设定初值
//tmpBank1=0x10
//选择 bank2
//设定初值
movwf
bsf
bsf
movlw
//选择 bank3
//设定初值
movwf
}
#endasm
//结束行内汇编
例 11-11
通过上面的代码实例,我们可以掌握这样一个规律:在行内汇编指令中寻址 C
义的全局变量时,除了在寻址前设定正确的 bank 外,在指令描述时还必须在变量上异或其
所在 bank 的起始地址,实际上位于 bank0 的变量在汇编指令中寻址时也可以这样理解,只
是异或的是 0x00,可以省略。如果你了解 PIC 单片机的汇编指令编码格式,上面异或的 bank
起始地址是无法在真正的汇编指令中体现的,其目的纯粹是为了告诉 PICC 连接器变量所在
的 bank,以便连接器进行 bank 类别检查。
11.9.3
前面已经提到,PICC 对自动型局部变量(包括函数调用时的入口参数)采用一种“静
态覆盖”技术对每一个变量确定一个固定地址(位于
址时只需采用数据寄存器的直接寻址方式即可,唯一要考虑的是如何才能在编写程序时知道
这些局部变量的寻址符号(具体地址在最后连接后才能决定,编程时无需关心)。一个最实
用也是最可靠的方法是先编写一小段
考图 11-5(B)对话框选择“Compile to assembly only”,把此 C 原代码编译成对应的 PICC 汇
编指令;查看 C 编译器生成的汇编指令是如何寻址这些局部变量的,你自己编写的行内汇
编指令就采用同样的寻址方式。例如,例 11-12 的一小段 C 原代码编译出的汇编指令
//C 原程序代码
void Test(unsigned char inVar1, inVar2)
{
inVar1++;
inVar2--;
tmp1 = 1;
tmp2 = 2;
}
//编译器生成的汇编指令
_Test
;
_Test$tmp1 set
?a_Test
;
_Test$tmp2 set
?a_Test+1
;
_Test$inVar1 set ?a_Test+2
44line
;_inVar1 stored from w
bcf
bcf
movwf ?a_Test+2
//第一个字符型行参由 W 寄存器传递
;ht16.c: 43: unsigned char tmp1, tmp2;
incf
45line
;ht16.c: 45: inVar2--;
decf
46line
;ht16.c: 46: tmp1 = 1;
clrf
incf
47line
;ht16.c: 47: tmp2 = 2;
movlw 2
movwf ?a_Test+1
48line
;ht16.c: 48: }
return
//行参 inVar2 的寻址符为 ?_Test
例 11-12
基于上面得到的 PICC 编译后局部变量的寻址方式,我们在 C 语言程序中用嵌入汇编指
令时必须采样同样的寻址符以实现对应变量的存取操作,见下面的例 11-13。
//C 原程序代码
void Test(unsigned char inVar1, inVar2)
{
#asm
//开始嵌入汇编
incf
decf
movlw
addwf
rrf
0x10
?a_Test+0,f
?a_Test+1,f
?a_Test+2,f
?_Test,w
//tmp1++;
//tmp2--;
//inVar1 +=
//inVar2 循环右移一位
}
rrf
#endasm
?_Test,f
//结束嵌入汇编
例 11-13
如果局部变量为多字节形式组成,例如整型数、长整型等,必须按照 PICC 约定的存储
格式进行存取。前面已经说明了 PICC 采用“Little endian”格式,低字节放在低地址,高字
节放在高地址。下面的例 11-14 实现了一个整型数的循环移位,在 C 语言中没有直接针对循
环移位的语法操作,用标准 C 指令实现的效率较低。
//16 位整型数循环右移若干位
unsigned int RR_Shift16(unsigned int var, unsigned char count)
{
}
while(count--)
{
#endasm
}
return(var);
//移位次数控制
//开始嵌入汇编
//最低位送入 C
//var 高字节右移 1 位,C 移入最高位
//var 低字节右移 1 位
//结束嵌入汇编
//返回结果
例 11-14
11.9.4
C
最佳的配合。笔者从实际应用中得到一些经验供读者一起分享。
㈠
相比于汇编语言,用 C
语句指令加上模块化的程序易于日常管理和维护、程序在不同平台间的移植方便。所以既然
用了 C 语言编程,就尽量避免使用嵌入汇编指令或整个地编写汇编指令模块文件。PICC 已
具备高效的优化功能,如果在写 C 原程序时就十分注意程序的编译和运行效率问题,加上
PICC 的后道编译优化,最后得到的代码效率不会比全部用汇编编写的代码差多少,尤其是
程序量较大时。另外,PICC 对数据存储空间的利用率肯定比用户人工定位变量时的利用率
要高,同时还提供完整的库函数支持。C 语言的语法功能强大,能够高效率地实现绝大部分
控制和运算功能。因此,除了一些十分强调单片机运行时间的代码或 C 语言没有直接对应
的操作可以考虑用汇编指令实现外,其它部分都应该用 C 语言编写。
以上面的例 11-14 进一步说明,变量的循环右移操作用 C 语言实现非常不方便,PIC 单
片机已有对应的移位操作汇编指令,因此用嵌入汇编的形式实现效率最高。同时对移位次数
的控制,本质上说变量 count 的递减判零也可以直接用汇编指令实现,但这样做节约不了多
少代码,用标准的 C 语言描述更直观,更易于维护。
一句话:用了 C 语言后,就不要再老想着用汇编。
㈡
这和上面的慎用汇编指令的说法并不矛盾。如果确实需要用汇编指令实现部分代码以提
高运行效率,应尽量使用行内汇编,避免编写纯汇编文件(*.as 文件)。
虽然 PICC 支持 C 和汇编原程序模块存在于同一个项目中,但要编写纯汇编文件必须首
先了解 PICC 特有的汇编语法结构。Hitech 公司提供了完整的文档介绍其汇编器的使用方法,
有兴趣者可以从其网站上下载 PICC 的用户使用手册查看。
笔者认为,类似于纯汇编文件的代码也可以在 C 语言框架下实现,方法是基于 C 标准
语法定义所有的变量和函数名,包括需要传递的形式参数、返回参数和局部变量,但函数内
部的指令基本用嵌入汇编指令编写,只有最后的返回参数用 C 语句实现。这样做后函数的
运行效率和纯汇编编写时几乎一模一样,但各参数的传递统一用
维护就比较方便。例如下面的例 11-15 实现一个字节变量的偶校验位计算。
bit EvenParity(unsigned char data)
{
}
//入口参数 data 的寻址符为 ?a_EvenParity+0
例 11-15
㈢
使用全局变量最大的好处是寻址直观,只需在 C 语言定义的变量名前增加一个下划线
符即可在汇编语句中寻址;使用全局变量进行参数传递的效率也比形参高。编写单片机的 C
程序时不能死硬强求教科书上的模块化编程而大量采用行参和局部变量的做法,在开发编程
时应视实际情况灵活变通,一切以最高的代码效率为目标。
上一篇:PIC16F72单片机控制的电动自行车C源程序
下一篇:PIC 单片机 C 语言编程简介(3)
推荐阅读最新更新时间:2024-03-16 14:48