PIC单片机C语言编程教程.docx
- 文档编号:9122562
- 上传时间:2023-02-03
- 格式:DOCX
- 页数:48
- 大小:43.30KB
PIC单片机C语言编程教程.docx
《PIC单片机C语言编程教程.docx》由会员分享,可在线阅读,更多相关《PIC单片机C语言编程教程.docx(48页珍藏版)》请在冰豆网上搜索。
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的业余爱好者和初学者还提供了完全免费的学
习版PICC-Lite编译器套件,它的使用方式和完全版相同,只是支持的PIC单片机型号限制
在PIC16F84、PIC16F877和PIC16F628等几款。
这几款Flash型的单片机因其所具备的丰富
的片上资源而最适用于单片机学习入门,因此笔者建议感兴趣的读者可从PICC-Lite入手掌
握PIC单片机的C语言编程。
在此列出几个主要的针对PIC单片机的C编译器相关连接网址,供读者参考:
Hitech-PICC:
IAR:
CCS:
ByteCraft:
本章将介绍Hitech-PICC编译器的一些基本概念,由于篇幅所限将不涉及C语言的标准
语法和基础知识介绍,因为在这些方面都有大量的书籍可以参考。
重点突出针对 PIC 单片
机的特点而所需要特别注意的地方。
11.2
Hitech-PICC编译器
PICC基本上符合ANSI标准,除了一点:
它不支持函数的递归调用。
其主要原因是因
为PIC单片机特殊的堆栈结构。
在前面介绍PIC单片机架构时已经详细说明了PIC单片机
中的堆栈是硬件实现的,其深度已随芯片而固定,无法实现需要大量堆栈操作的递归算法;
另外在PIC单片机中实现软件堆栈的效率也不是很高,为此,PICC编译器采用一种叫做“静
态覆盖”的技术以实现对 C 语言函数中的局部变量分配固定的地址空间。
经这样处理后产
生出的机器代码效率很高,按笔者实际使用的体会,当代码量超过4K字后,C语言编译出
的代码长度和全部用汇编代码实现时的差别已经不是很大(<10%),当然前提是在整个 C
代码编写过程中须时时处处注意所编写语句的效率,而如果没有对PIC单片机的内核结构、
各功能模块及其汇编指令深入了解,要做到这点是很难的。
11.3
MPLAB-IDE内挂接PICC
PICC编译器可以直接挂接在MPLAB-IDE集成开发平台下,实现一体化的编译连接和
原代码调试。
使用MPLAB-IDE内的调试工具ICE2000、ICD2和软件模拟器都可以实现原
代码级的程序调试,非常方便。
首先必须在你的计算机中安装PICC编译器,无论是完全版还是学习版都可以和
MPLAB-IDE挂接。
安装成功后可以进入IDE,选择菜单项ProjectSetLanguageTool
Locations…,打开语言工具挂接设置对话框,如图11-1所示:
图11-1 MPLAB-IDE语言工具设置对话框
在对话框中选择“HI-TECHPICCToolsuite”栏,展开可执行文件组“Executable”后,
列出了将被 MPLAB-IDE 后台调用的编译器所用到的所有可执行文件,其中有汇编编译器
“PICCAssembler”、C原程序编译器“PICCCompiler”和连接定位程序“PICCLinker”。
同
时在此列表中还显示了对应的可执行程序名,请注意在这里都是“PICC.EXE”。
用鼠标分别
点击选中这三项可执行文件,观察对话框下面“Location”一栏中显示的文件路径,用
“Browse…”按纽,从计算机中已经安装的PICC编译器文件夹中选择PICC.EXE文件。
实
际上PICC.EXE只是一个调度管理程序,它会按照所输入的文件扩展名自动调用对应的编译
器和连接器,用户要注意的是C语言原程序扩展名用“.c”,汇编原程序用“.as”即可。
工具挂接完成后,在建立项目时可以选择语言工具为“HI-TECHPICC”,具体步骤可以
参阅第三章3.1.3节,此处不再重复。
项目建立完成后可以加入C或汇编原程序,也可以加
入已有的库文件或已经编译的目标文件。
最常见的是只加入C原程序。
用C语言编程的好
处是可以实现模块化编程。
程序编写者应尽量把相互独立的控制任务用多个独立的C原程序文件实
现,如果程序量较大,一般不要把所有的代码写在一个文件内。
图11-2列出的是笔者建立的一个项目中所有C原程序模块,其中主控、数值计算、I2C总线操
作、命令按键处理和液晶显示驱动等不同的功能分别在不同的独立的原程序模块中实现。
图11-2 C语言多模块编程
11.4 PIC单片机的C语言原程序基本框架
基于PICC编译环境编写PIC单片机程序的基本方式和标准C程序类似,程序一般由以
下几个主要部分组成:
&O1540; 在程序的最前面用#include预处理指令引用包含头文件,其中必须包含一个编译器
提供的“pic.h”文件,实现单片机内特殊寄存器和其它特殊符号的声明;
&O1540; 用“__CONFIG”预处理指令定义芯片的配置位;
&O1540; 声明本模块内被调用的所有函数的类型,PICC将对所调用的函数进行严格的类型
匹配检查;
&O1540; 定义全局变量或符号替换;
&O1540; 实现函数(子程序),特别注意main函数必须是一个没有返回的死循环。
下面的例11-1为一个C原程序的范例,供大家参考。
#include //包含单片机内部资源预定义
#include“pc68.h” //包含自定义头文件
//定义芯片工作时的配置位
__CONFIG(HS&PROTECT&PWRTEN&BOREN&WDTDIS);
//声明本模块中所调用的函数类型
voidSetSFR(void);
voidClock(void);
voidKeyScan(void);
voidMeasure(void);
voidLCD_Test(void);
voidLCD_Disp(unsignedchar);
//定义变量
unsignedcharsecond,minute,hour;
bitflag1,flag2;
//函数和子程序
voidmain(void)
{
SetSFR();
PORTC= 0x00;
TMR1H+= TMR1H_CONST;
LED1 =LED_OFF;
LCD_Test();
//程序工作主循环
while
(1){
asm(“clrwdt”);
Clock();
KeyScan();
Measure();
SetSFR();
}
}
//清看门狗
//更新时钟
//扫描键盘
//数据测量
//刷新特殊功能寄存器
11.5
PICC中的变量定义
例11-1 C语言原程序框架举例
11.5.1 PICC中的基本变量类型
PICC遵循Little-endian 标准,多字节变量的低字节放在存储空间的低地址,高字节放
在高地址。
11.5.2 PICC中的高级变量
基于表11-1的基本变量,除了bit型位变量外,PICC完全支持数组、结构和联合等复
合型高级变量,这和标准的C语言所支持的高级变量类型没有什么区别。
例如:
数组:
unsignedintdata[10];
结构:
structcommInData{
unsignedchar inBuff[8];
unsignedchar getPtr,putPtr;
};
联合:
unionint_Byte{
unsignedchar c[2];
unsignedinti;
};
例11-2 C语言高级变量举例
11.5.3 PICC对数据寄存器bank的管理
为了使编译器产生最高效的机器码,PICC把单片机中数据寄存器的bank问题交由编程
员自己管理,因此在定义用户变量时你必须自己决定这些变量具体放在哪一个bank中。
如
果没有特别指明,所定义的变量将被定位在bank0,例如下面所定义的这些变量:
unsignedcharbuffer[32];
bitflag1,flag2;
floatval[8];
除了bank0内的变量声明时不需特殊处理外,定义在其它bank内的变量前面必须加上
相应的bank序号,例如:
bank1unsignedcharbuffer[32]; //变量定位在bank1中
bank2bitflag1,flag2;
bank3float val[8];
//变量定位在bank2中
//变量定位在bank3中
中档系列PIC单片机数据寄存器的一个bank大小为128字节,刨去前面若干字节的特
殊功能寄存器区域,在C语言中某一bank内定义的变量字节总数不能超过可用RAM字节
数。
如果超过bank容量,在最后连接时会报错,大致信息如下:
Error[000] :
Can'tfind0x12Cwordsforpsectrbss_1insegmentBANK1
连接器告诉你总共有0x12C(300)个字节准备放到bank1中但bank1容量不够。
显然,只
有把一部分原本定位在bank1中的变量改放到其它bank中才能解决此问题。
虽然变量所在的bank定位必须由编程员自己决定,但在编写原程序时进行变量存取操
作前无需再特意编写设定bank的指令。
C编译器会根据所操作的对象自动生成对应bank设
定的汇编指令。
为避免频繁的bank切换以提高代码效率,尽量把实现同一任务的变量定位
在同一个bank内;对不同bank内的变量进行读写操作时也尽量把位于相同bank内的变量
归并在一起进行连续操作。
11.5.4 PICC中的局部变量
PICC把所有函数内部定义的auto型局部变量放在bank0。
为节约宝贵的存储空间,它
采用了一种被叫做“静态覆盖”的技术来实现局部变量的地址分配。
其大致的原理是在编译
器编译原代码时扫描整个程序中函数调用的嵌套关系和层次,算出每个函数中的局部变量字
节数,然后为每个局部变量分配一个固定的地址,且按调用嵌套的层次关系各变量的地址可
以相互重叠。
利用这一技术后所有的动态局部变量都可以按已知的固定地址地进行直接寻
址,用PIC汇编指令实现的效率最高,但这时不能出现函数递归调用。
PICC在编译时会严
格检查递归调用的问题并认为这是一个严重错误而立即终止编译过程。
既然所有的局部变量将占用bank0的存储空间,因此用户自己定位在bank0内的变量字
节数将受到一定的限制,在实际使用时需注意。
11.5.5 PICC中的位变量
bit型位变量只能是全局的或静态的。
PICC将把定位在同一bank内的8个位变量合并
成一个字节存放于一个固定地址。
因此所有针对位变量的操作将直接使用 PIC 单片机的位
操作汇编指令高效实现。
基于此,位变量不能是局部自动型变量,也无法将其组合成复合型
高级变量。
PICC对整个数据存储空间实行位编址,0x000单元的第0位是位地址0x0000,以此后
推,每个字节有8个位地址。
编制位地址的意义纯粹是为了编译器最后产生汇编级位操作指
令而用,对编程人员来说基本可以不管。
但若能了解位变量的位地址编址方式就可以在最后
程序调试时方便地查找自己所定义的位变量,如果一个位变量flag1被编址为0x123,那么
实际的存储空间位于:
字节地址=0x123/8=0x24
位偏移 =0x123%8=3
即flag1位变量位于地址为0x24字节的第3位。
在程序调试时如果要观察flag1的变化,必
须观察地址为0x24的字节而不是0x123。
PIC 单片机的位操作指令是非常高效的。
因此,PICC在编译原代码时只要有可能,对
普通变量的操作也将以最简单的位操作指令来实现。
假设一个字节变量 tmp 最后被定位在
地址0x20,那么
tmp|=0x80
tmp&=0xf7
=> bsf
=> bcf
0x20,7
0x20,3
if(tmp&0xfe)
=> btfsc0x20,0
即所有只对变量中某一位操作的C语句代码将被直接编译成汇编的位操作指令。
虽然编程
时可以不用太关心,但如果能了解编译器是如何工作的,那将有助于引导我们写出高效简介
的C语言原程序。
在有些应用中需要将一组位变量放在同一个字节中以便需要时一次性地进行读写,这一
功能可以通过定义一个位域结构和一个字节变量的联合来实现,例如:
union{
struct {
unsigned b0:
1;
unsigned b1:
1;
unsigned b2:
1;
unsigned b3:
1;
unsigned b4:
1;
unsigned b5:
1;
unsigned :
2;//最高两位保留
}oneBit;
unsignedcharallBits;
}myFlag;
例11-3 定义位变量于同一字节
需要存取其中某一位时可以
myFlag.oneBit.b3=1;//b3位置1
一次性将全部位清零时可以
myFlag.allBits=0; //全部位变量清0
当程序中把非位变量进行强制类型转换成位变量时,要注意编译器只对普通变量的最低
位做判别:
如果最低位是0,则转换成位变量0;如果最低位是1,则转换成位变量1。
而标
准的ANSI-C做法是判整个变量值是否为0。
另外,函数可以返回一个位变量,实际上此返
回的位变量将存放于单片机的进位位中带出返回。
11.5.6 PICC中的浮点数
PICC中描述浮点数是以IEEE-754标准格式实现的。
此标准下定义的浮点数为32位长,
在单片机中要用4个字节存储。
为了节约单片机的数据空间和程序空间,PICC专门提供了
一种长度为24位的截短型浮点数,它损失了浮点数的一点精度,但浮点运算的效率得以提
高。
在程序中定义的float型标准浮点数的长度固定为24位,双精度double型浮点数一般
也是24位长,但可以在程序编译选项中选择double型浮点数为32位,以提高计算的精度。
一般控制系统中关心的是单片机的运行效率,因此在精度能够满足的前提下尽量选择
24位的浮点数运算。
11.5.7 PICC中变量的绝对定位
首先必须强调,在用 C 语言写程序时变量一般由编译器和连接器最后定位,在写程序
之时无需知道所定义的变量具体被放在哪个地址(除了bank必须声明)。
真正需要绝对定位的只是单片机中的那些特殊功能寄存器,而这些寄存器的地址定位在
PICC编译环境所提供的头文件中已经实现,无需用户操心。
编程员所要了解的也就是PICC
是如何定义这些特殊功能寄存器和其中的相关控制位的名称。
好在PICC的定义标准基本上
按照芯片的数据手册中的名称描述进行,这样就秉承了变量命名的一贯性。
一个变量绝对定
位的例子如下:
unsignedchartmpData@0x20;//tmpData定位在地址0x20
千万注意,PICC对绝对定位的变量不保留地址空间。
换句话说,上面变量tmpData的
地址是0x20,但最后0x20处完全有可能又被分配给了其它变量使用,这样就发生了地址冲
突。
因此针对变量的绝对定位要特别小心。
从笔者的应用经验看,在一般的程序设计中用户
自定义的变量实在是没有绝对定位的必要。
如果需要,位变量也可以绝对定位。
但必须遵循上面介绍的位变量编址的方式。
如果一
个普通变量已经被绝对定位,那么此变量中的每个数据位就可以用下面的计算方式实现位变
量指派:
unsignedchartmpData@0x20;//tmpData定位在地址0x20
bittmpBit0@tmpData*8+0; //tmpBit0对应于tmpData第0位
bittmpBit1@tmpData*8+1; //tmpBit0对应于tmpData第1位
bittmpBit2@tmpData*8+2; //tmpBit0对应于tmpData第2位
如果tmpData事先没有被绝对定位,那就不能用上面的位变量定位方式。
11.5.8 PICC的其它变量修饰关键词
&O1540; extern — 外部变量声明
如果在一个C 程序文件中要使用一些变量但其原型定义写在另外的文件中,那么在本
文件中必须将这些变量声明成“extern”外部类型。
例如程序文件code1.c中有如下定义:
bank1unsignedcharvar1,var2;
//定义了bank1中的两个变量
在另外一个程序文件code2.c中要对上面定义的变量进行操作,则必须在程序的开头定义:
externbank1unsignedcharvar1,var2; //声明位于bank1的外部变量
&O1540; volatile —易变型变量声明
PICC 中还有一个变量修饰词在普通的 C 语言介绍中一般是看不到的,这就是关键词
“volatile”。
顾名思义,它说明了一个变量的值是会随机变化的,即使程序没有刻意对它进
行任何赋值操作。
在单片机中,作为输入的IO端口其内容将是随意变化的;在中断内被修
改的变量相对主程序流程来讲也是随意变化的;很多特殊功能寄存器的值也将随着指令的运
行而动态改变。
所有这种类型的变量必须将它们明确定义成“volatile”类型,例如:
volatileunsignedcharSTATUS@0x03;
volatilebitcommFlag;
“volatile”类型定义在单片机的 C语言编程中是如此的重要,是因为它可以告诉编译
器的优化处理器这些变量是实实在在存在的,在优化过程中不能无故消除。
假定你的程序定
义了一个变量并对其作了一次赋值,但随后就再也没有对其进行任何读写操作,如果是非
volatile 型变量,优化后的结果是这个变量将有可能被彻底删除以节约存储空间。
另外一种
情形是在使用某一个变量进行连续的运算操作时,这个变量的值将在第一次操作时被复制到
中间临时变量中,如果它是非volatile型变量,则紧接其后的其它操作将有可能直接从临时
变量中取数以提高运行效率,显然这样做后对于那些随机变化的参数就会出问题。
只要将其
定义成volatile类型后,编译后的代码就可以保证每次操作时直接从变量地址处取数。
&O1540; const— 常数型变量声明
如果变量定义前冠以“const”类型修饰,那么所有这些变量就成为常数,程序运行过
程中不能对其修改。
除了位变量,其它所有基本类型的变量或高级组合变量都将被存放在程
序空间(ROM区)以节约数据存储空间。
显然,被定义在ROM区的变量是不能再在程序
中对其进行赋值修改的,这也是“const”的本来意义。
实际上这些数据最终都将以“retlw”
的指令形式存放在程序空间,但PICC会自动编译生成相关的附加代码从程序空间读取这些
常数,编程员无需太多操心。
例如:
constunsignedcharname[]=”Thisisademo”; //定义一个常量字符串
如果定义了 “const”类型的位变量,那么这些位变量还是被放置在RAM中,但程序
不能对其赋值修改。
本来,不能修改的位变量没有什么太多的实际意义,相信大家在实际编
程时不会大量用到。
&O1540; persistent — 非初始化变量声明
按照标准 C 语言的做法,程序在开始运行前首先要把所有定义的但没有预置初值的变
量全部清零。
PICC会在最后生成的机器码中加入一小段初始化代码来实现这一变量清零操
作,且这一操作将在main函数被调用之前执行。
问题是作为一个单片机的控制系统有很多
变量是不允许在程序复位后被清零的。
为了达到这一目的,PICC 提供了“persistent”修饰
词以声明此类变量无需在复位时自动清零
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- PIC 单片机 语言 编程 教程
![提示](https://static.bdocx.com/images/bang_tan.gif)