PIC单片机的C语言编程指南

更新时间:2023-07-22 11:26:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

PIC单片机的C语言编程指南

PIC 单片机的C 语言编程指南

2005-8

PIC单片机的C语言编程指南

目 录

1.PIC 单片机C 语言编程简介.................................................................................................5 2.HITECH-PICC 编译器............................................................................................................5 3.MPLAB-IDE 挂接PICC.........................................................................................................5 4.C 语言程序基本框架..............................................................................................................6 5.PICC编译选项设置.................................................................................................................7

5.1 选择单片机型号...........................................................................................................8 5.2 普通编译选项(General)设定..................................................................................8 5.3 全局选项设定(PICC Global)..................................................................................8 5.4 编译器选项设定(PICC Compiler)..........................................................................8 5.5 连接器选项设定(PICC Linker)..............................................................................9 5.6 汇编器选项设定(PICC Assembler)........................................................................9 6.PICC 中的变量定义..............................................................................................................10

6.1 基本变量类型.............................................................................................................10 6.2 高级变量.....................................................................................................................10 6.3 数据寄存器bank 的管理..........................................................................................11 6.4 局部变量.....................................................................................................................11 6.5 位变量.........................................................................................................................12 6.6 浮点数.........................................................................................................................13 6.7 变量的绝对定位.........................................................................................................13 6.8 变量修饰关键词.........................................................................................................14 6.9 指针.............................................................................................................................15 7. PICC 中的子程序和函数....................................................................................................16

7.1 函数的代码长度限制.................................................................................................16 7.2 调用层次的控制.........................................................................................................17 7.3 函数类型声明.............................................................................................................17 7.4 中断函数的实现.........................................................................................................17 7.5 标准库函数.................................................................................................................18 8. PICC 定义特殊区域值........................................................................................................19

8.1 定义工作配置字.........................................................................................................19 8.2 定义芯片标记单元.....................................................................................................19 9. C 和汇编混合编程..............................................................................................................20

9.1 嵌入行内汇编的方法.................................................................................................20 9.2 汇编指令寻址C 语言定义的全局变量...................................................................20 9.3 汇编指令寻址C 函数的局部变量...........................................................................21 9.4 混合编程的一些经验.................................................................................................22 10.PICC库函数指南.................................................................................................................23

10.1 ABS函数...................................................................................................................23 10.2 ACOS函数................................................................................................................23 10.3 ASCTIME函数.........................................................................................................24 10.4 ASIN函数.................................................................................................................25 10.5 ATAN2函数..............................................................................................................25 10.6 ATAN函数................................................................................................................26

PIC单片机的C语言编程指南

10.7 ATOF函数.................................................................................................................26 10.8 ATOI函数..................................................................................................................27 10.9 ATOL函数.................................................................................................................27 10.10 CEIL函数................................................................................................................28 10.11 COSH函数..............................................................................................................28 10.12 COS函数.................................................................................................................29 10.13 CTIME函数............................................................................................................29 10.14 DIV函数..................................................................................................................30 10.15 DI函数....................................................................................................................30 10.16 EEPROM_READ函数............................................................................................31 10.17 EVAL_POLY函数...................................................................................................31 10.18 EXP函数.................................................................................................................32 10.19 FABS函数...............................................................................................................32 10.20 FLOOR函数............................................................................................................33 10.21 FREXP函数............................................................................................................33 10.22 GET_CAL_DATA函数...........................................................................................34 10.23 GMTIME函数.........................................................................................................34 10.24 ISALNUM函数.......................................................................................................35 10.25 KBHIT函数............................................................................................................36 10.26 LDEXP函数............................................................................................................36 10.27 LDIV函数...............................................................................................................37 10.28 LOCALTIME函数..................................................................................................37 10.29 LOG函数................................................................................................................38 10.30 MEMCHR函数.......................................................................................................38 10.31 MEMCMP函数.......................................................................................................39 10.32 MEMCPY函数.......................................................................................................40 10.32 MEMMOVE函数...................................................................................................40 10.33 MEMSET函数........................................................................................................41 10.34 MODF函数.............................................................................................................41 10.35 PERSIST_CHECK函数..........................................................................................42 10.36 POW函数................................................................................................................42 10.38 PRINTF函数...........................................................................................................43 10.39 RAND函数.............................................................................................................44 10.40 SIN函数..................................................................................................................45 10.41 SPRINTF函数.........................................................................................................45 10.42 SQRT函数...............................................................................................................46 10.43 SRAND函数...........................................................................................................46 10.44 STRCAT函数..........................................................................................................47 10.45 STRCHR函数.........................................................................................................47 10.46 STRCMP函数.........................................................................................................48 10.47 STRCPY函数..........................................................................................................48 10.48 STRCSPN函数.......................................................................................................49 10.49 STRNCAT函数.......................................................................................................49 10.50 STRNCMP函数......................................................................................................50 10.51 STRNCPY函数.......................................................................................................51 10.52 STRPBRK函数.......................................................................................................51 10.53 STRRCHR函数.........................................................................................................52

PIC单片机的C语言编程指南

10.54 STRSPN函数..........................................................................................................53 10.55 STRSTR函数..........................................................................................................53 10.55 STRTOK函数.........................................................................................................54 10.56 TAN函数.................................................................................................................54 10.57 TIME函数...............................................................................................................55 10.58 TOLOWER函数.....................................................................................................55 10.59 STRLEN函数..........................................................................................................56 10.60 VA_START函数.....................................................................................................56 10.61 XTOI函数...............................................................................................................57

