11.5.7
首要有必要着重,在用
之时无需知道所界说的变量具体被放在哪个地址(除了 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;
bit tmpBit1 @ tmpData*8+1;
bit tmpBit2 @ tmpData*8+2;
假如 tmpData 事前没有被必定定位,那就不能用上面的位变量定位办法。
11.5.8
&O1540;
假如在一个 C
文件中有必要将这些变量声明成“extern”外部类型。例如程序文件 code1.c 中有如下界说:
bank1 unsigned char var1, var2;
//界说了 bank1 中的两个变量
在别的一个程序文件 code2.c 中要对上面界说的变量进行操作,则有必要在程序的最初界说:
extern bank1 unsigned char var1, var2;
&O1540;
PICC
“volatile”。望文生义,它说明晰一个变量的值是会随机改动的,即便程序没有故意对它进
行任何赋值操作。在单片机中,作为输入的 IO 端口其内容将是随意改动的;在中止内被修
改的变量相对主程序流程来讲也是随意改动的;许多特别功用寄存器的值也将跟着指令的运
行而动态改动。一切这种类型的变量有必要将它们明晰界说成“volatile”类型,例如:
volatile unsigned char STATUS @ 0x03;
volatile bit commFlag;
“volatile”类型界说在单片机的
器的优化处理器这些变量是实实在在存在的,在优化进程中不能无故消除。假定你的程序定
义了一个变量并对其作了一次赋值,但随后就再也没有对其进行任何读写操作,假如对错
volatile
景象是在运用某一个变量进行接连的运算操作时,这个变量的值将在第一次操作时被复制到
中心暂时变量中,假如它对错 volatile 型变量,则紧接这以后的其它操作将有或许直接从暂时
变量中取数以进步运转功率,明显这样做后关于那些随机改动的参数就会出问题。只需将其
界说成 volatile 类型后,编译后的代码就能够确保每次操作时直接从变量地址处取数。
&O1540;
假如变量界说前冠以“const”类型润饰,那么一切这些变量就成为常数,程序运转过
程中不能对其修正。除了位变量,其它一切根本类型的变量或高档组合变量都将被寄存在程
序空间(ROM 区)以节省数据存储空间。明显,被界说在 ROM 区的变量是不能再在程序
中对其进行赋值修正的,这也是“const”的原本意义。实践上这些数据终究都将以“retlw”
的指令办法寄存在程序空间,但 PICC 会主动编译生成相关的附加代码从程序空间读取这些
常数,编程员无需太多操心。例如:
const unsigned char name[]=”This is a demo”;
假如界说了
不能对其赋值修正。原本,不能修正的位变量没有什么太多的实践意义,信任咱们在实践编
程时不会许多用到。
&O1540;
依照规范
量悉数清零。PICC 会在终究生成的机器码中参加一小段初始化代码来完成这一变量清零操
作,且这一操作将在 main 函数被调用之前履行。问题是作为一个单片机的操控系统有许多
变量是不允许在程序复位后被清零的。为了到达这一意图,PICC
词以声明此类变量无需在复位时主动清零,编程员应该自己决议程序中的那些变量是有必要声
明成“persisten”类型,并且须自己判别什么时候需求对其进行初始化赋值。例如:
persistent unsigned char hour,minute,second;
常常用到的是假如程序经上电复位后开端运转,那么需求将 persistent 型的变量初始化,
假如是其它办法的复位,例如看门狗引发的复位,则无需对 persistent 型变量作任何修正。
PIC
形。
11.5.9
PICC 中指针的根本概念和规范 C 语法没有太多的不同。但是在 PIC 单片机这一特定的
架构上,指针的界说办法仍是有几点需求特别留心。
&O1540;
假如是汇编言语编程,完成指针寻址的办法必定便是用 FSR 寄存器,PICC 也不破例。
为了生成高效的代码,PICC 在编译 C 原程序时将指向 RAM 的指针操作终究用 FSR 来完成
直接寻址。这样就必然发生一个问题:FSR 能够直接接连寻址的规模是 256 字节(bank0/1
或 bank2/3),要掩盖最大 512 字节的内部数据存储空间,又该怎么让界说指针?PICC 仍是
将这一问题留给编程员自己处理:在界说指针时有必要明晰指定该指针所适用的寻址区域,例
如:
unsigned char *ptr0; //①界说掩盖 bank0/1 的指针
bank2 unsigned char *ptr1;
bank3 unsigned char *ptr2;
上面界说了三个指针变量,其间①指针没有任何 bank 约束,缺省便是指向 bank0 和 bank1;
②和③一个指明晰 bank2,另一个指明晰 bank3,但实践上两者是相同的,由于一个指针可
以一起掩盖两个 bank 的存储区域。别的,上面三个指针变量自身都寄存在 bank0 中。咱们
将在稍后介绍怎么在其它 bank 中寄存指针变量。
已然界说的指针有明晰的 bank 适用区域,在对指针变量赋值时就有必要完成类型匹配,
下面的指针赋值将发生一个丧命过错:
unsigned char *ptr0;
bank2 unsigned char buff[8];
程序句子:
//界说指向 bank0/1 的指针
//界说 bank2 中的一个缓冲区
ptr0 = buff;
若呈现此类过错的指针操作,PICC 在终究衔接时会奉告类似于下面的信息:
Fixup overflow in expression_r(…)
相同的道理,若函数调用时用了指针作为传递参数,也有必要留心 bank 效果域的匹配,
而这点往往简略被忽视。假定有下面的函数完成发送一个字符串的功用:
void SendMessage(unsigned char *);
那么被发送的字符串有必要坐落 bank0 或 bank1 中。假如你还要发送坐落 bank2 或 bank3 内的
字符串,有必要再别的独自写一个函数:
void SendMessage_2(bank2 unsigned char *);
这两个函数从内部代码的完成来看能够一模相同,但传递的参数类型不同。
按笔者的运用经历领会,假如你看到了“Fixup overflow”的过错指示,简直能够必定
是指针类型不匹配的赋值所至。请要点查看程序中有关指针的操作。
&O1540;
假如一组变量是现已被界说在 ROM 区的常数,那么指向它的指针能够这样界说:
const unsigned char company[]=”Microchip”;
const unsigned char *romPtr;
程序中能够对上面的指针变量赋值和完成取数操作:
romPtr
data = *romPtr++;
//界说 ROM 中的常数
//界说指向 ROM 的指针
反过来,下面的操作将是一个过错,由于该指针指向的是常数型变量,不能赋值。
*romPtr = data; //往指针指向的地址写一个数
&O1540;
单片机编程时函数指针的运用相对较少,但作为规范 C 语法的一部分,PICC 相同支撑
函数指针调用。假如你对编译原理有必定的了解,就应该了解在
构上完成函数指针调用的功率是不高的:PICC 将在 RAM 中树立一个调用回来表,真实的
调用和回来进程是靠直接修正 PC 指针来完成的。因而,除非特别算法的需求,主张咱们尽
量不要运用函数指针。
&O1540;
前面介绍的指针界说都是最根本的办法。和一般变量相同,指针界说也能够在前面加上
特别类型的润饰要害词,例如“persistent”、“volatile”等。考虑指针自身还要约束其效果域,
因而 PICC 中的指针界说初看起来显得有点杂乱,但只需了解各部分的具体意义,了解一个
指针的实践用图就变得很直接。
㈠ bank 润饰词的方位意义
前面介绍的一些指针有的效果于 bank0/1,有的效果于 bank2/3,但它们自身的寄存方位
悉数在 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 中。
㈡ 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;
亦即呈现在前面的润饰词其效果目标是指针所指处的变量;呈现在后边的润饰词其效果目标
便是指针变量自己。
代码的分页问题。由于一切函数或子程序调用时的页面设定(假如代码超越一个页面)都由
编译器主动生成的指令完成。
11.6.1
PICC 决议了 C 原程序中的一个函数经编译后生成的机器码必定会放在同一个程序页面
内。中档系列的 PIC 单片机其一个程序页面的长度是 2K 字,换句话说,用 C 言语编写的任
何一个函数终究生成的代码不能超越 2K 字。一个杰出的程序规划应该有一个明晰的安排结
构,把不同的功用用不同的函数完成是最好的办法,因而一个函数 2K 字长的约束一般不会
对程序代码的编写发生太多影响。假如为完成特定的功用的确要接连编写很长的程序,这时
就有必要把这些接连的代码拆分红若干函数,以确保每个函数终究编译出的代码不超越一个页
面空间。
11.6.2
中档系列 PIC 单片机的硬件仓库深度为 8 级,考虑中止呼应需占用一级仓库,所
有函数调用嵌套的最大深度不要超越 7 级。编程员有必要自己操控子程序调用时的嵌套深
度以契合这一约束要求。
PICC 在终究编译衔接成功后能够生成一个衔接定位映射文件(*.map),在此文件
中有具体的函数调用嵌套指示图“call graph”,主张咱们要留心一下。其信息大致如下
(取自于一演示程序的编译成果):
Call graph:
*_main size 0,0 offset 0
*
例 11-4
上面所举的信息标明整个程序在正常调用子程序时嵌套最多为两级(没有考虑中止)。由于
main
参加的库函数,这些函数调用从 C 原程序中无法直接看出,但在此嵌套指示图上则一目了
然。
11.6.3
PICC 在编译时将严厉进行函数调用时的类型查看。一个杰出的习气是在编写程序代码
前先声明一切用到的函数类型。例如:
void Task(void);
unsigned char Temperature(void);
void BIN2BCD(unsigned char);
void TimeDisplay(unsigned char, unsigned char);
这些类型声明晰定了函数的进口参数和回来值类型,这样编译器在编译代码时就能确保生成
正确的机器码。笔者在实践工作中有时碰到一些用户宣称发现 C 编译器生成了过错的代码,
终究究其原因便是由于没有事前声明函数类型所造成的。
主张咱们在编写一个函数的原代码时,当即将此函数的类型声明复制到原文件的开始
处,见例 11-1;或是复制到专门的包括头文件中,再在每个原程序模块中引证。
11.6.4
PICC 能够完成 C 言语的中止服务程序。中止服务程序有一个特别的界说办法:
void interrupt ISR(void);
其间的函数名“ISR”能够改成恣意合法的字母或数字组合,但其进口参数和回来参数类型
有必要是“void”型,亦即没有进口参数和回来参数,且中心有必要有一个要害词“interrupt”。
中止函数能够被放置在原程序的恣意方位。由于已有要害词“interrupt”声明,PICC 在
终究进行代码衔接时会主动将其定位到 0x0004 中止进口处,完成中止服务呼应。编译器也
会完成中止函数的回来指令“retfie”。一个简略的中止服务演示函数如下:
void
{
T0IF = 0;
//判 TMR0 中止
//铲除 TMR0 中止标志
TMR1IF0;
}
//铲除 TMR1 中止标志
//中止完毕并回来