下载

5下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 PIC单片机C语言编程教程

PIC单片机C语言编程教程.pdf

PIC单片机C语言编程教程

wuyd1980
2009-06-17 0人阅读 举报 0 0 暂无简介

简介:本文档为《PIC单片机C语言编程教程pdf》,可适用于IT/计算机领域

张明峰于上海of第十一章PIC单片机的C语言编程PIC单片机C语言编程简介用C语言来开发单片机系统软件最大的好处是编写代码效率高、软件调试直观、维护升级方便、代码的重复利用率高、便于跨平台的代码移植等等因此C语言编程在单片机系统设计中已得到越来越广泛的运用。针对PIC单片机的软件开发同样可以用C语言实现。但在单片机上用C语言写程序和在PC机上写程序绝对不能简单等同。现在的PC机资源十分丰富运算能力强大因此程序员在写PC机的应用程序时几乎不用关心编译后的可执行代码在运行过程中需要占用多少系统资源也基本不用担心运行效率有多高。写单片机的C程序最关键的一点是单片机内的资源非常有限控制的实时性要求又很高因此如果没有对单片机体系结构和硬件资源作详尽的了解以笔者的愚见认为是无法写出高质量实用的C语言程序。这就是为什么前面所有章节中的的示范代码全部用基础的汇编指令实现的原因希望籍此能使读者对PIC单片机的指令体系和硬件资源有深入了解在这基础之上再来讨论C语言编程就有水到渠成的感觉。本书围绕中档系列PIC单片机来展开讨论Microchip公司自己没有针对中低档系列PIC单片机的C语言编译器但很多专业的第三方公司有众多支持PIC单片机的C语言编译器提供常见的有Hitech、CCS、IAR、Bytecraft等公司。其中笔者最常用的是Hitech公司的PICC编译器它稳定可靠编译生成的代码效率高在用PIC单片机进行系统设计和开发的工程师群体中得到广泛认可。其正式完全版软件需要购置但在其网站上有限时的试用版供用户评估。另外Hitech公司针对广大PIC的业余爱好者和初学者还提供了完全免费的学习版PICCLite编译器套件它的使用方式和完全版相同只是支持的PIC单片机型号限制在PICF、PICF和PICF等几款。这几款Flash型的单片机因其所具备的丰富的片上资源而最适用于单片机学习入门因此笔者建议感兴趣的读者可从PICCLite入手掌握PIC单片机的C语言编程。在此列出几个主要的针对PIC单片机的C编译器相关连接网址供读者参考:HitechPICC:wwwhtsoftcomIAR:wwwiarcomCCS:wwwccsinfocompiccshtmlByteCraft:wwwbytecraftcommpccapshtml本章将介绍HitechPICC编译器的一些基本概念由于篇幅所限将不涉及C语言的标准语法和基础知识介绍因为在这些方面都有大量的书籍可以参考。重点突出针对PIC单片机的特点而所需要特别注意的地方。HitechPICC编译器PICC基本上符合ANSI标准除了一点:它不支持函数的递归调用。其主要原因是因为PIC单片机特殊的堆栈结构。在前面介绍PIC单片机架构时已经详细说明了PIC单片机张明峰于上海of中的堆栈是硬件实现的其深度已随芯片而固定无法实现需要大量堆栈操作的递归算法另外在PIC单片机中实现软件堆栈的效率也不是很高为此PICC编译器采用一种叫做“静态覆盖”的技术以实现对C语言函数中的局部变量分配固定的地址空间。经这样处理后产生出的机器代码效率很高按笔者实际使用的体会当代码量超过K字后C语言编译出的代码长度和全部用汇编代码实现时的差别已经不是很大(<)当然前提是在整个C代码编写过程中须时时处处注意所编写语句的效率而如果没有对PIC单片机的内核结构、各功能模块及其汇编指令深入了解要做到这点是很难的。MPLABIDE内挂接PICCPICC编译器可以直接挂接在MPLABIDE集成开发平台下实现一体化的编译连接和原代码调试。使用MPLABIDE内的调试工具ICE、ICD和软件模拟器都可以实现原代码级的程序调试非常方便。首先必须在你的计算机中安装PICC编译器无论是完全版还是学习版都可以和MPLABIDE挂接。安装成功后可以进入IDE选择菜单项ProjectÆSetLanguageToolLocations…打开语言工具挂接设置对话框如图所示:在对话框中选择“HITECHPICCToolsuite”栏展开可执行文件组“Executable”后列出了将被MPLABIDE后台调用的编译器所用到的所有可执行文件其中有汇编编译器“PICCAssembler”、C原程序编译器“PICCCompiler”和连接定位程序“PICCLinker”。同时在此列表中还显示了对应的可执行程序名请注意在这里都是“PICCEXE”。用鼠标分别点击选中这三项可执行文件观察对话框下面“Location”一栏中显示的文件路径用“Browse…”按纽从计算机中已经安装的PICC编译器文件夹中选择PICCEXE文件。实际上PICCEXE只是一个调度管理程序它会按照所输入的文件扩展名自动调用对应的编译器和连接器用户要注意的是C语言原程序扩展名用“c”汇编原程序用“as”即可。工具挂接完成后在建立项目时可以选择语言工具为“HITECHPICC”具体步骤可以参阅第三章节此处不再重复。项目建立完成后可以加入C或汇编原程序也可以加入已有的库文件或已经编译的目标文件。最常见的是只加入C原程序。用C语言编程的好图MPLABIDE语言工具设置对话框张明峰于上海of处是可以实现模块化编程。程序编写者应尽量把相互独立的控制任务用多个独立的C原程序文件实现如果程序量较大一般不要把所有的代码写在一个文件内。图列出的是笔者建立的一个项目中所有C原程序模块其中主控、数值计算、IC总线操作、命令按键处理和液晶显示驱动等不同的功能分别在不同的独立的原程序模块中实现。PIC单片机的C语言原程序基本框架基于PICC编译环境编写PIC单片机程序的基本方式和标准C程序类似程序一般由以下几个主要部分组成:z在程序的最前面用#include预处理指令引用包含头文件其中必须包含一个编译器提供的“pich”文件实现单片机内特殊寄存器和其它特殊符号的声明z用“CONFIG”预处理指令定义芯片的配置位z声明本模块内被调用的所有函数的类型PICC将对所调用的函数进行严格的类型匹配检查z定义全局变量或符号替换z实现函数(子程序)特别注意main函数必须是一个没有返回的死循环。下面的例为一个C原程序的范例供大家参考。#include<pich>包含单片机内部资源预定义#include“pch”包含自定义头文件定义芯片工作时的配置位CONFIG(HSPROTECTPWRTENBORENWDTDIS)声明本模块中所调用的函数类型voidSetSFR(void)voidClock(void)voidKeyScan(void)voidMeasure(void)voidLCDTest(void)voidLCDDisp(unsignedchar)定义变量unsignedcharsecond,minute,hourbitflag,flag函数和子程序图C语言多模块编程张明峰于上海ofvoidmain(void){SetSFR()PORTC=xTMRH=TMRHCONSTLED=LEDOFFLCDTest()程序工作主循环while(){asm(“clrwdt”)清看门狗Clock()更新时钟KeyScan()扫描键盘Measure()数据测量SetSFR()刷新特殊功能寄存器}}例C语言原程序框架举例PICC中的变量定义PICC中的基本变量类型PICC支持的基本变量类型见表:类型长度(位数)数学表达bit布尔型位变量或两种取值char有符号或无符号字符变量PICC缺省认定char型变量为无符号数但可以通过编译选项改为有符号字节变量unsignedchar无符号字符变量short有符号整型数unsignedshort无符号整型数int有符号整型数unsignedint无符号整型数long有符号长整型数unsignedlong无符号长整型数float浮点数double或浮点数PICC缺省认定double型变量为位长但可以改变编译选项改成位表PICC的基本变量类型PICC遵循Littleendian标准多字节变量的低字节放在存储空间的低地址高字节放在高地址。张明峰于上海ofPICC中的高级变量基于表的基本变量除了bit型位变量外PICC完全支持数组、结构和联合等复合型高级变量这和标准的C语言所支持的高级变量类型没有什么区别。例如:数组:unsignedintdata结构:structcommInData{unsignedcharinBuffunsignedchargetPtr,putPtr}联合:unionintByte{unsignedcharcunsignedinti}例C语言高级变量举例PICC对数据寄存器bank的管理为了使编译器产生最高效的机器码PICC把单片机中数据寄存器的bank问题交由编程员自己管理因此在定义用户变量时你必须自己决定这些变量具体放在哪一个bank中。如果没有特别指明所定义的变量将被定位在bank例如下面所定义的这些变量:unsignedcharbufferbitflag,flagfloatval除了bank内的变量声明时不需特殊处理外定义在其它bank内的变量前面必须加上相应的bank序号例如:bankunsignedcharbuffer变量定位在bank中bankbitflag,flag变量定位在bank中bankfloatval变量定位在bank中中档系列PIC单片机数据寄存器的一个bank大小为字节刨去前面若干字节的特殊功能寄存器区域在C语言中某一bank内定义的变量字节总数不能超过可用RAM字节数。如果超过bank容量在最后连接时会报错大致信息如下:Error:Can'tfindxCwordsforpsectrbssinsegmentBANK连接器告诉你总共有xC()个字节准备放到bank中但bank容量不够。显然只有把一部分原本定位在bank中的变量改放到其它bank中才能解决此问题。虽然变量所在的bank定位必须由编程员自己决定但在编写原程序时进行变量存取操作前无需再特意编写设定bank的指令。C编译器会根据所操作的对象自动生成对应bank设定的汇编指令。为避免频繁的bank切换以提高代码效率尽量把实现同一任务的变量定位在同一个bank内对不同bank内的变量进行读写操作时也尽量把位于相同bank内的变量归并在一起进行连续操作。张明峰于上海ofPICC中的局部变量PICC把所有函数内部定义的auto型局部变量放在bank。为节约宝贵的存储空间它采用了一种被叫做“静态覆盖”的技术来实现局部变量的地址分配。其大致的原理是在编译器编译原代码时扫描整个程序中函数调用的嵌套关系和层次算出每个函数中的局部变量字节数然后为每个局部变量分配一个固定的地址且按调用嵌套的层次关系各变量的地址可以相互重叠。利用这一技术后所有的动态局部变量都可以按已知的固定地址地进行直接寻址用PIC汇编指令实现的效率最高但这时不能出现函数递归调用。PICC在编译时会严格检查递归调用的问题并认为这是一个严重错误而立即终止编译过程。既然所有的局部变量将占用bank的存储空间因此用户自己定位在bank内的变量字节数将受到一定的限制在实际使用时需注意。PICC中的位变量bit型位变量只能是全局的或静态的。PICC将把定位在同一bank内的个位变量合并成一个字节存放于一个固定地址。因此所有针对位变量的操作将直接使用PIC单片机的位操作汇编指令高效实现。基于此位变量不能是局部自动型变量也无法将其组合成复合型高级变量。PICC对整个数据存储空间实行位编址x单元的第位是位地址x以此后推每个字节有个位地址。编制位地址的意义纯粹是为了编译器最后产生汇编级位操作指令而用对编程人员来说基本可以不管。但若能了解位变量的位地址编址方式就可以在最后程序调试时方便地查找自己所定义的位变量如果一个位变量flag被编址为x那么实际的存储空间位于:字节地址=x=x位偏移=x=即flag位变量位于地址为x字节的第位。在程序调试时如果要观察flag的变化必须观察地址为x的字节而不是x。PIC单片机的位操作指令是非常高效的。因此PICC在编译原代码时只要有可能对普通变量的操作也将以最简单的位操作指令来实现。假设一个字节变量tmp最后被定位在地址x那么tmp|=x=>bsfx,tmp=xf=>bcfx,if(tmpxfe)=>btfscx,即所有只对变量中某一位操作的C语句代码将被直接编译成汇编的位操作指令。虽然编程时可以不用太关心但如果能了解编译器是如何工作的那将有助于引导我们写出高效简介的C语言原程序。在有些应用中需要将一组位变量放在同一个字节中以便需要时一次性地进行读写这一功能可以通过定义一个位域结构和一个字节变量的联合来实现例如:张明峰于上海ofunion{struct{unsignedb:unsignedb:unsignedb:unsignedb:unsignedb:unsignedb:unsigned:最高两位保留}oneBitunsignedcharallBits}myFlag例定义位变量于同一字节需要存取其中某一位时可以myFlagoneBitb=b位置一次性将全部位清零时可以myFlagallBits=全部位变量清当程序中把非位变量进行强制类型转换成位变量时要注意编译器只对普通变量的最低位做判别:如果最低位是则转换成位变量如果最低位是则转换成位变量。而标准的ANSIC做法是判整个变量值是否为。另外函数可以返回一个位变量实际上此返回的位变量将存放于单片机的进位位中带出返回。PICC中的浮点数PICC中描述浮点数是以IEEE标准格式实现的。此标准下定义的浮点数为位长在单片机中要用个字节存储。为了节约单片机的数据空间和程序空间PICC专门提供了一种长度为位的截短型浮点数它损失了浮点数的一点精度但浮点运算的效率得以提高。在程序中定义的float型标准浮点数的长度固定为位双精度double型浮点数一般也是位长但可以在程序编译选项中选择double型浮点数为位以提高计算的精度。一般控制系统中关心的是单片机的运行效率因此在精度能够满足的前提下尽量选择位的浮点数运算。PICC中变量的绝对定位首先必须强调在用C语言写程序时变量一般由编译器和连接器最后定位在写程序之时无需知道所定义的变量具体被放在哪个地址(除了bank必须声明)。真正需要绝对定位的只是单片机中的那些特殊功能寄存器而这些寄存器的地址定位在PICC编译环境所提供的头文件中已经实现无需用户操心。编程员所要了解的也就是PICC是如何定义这些特殊功能寄存器和其中的相关控制位的名称。好在PICC的定义标准基本上按照芯片的数据手册中的名称描述进行这样就秉承了变量命名的一贯性。一个变量绝对定位的例子如下:张明峰于上海ofunsignedchartmpDataxtmpData定位在地址x千万注意PICC对绝对定位的变量不保留地址空间。换句话说上面变量tmpData的地址是x但最后x处完全有可能又被分配给了其它变量使用这样就发生了地址冲突。因此针对变量的绝对定位要特别小心。从笔者的应用经验看在一般的程序设计中用户自定义的变量实在是没有绝对定位的必要。如果需要位变量也可以绝对定位。但必须遵循上面介绍的位变量编址的方式。如果一个普通变量已经被绝对定位那么此变量中的每个数据位就可以用下面的计算方式实现位变量指派:unsignedchartmpDataxtmpData定位在地址xbittmpBittmpData*tmpBit对应于tmpData第位bittmpBittmpData*tmpBit对应于tmpData第位bittmpBittmpData*tmpBit对应于tmpData第位如果tmpData事先没有被绝对定位那就不能用上面的位变量定位方式。PICC的其它变量修饰关键词zextern外部变量声明如果在一个C程序文件中要使用一些变量但其原型定义写在另外的文件中那么在本文件中必须将这些变量声明成“extern”外部类型。例如程序文件codec中有如下定义:bankunsignedcharvar,var定义了bank中的两个变量在另外一个程序文件codec中要对上面定义的变量进行操作则必须在程序的开头定义:externbankunsignedcharvar,var声明位于bank的外部变量zvolatile易变型变量声明PICC中还有一个变量修饰词在普通的C语言介绍中一般是看不到的这就是关键词“volatile”。顾名思义它说明了一个变量的值是会随机变化的即使程序没有刻意对它进行任何赋值操作。在单片机中作为输入的IO端口其内容将是随意变化的在中断内被修改的变量相对主程序流程来讲也是随意变化的很多特殊功能寄存器的值也将随着指令的运行而动态改变。所有这种类型的变量必须将它们明确定义成“volatile”类型例如:volatileunsignedcharSTATUSxvolatilebitcommFlag“volatile”类型定义在单片机的C语言编程中是如此的重要是因为它可以告诉编译器的优化处理器这些变量是实实在在存在的在优化过程中不能无故消除。假定你的程序定义了一个变量并对其作了一次赋值但随后就再也没有对其进行任何读写操作如果是非volatile型变量优化后的结果是这个变量将有可能被彻底删除以节约存储空间。另外一种情形是在使用某一个变量进行连续的运算操作时这个变量的值将在第一次操作时被复制到中间临时变量中如果它是非volatile型变量则紧接其后的其它操作将有可能直接从临时变量中取数以提高运行效率显然这样做后对于那些随机变化的参数就会出问题。只要将其张明峰于上海of定义成volatile类型后编译后的代码就可以保证每次操作时直接从变量地址处取数。zconst常数型变量声明如果变量定义前冠以“const”类型修饰那么所有这些变量就成为常数程序运行过程中不能对其修改。除了位变量其它所有基本类型的变量或高级组合变量都将被存放在程序空间(ROM区)以节约数据存储空间。显然被定义在ROM区的变量是不能再在程序中对其进行赋值修改的这也是“const”的本来意义。实际上这些数据最终都将以“retlw”的指令形式存放在程序空间但PICC会自动编译生成相关的附加代码从程序空间读取这些常数编程员无需太多操心。例如:constunsignedcharname=”Thisisademo”定义一个常量字符串如果定义了“const”类型的位变量那么这些位变量还是被放置在RAM中但程序不能对其赋值修改。本来不能修改的位变量没有什么太多的实际意义相信大家在实际编程时不会大量用到。zpersistent非初始化变量声明按照标准C语言的做法程序在开始运行前首先要把所有定义的但没有预置初值的变量全部清零。PICC会在最后生成的机器码中加入一小段初始化代码来实现这一变量清零操作且这一操作将在main函数被调用之前执行。问题是作为一个单片机的控制系统有很多变量是不允许在程序复位后被清零的。为了达到这一目的PICC提供了“persistent”修饰词以声明此类变量无需在复位时自动清零编程员应该自己决定程序中的那些变量是必须声明成“persisten”类型而且须自己判断什么时候需要对其进行初始化赋值。例如:persistentunsignedcharhour,minute,second定义时分秒变量经常用到的是如果程序经上电复位后开始运行那么需要将persistent型的变量初始化如果是其它形式的复位例如看门狗引发的复位则无需对persistent型变量作任何修改。PIC单片机内提供了各种复位的判别标志用户程序可依具体设计灵活处理不同的复位情形。PICC中的指针PICC中指针的基本概念和标准C语法没有太多的差别。但是在PIC单片机这一特定的架构上指针的定义方式还是有几点需要特别注意。z指向RAM的指针如果是汇编语言编程实现指针寻址的方法肯定就是用FSR寄存器PICC也不例外。为了生成高效的代码PICC在编译C原程序时将指向RAM的指针操作最终用FSR来实现间接寻址。这样就势必产生一个问题:FSR能够直接连续寻址的范围是字节(bank或bank)要覆盖最大字节的内部数据存储空间又该如何让定义指针?PICC还是将这一问题留给编程员自己解决:在定义指针时必须明确指定该指针所适用的寻址区域例如:张明峰于上海ofunsignedchar*ptr①定义覆盖bank的指针bankunsignedchar*ptr②定义覆盖bank的指针bankunsignedchar*ptr③定义覆盖bank的指针上面定义了三个指针变量其中①指针没有任何bank限定缺省就是指向bank和bank②和③一个指明了bank另一个指明了bank但实际上两者是一样的因为一个指针可以同时覆盖两个bank的存储区域。另外上面三个指针变量自身都存放在bank中。我们将在稍后介绍如何在其它bank中存放指针变量。既然定义的指针有明确的bank适用区域在对指针变量赋值时就必须实现类型匹配下面的指针赋值将产生一个致命错误:unsignedchar*ptr定义指向bank的指针bankunsignedcharbuff定义bank中的一个缓冲区程序语句:ptr=buff错误!试图将bank内的变量地址赋给指向bank的指针若出现此类错误的指针操作PICC在最后连接时会告知类似于下面的信息:Fixupoverflowinexpression()同样的道理若函数调用时用了指针作为传递参数也必须注意bank作用域的匹配而这点往往容易被忽视。假定有下面的函数实现发送一个字符串的功能:voidSendMessage(unsignedchar*)那么被发送的字符串必须位于bank或bank中。如果你还要发送位于bank或bank内的字符串必须再另外单独写一个函数:voidSendMessage(bankunsignedchar*)这两个函数从内部代码的实现来看可以一模一样但传递的参数类型不同。按笔者的应用经验体会如果你看到了“Fixupoverflow”的错误指示几乎可以肯定是指针类型不匹配的赋值所至。请重点检查程序中有关指针的操作。z指向ROM常数的指针如果一组变量是已经被定义在ROM区的常数那么指向它的指针可以这样定义:constunsignedcharcompany=”Microchip”定义ROM中的常数constunsignedchar*romPtr定义指向ROM的指针程序中可以对上面的指针变量赋值和实现取数操作:romPtr=company指针赋初值data=*romPtr取指针指向的一个数然后指针加反过来下面的操作将是一个错误因为该指针指向的是常数型变量不能赋值。*romPtr=data往指针指向的地址写一个数z指向函数的指针单片机编程时函数指针的应用相对较少但作为标准C语法的一部分PICC同样支持函数指针调用。如果你对编译原理有一定的了解就应该明白在PIC单片机这一特定的架构上实现函数指针调用的效率是不高的:PICC将在RAM中建立一个调用返回表真正的张明峰于上海of调用和返回过程是靠直接修改PC指针来实现的。因此除非特殊算法的需要建议大家尽量不要使用函数指针。z指针的类型修饰前面介绍的指针定义都是最基本的形式。和普通变量一样指针定义也可以在前面加上特殊类型的修饰关键词例如“persistent”、“volatile”等。考虑指针本身还要限定其作用域因此PICC中的指针定义初看起来显得有点复杂但只要了解各部分的具体含义理解一个指针的实际用图就变得很直接。㈠bank修饰词的位置含义前面介绍的一些指针有的作用于bank有的作用于bank但它们本身的存放位置全部在bank。显然在一个程序设计中指针变量将有可能被定位在任何可用的地址空间这时bank修饰词出现的位置就是一个关键看下面的例子:定义指向bank的指针指针变量为于bank中unsignedchar*ptr定义指向bank的指针指针变量为于bank中bankunsignedchar*ptr定义指向bank的指针指针变量为于bank中bankunsignedchar*bankptr从中可以看出规律:前面的bank修饰词指明了此指针的作用域后面的bank修饰词定义了此指针变量自身的存放位置。只要掌握了这一法则你就可以定义任何作用域的指针且可以将指针变量放于任何bank中。㈡volatile、persistent和const修饰词的位置含义如果能理解上面介绍的bank修饰词的位置含义实际上volatile、persistent和const这些关键词出现在前后不同位置上的含义规律是和bank一词相一致的。例如:定义指向bank易变型字符变量的指针指针变量位于bank中且自身为非易变型volatileunsignedchar*ptr定义指向bank非易变型字符变量的指针指针变量位于bank中且自身为易变型bankunsignedchar*volatilebankptr定义指向ROM区的指针指针变量本身也是存放于ROM区的常数constunsignedchar*constptr亦即出现在前面的修饰词其作用对象是指针所指处的变量出现在后面的修饰词其作用对象就是指针变量自己。PICC中的子程序和函数中档系列的PIC单片机程序空间有分页的概念但用C语言编程时基本不用太多关心代码的分页问题。因为所有函数或子程序调用时的页面设定(如果代码超过一个页面)都由编译器自动生成的指令实现。函数的代码长度限制张明峰于上海ofPICC决定了C原程序中的一个函数经编译后生成的机器码一定会放在同一个程序页面内。中档系列的PIC单片机其一个程序页面的长度是K字换句话说用C语言编写的任何一个函数最后生成的代码不能超过K字。一个良好的程序设计应该有一个清晰的组织结构把不同的功能用不同的函数实现是最好的方法因此一个函数K字长的限制一般不会对程序代码的编写产生太多影响。如果为实现特定的功能确实要连续编写很长的程序这时就必须把这些连续的代码拆分成若干函数以保证每个函数最后编译出的代码不超过一个页面空间。调用层次的控制中档系列PIC单片机的硬件堆栈深度为级考虑中断响应需占用一级堆栈所有函数调用嵌套的最大深度不要超过级。编程员必须自己控制子程序调用时的嵌套深度以符合这一限制要求。PICC在最后编译连接成功后可以生成一个连接定位映射文件(*map)在此文件中有详细的函数调用嵌套指示图“callgraph”建议大家要留意一下。其信息大致如下(取自于一示范程序的编译结果):Callgraph:*mainsize,offsetRightShiftC*Tasksize,offsetlwtoftftmulsize,offsetftunpackftunpackftaddsize,offsetftunpackftunpackftdenorm例C函数调用层次图上面所举的信息表明整个程序在正常调用子程序时嵌套最多为两级(没有考虑中断)。因为main函数不可能返回故其不用计算在嵌套级数中。其中有些函数调用是编译代码时自动加入的库函数这些函数调用从C原程序中无法直接看出但在此嵌套指示图上则一目了然。函数类型声明PICC在编译时将严格进行函数调用时的类型检查。一个良好的习惯是在编写程序代码前先声明所有用到的函数类型。例如:voidTask(void)unsignedcharTemperature(void)voidBINBCD(unsignedchar)voidTimeDisplay(unsignedchar,unsignedchar)这些类型声明确定了函数的入口参数和返回值类型这样编译器在编译代码时就能保证生成张明峰于上海of正确的机器码。笔者在实际工作中有时碰到一些用户声称发现C编译器生成了错误的代码最后究其原因就是因为没有事先声明函数类型所致。建议大家在编写一个函数的原代码时立即将此函数的类型声明复制到原文件的起始处见例或是复制到专门的包含头文件中再在每个原程序模块中引用。中断函数的实现PICC可以实现C语言的中断服务程序。中断服务程序有一个特殊的定义方法:voidinterruptISR(void)其中的函数名“ISR”可以改成任意合法的字母或数字组合但其入口参数和返回参数类型必须是“void”型亦即没有入口参数和返回参数且中间必须有一个关键词“interrupt”。中断函数可以被放置在原程序的任意位置。因为已有关键词“interrupt”声明PICC在最后进行代码连接时会自动将其定位到x中断入口处实现中断服务响应。编译器也会实现中断函数的返回指令“retfie”。一个简单的中断服务示范函数如下:voidinterruptISR(void)中断服务程序{if(TIETIF)判TMR中断{TIF=清除TMR中断标志在此加入TMR中断服务}if(TMRIETMRIF)判TMR中断{TMRIF=清除TMR中断标志在此加入TMR中断服务}}中断结束并返回例C语言中断函数举例PICC会自动加入代码实现中断现场的保护并在中断结束时自动恢复现场所以编程员无需象编写汇编程序那样加入中断现场保护和恢复的额外指令语句。但如果在中断服务程序中需要修改某些全局变量时是否需要保护这些变量的初值将由编程员自己决定和实施。用C语言编写中断服务程序必须遵循高效的原则:z代码尽量简短中断服务强调的是一个“快”字。z避免在中断内使用函数调用。虽然PICC允许在中断里调用其它函数但为了解决递归调用的问题此函数必须为中断服务独家专用。既如此不妨把原本要写在其它函数内的代码直接写在中断服务程序中。z避免在中断内进行数学运算。数学运算将很有可能用到库函数和许多中间变量就算不出现递归调用的问题光在中断入口和出口处为了保护和恢复这些中间临时变量就需要大量的开销严重影响中断服务的效率。张明峰于上海of中档系列PIC单片机的中断入口只有一个因此整个程序中只能有一个中断服务函数。标准库函数PICC提供了较完整的C标准库函数支持其中包括数学运算函数和字符串操作函数。在程序中使用这些现成的库函数时需要注意的是入口参数必须在bank中。如果需要用到数学函数则应在程序前“#include<mathh>”包含头文件如果要使用字符串操作函数就需要包含“#include<stringh>”头文件。在这些头文件中提供了函数类型的声明。通过直接查看这些头文件就可以知道PICC提供了哪些标准库函数。C语言中常用的格式化打印函数“printfsprintf”用在单片机的程序中时要特别谨慎。printfsprintf是一个非常大的函数一旦使用你的程序代码长度就会增加很多。除非是在编写试验性质的代码可以考虑使用格式化打印函数以简化测试程序一般的最终产品设计都是自己编写最精简的代码实现特定格式的数据显示和输出。本来在单片机应用中输出的数据格式都相对简单而且固定实现起来应该很容易。对于标准C语言的控制台输入(scanf)/输出(printf)函数PICC需要用户自己编写其底层函数getch()和putch()。在单片机系统中实现scanfprintf本来就没什么太多意义如果一定要实现只要编写好特定的getch()和putch()函数你就可以通过任何接口输入或输出格式化的数据。PICC定义特殊区域值PICC提供了相关的预处理指令以实现在原程序中定义单片机的配置字和标记单元。定义工作配置字在原程序中定义PIC单片机工作配置字的重要性在前面章节中已经阐述。在用PICC写程序时同样可以在C原程序中定义具体方式如下:CONFIG(HSUNPROTECTPWRTENBORDISWDTEN)上面的关键词“CONFIG”(注意前面有两个下划线符)专门用于是芯片配置字的设定后面括号中的各项配置位符号在特定型号单片机的头文件中已经定义(注意不是pich头文件)相互之间用逻辑“与”操作符组合在一起。这样定义的配置字信息最后将和程序代码一起放入同一个HEX文件。在这里列出了适用于Fx系列单片机配置位符号预定义其它型号或系列的单片机配置字定义方式类似使用前查阅一下对应的头文件即可。*振荡器配置*#defineRCxFFFRC振荡#defineHSxFFEHS模式张明峰于上海of#defineXTxFFDXT模式#defineLPxFFCLP模式*看门狗配置*#defineWDTENxFFF看门狗打开#defineWDTDISxFFB看门狗关闭*上电延时定时器配置*#definePWRTENxFF上电延时定时器打开#definePWRTDISxFFF上电延时定时器关闭*低电压复位配置*#defineBORENxFFF低电压复位允许#defineBORDISxFBF低电压复位禁止*代码保护配置*#defineUNPROTECTxFFF没有代码保护#definePROTECTxFEF程序代码保护例头文件预定义的配置信息符号定义芯片标记单元PIC单片机中的标记单元定义可以用下面的IDLOC(注意前面有两个下划线符)预处理指令实现方法如下:IDLOC()其特殊之处是括号内的值全部为进制数不需要用“x”引导。这样上面的定义就设定了标记单元内容为。MPLABIDE中实现PICC的编译选项设置在节中已经介绍了如何实现PICC和MPLABIDE开发平台的挂接。一旦项目建立成功、程序编写完成后即可以通过MPLAB环境下的项目管理工具实现程序的编译、连接和调试。对应于整个项目编译最常用的MPLAB快捷图标为“”和“”。它们的含义分别是:z-项目维护(Make):MPLAB检查项目中的原程序文件只编译那些在上次编译后又被修改过的原程序最后进行连接z-项目重建(BuildAll):项目中的所有原程序文件不管是否有修改都将被重新编译一次最后进行连接。也可以通过Project菜单选择“Make”或“BuildAll”实现项目编译。不管采用何种方式在启动编译过程前一般都要设定一些编译选项。选择单片机型号张明峰于上海of在选择PICC作为语言工具并建立了项目后同样通过菜单项ConfigureÆSelectDevice在MPLAB环境中选择具体单片机型号。请回顾一下例的代码我们在原程序一开始使用了“#include<pich>”实现了相关单片机的一些预定义符号的直接引用但没有具体指明是哪一个型号。实际上“pich”头文件只是一个简单的管理工具(条件判别)它会按照MPLAB所选择的特定型号的单片机把真正对应的头文件包含进来。有兴趣者可以直接用文本编辑工具打开pich文件查看其是如何根据不同的单片机型号包含对应的头文件。这样对编程员而言程序中只需加上一句“#include<pich>”即可。PICC普通编译选项(General)设定参考第三章节的内容和图的指示说明启动编译选项设定对话框。在使用PICC语言工具时对话框的内容和用MPAMS汇编工具相比完全不同。图为PICC编译环境下普通选项设定的界面。在此界面中用户唯一能改变的是编译器查找头文件时的指定路径(IncludePath)实际上如果编译器安装没有问题在此界面中这些普通选项的设定无需任何改动编译器会自动到缺省认定的路径中(编译器安装后的相关路径)查找编译所需的各类文件。PICC全局选项设定(PICCGlobal)全局选项将影响项目中所有C和汇编原程序的编译详细的设定内容见图。其中必须关注的有:zCompileforMPLABICD:如果你准备用ICD调试C语言编译后的代码那么此项就必须打钩选中。这样编译后的结果就能保证ICD本身使用的芯片资源(一小部分的程序和数据空间)不被应用程序所占用。zTreat‘char’assigned:为了提高编译后的代码效率PICC缺省认定‘char’型变量也图PICC普通选项设定图PICC全局选项设定张明峰于上海of是无符号数。如果在设计中需要使用带符号的‘char’型变量此项就应该被选中。zFloatingpoint‘double’width:同样为了提高编译后的代码效率PICC缺省认定‘double’型的双精度浮点数变量的实现长度为位(等同于普通float型浮点数)。在这里可以选择使其长度达位。这样数值计算的精度将得到提高但代码长度将增加计算速度也会降低所以请在权衡利弊后作出你自己的决定。C编译器选项设定(PICCCompiler)项目中所有的C原程序都将通过C编译器编译成机器码这些选项决定了C编译器是如何工作的。所有选项又分为两组:普通选项(General)和高级选项(Advanced)分别见图A和B。C编译器的普通选项最重要的就是针对代码优化的设定。如果没有特殊原因应该设定全局优化级别为级(最高级别优化)同时使用汇编级优化这样最终得到的代码效率最高(长度和执行速度两方面)。按笔者的使用经验仅从代码长度去比较使用最高级别优化后代码长度至少可以减少(K字以上的程序)。而且PICC的优化器相当可靠一般不会因为使用优化从而使生成的程序出现错误。碰到的一些问题也基本都是用户编写的原程序有漏洞所导致例如一些变量应该是volatile型但编程员没有明确定义在优化前程序可以正常运行一旦使用优化程序运行就出现异常。显然把出现的这些问题归罪到编译器是毫无道理的。使用优化后可能对原程序级的调试带来一些不便之处。因PICC可能会重组编译后的代码例如多处重复的代码可能会改成同一个子程序调用以节约程序空间这样在调试过程中跟踪原程序时可能会出现程序乱跳的现象这基本是正常的。若为了强调更直观的代码调试过程你可以将优化级别降低甚至关闭所有优化功能这样调试时程序的运行就可以按部就班了。

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/26

PIC单片机C语言编程教程

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利