//定义指向 bank2/3 非易变型字符变量的指针,指针变量位于 bank1 中且自身为易变型
bank2 unsigned char * volatile bank1 ptr0;
//定义指向 ROM 区的指针,指针变量本身也是存放于 ROM 区的常数
const unsigned char * const ptr0;
亦即出现在前面的修饰词其作用对象是指针所指处的变量;出现在后面的修饰词其作用对象
就是指针变量自己。
11.6
PICC 中的子程序和函数
中档系列的 PIC 单片机程序空间有分页的概念,但用 C 语言编程时基本不用太多关心
代码的分页问题。因为所有函数或子程序调用时的页面设定(如果代码超过一个页面)都由
编译器自动生成的指令实现。
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
_RightShift_C
* _Task size 0,1 offset 0
lwtoft
ftmul size 0,0 offset 0
ftunpack1
ftunpack2
ftadd size 0,0 offset 0
ftunpack1
ftunpack2
ftdenorm
例 11-4 C 函数调用层次图
上面所举的信息表明整个程序在正常调用子程序时嵌套最多为两级(没有考虑中断)。因为
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 interrupt ISR(void) //中断服务程序
{
if (T0IE && T0IF)
{
T0IF = 0;
//在此加入 TMR0 中断服务
}
//判 TMR0 中断