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

发布者:温馨阳光最新更新时间:2016-03-28 来源: eefocus关键字:PIC  单片机  C语言编程 手机看文章 扫描二维码
随时随地手机看文章
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

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

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

关键字:PIC  单片机  C语言编程 引用地址:PIC 单片机 C 语言编程简介(4)

上一篇:PIC16F72单片机控制的电动自行车C源程序
下一篇:PIC 单片机 C 语言编程简介(3)

推荐阅读最新更新时间:2024-03-16 14:48

51单片机串行口及存储器工作原理分析
一、 51单片机串行口工作原理 MCS-51系列单片机片内有一个串行I/O端口,通过引脚RXD(P3.0)和TXD(P3.1)可与外设电路进行全双工的串行异步通信。 1.串行端口的基本特点 8031单片机的串行端口有4种基本工作方式,通过编程设置,可以使其工作在任一方式,以满足不同应用场合的需要。其中,方式0主要用于外接移位寄存器,以扩展单片机的I/O电路;方式1多用于双机之间或与外设电路的通信;方式2,3除有方式l的功能外,还可用作多机通信,以构成分布式多微机系统。 串行端口有两个控制寄存器,用来设置工作方式、发送或接收的状态、特征位、数据传送的波特率(每秒传送的位数)以及作为中断标志等。 串行端口有一个数据寄存器S
[单片机]
51<font color='red'>单片机</font>串行口及存储器工作原理分析
有关51单片机有关晶振的问题总结(干货)
在初学51单片机的时候,总是伴随很多有关于晶振的问题,其实晶振就是如同人的心脏,是血液的脉搏,把单片机的晶振问题搞明白了,51单片机的其他问题迎刃而解…… 有关51单片机有关晶振的问题一并总结出来,希望对学51的童鞋来说能有帮助。 一、为什么51单片机爱用11.0592MHZ晶振? 其一:因为它能够准确地划分成时钟频率,与UART(通用异步接收器/发送器)量常见的波特率相关。特别是较高的波特率(19600,19200),不管多么古怪的值,这些晶振都是准确,常被使用的。 其二:用11.0592晶振的原因是51单片机的定时器导致的。用51单片机的定时器做波特率发生器时,如果用11.0592Mhz的晶振,根据公式算下来需要定时器
[单片机]
基于单片机的FIash存储器坏块自动检测
  随着电子技术飞速发展,智能电子产品随处可见,如PC机、移动电话、PDA、数码相机、游戏机、数字电视等,而诸如此类的电子产品的核心器件往往离不开存储器。无论是从存储器的物理结构、存储容量、数据读写速度、可靠性、耐用性,还是产品的实用性方面。其种类繁多。然而由于种种原因,越来越多的电子产品采用数据传输快、容量大的NAND型Flash存储器。虽然NAND型Flash具有许多优点,但其有随机产生不可避免的坏块,如果不能很好解决该坏块将导致高故障率。因此,这里提出一种基于DSP的Flash存储器坏块自动检测系统。    1 系统设计方案   图l为Flash存储器坏块自动检测系统结构框图。   本系统设计采用AT89C51自动
[单片机]
MCS - 51单片机寄存器功能
21个特殊功能寄存器(52系列是26个)不连续地分布在128个字节的SFR存储空间中,地址空间为80H-FFH,在这片SFR空间中,包含有128个位地址空间,地址也是80H-FFH,但只有83个有效位地址,可对11个特殊功能寄存器的某些位作位寻址操作(这里介绍一个技巧:其地址能被8整除的都可以位寻址)。 在51单片机内部有一个CPU用来运算、控制,有四个并行I/O口,分别是P0、P1、P2、P3,有ROM,用来存放程序,有RAM,用来存放中间结果,此外还有定时/计数器,串行I/O口,中断系统,以及一个内部的时钟电路。在单片机中有一些独立的存储单元是用来控制这些器件的,被称之为特殊功能寄存器(SFR)。这样的特殊功能寄存器51单片机
[单片机]
MCS - 51<font color='red'>单片机</font>寄存器功能
Atmel推出面向汽车的Cortex-M7 MCU
Atmel面向汽车、物联网和工业市场推出最高性能的ARM Cortex-M7系列MCU,具备优越的内存架构和连接能力 拓展了Atmel | SMART MCU产品系列,超越行业最高性能的ARM Cortex -M处理器系列MCU,CoreMark评分高达1500分 具备性能卓越的连接能力和独特内存架构,针对实时决定性代码执行和低延迟外设数据访问实现了优化 业内首款符合汽车使用要求的Cortex-M7系列MCU,为实现汽车联网和音频应用程序提供了以太网AVB和媒体LB外设功能 全球微控制器(MCU)和触控解决方案领域的领导者Atmel 公司(NASDAQ:ATML)近日发布了4个新系列产品,均
[汽车电子]
8051单片机指令定义详解——AJMP addr11(4)
8051单片机指令定义详解 (AJMP addr11) AJMP addr11 功能:绝对跳转。 描述:AJMP指令用于将程序转到相应的目的地址去执行,该地址在程序执行过程之中产生,由PC值(两次递增之后)的高5位、操作码的7-5位和指令的第2字节连接形成。要求跳转的目的地址和AJMP指令的后一条指令的第1字节位于同一2KB的程序存储页内。 示例:假设标号AJMADR位于程序存储器的0123H,指令 AJMP JMPADR 位于0345H,执行完该指令后PC值变为0123H。 AJMP addr11 字节数:2 执行周期:2 机器码:aaa00001 aaaaaaaa 注意:目的地
[单片机]
MCU价格腰斩,汽车芯片慌“拐点”已至?
一度价格攀高的半导体芯片近日出现拐点。据媒体报道,半导体芯片砍单降价风暴愈演愈烈,即便是之前供不应求的MCU的报价也开始出现大幅度降价。更有报道直指,全球前五大MCU厂出厂产品价格腰斩。至于台湾芯片代工行业的龙头老大——台积电更是下调了其营收目标。而背后的原因是台积电的前三大客户——苹果、AMD和英伟达纷纷下调了订单。 曾经不起眼的汽车MCU为什么突然会短缺? 其实之前车企普遍缺少的是制程较低的MCU芯片,相反被三星、台积电等少数芯片代工企业掌握的高制程芯片产能反倒非常充足。其实之前汽车芯片短缺是由多个因素共同作用的结果: 不期而至的新冠疫情撕开了芯片短缺的口子。全球大部分汽车芯片的产能都位于东南亚以及日韩、台湾
[汽车电子]
<font color='red'>MCU</font>价格腰斩,汽车芯片慌“拐点”已至?
单片机开发调试应注意的问题
1、使用总线不外引的单片机 ·是最正统的单片机使用模式 ·符合小型、简单、可靠、廉价的单片机设计初衷 ·总线封闭的产品最可靠 2、使用单片机C语言编程 * C语言是简洁、高效、而又最贴近硬件的高级编程语言 * 90年代初单片机C语言就已成熟为专业水平的高级语言,不应再有顾虑 * 当前厂商在推出新的单片机产品时纷纷配套C语言编译器 3、使用中、高档的单片机仿真工具 * 只有中、高档仿真工具才能仿真总线封闭式的单片机 * 仿真器必须使用band-out chip或hooks chip * 应支持高级语言的调试,提供全数据类型的查看和修改 * 支持多家软件公司汇编和编译产生的目标代码格式 * 中档仿真器的起步要求是至少解决了上
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved