1154PICC中的局部变量 PICC把所有函数内部定义的auto型局部变量放在bank0。为节约宝贵的存储空间,它 采用了一种被叫做“静态覆盖”的技术来实现局部变量的地址分配。其大致的原理是在编译 器编译原代码时扫描整个程序中函数调用的嵌套关系和层次,算出每个函数中的局部变量字 节数,然后为每个局部变量分配一个固定的地址,且按调用嵌套的层次关系各变量的地址可 以相互重叠。利用这一技术后所有的动态局部变量都可以按已知的固定地址地进行直接寻 址,用PC汇编指令实现的效率最高,但这时不能出现函数递归调用。PICC在编译时会严 格检査递归调用的问题并认为这是一个严重错误而立即终止编译过程, 既然所有的局部变量将占用bank0的存储空间,因此用户自己定位在bank0内的变量字 节数将受到一定的限制,在实际使用时需注意。 1155PICC中的位变量 bit型位变量只能是全局的或静态的。PICC将把定位在同一bank内的8个位变量合并 成一个字节存放于一个固定地址。因此所有针对位变量的操作将直接使用PIC单片机的位 操作汇编指令高效实现。基于此,位变量不能是局部自动型变量,也无法将其组合成复合型 高级变量 PICC对整个数据存储空间实行位编址,0x000单元的第0位是位地址0x0000,以此后 推,每个字节有8个位地址。编制位地址的意义纯粹是为了编译器最后产生汇编级位操作指 令而用,对编程人员来说基本可以不管。但若能了解位变量的位地址编址方式就可以在最后 程序调试时方便地査找自己所定义的位变量,如果一个位变量fagl被编址为0x123,那么 实际的存储空间位于 字节地址=0x123/8=0x24 位偏移=0x123%8=3 即fagl位变量位于地址为0x24字节的第3位。在程序调试时如果要观察fagl的变化,必 须观察地址为0x24的字节而不是0x123。 PC单片机的位操作指令是非常高效的。因此,PCC在编译原代码时只要有可能,对 普通变量的操作也将以最简单的位操作指令来实现。假设一个字节变量tmp最后被定位在 地址0x20,那么 tmp I= 0x80 = bsf 0x20,7 tmp & Oxf 0x20,3 0x20,0 即所有只对变量中某一位操作的C语句代码将被直接编译成汇编的位操作指令。虽然编程 时可以不用太关心,但如果能了解编译器是如何工作的,那将有助于引导我们写出高效简介 的C语言原程序 在有些应用中需要将一组位变量放在同一个字节中以便需要时一次性地进行读写,这 功能可以通过定义一个位域结构和一个字节变量的联合来实现,例如 张明峰2004-4-7于上海6of26
张明峰 2004-4-7 于上海 6 of 26 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 => bsf 0x20,7 tmp &= 0xf7 => bcf 0x20,3 if (tmp&0xfe) => btfsc 0x20,0 即所有只对变量中某一位操作的 C 语句代码将被直接编译成汇编的位操作指令。虽然编程 时可以不用太关心,但如果能了解编译器是如何工作的,那将有助于引导我们写出高效简介 的 C 语言原程序。 在有些应用中需要将一组位变量放在同一个字节中以便需要时一次性地进行读写,这一 功能可以通过定义一个位域结构和一个字节变量的联合来实现,例如:
union t unsigned bl: 1 unsigned b3: 1 unsigned b4: 1 unsigned b5: 1 2;∥/最高两位保留 I oneBit insigned char allBits 例11-3定义位变量于同一字节 需要存取其中某一位时可以 myF1ag. oneBit.b3=1;//b3位置1 次性将全部位清零时可以 myFlag. allBits=O //全部位变量清0 当程序中把非位变量进行强制类型转换成位变量时,要注意编译器只对普通变量的最低 位做判别:如果最低位是0,则转换成位变量0:如果最低位是1,则转换成位变量1。而标 准的 ANSI-C做法是判整个变量值是否为0。另外,函数可以返回一个位变量,实际上此返 回的位变量将存放于单片机的进位位中带出返回。 1156PCC中的浮点数 PICC中描述浮点数是以IEEE-754标准格式实现的。此标准下定义的浮点数为32位长 在单片机中要用4个字节存储。为了节约单片机的数据空间和程序空间,PICC专门提供了 种长度为24位的截短型浮点数,它损失了浮点数的一点精度,但浮点运算的效率得以提 高。在程序中定义的foat型标准浮点数的长度固定为24位,双精度 double型浮点数一般 也是24位长,但可以在程序编译选项中选择 double型浮点数为32位,以提高计算的精度。 般控制系统中关心的是单片机的运行效率,因此在精度能够满足的前提下尽量选择 24位的浮点数运算。 1157PCC中变量的绝对定位 首先必须强调,在用C语言写程序时变量一般由编译器和连接器最后定位,在写程序 之时无需知道所定义的变量具体被放在哪个地址(除了bank必须声明) 真正需要绝对定位的只是单片机中的那些特殊功能寄存器,而这些寄存器的地址定位在 PCC编译环境所提供的头文件中已经实现,无需用户操心。编程员所要了解的也就是PICC 是如何定义这些特殊功能寄存器和其中的相关控制位的名称。好在PCC的定义标准基本上 按照芯片的数据手册中的名称描述进行,这样就秉承了变量命名的一贯性。一个变量绝对定 位的例子如下 张明峰2004-4-7于上海7of26
张明峰 2004-4-7 于上海 7 of 26 union { struct { unsigned b0: 1; unsigned b1: 1; unsigned b2: 1; unsigned b3: 1; unsigned b4: 1; unsigned b5: 1; unsigned : 2; //最高两位保留 } oneBit; unsigned char allBits; } 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 的定义标准基本上 按照芯片的数据手册中的名称描述进行,这样就秉承了变量命名的一贯性。一个变量绝对定 位的例子如下:
unsigned char tmpData a0x20;// mpaTa定位在地址0x20 千万注意,PCC对绝对定位的变量不保留地址空间。换句话说,上面变量 tmpData的 地址是0x20,但最后0x20处完全有可能又被分配给了其它变量使用,这样就发生了地址冲 突。因此针对变量的绝对定位要特别小心。从笔者的应用经验看,在一般的程序设计中用户 自定义的变量实在是没有绝对定位的必要。 如果需要,位变量也可以绝对定位。但必须遵循上面介绍的位变量编址的方式。如果 个普通变量已经被绝对定位,那么此变量中的每个数据位就可以用下面的计算方式实现位变 量指派 unsigned char tmpData@0x20;// mpaTa定位在地址0x20 第0位 bit tmpBitl mpaTa*8+1 // tmpBlt0对应于 mpaTa第1位 bit tmpBit2 tmpData8+2 // tmpBlt0对应于 mpaTa第2位 如果 mpaTa事先没有被绝对定位,那就不能用上面的位变量定位方式 1158PICC的其它变量修饰关键词 extern一外部变量声明 如果在一个C程序文件中要使用一些变量但其原型定义写在另外的文件中,那么在本 文件中必须将这些变量声明成“ extern”外部类型。例如程序文件 codel.c中有如下定义: bankl unsigned char varl, var2; //定义了bank1中的两个变量 在另外一个程序文件code2c中要对上面定义的变量进行操作,则必须在程序的开头定义 extern bank1 unsigned char var1,var2;//声明位于bank1的外部变量 volatile一易变型变量声明 PICC中还有一个变量修饰词在普通的C语言介绍中一般是看不到的,这就是关键词 volatile”"。顾名思义,它说明了一个变量的值是会随机变化的,即使程序没有刻意对它进 行任何赋值操作。在单片机中,作为输入的IO端口其内容将是随意变化的:在中断内被修 改的变量相对主程序流程来讲也是随意变化的:很多特殊功能寄存器的值也将随着指令的运 行而动态改变。所有这种类型的变量必须将它们明确定义成“ volatile”类型,例如 volatile unsi gned char sTATus 0x03 volatile bit commIT “ volatile”类型定义在单片机的C语言编程中是如此的重要,是因为它可以告诉编译 器的优化处理器这些变量是实实在在存在的,在优化过程中不能无故消除。假定你的程序定 义了一个变量并对其作了一次赋值,但随后就再也没有对其进行任何读写操作,如果是非 volatile型变量,优化后的结果是这个变量将有可能被彻底删除以节约存储空间。另外一种 情形是在使用某一个变量进行连续的运算操作时,这个变量的值将在第一次操作时被复制到 中间临时变量中,如果它是非 volatile型变量,则紧接其后的其它操作将有可能直接从临时 变量中取数以提高运行效率,显然这样做后对于那些随机变化的参数就会出问题。只要将其 张明峰2004-4-7于上海8of26
张明峰 2004-4-7 于上海 8 of 26 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 事先没有被绝对定位,那就不能用上面的位变量定位方式。 11.5.8 PICC 的其它变量修饰关键词 z extern — 外部变量声明 如果在一个 C 程序文件中要使用一些变量但其原型定义写在另外的文件中,那么在本 文件中必须将这些变量声明成“extern”外部类型。例如程序文件 code1.c 中有如下定义: bank1 unsigned char var1, var2; //定义了 bank1 中的两个变量 在另外一个程序文件 code2.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类型后,编译后的代码就可以保证每次操作时直接从变量地址处取数。 const一常数型变量声明 如果变量定义前冠以“ const”类型修饰,那么所有这些变量就成为常数,程序运行过 程中不能对其修改。除了位变量,其它所有基本类型的变量或高级组合变量都将被存放在程 序空间(ROM区)以节约数据存储空间。显然,被定义在ROM区的变量是不能再在程序 中对其进行赋值修改的,这也是“ const”的本来意义。实际上这些数据最终都将以“ retlw 的指令形式存放在程序空间,但PICC会自动编译生成相关的附加代码从程序空间读取这些 常数,编程员无需太多操心。例如: const unsigned char name[]=" This is a demo";//定义一个常量字符串 如果定义了“ const”类型的位变量,那么这些位变量还是被放置在RAM中,但程序 不能对其赋值修改。本来,不能修改的位变量没有什么太多的实际意义,相信大家在实际编 程时不会大量用到。 · persistent-非初始化变量声明 按照标准C语言的做法,程序在开始运行前首先要把所有定义的但没有预置初值的变 量全部清零。PICC会在最后生成的机器码中加入一小段初始化代码来实现这一变量清零操 作,且这一操作将在man函数被调用之前执行。问题是作为一个单片机的控制系统有很多 变量是不允许在程序复位后被清零的。为了达到这一目的,PCC提供了“ persistent”修饰 词以声明此类变量无需在复位时自动清零,编程员应该自己决定程序中的那些变量是必须声 明成“ persisten”类型,而且须自己判断什么时候需要对其进行初始化赋值。例如: persistent unsigned char hour, minute, second;//定义时分秒变量 经常用到的是如果程序经上电复位后开始运行,那么需要将 persistent型的变量初始化 如果是其它形式的复位,例如看门狗引发的复位,则无需对 persistent型变量作任何修改。 PC单片机内提供了各种复位的判别标志,用户程序可依具体设计灵活处理不同的复位情 形 1159PICC中的指针 PICC中指针的基本概念和标准C语法没有太多的差别。但是在PIC单片机这一特定的 架构上,指针的定义方式还是有几点需要特别注意。 ●指向RAM的指针 如果是汇编语言编程,实现指针寻址的方法肯定就是用FSR寄存器,PICC也不例外 为了生成高效的代码,PICC在编译C原程序时将指向RAM的指针操作最终用FSR来实现 间接寻址。这样就势必产生一个问题:FSR能够直接连续寻址的范围是256字节( banko/1 或bank2/3),要覆盖最大512字节的内部数据存储空间,又该如何让定义指针?PCC还是 将这一问题留给编程员自己解决:在定义指针时必须明确指定该指针所适用的寻址区域,例 张明峰2004-4-7于上海9of26
张明峰 2004-4-7 于上海 9 of 26 定义成 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”类型,而且须自己判断什么时候需要对其进行初始化赋值。例如: persistent unsigned char hour,minute,second; //定义时分秒变量 经常用到的是如果程序经上电复位后开始运行,那么需要将 persistent 型的变量初始化, 如果是其它形式的复位,例如看门狗引发的复位,则无需对 persistent 型变量作任何修改。 PIC 单片机内提供了各种复位的判别标志,用户程序可依具体设计灵活处理不同的复位情 形。 11.5.9 PICC 中的指针 PICC 中指针的基本概念和标准 C 语法没有太多的差别。但是在 PIC 单片机这一特定的 架构上,指针的定义方式还是有几点需要特别注意。 z 指向 RAM 的指针 如果是汇编语言编程,实现指针寻址的方法肯定就是用 FSR 寄存器,PICC 也不例外。 为了生成高效的代码,PICC 在编译 C 原程序时将指向 RAM 的指针操作最终用 FSR 来实现 间接寻址。这样就势必产生一个问题:FSR 能够直接连续寻址的范围是 256 字节(bank0/1 或 bank2/3),要覆盖最大 512 字节的内部数据存储空间,又该如何让定义指针?PICC 还是 将这一问题留给编程员自己解决:在定义指针时必须明确指定该指针所适用的寻址区域,例 如: