单片机
返回首页

PIC 单片机 C 语言编程简介(4)

2016-03-28 来源:eefocus

11.9

C 和汇编混合编程

有两个原因决定了用 C  语言进行单片机应用程序开发时使用汇编语句的必要性:单片


机的一些特殊指令操作在标准的 C 语言语法中没有直接对应的描述,例如 PIC 单片机的清

看门狗指令“clrwdt”和休眠指令“sleep”;单片机系统强调的是控制的实时性,为了实现这

一要求,有时必须用汇编指令实现部分代码以提高程序运行的效率。这样,一个项目中就会

出现 C 和汇编混合编程的情形,我们在此讨论一些混合编程的基本方法和技巧。

11.9.1     嵌入行内汇编的方法

在  原程序中直接嵌入汇编指令是最直接最容易的方法。如果只需要嵌入少量几条的

汇编指令,PICC 提供了一个类似于函数的语句:

asm(“clrwdt”);

双引号中可以编写任何一条 PIC 的标准汇编指令。例如:

for (;;) {

   asm('clrwdt');       //清看门狗

   Task();

   ClockRun();


   asm('sleep');

   asm('nop'); 

}


//休眠

//空操作延时

 

 

例 11-8       逐行嵌入汇编的方式

如果需要编写一段连续的汇编指令,PICC 支持另外一种语法描述:用“#asm”开始汇

编指令段,用“#endasm”结束。例如下面的一段嵌入汇编指令实现了将       0x20~0x7F  间的

RAM 全部清零:

#asm

   movlw    0x20

   movwf    _FSR

   clrf     _INDF

   incf     _FSR,f

   btfss    _FSR,7

   goto     $-3

#endasm

例 11-9    整段嵌入汇编的方式

11.9.2     汇编指令寻址 C 语言定义的全局变量

C 语言中定义的全局或静态变量寻址是最容易的,因为这些变量的地址已知且固定。按

C 语言的语法标准,所有 C 中定义的符号在编译后将自动在前面添加一下划线符“_”,因

此,若要在汇编指令中寻址 C 语言定义的各类变量,一定要在变量前加上一“_”符号,我

们在上面例 11-9 中已经体现了这一变量引用的法则,因为 FSR 和 INDF 等所有特殊寄存器

是以 C 语言语法定义的,因此汇编中需要对其寻址时前面必须添加下划线。

对于 C 语言中用户自定义的全局变量,用行内汇编指令寻址时也同样必须加上“_”   

下面的例 11-10 说明了具体的引用方法:

volatile unsigned char tmp;          //定义位于 bank0 的字符型全局变量

void Test(void)

{

   #asm

clrf     _STATUS 

   movlw    0x10

   movwf    _tmp

#endasm

   if (tmp==0x10) {

      ;

}

}

//测试程序

//开始行内汇编

//选择 bank0

//设定初值

//tmp=0x10

//结束行内汇编

//开始 C 语言程序


例 11-10       行内汇编寻址 C 全局变量(位于 bank0)

上面的例子说明了汇编指令中寻址 C 语言所定义变量的基本方法。PICC 在编译处理嵌

入的行内汇编指令时将会原封不动地把这些指令复制成最后的机器码。所有对 C 编译器所

作的优化设定对这些行内汇编指令而言将不起任何作用。编程员必须自己负责编写最高效的

汇编代码,同时处理变量所在的 bank 设定。对于定义在其它 bank 中的变量,还必须在汇编

指令中加以明确指示,见例 11-11 的代码范例。

volatile bank1 unsigned char tmpBank1;      //定义位于 bank1 的字符型全局变量

volatile bank2 unsigned char tmpBank2;      //定义位于 bank2 的字符型全局变量

volatile bank3 unsigned char tmpBank3;      //定义位于 bank3 的字符型全局变量

void Test(void)

{

   #asm

bcf      _STATUS,6

bsf      _STATUS,5

movlw   0x10

movwf    _tmpBank1^0x80

bsf      _STATUS,6

bcf      _STATUS,5

movlw   0x20

//测试程序

//开始行内汇编

//选择 bank1

//设定初值

//tmpBank1=0x10

//选择 bank2

//设定初值


movwf    _tmpBank1^0x100         //tmpBank2=0x20

bsf      _STATUS,6

bsf      _STATUS,5

movlw   0x30


//选择 bank3

//设定初值


movwf    _tmpBank1^0x180         //tmpBank1=0x30


}


#endasm


//结束行内汇编

例 11-11       行内汇编寻址 C 全局变量(非 bank0 变量)


通过上面的代码实例,我们可以掌握这样一个规律:在行内汇编指令中寻址 C  语言定

义的全局变量时,除了在寻址前设定正确的 bank 外,在指令描述时还必须在变量上异或其

所在 bank 的起始地址,实际上位于 bank0 的变量在汇编指令中寻址时也可以这样理解,只

是异或的是 0x00,可以省略。如果你了解 PIC 单片机的汇编指令编码格式,上面异或的 bank

起始地址是无法在真正的汇编指令中体现的,其目的纯粹是为了告诉 PICC 连接器变量所在

的 bank,以便连接器进行 bank 类别检查。

11.9.3     汇编指令寻址 C 函数的局部变量

前面已经提到,PICC 对自动型局部变量(包括函数调用时的入口参数)采用一种“静

态覆盖”技术对每一个变量确定一个固定地址(位于    bank0),因此嵌入的汇编指令对其寻

址时只需采用数据寄存器的直接寻址方式即可,唯一要考虑的是如何才能在编写程序时知道

这些局部变量的寻址符号(具体地址在最后连接后才能决定,编程时无需关心)。一个最实

用也是最可靠的方法是先编写一小段  C 代码,其中有最简单的局部变量操作指令,然后参

考图 11-5(B)对话框选择“Compile to assembly only”,把此 C 原代码编译成对应的 PICC 汇

编指令;查看 C 编译器生成的汇编指令是如何寻址这些局部变量的,你自己编写的行内汇

编指令就采用同样的寻址方式。例如,例 11-12 的一小段 C 原代码编译出的汇编指令

//C 原程序代码

void Test(unsigned char inVar1, inVar2) 

{

   unsigned char tmp1, tmp2;

inVar1++;

inVar2--;

tmp1 = 1;

tmp2 = 2;

}

//编译器生成的汇编指令

_Test

     _tmp1 assigned to ?a_Test+0      //tmp1 的寻址符为  ?a_Test+0


_Test$tmp1 set


?a_Test


     _tmp2 assigned to ?a_Test+1      //tmp2 的寻址符为  ?a_Test+1


_Test$tmp2 set


?a_Test+1


     _inVar1 assigned to ?a_Test+2     //inVar1 的寻址符为  ?a_Test+2

_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 实现局部变量操作的寻址方式

基于上面得到的 PICC 编译后局部变量的寻址方式,我们在 C 语言程序中用嵌入汇编指

令时必须采样同样的寻址符以实现对应变量的存取操作,见下面的例 11-13。

//C 原程序代码

void Test(unsigned char inVar1, inVar2) 

{

   unsigned char tmp1, tmp2;

#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--)

{

   #asm 

   rrf  ?_RR_Shift16+0,w

   rrf  ?_RR_Shift16+1,f

   rrf  ?_RR_Shift16+0,f

#endasm

}

return(var);


//移位次数控制

//开始嵌入汇编

//最低位送入 C

//var 高字节右移 1 位,C 移入最高位

//var 低字节右移 1 位

//结束嵌入汇编

//返回结果


例 11-14      嵌入汇编指令对多字节变量的操作

11.9.4     混合编程的一些经验

和汇编语言混合编程可以使单片机应用程序的开发效率和程序本身的运行效率达到

最佳的配合。笔者从实际应用中得到一些经验供读者一起分享。

㈠  慎用汇编指令

 

相比于汇编语言,用 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 语句实现。这样做后函数的

运行效率和纯汇编编写时几乎一模一样,但各参数的传递统一用  C 标准实现,这样管理和

维护就比较方便。例如下面的例 11-15 实现一个字节变量的偶校验位计算。

bit EvenParity(unsigned char data)

{

    #asm


    swapf   ?a_EvenParity+0,w

    xorwf   ?a_EvenParity+0,f

    rrf     ?a_EvenParity+0,w

    xorwf   ?a_EvenParity+0,f

    btfsc   ?a_EvenParity+0,2

    incf    ?a_EvenParity+0,f

    #endasm

    //至此,data 的最低位即为偶校验位

    if  (data&0x01)  return(1);

    else  return(0);

}


//入口参数 data 的寻址符为 ?a_EvenParity+0


例 11-15  C 函数框架中使用嵌入汇编指令

㈢  尽量使用全局变量进行参数传递

 

使用全局变量最大的好处是寻址直观,只需在 C 语言定义的变量名前增加一个下划线

符即可在汇编语句中寻址;使用全局变量进行参数传递的效率也比形参高。编写单片机的 C

程序时不能死硬强求教科书上的模块化编程而大量采用行参和局部变量的做法,在开发编程

时应视实际情况灵活变通,一切以最高的代码效率为目标。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 用数字电路CD4069制作的万能遥控轻触开关

  • 红外线探测报警器

  • 短波AM发射器电路设计图

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 带有短路保护系统的5V直流稳压电源电路图

  • 基于ICL296的大电流开关稳压器电源电路

    相关电子头条文章