摘要: 介绍PIC系列单片机C语言的发展;以HI-TECH Software公司的HI-TECH PICC为例 , 介绍PICC编译器的特点和用其开发PIC系列单片机时应注意的一些问题。
引言
目前,在市场上应用最广泛的应该属于8位单片机,Microchip Technoloogy公司推出的8位PIC系列单片机,目前在国内市场上深受用户欢迎,已经逐渐成为单片机应用的新潮流;但遗憾的是,目前国内介绍它的C语言开发工具的书籍和文章却比较少,而且用的人也不多,广大的程序员在用其开发的过程中都在慢慢摸索,可能会走一些弯路。笔者最近在用PIC的C语言时就遇到了好些问题,在这里想和最近一段时间用PIC的C语言的一些经验和广大的底层软件程序员做一下交流和介绍希望本文对用PICC开发PIC系列单片机的人有所帮助。
目前,在国内用得比较多的是Hi-Tech的Hi-Tech PICC编译器,而且目前市场上一些国内的PIC单片机仿真器也开始支持Hi-Tech PICC编译格式;因此,本文主要以Hi-Tech的PICC为基础,介绍一下PIC的C语言的基本特点。
1 Hi-Tech PICC的C语言开发工具的语言特点
PICC的C语言按ANSI C来定义,并进行了C语言的扩展。PICC和ANSI C有一个根本的区别就是,PICC不支持函数的递归调用。这是因为PIC单片机的堆栈大小是由硬件决定的,资源有限,所以不支持递归调用。它的数据也遵从标准C的数据结构,PICC的数据结构是以数据类型的形式出现的。PICC编译器支持的数据类型有位类型(bit)、无符号字符(unsigned char)、有符号字符(signed char)、无符号整型(unsigned int)、有符号整形(signed int)、无符号长整型(unsigned long)、有符号长整型(signed long)、浮点(float)和指针类型等。需要注意的是,PICC支持的多字节数据都采用低字节在前,高字节在后的原则。即一个多字节数,比如int型,在内存单元中存储顺序为低位字节存储在地址低的存储单元。高位字节存储在地址高的存储单元中,程序员在用union定义变量时一定要注意这一特点。
PIC的C语言变量分为局部变量和全局变量,所有变量在使用前必须先定义后使用。全局变量是在任何函数之外说明的、可被任意模块使用的、在整个程序执行期间都保持有效的变量。局部变量在函数内部说明。局部变量有两种:自动变量和静态变量。缺省类型为自动变量,除非明确将其声明为静态变量。而且,所有的自动变量都被分配在寄存器页0,所以bank限定词不能用于自动变量,便可以用于静态的局部变量。当程序退出时,自动变量占用的空间释放,自动变量也就失去意义。静态变量是一种局部变量,只在声明它的函数内部有效;但它占用固定的存储单元,而这个存储单元不会被别的函数使用,因此其它函数可以通过指针访问或修改静态变量的值。静态变量在程序开始只初始化一次,因此若只在某函数内部使用一变量,而又希望其值在2次函数调用期间保持不变,为实现程序模块化,则可将其声明为静态变量。例如以下声明中,有些为合法,有些为非法:
void max(void)
unsigned char var1; //合法声明
unsigned char bank1 var2; //非法声明
static unsigned char bank1 ver3; //合法声明
unsigned char var4=0x02; //合法声明,每次调用都初始化
static unsigned char bank1 var5=0x02; //合法声明,但只初始化一次
…………
}
PICC编译器对局部变量及传递参数使用RAM覆盖技术。编译时,连接器会自动把一些不可能被同时调用的函数的自动变量区重叠在一起,以达到内存的高效利用,因此其内部RAM的利用效率非常高。
2 函数调用时参数的传递
PICC函数参数的传递是根据被传参数的长度,用W、被调函数的自动变量区域或被调函数的参数区域传递,传递代码比较高效。传递给函数的参数可以通过一个由问号“?”、下划线“_”及函数名加一个偏移量构成的标号获取。下面为一调用求和子程序的源泉代码:
Unsigned char add_function(unsigned char augend,unsigned char addend);
Void main(void)
{
unsigned char temp1,temp2,temp3;
tem3=add_function(temp1,temp2);
}
unsigned char add_function(unsigned char augend,unsigned char addend)
{
return(augend + addend);
}
编译后生成的汇编程序为:
_main
; _temp2 assigned to?a_main+0
;_temp3 assigned to ?a_main+1
; _temp1 assigned to ?a_main+2
bcf status,5
bcf status,6
MOVf (((?a_main+0))),w
MOVwf(((?_add_function)))
MOVf (((?a_main+2))),w
fcall (_add_function)
MOVwf(((?a_main+1)))
_add_function
; _augend assigned to ?a_add_function+0
; _augend stored from w
bcf status,5
bcf status,6
MOVwf(((?a_add_function+0)))
MOVf (((?a_add_function+0))),w
addwf (((?_add_function+0))),w
return
3 PICC语言和汇编语言的混合编程
一般情况下,主程序都是用C语言编写的。C语言与汇编语言最大的区别在于,汇编程序执行效率较高,因为,C语言首先要用C编译器生成汇编代码,在不少情况下,C编译器生成的汇编代码不如用手工生成的汇编代码效率高。在PICC中,可以用两种方法在C程序中调用汇编程序。一种方法是使用#asm,#endasm及asm()在C语言中直接嵌入汇编代码,#asm和#endasm指令分别用于标示嵌入汇编程序块的开头和结属;asm()用于将单条汇编指令嵌入到编译器生成的代码中,如下所示:
void func1(void){
asm("NOP");
#asm
nop
rlf_var,f
#endasm
asm("rlf_var,f");
}
需要注意的是,嵌入汇编不是完整意义上的汇编,是一种伪汇编指令,使用时必须注意它们与编译器生成代码之间的互相影响。
另一种方法是将汇编作为一个独立的模块,用汇编编译器(ASPIC)生成目标文件,然后用链接器和C语言生成的其它模块的目标文件链接在一起。如果变量要公用时,则在另一个模块中说明为外部类型,并允许使用形式参数和返回值。
例如,如果在C模块中使用汇编模块中的函数,那么在C中可知下声明:
extern char rotate_left(char);
本声明说明了要调用的这个外部函数有一个char型形式参数,并返回一个char型的值。而rotate_left()函数的真正函数体在外部可以被ASPIC编译的汇编模块(文件名后缀.as)中,具体代码可以如下编写:
processor16C84
PSECT text0,class=CODE,local,delta=2
GLOBAL _rotate_left
SIGNAT _rotate_4201
_rotate_left
MOVwf?a_rotate_left
rlf?a_rotate_left,w
return
FNSIZE _rotate_left,1,0
GLOBAL?a_rotate_left
END
需要注意的是,以C模块中声明的函数名称,在汇编模块中是以下划线开头的。GLOBAL定义了一个全局变量,也等同于C模块中的extern,SIGNAL强制链接器在链接各个目标文件模块时进行类型匹配检查,FNSIZE定义局部变量和形式参数的内存分配。
这种方法比较麻烦,如果对某一模块的执行效率要求较高时,可以采取这种办法;但是,为了保证汇编程序能正常运行,必须严格遵守函数参数传递和返回规则。当然,为避免这些规则带来的麻烦,一般情况下,可以先用C语言大致编写一个类似功能的函数,预先定义好各种变量,采用PICC-S选项对程序进行编译,然后手工优化编译器产生的汇编代码后将其作为独立的模块就可以了。
4 注意事项
使用PICC时,为了更有效地利用资源,应注意以下几点:
①尽量使用无符号数和字节变量。
②在寄存器资源允许的情况下,对某些执行效率要求较高的平级元相互调用函数中用到的内部变量,可将其定义为全局临时变量,编程时覆盖使用,这样可减少很多编译代码。而对于中断函数内部用到的变量,可用全局变量。
③对于有一定汇编经验的人在开始使用PICC时,应多注意观看编译后产生的汇编源代码,并经常观看经正确编译链接后产生的映像文件(.MAP文件)。在该文件中,详细列出了分配给变量和代码的地址和生成代码的大小等信息。使用者可了解代码是否优化,变量分配是否合理,堆栈是否溢出等,从而写出高效简洁的C源代码。
④在很多情况下,PICC不支持类型强制转换。即在类型不匹配时须查验编译后的汇编代码,看是否正确,尤其是对指针操作的时候一定要注意。
⑤对某位变量自操作时,比如求反,不可以直接简写,例如:!flag;编译后不能正确产生代码,而须写成:“flag=!flag;”
⑥尽量选择全局优化编译选项。为保证寄存器页(包括程序存储期页面和RAM寄存器页)的正确转换,PICC的编译代码中有大量的变换寄存器页的代码,选择全局优化PICC会优化去大量有关RP0、RP1、PCLAPH所增加的变换代码,从而加快程序执行速度,并节省大量的程序空间。
⑦若有某一代码很短的函数被多个函数经常调用,最好将其定义为宏。因为若函数代码很短时,由于被调函数和调用函数不在同一代码页所产生的附加代码可能都会超过函数代码本身的长度。
5 结论
PICC编译器产生的代码在有些时候虽然比较繁琐,但结构和逻辑性很强,开发效率大大提高,调试与维护都很方便。不论是从程序的开发速度、软件质量还是从程序的可维护性和可移植性上讲,PICC的优点绝非汇编语言所能比拟的。
|