PIC单片机的C语言编程指南

1.PIC 单片机C 语言编程简介

用C 语言来开发单片机系统软件最大的好处是编写代码效率高、软件调试直观、维护升级方便、代码的重复利用率高、便于跨平台的代码移植等等,因此C 语言编程在单片机系统设计中已得到越来越广泛的运用。针对PIC 单片机的软件开发,同样可以用C 语言实 现。

但在单片机上用C 语言写程序和在PC 机上写程序绝对不能简单等同。现在的PC 机资源十分丰富,运算能力强大,因此程序员在写PC 机的应用程序时几乎不用关心编译后的可执行代码在运行过程中需要占用多少系统资源,也基本不用担心运行效率有多高。写单片机的C 程序最关键的一点是单片机内的资源非常有限,控制的实时性要求又很高,因此,如果没有对单片机体系结构和硬件资源作详尽的了解,是无法写出高质量实用的C 语言程序。Microchip 公司自己没有针对中低档系列PIC单片机的C 语言编译器,但很多专业的第三方公司有众多支持PIC 单片机的C 语言编译器提供,常见的有HITECH、CCS、IAR、ByteCraft 等公司。其中笔者最常用的是HITECH 公司的PICC 编译器,它稳定可靠,编译生成的代码效率高,在用PIC 单片机进行系统设计和开发的工程师群体中得到广泛认可。

HITECH 公司针对广大PIC 的业余爱好者和初学者还提供了完全免费的学习版PICC-Lite 编译器套件,它的使用方式和完全版相同,只是支持的PIC 单片机型号限制在PIC12F629、PIC12F675、PIC16F84、PIC16F877 和PIC16F627 等几款。这几款Flash 型的单片机因其所具备的丰富的片上资源而最适用于单片机学习入门,因此可从PICC-Lite 入手掌握PIC 单片机的C 语言编程。

2.HITECH-PICC 编译器

PICC 基本上符合ANSI 标准,除了一点:它不支持函数的递归调用。其主要原因是因为PIC 单片机特殊的堆栈结构。PIC 单片机中的堆栈是硬件实现的,其深度已随芯片而固定,无法实现需要大量堆栈操作的递归算法;

另外在PIC 单片机中实现软件堆栈的效率也不是很高,为此,PICC 编译器采用一种叫做“静态覆盖”的技术以实现对C 语言函数中的局部变量分配固定的地址空间。经这样处理后产 生出的机器代码效率很高。

3.MPLAB-IDE 挂接PICC

PICC 编译器可以直接挂接在MPLAB-IDE 集成开发平台下,实现一体化的编译连接和原代码调试。使用MPLAB-IDE 内的调试工具ICE2000、ICD2 和软件模拟器都可以实现原代码级的程序调试,非常方便。

首先必须在你的计算机中安装PICC 编译器,无论是完全版还是学习版都可以和MPLAB-IDE 挂接。在建立项目时可以选择语言工具为“HI-TECH PICC”,项目建立完成后可以加入C 或汇编源程序,也可以加入已有的库文件或已经编译的目标文件。最常见的是只加入C 源程序。用C 语言编程的好处是可以实现模块化编程。程序编写者应尽量把相互独立的控制任务用多个独立的C 源程序文件实现,如果程序量较大,一般不要把所有的代码写在一个文件

PIC单片机的C语言编程指南

内。

4.C 语言程序基本框架

基于PICC 编译环境编写PIC 单片机程序的基本方式和标准C 程序类似,程序一般由以 下几个主要部分组成:

在程序的最前面用#include 预处理指令引用包含头文件,其中必须包含一个编译器提供的“pic.h”文件,实现单片机内特殊寄存器和其它特殊符号的声明;

用“__CONFIG”预处理指令定义芯片的配置位;

声明本模块内被调用的所有函数的类型,PICC 将对所调用的函数进行严格的类型匹配检查;

定义全局变量或符号替换; 实现函数(子程序),特别注意main 函数必须是一个没有返回的死循环。

#include <pic.h> #include <stdio.h> #include "lcd.h"

/* this is the maximum value of an A2D conversion. */ #define MAXVOLTAGE 5 //定义芯片工作时的配置位

__CONFIG(WDTDIS & XT & UNPROTECT);

