使用HITECH编译器编写C程序的心得
用 C 语言来开发单片机系统有很多好处例如:编写代码效率高、软件调试直观、维护升级方便、代码的重复利用率高、等等。但在单片机上用 C 语言写程序和在 PC 机上写程序绝对不能简单等同。因为单片机内的资源非常有限,控制的实时性要求又很高,因此,写单片机程序需要对单片机及编译器有足够的了解才能写出高效的代码。
在使用PIC单片机编程时,尤其的对于软件工程师来说,漂亮的语法,完美的编程思想,系统的函数构造等等都是考虑的重点,也是用软件思维评判代码的标准。但是,对于单片机及编译器的了解也是编写程序不可忽视的部分。他们在提高空间利用率和代码运行速度上往往有着不可替代的作用。对他们多一些了解也会让调试程序的过程豁然开朗。
现在我以HETICH编译器为例,根据工作中程序编写经历介绍几点提高编程效率的方法。
一、优化配置问题
很多时候,对程序的编写主张不开优化,由于对汇编不是十分了解,但是查询了很多资料证明了PICC的优化器还是很稳定的。而且我曾经做过一个小试验,打开优化,可以节约很多资源。也有可靠资料显示,开启优化可以节约20%左右的。如果对自己所编写的代码有足够的信心完全可以开启优化。
项目中所有的 C 原程序都将通过 C 编译器编译成机器码,这些选项决定了 C 编译器是如何工作的。所有选项又分为两组:普通选项(General)和高级选项(Advanced) ,分别见C 编译器的普通选项最重要的就是针对代码优化的设定。如果没有特殊原因,应该设定全局优化级别为 9 级(最高级别优化) ,同时使用汇编级优化,这样最终得到的代码效率最高(长度和执行速度两方面) 。如果碰到的一些问题很大可能是自己的程序问题,例如一些变量应该是 volatile 型但编程员没有明确定义,在优化前程序可以正常运行,一旦使用优化,程序运行就出现异常。
但是使用优化后可能对原程序级的调试带来一些不便之处。一次编写代码,为调试程序的临时变量无法中断。可能是因 PICC 可能会重组编译后的代码,例如多处重复的代码可能会改成同一个子程序调用以节约程序空间,这样在调试过程中跟踪原程序时可能会出现程序乱跳的现象。若为了强调更直观的代码调试过程,你可以将优化级别降低甚至关闭所有优化功能,这样调试时程序的运行就可以按部就班了。
二、汇编程序的使用问题
HITECH编译器支持内联汇编的使用,这给程序的编写带来很大的方便。因为单片机的一些特殊指令操作在标准的 C 语言语法中没有直接对应的描述,例如 PIC 单片机的清看门狗指令“clrwdt”和休眠指令“sleep”;单片机系统强调的是控制的实时性,为了实现这一要求,有时必须用汇编指令实现部分代码以提高程序运行的效率。这样,一个项目中必然会出现 C 和汇编混合编程的情形,在代码中常见的有如下指令 :
asm(\"clrwdt\"); asm(\"sleep\"); asm(\"nop\");
同时单片机中的汇编指令实现有些功能的效率要高与C。只要通过试验就可以用于代码中。给出两个实例:
1、实现循环右移若干位
unsigned int RR_Shift16(unsigned int var, unsigned char count) { 1
路漫漫其修远兮,吾将上下而求索 -
while(count--) //移位次数控制 {
#asm //开始嵌入汇编
rrf ?_RR_Shift16+0,w //最低位送入C
rrf ?_RR_Shift16+1,f //var高字节右移1位,C移入最高位 rrf ?_RR_Shift16+0,f //var低字节右移1位 #endasm //结束嵌入汇编 }
return(var); //返回结果 }
实现偶校验:
bit EvenParity(unsigned char data) {
#asm
swapf ?a_EvenParity+0,w //入口参数data 的寻址符 xorwf ?a_EvenParity+0,f rrf ?a_EvenParity+0,w xorwf ?a_EvenParity+0,f btfsc ?a_EvenParity+0,2 incf ?a_EvenParity+0,f #endasm
//至此,data的最低位即为偶校验位 if (data&0x01) return(1); else return(0); }
三中断问题
要使程序稳定快速的运行,中断程序的处理很重要,因此,中断程序必须要高效,代码要尽量简短,中断服务强调的是一个“快”字。 避免在中断内使用函数调用。虽然 PICC 允许在中断里调用其它函数,但为了解决递归调用的问题,此函数必须为中断服务独家专用。既如此,不妨把原本要写在其它函数内的代码直接写在中断服务程序中。
z 避免在中断内进行数学运算。数学运算将很有可能用到库函数和许多中间变量,就 算不出现递归调用的问题, 光在中断入口和出口处为了保护和恢复这些中间临时变 量就需要大量的开销,严重影响中断服务的效率。
四、定义配置字
很多代码的配置字是在CONFIG BIT中进行配置的。这样做直观清晰方便,但是也存在着很多的弊端。尤其是在将代码移交生产时,如果通过CONFIG BIT配置则需要移交整个工程的文件。
配置字同样可以在 C 原程序中定义,具体方式如下:
__CONFIG (HS & UNPROTECT & PWRTEN & BORDIS & WDTEN);
上面的关键词“__CONFIG” (注意前面有两个下划线符)专门用于是芯片配置字的设定,后面括号中的各项配置位符号在特定型号单片机的头文件中已经定义(注意不是 pic.h头文件) ,相互之间用逻辑“与”操作符组合在一起。这样定义的配置字信息最后将和程2
路漫漫其修远兮,吾将上下而求索 -
序代码一起放入同一个 HEX文件。
四、变量的使用
PICC 把所有函数内部定义的 auto 型局部变量放在 bank0。为节约宝贵的存储空间,它采用了一种被叫做“静态覆盖”的技术来实现局部变量的地址分配。其大致的原理是在编译器编译原代码时扫描整个程序中函数调用的嵌套关系和层次, 算出每个函数中的局部变量字节数,然后为每个局部变量分配一个固定的地址,且按调用嵌套的层次关系各变量的地址可以相互重叠。利用这一技术后所有的动态局部变量都可以按已知的固定地址地进行直接寻址,用 PIC 汇编指令实现的效率最高。
bit 型位变量只能是全局的或静态的。PICC 将把定位在同一 bank 内的 8 个位变量合并成一个字节存放于一个固定地址。因此所有针对位变量的操作将直接使用 PIC 单片机的位当程序中把非位变量进行强制类型转换成位变量时, 要注意编译器只对普通变量的最低 位做判别:如果最低位是 0,则转换成位变量 0;如果最低位是 1,则转换成位变量 1。而标准的 ANSI-C 做法是判整个变量值是否为 0。另外,函数可以返回一个位变量,实际上此返回的位变量将存放于单片机的进位位中带出返回。
“volatile”类型定义在单片机的 C 语言编程中是如此的重要,是因为它可以告诉编译器的优化处理器这些变量是实实在在存在的,在优化过程中不能无故消除。假定你的程序定义了一个变量并对其作了一次赋值,但随后就再也没有对其进行任何读写操作,如果是非volatile 型变量,优化后的结果是这个变量将有可能被彻底删除以节约存储空间。另外一种情形是在使用某一个变量进行连续的运算操作时, 这个变量的值将在第一次操作时被复制到中间临时变量中,如果它是非 volatile 型变量,则紧接其后的其它操作将有可能直接从临时变量中取数以提高运行效率,显然这样做后对于那些随机变化的参数就会出问题。只要将其定义成 volatile 类型后,编译后的代码就可以保证每次操作时直接从变量地址处取数。
persistent — 非初始化变量声明
按照标准 C 语言的做法,程序在开始运行前首先要把所有定义的但没有预置初值的变量全部清零。PICC 会在最后生成的机器码中加入一小段初始化代码来实现这一变量清零操作,且这一操作将在 main 函数被调用之前执行。问题是作为一个单片机的控制系统有很多变量是不允许在程序复位后被清零的。为了达到这一目的,PICC 提供了“persistent”修饰词以声明此类变量无需在复位时自动清零, 编程员应该自己决定程序中的那些变量是必须声明成“persisten”类型,而且须自己判断什么时候需要对其进行初始化赋值。例如:
persistent unsigned char hour,minute,second; //定义时分秒变量 经常用到的是如果程序经上电复位后开始运行, 那么需要将 persistent 型的变量初始化,如果是其它形式的复位,例如看门狗引发的复位,则无需对 persistent 型变量作任何修改。PIC 单片机内提供了各种复位的判别标志,用户程序可依具体设计灵活处理不同的复位情形。
五、代码的组织
中档系列 PIC 单片机的硬件堆栈深度为 8 级,考虑中断响应需占用一级堆栈,所有函数调用嵌套的最大深度不要超过 7 级。 这也是不可以递归编程的原因,编程员必须自己控制子程序调用时的嵌套深度以符合这一限制要求。
PICC决定了C原程序中的一个函数经编译后生成的机器码一定会放在同一个程序页面 内。中档系列的 PIC 单片机其一个程序页面的长度是 2K字,换句话说,用 C 语言3
路漫漫其修远兮,吾将上下而求索 -
编写的任
何一个函数最后生成的代码不能超过 2K字。一个良好的程序设计应该有一个清晰的组织结构,把不同的功能用不同的函数实现是最好的方法,因此一个函数 2K字长的限制一般不会对程序代码的编写产生太多影响。如果为实现特定的功能确实要连续编写很长的程序,这时就必须把这些连续的代码拆分成若干函数, 以保证每个函数最后编译出的代码不超过一个页面空间。
对于C提高程序效率的方法有很多,从语言语法方面,可以找到很多资料参考,也相信大家也都有自己的编程风格和方法。但是,编译器中我们也能学到许多窍门。以上几点仅做抛砖引玉之用,编译器中还隐含了好多知识,希望可以继续探索。
4
因篇幅问题不能全部显示,请点此查看更多更全内容