void init(void) {

lcd_init(FOURBIT_MODE); ADON=1; /* enable A2D converter */ ADIE=0; /* not interrupt driven */ ADCON1=0x0E; ADCON0=1; TRISB=0x00; // Set PORTB in output mode T1CON=0x31; // turn on timer 1 TMR1IE=1; // timer 1 is interrupt enabled PEIE=1; // enable peripheral interrupts GIE=1; // turn on interrupts }

void interrupt isr(void) {

if(TMR1IE && TMR1IF)

{

PORTB++; TMR1IF=0; }

PIC单片机的C语言编程指南

}

void main(void) { unsigned char last_value; unsigned char volts; unsigned char decivolts; unsigned char outString[20];

init();

lcd_puts("Adust the"); lcd_home2(); // select line 2 lcd_puts("potentiometer");

while(1)

{

ADGO=1;

while(ADGO)continue; ADIF=0;

if(ADRESH!=last_value) {

volts=0;

lcd_clear();

sprintf(outString,"A2D = %d.%d lcd_puts(outString); }

last_value=ADRESH; } }

5.PICC编译选项设置

一旦项目建立成功、程序编写完成后即可以通过MPLAB 环境下的项目管理工具实现程序的编译、连接和调试。对应于整个项目编译最常用的分别是: -项目维护(Make):MPLAB 检查项目中的源程序文件,只编译那些在上次编译后又被修改过的源程序,最后进行连接; -项目重建(Build All):项目中的所有源程序文件,不管是否有修改,都将被重新编译一次,最后进行连接。可以通过Project 菜单选择“Make”或“Build All”实现项目编译。

PIC单片机的C语言编程指南

5.1 选择单片机型号

在选择PICC 作为语言工具并建立了项目后,同样通过菜单项Configure Select Device在MPLAB 环境中选择具体单片机型号。

5.2 普通编译选项(General)设定

在此界面中用户唯一能改变的是编译器查找头文件时的指定路径(Include Path),实际上如果编译器安装没有问题,在此界面中这些普通选项的设定无需任何改动,编译器会自动到缺省认定的路径中(编译器安装后的相关路径)查找编译所需的各类文件。

5.3 全局选项设定(PICC Global)

全局选项将影响项目中所有C 和汇编源程序的编译,其中有: Compile for MPLAB ICD:如果你准备用ICD 调试C 语言编译后的代码,那么此项就必须打钩选中。这样编译后的结果就能保证ICD 本身使用的芯片资源(一小部分的程序和数据空间)不被应用程序所占用。

Treat ‘char’ as signed:为了提高编译后的代码效率,PICC 缺省认定‘char’型变量也是无符号数。如果在设计中需要使用带符号的‘char’型变量,此项就应该被选中。

Floating point ‘double’ width:同样为了提高编译后的代码效率,PICC 缺省认定‘double’型的双精度浮点数变量的实现长度为24 位(等同于普通float 型浮点数)。在这里可以选择使其长度达32 位。这样数值计算的精度将得到提高,但代码长度将增加,计算速度也会降低,所以请在权衡利弊后作出你自己的决定。

5.4 编译器选项设定(PICC Compiler)

项目中所有的C 源程序都将通过C 编译器编译成机器码,这些选项决定了C 编译器是如何工作的。所有选项又分为两组:普通选项(General)和高级选项(Advanced),分别见C 编译器的普通选项最重要的就是针对代码优化的设定。如果没有特殊原因,应该设定全局优化级别为9 级(最高级别优化),同时使用汇编级优化,这样最终得到的代码效率最高(长度和执行速度两方面)。而且PICC 的优化器相当可靠,一般不会因为使用优化从而使生成的程序出现错误。碰到的一些问题也基本都是用户编写的源程序有漏洞所导致,例如一些变量应该是volatile 型但编程员没有明确定义,在优化前程序可以正常运行,一旦使用优化,程序运行就出现异常。显然,把出现的这些问题归罪到编译器是毫无道理的。

使用优化后可能对源程序级的调试带来一些不便之处。因PICC 可能会重组编译后的代码,例如多处重复的代码可能会改成同一个子程序调用以节约程序空间,这样在调试过程中跟踪源程序时可能会出现程序乱跳的现象,这基本是正常的。若为了强调更直观的代码调试过程,你可以将优化级别降低甚至关闭所有优化功能,这样调试时程序的运行就可以按部就

PIC单片机的C语言编程指南

班了。

C 编译器的高级选项设定基本都是针对诊断信息输出的,和生成的代码无关。用得相对 较多的选项有: Generate assembly list file:编译器生成C 源程序的汇编列表文件(*.lst)。在此文件中列出了每一行C 原代码对应的汇编指令,但这些都是优化前的代码。简单的一条C 语句被翻译成汇编指令后可能有好几条。有时汇编列表文件可以作为解决问题的辅助手段。如果你怀疑编译器生成的代码有错误,不妨先产生对应的汇编列表文件,看看在优化前一条C 语句被编译后的汇编码到底是什么。

Compile to assembly only:这一选项的作用是把C 源程序编译成汇编指令文件(*.as),此时将不生成目标文件,也不进行最后的连接定位。这一选项在C 和汇编混合编程时特别有用。通过解读C 程序对应的汇编指令,可以掌握C 程序中存取变量的具体方法,然后用在自己编写的汇编指令中。

5.5 连接器选项设定(PICC Linker)

连接器PICC Linker 的选项基本不用作太多的改变,其中有两项有用的信息输出可以考虑加以利用: Generate map file:生成连接定位映射文件。在此映射文件中详细列出了所有程序用到的变量的具体物理地址;所有函数的入口地址;函数相互之间调用的层次关系和深度等。这些信息对于程序的调试将非常有用。此文件将以扩展名“*.map”的形式存放在同一个项目路径下,需要时可以用任何文本编辑器打开观察。

Display memory-segment usage:显示详细的内存分配和使用情况报告。用户可以了解到程序空间和数据存储器空间资源分配的细节。

5.6 汇编器选项设定(PICC Assembler)

PICC 环境提供了自己的汇编编译器,它和Microchip 公司提供的MPASM 编译器在源程序的语法表达方面要求稍有不同。另外,PICC 的汇编编译器要求输入源程序文件的扩展名是“*.as”,而MPASM 缺省认定的源程序以“*.asm”为扩展名。

在基于PICC 编译环境下开发PIC 单片机的C 语言应用程序时基本无需关心其汇编编译器,除非是在混合语言编程时用汇编语言编写完整的汇编源程序模块文件。最重要的是优化使能控制项“Enable optimization”,一般情况下应该使用汇编器的优化以节约程序空间。

PIC单片机的C语言编程指南

6.PICC 中的变量定义

6.1 基本变量类型

类型 长度(位数)数学表达 布尔型位变量,0 或1 两种取值

有符号或无符号字符变量,PICC 缺省认定char 型变量

为无符号数,但可以通过编译选项改为有符号字节变量

unsigned char 8 无符号字符变量 有符号整型数 unsigned short 16 无符号整型数 有符号整型数 unsigned int 16 无符号整型数 有符号长整型数 unsigned long 32 无符号长整型数 浮点数 double 24 或32 浮点数,PICC 缺省认定double 型变量为24 位长,但

可以改变编译选项改成32 位

PICC 遵循Little-endian 标准,多字节变量的低字节放在存储空间的低地址,高字节放在高地址。

6.2 高级变量

PICC 完全支持数组、结构和联合等复合型高级变量,这和标准的C 语言所支持的高级变量类型没有什么区别。例如:

unsigned int data[10]; 数组

struct tm { 结构

int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };

union int_Byte { 联合

unsigned char c[2];

PIC单片机的C语言编程指南

unsigned int i; };

6.3 数据寄存器bank 的管理

为了使编译器产生最高效的机器码,PICC 把单片机中数据寄存器的bank 问题交由编程员自己管理,因此在定义用户变量时你必须自己决定这些变量具体放在哪一个bank 中。如果没有特别指明,所定义的变量将被定位在bank0,例如下面所定义的这些变量:

unsigned char buffer[32]; bit flag1,flag2; float val[8];

除了bank0 内的变量声明时不需特殊处理外,定义在其它bank 内的变量前面必须加上相应的bank 序号,例如:

bank1 unsigned char buffer[32]; //变量定位在bank1 中 bank2 bit flag1,flag2; //变量定位在bank2 中 bank3 float val[8]; //变量定位在bank3 中

中档系列PIC 单片机数据寄存器的一个bank 大小为128 字节,刨去前面若干字节的特殊功能寄存器区域,在C 语言中某一bank 内定义的变量字节总数不能超过可用RAM 字节数。如果超过bank 容量,在最后连接时会报错

虽然变量所在的bank 定位必须由编程员自己决定,但在编写源程序时进行变量存取操作前无需再特意编写设定bank 的指令。C 编译器会根据所操作的对象自动生成对应bank 设定的汇编指令。为避免频繁的bank 切换以提高代码效率,尽量把实现同一任务的变量定位在同一个bank 内;对不同bank 内的变量进行读写操作时也尽量把位于相同bank 内的变量归并在一起进行连续操作。

6.4 局部变量

PICC 把所有函数内部定义的auto 型局部变量放在bank0。为节约宝贵的存储空间,它采用了一种被叫做“静态覆盖”的技术来实现局部变量的地址分配。其大致的原理是在编译器编译原代码时扫描整个程序中函数调用的嵌套关系和层次,算出每个函数中的局部变量字节数,然后为每个局部变量分配一个固定的地址,且按调用嵌套的层次关系各变量的地址可以相互重叠。利用这一技术后所有的动态局部变量都可以按已知的固定地址地进行直接寻址,用PIC 汇编指令实现的效率最高,但这时不能出现函数递归调用。PICC 在编译时会严格检查递归调用的问题并认为这是一个严重错误而立即终止编译过程。既然所有的局部变量将占用bank0 的存储空间,因此用户自己定位在bank0 内的变量字节数将受到一定的限制,在实际使用时需注意。

PIC单片机的C语言编程指南

6.5 位变量

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,那么 C 语句代码 汇编代码 tmp |= 0x80 bsf 0x20,7 tmp &= 0xf7 bcf 0x20,3 if (tmp&0xfe) btfsc 0x20,0

即所有只对变量中某一位操作的C 语句代码将被直接编译成汇编的位操作指令。虽然编程时可以不用太关心,但如果能了解编译器是如何工作的,那将有助于引导我们写出高效简介的C 语言源程序。在有些应用中需要将一组位变量放在同一个字节中以便需要时一次性地进行读写,这一功能可以通过定义一个位域结构和一个字节变量的联合来实现,例如:

union {

struct {

unsigned b0:1; unsigned b1:1; unsigned b2:1; unsigned b3:1; unsigned b4:1; unsigned b5:1; unsigned b6:1; unsigned b7:1; }oneBit; unsigned char allBit; }MyData;

需要存取其中某一位时可以MyData.oneBit.b3=1; //b3 位置1

一次性将全部位清零时可以MyData.allBits =0; //全部位变量清0

当程序中把非位变量进行强制类型转换成位变量时,要注意编译器只对普通变量的最低位

PIC单片机的C语言编程指南

做判别:

如果最低位是0,则转换成位变量0; 如果最低位是1,则转换成位变量1。

而标准的ANSI-C 做法是判整个变量值是否为0。另外,函数可以返回一个位变量,实际上此返回的位变量将存放于单片机的进位位中带出返回

6.6 浮点数

PICC 中描述浮点数是以IEEE-754 标准格式实现的。此标准下定义的浮点数为32 位长,在单片机中要用4 个字节存储。为了节约单片机的数据空间和程序空间,PICC 专门提供了一种长度为24 位的截短型浮点数,它损失了浮点数的一点精度,但浮点运算的效率得以提高。在程序中定义的float 型标准浮点数的长度固定为24 位,双精度double 型浮点数一般也是24 位长,但可以在程序编译选项中选择double 型浮点数为32 位,以提高计算的精度。一般控制系统中关心的是单片机的运行效率,因此在精度能够满足的前提下尽量选择24 位的浮点数运算。

6.7 变量的绝对定位

首先必须强调,在用C 语言写程序时变量一般由编译器和连接器最后定位,在写程序之时无需知道所定义的变量具体被放在哪个地址(除了bank 必须声明)。真正需要绝对定位的只是单片机中的那些特殊功能寄存器,而这些寄存器的地址定位在PICC 编译环境所提供的头文件中已经实现,无需用户操心。编程员所要了解的也就是PICC是如何定义这些特殊功能寄存器和其中的相关控制位的名称。好在PICC 的定义标准基本上按照芯片的数据手册中的名称描述进行,这样就秉承了变量命名的一贯性。

一个变量绝对定位的例子如下:

unsigned char tmpData @ 0x20; //tmpData 定位在地址0x20

千万注意,PICC 对绝对定位的变量不保留地址空间。换句话说,上面变量tmpData 的地址是0x20,但最后0x20 处完全有可能又被分配给了其它变量使用,这样就发生了地址冲突。因此针对变量的绝对定位要特别小心。在一般的程序设计中用户自定义的变量实在是没有绝对定位的必要。如果需要,位变量也可以绝对定位。但必须遵循上面介绍的位变量编址的方式。如果一个普通变量已经被绝对定位,那么此变量中的每个数据位就可以用下面的计算方式实现位变量指派:

unsigned char tmpData @ 0x20; //tmpData 定位在地址0x20 bit tmpBit0 @ tmpData*8+0; //tmpBit0 对应于tmpData 第0 位 bit tmpBit1 @ tmpData*8+1; //tmpBit0 对应于tmpData 第1 位 bit tmpBit2 @ tmpData*8+2; //tmpBit0 对应于tmpData 第2 位

如果tmpData 事先没有被绝对定位,那就不能用上面的位变量定位方式。

PIC单片机的C语言编程指南

6.8 变量修饰关键词

z extern — 外部变量声明

如果在一个C 程序文件中要使用一些变量但其原型定义写在另外的文件中,那么在本文件中必须将这些变量声明成“extern”外部类型。例如程序文件test1.c 中有如下定义:

bank1 unsigned char var1, var2; //定义了bank1 中的两个变量

在另外一个程序文件test2.c 中要对上面定义的变量进行操作,则必须在程序的开头定义:extern bank1 unsigned char var1, var2; //声明位于bank1 的外部变量

z volatile — 易变型变量声明

PICC 中还有一个变量修饰词在普通的C 语言介绍中一般是看不到的,这就是关键词“volatile”。顾名思义,它说明了一个变量的值是会随机变化的,即使程序没有刻意对它进行任何赋值操作。在单片机中,作为输入的IO 端口其内容将是随意变化的;在中断内被修改的变量相对主程序流程来讲也是随意变化的;很多特殊功能寄存器的值也将随着指令的运行而动态改变。

所有这种类型的变量必须将它们明确定义成“volatile”类型,例如: volatile unsigned char STATUS @ 0x03; volatile bit commFlag;

“volatile”类型定义在单片机的C 语言编程中是如此的重要,是因为它可以告诉编译器的优化处理器这些变量是实实在在存在的,在优化过程中不能无故消除。假定你的程序定义了一个变量并对其作了一次赋值,但随后就再也没有对其进行任何读写操作,如果是非volatile 型变量,优化后的结果是这个变量将有可能被彻底删除以节约存储空间。

另外一种情形是在使用某一个变量进行连续的运算操作时,这个变量的值将在第一次操作时被复制到中间临时变量中,如果它是非volatile 型变量,则紧接其后的其它操作将有可能直接从临时变量中取数以提高运行效率,显然这样做后对于那些随机变化的参数就会出问题。只要将其定义成volatile 类型后,编译后的代码就可以保证每次操作时直接从变量地址处取数。

z const — 常数型变量声明

如果变量定义前冠以“const”类型修饰,那么所有这些变量就成为常数,程序运行过程中不能对其修改。除了位变量,其它所有基本类型的变量或高级组合变量都将被存放在程序空间(ROM 区)以节约数据存储空间。显然,被定义在ROM 区的变量是不能再在程序中对其进行赋值修改的,这也是“const”的本来意义。实际上这些数据最终都将以“retlw”的指令形式存放在程序空间,但PICC 会自动编译生成相关的附加代码从程序空间读取这些常数,编程员无需太多操心。例如:

const unsigned char name[]=”This is a demo”; //定义一个常量字符串

如果定义了“const”类型的位变量,那么这些位变量还是被放置在RAM 中,但程序不能对其赋值修改。本来,不能修改的位变量没有什么太多的实际意义,相信大家在实际编程时不会大量用到。

z persistent — 非初始化变量声明 按照标准C 语言的做法,程序在开始运行前首先要把所有定义的但没有预置初值的变量全部清零。PICC 会在最后生成的机器码中加入一小段初始化代码来实现这一变量清零操作,且这一操作将在main 函数被调用之前执行。问题是作为一个单片机的控制系统有很多变量是不允许在程序复位后被清零的。为了达到这一目的,PICC 提供了“persistent”修饰词以声明此类变量无需在复位时自动清零,编程员应该自己决定程序中的那些变量是必须声明成“persisten”类型,而且须自己判断什么时候需要对其进行初始化赋值。例如:

PIC单片机的C语言编程指南

persistent unsigned char hour,minute,second; //定义时分秒变量 经常用到的是如果程序经上电复位后开始运行,那么需要将persistent 型的变量初始化,如果是其它形式的复位,例如看门狗引发的复位,则无需对persistent 型变量作任何修改。PIC 单片机内提供了各种复位的判别标志,用户程序可依具体设计灵活处理不同的复位情形。

6.9 指针

PICC 中指针的基本概念和标准C 语法没有太多的差别。但是在PIC 单片机这一特定的架构上,指针的定义方式还是有几点需要特别注意。

z 指向RAM 的指针

在定义指针时必须明确指定该指针所适用的寻址区域,例如: unsigned char *ptr0; //①定义覆盖bank0/1 的指针 bank2 unsigned char *ptr1; //②定义覆盖bank2/3 的指针 bank3 unsigned char *ptr2; //③定义覆盖bank2/3 的指针 上面定义了三个指针变量,其中①指针没有任何bank 限定,缺省就是指向bank0 和bank1;②和③一个指明了bank2,另一个指明了bank3,但实际上两者是一样的,因为一个指针可以同时覆盖两个bank 的存储区域。另外,上面三个指针变量自身都存放在bank0 中。

既然定义的指针有明确的bank 适用区域,在对指针变量赋值时就必须实现类型匹配,若函数调用时用了指针作为传递参数,也必须注意bank 作用域的匹配

z 指向ROM 常数的指针

如果一组变量是已经被定义在ROM 区的常数,那么指向它的指针可以这样定义: const unsigned char company[]=”Microchip”; //定义ROM 中的常数 const unsigned char *romPtr; //定义指向ROM 的指针 程序中可以对上面的指针变量赋值和实现取数操作: romPtr = company; //指针赋初值

data = *romPtr++; //取指针指向的一个数,然后指针加1

反过来,下面的操作将是一个错误,因为该指针指向的是常数型变量,不能赋值。 *romPtr = data; //往指针指向的地址写一个数

z 指向函数的指针

单片机编程时函数指针的应用相对较少,但作为标准C 语法的一部分,PICC 同样支持函数指针调用。如果你对编译原理有一定的了解,就应该明白在PIC 单片机这一特定的架构上实现函数指针调用的效率是不高的:PICC 将在RAM 中建立一个调用返回表,真正的调用和返回过程是靠直接修改PC 指针来实现的。因此,除非特殊算法的需要,建议大家尽量不要使用函数指针。

z 指针的类型修饰

前面介绍的指针定义都是最基本的形式。和普通变量一样,指针定义也可以在前面加上特殊类型的修饰关键词,例如“persistent”、“volatile”等。考虑指针本身还要限定其作用域,因此PICC 中的指针定义初看起来显得有点复杂,但只要了解各部分的具体含义,理解一个指针的实际用图就变得很直接。

A. bank 修饰词的位置含义

前面介绍的一些指针有的作用于bank0/1,有的作用于bank2/3,但它们本身的存放位置全

PIC单片机的C语言编程指南

部在bank0。显然,在一个程序设计中指针变量将有可能被定位在任何可用的地址空间,这时,bank 修饰词出现的位置就是一个关键,看下面的例子:

//定义指向bank0/1 的指针,指针变量为于bank0 中 unsigned char *ptr0;

//定义指向bank2/3 的指针,指针变量为于bank0 中 bank2 unsigned char *ptr0;

//定义指向bank2/3 的指针,指针变量为于bank1 中 bank2 unsigned char * bank1 ptr0;

从中可以看出规律:前面的bank 修饰词指明了此指针的作用域;后面的bank 修饰词定义了此指针变量自身的存放位置。只要掌握了这一法则,你就可以定义任何作用域的指针且可以将指针变量放于任何bank 中。

B. volatile、persistent 和const 修饰词的位置含义

如果能理解上面介绍的bank 修饰词的位置含义,实际上volatile、persistent 和const 这些关键词出现在前后不同位置上的含义规律是和bank 一词相一致的。例如:

//定义指向bank0/1 易变型字符变量的指针,指针变量位于bank0 中且自身为非易变型volatile unsigned char *ptr0;

//定义指向bank2/3 非易变型字符变量的指针,指针变量位于bank1 中且自身为易变型bank2 unsigned char * volatile bank1 ptr0;

//定义指向ROM 区的指针,指针变量本身也是存放于ROM 区的常数 const unsigned char * const ptr0;

亦即出现在前面的修饰词其作用对象是指针所指处的变量;出现在后面的修饰词其作用对象就是指针变量自己。

7. PICC 中的子程序和函数

中档系列的PIC 单片机程序空间有分页的概念,但用C 语言编程时基本不用太多关心代码的分页问题。因为所有函数或子程序调用时的页面设定(如果代码超过一个页面)都由编译器自动生成的指令实现

7.1 函数的代码长度限制

PICC 决定了C 源程序中的一个函数经编译后生成的机器码一定会放在同一个程序页面内。中档系列的PIC 单片机其一个程序页面的长度是2K 字,换句话说,用C 语言编写的任何一个函数最后生成的代码不能超过2K 字。一个良好的程序设计应该有一个清晰的组织结构,把不同的功能用不同的函数实现是最好的方法,因此一个函数2K 字长的限制一般不会对程序代码的编写产生太多影响。如果为实现特定的功能确实要连续编写很长的程序,这时就必须把这些连续的代码拆分成若干函数,以保证每个函数最后编译出的代码不超过一个页面空间。

PIC单片机的C语言编程指南

7.2 调用层次的控制

中档系列PIC 单片机的硬件堆栈深度为8 级,考虑中断响应需占用一级堆栈,所有函数调用嵌套的最大深度不要超过7 级。编程员必须自己控制子程序调用时的嵌套深度以符合这一限制要求。

7.3 函数类型声明

PICC 在编译时将严格进行函数调用时的类型检查。一个良好的习惯是在编写程序代码前先声明所有用到的函数类型。例如:

#ifndef _LCD_H_ #define _LCD_H_

static bit LCD_RS @ ((unsigned)&PORTB*8+7); // Register select static bit LCD_RW @ ((unsigned)&PORTB*8+6); // Register select static bit LCD_EN @ ((unsigned)&PORTB*8+5); // Enable

static bit LCD_D4 @ ((unsigned)&PORTB*8+1); // Data.4 static bit LCD_D5 @ ((unsigned)&PORTB*8+2); // Data.5 static bit LCD_D6 @ ((unsigned)&PORTB*8+3); // Data.6 static bit LCD_D7 @ ((unsigned)&PORTB*8+4); // Data.7

extern void lcd_write(unsigned char); extern void lcd_clear(void);

extern void lcd_puts(const char * s); extern void lcd_goto(unsigned char pos); extern void lcd_init(void); extern void lcd_putch(char);

#endif

这些类型声明确定了函数的入口参数和返回值类型,这样编译器在编译代码时就能保证生成正确的机器码。

7.4 中断函数的实现

PICC 可以实现C 语言的中断服务程序。中断服务程序有一个特殊的定义方法: void interrupt ISR(void);

其中的函数名“ISR”可以改成任意合法的字母或数字组合,但其入口参数和返回参数类型必须是“void”型,亦即没有入口参数和返回参数,且中间必须有一个关键词“interrupt”。中

PIC单片机的C语言编程指南

断函数可以被放置在源程序的任意位置。因为已有关键词“interrupt”声明,PICC 在最后进行代码连接时会自动将其定位到0x0004 中断入口处,实现中断服务响应。编译器也会实现中断函数的返回指令“retfie”。一个简单的中断服务示范函数如下:

static void interrupt isr(void) // Here be interrupt function - the name is // unimportant. { if(!T0IF) // Was this a timer overflow? bad_intr = 1; // NO! Shock horror! count++; // Add 1 to count - insert idle comment T0IF = 0; // Clear interrupt flag, ready for next PORTA ^= 1; // toggle bit 0 of Port A, to show we're alive }

PICC 会自动加入代码实现中断现场的保护,并在中断结束时自动恢复现场,所以编程员无需象编写汇编程序那样加入中断现场保护和恢复的额外指令语句。但如果在中断服务程序中需要修改某些全局变量时,是否需要保护这些变量的初值将由编程员自己决定和实施。用C 语言编写中断服务程序必须遵循高效的原则:

代码尽量简短,中断服务强调的是一个“快”字。

避免在中断内使用函数调用。虽然PICC 允许在中断里调用其它函数,但为了解决递归调用的问题,此函数必须为中断服务独家专用。既如此,不妨把原本要写在其它函数内的代码直接写在中断服务程序中。

避免在中断内进行数学运算。数学运算将很有可能用到库函数和许多中间变量,就算不出现递归调用的问题,光在中断入口和出口处为了保护和恢复这些中间临时变量就需要大量的开销,严重影响中断服务的效率。

中档系列PIC 单片机的中断入口只有一个,因此整个程序中只能有一个中断服务函数。

7.5 标准库函数

PICC 提供了较完整的C 标准库函数支持,其中包括数学运算函数和字符串操作函数。在程序中使用这些现成的库函数时需要注意的是入口参数必须在bank0 中。如果需要用到数学函数,则应在程序前“#include <math.h>” 包含头文件;如果要使用字符串操作函数,就需要包含“#include <string.h>”头文件。在这些头文件中提供了函数类型的声明。通过直接查看这些头文件就可以知道PICC 提供了哪些标准库函数。

C 语言中常用的格式化打印函数“printf/sprintf”用在单片机的程序中时要特别谨慎。printf/sprintf 是一个非常大的函数,一旦使用,你的程序代码长度就会增加很多。除非是在编写试验性质的代码,可以考虑使用格式化打印函数以简化测试程序;一般的最终产品设计都是自己编写最精简的代码实现特定格式的数据显示和输出。本来,在单片机应用中输出的数据格式都相对简单而且固定,实现起来应该很容易。

对于标准C 语言的控制台输入(scanf)/输出(printf)函数,PICC 需要用户自己编写其底层函数getch()和putch()。在单片机系统中实现scanf/printf 本来就没什么太多意义,如果一定要实现,只要编写好特定的getch()和putch()函数,你就可以通过任何接口输入或输出格式化的数据。

PIC单片机的C语言编程指南

8. PICC 定义特殊区域值

PICC 提供了相关的预处理指令以实现在源程序中定义单片机的配置字和标记单元。

8.1 定义工作配置字

在源程序中定义PIC 单片机工作配置字的重要性在前面章节中已经阐述。在用PICC 写程序时同样可以在C 源程序中定义,具体方式如下:

__CONFIG (HS & UNPROTECT & PWRTEN & BORDIS & WDTEN); 上面的关键词“__CONFIG”(注意前面有两个下划线符)专门用于是芯片配置字的设定,后面括号中的各项配置位符号在特定型号单片机的头文件中已经定义(注意不是pic.h头文件),相互之间用逻辑“与”操作符组合在一起。这样定义的配置字信息最后将和程序代码一起放入同一个HEX 文件。

在这里列出了适用于16F7x 系列单片机配置位符号预定义,其它型号或系列的单片机配置字定义方式类似,使用前查阅一下对应的头文件即可。

/*振荡器配置*/ #define RC 0x3FFF // RC 振荡 #define HS 0x3FFE // HS 模式 #define XT 0x3FFD // XT 模式 #define LP 0x3FFC // LP 模式 /*看门狗配置*/

#define WDTEN 0x3FFF // 看门狗打开 #define WDTDIS 0x3FFB // 看门狗关闭 /*上电延时定时器配置*/

#define PWRTEN 0x3FF7 // 上电延时定时器打开 #define PWRTDIS 0x3FFF // 上电延时定时器关闭 /*低电压复位配置*/

#define BOREN 0x3FFF // 低电压复位允许 #define BORDIS 0x3FBF // 低电压复位禁止 /*代码保护配置*/

#define UNPROTECT 0x3FFF // 没有代码保护 #define PROTECT 0x3FEF // 程序代码保护

8.2 定义芯片标记单元

PIC 单片机中的标记单元定义可以用下面的__IDLOC(注意前面有两个下划线符)预处理指令实现,方法如下:

__IDLOC (1234);

其特殊之处是括号内的值全部为16 进制数,不需要用“0x”引导。这样上面的定义就设定了标记单元内容为01020304。

PIC单片机的C语言编程指南

9. C 和汇编混合编程

有两个原因决定了用C 语言进行单片机应用程序开发时使用汇编语句的必要性:单片机的一些特殊指令操作在标准的C 语言语法中没有直接对应的描述,例如PIC 单片机的清看门狗指令“clrwdt”和休眠指令“sleep”;单片机系统强调的是控制的实时性,为了实现这一要求,有时必须用汇编指令实现部分代码以提高程序运行的效率。这样,一个项目中就会出现C 和汇编混合编程的情形,我们在此讨论一些混合编程的基本方法和技巧.

9.1 嵌入行内汇编的方法

在C 源程序中直接嵌入汇编指令是最直接最容易的方法。如果只需要嵌入少量几条的汇编指令,PICC 提供了一个类似于函数的语句:asm(“clrwdt”);双引号中可以编写任何一条PIC 的标准汇编指令。如果需要编写一段连续的汇编指令,PICC 支持另外一种语法描述:用“#asm”开始汇编指令段,用“#endasm”结束。

例如: #asm

movlw 0x20 movwf _FSR clrf _INDF incf _FSR,f btfss _FSR,7 goto $-3 #endasm

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

C 语言中定义的全局或静态变量寻址是最容易的,因为这些变量的地址已知且固定。按C 语言的语法标准,所有C 中定义的符号在编译后将自动在前面添加一下划线符“_”,因此,若要在汇编指令中寻址C 语言定义的各类变量,一定要在变量前加上一“_”符号,对于C 语言中用户自定义的全局变量,用行内汇编指令寻址时也同样必须加上“_”,下面的例子说明了具体的引用方法:

volatile unsigned char tmp; //定义位于bank0 的字符型全局变量 void Test(void) //测试程序 {

#asm //开始行内汇编

clrf _STATUS //选择bank0 movlw 0x10 //设定初值 movwf _tmp //tmp=0x10 #endasm //结束行内汇编

if (tmp==0x10) { //开始C 语言程序

PIC单片机的C语言编程指南

; } }

PICC 在编译处理嵌入的行内汇编指令时将会原封不动地把这些指令复制成最后的机器码。所有对C 编译器所作的优化设定对这些行内汇编指令而言将不起任何作用。编程员必须自己负责编写最高效的汇编代码,同时处理变量所在的bank 设定。对于定义在其它bank 中的变量,还必须在汇编指令中加以明确指示,

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 //选择bank1 bsf _STATUS,5

movlw 0x10 //设定初值

movwf _tmpBank1^0x80 //tmpBank1=0x10 bsf _STATUS,6 //选择bank2 bcf _STATUS,5

movlw 0x20 //设定初值

movwf _tmpBank1^0x100 //tmpBank2=0x20 bsf _STATUS,6 //选择bank3 bsf _STATUS,5

movlw 0x30 //设定初值

movwf _tmpBank1^0x180 //tmpBank1=0x30 #endasm //结束行内汇编 }

在行内汇编指令中寻址C 语言定义的全局变量时,除了在寻址前设定正确的bank 外,在指令描述时还必须在变量上异或其所在bank 的起始地址,实际上位于bank0 的变量在汇编指令中寻址时也可以这样理解,只是异或的是0x00,可以省略。如果你了解PIC 单片机的汇编指令编码格式,上面异或的bank起始地址是无法在真正的汇编指令中体现的,其目的纯粹是为了告诉PICC 连接器变量所在的bank,以便连接器进行bank 类别检查。

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

前面已经提到,PICC 对自动型局部变量(包括函数调用时的入口参数)采用一种“静态覆盖”技术对每一个变量确定一个固定地址(位于bank0),因此嵌入的汇编指令对其寻址时只需采用数据寄存器的直接寻址方式即可,唯一要考虑的是如何才能在编写程序时知道这些局部变量的寻址符号。

在C 语言程序中用嵌入汇编指令时实现对应变量的存取操作,见下例

//C 源程序代码

void Test(unsigned char inVar1, inVar2) {

本文来源:https://www.bwwdw.com/article/3o9m.html

Top