首页 理论教育 嵌入式C语法扩充:单片机与嵌入式系统实践

嵌入式C语法扩充:单片机与嵌入式系统实践

时间:2023-11-19 理论教育 版权反馈
【摘要】:CodeWarrior中针对飞思卡尔的8位单片机C语言编程基本符合ANSI规范,因此关于标准C语言编程的话题就不再重复。这里主要介绍和单片机资源密切相关的一些嵌入式C语言编程扩展。当然CodeWarrior编译器本身可以设定改变模式,但单片机内部寄存器地址排列顺序无法改变,故这样做将使最终的程序代码效率降低。在嵌入式程序设计中经常会用到一些“位”变量来作为一些标志。

嵌入式C语法扩充:单片机与嵌入式系统实践

CodeWarrior中针对飞思卡尔的8位单片机C语言编程基本符合ANSI规范,因此关于标准C语言编程的话题就不再重复。这里主要介绍和单片机资源密切相关的一些嵌入式C语言编程扩展。

1.变量类型和定义

CodeWarrior中S08系列单片机C编译器支持的基本变量类型及其默认的长度位数由表3-1所示。当然,在使用时有些变量的长度可以按实际项目需要而改变,CodeWarrior中数据长度设置界面如图3-23所示,该对话框可由图3-18中Type Size配置按钮打开。

表3-1 数据类型(数据长度)一览表

978-7-111-50417-7-Chapter03-33.jpg

978-7-111-50417-7-Chapter03-34.jpg

图3-23 CodeWarrior中数据长度设置界面

表3-1所示的变量char、short、int和long等都有对应的无符号型式,即在对应关键字前添加unsigned关键字。CodeWarrior给定的头文件已经将最常用的一些无符号变量类型做了类型名简化替换,例如用byte代替unsigned char,用word代替unsigned int。这样在程序编写是可以节约键盘输入的时间。

在嵌入式程序设计中对于变量类型的选择有两条最基本的原则须遵循:第一是能用短的变量类型就不用长的;第二是能用无符号数就不用有符号数。这两条基本原则将在很大程度上决定用户代码的长度和效率。因此在编程时建议读者多使用byte或word类型变量。由于HCS08系列单片机内部硬件寄存器定义的特点,对于多字节组成的变量,例如int、long等,C编译器默认的变量内存排列方式是高位字节放在低地址,低位字节放在高地址,这一点相比普通Intel格式,例如51系列和PIC系列单片机正好相反,因而在程序跨平台移植时请特别注意。当然CodeWarrior编译器本身可以设定改变模式,但单片机内部寄存器地址排列顺序无法改变,故这样做将使最终的程序代码效率降低。

在嵌入式程序设计中经常会用到一些“位”变量来作为一些标志。CodeWarrior中没有特别的位变量定义关键词,位变量必须由位域结构体的型式来定义。例如:

978-7-111-50417-7-Chapter03-35.jpg

若引用某个位变量,只需

myFlag.alarmOn=1;

myFlag.sysError=0;

这样定义的各个位变量将被顺序排放在一起,以字节为基本单位,字节的第0位放第一个位变量,一个字节可含8个位变量。因此如果位域结构中定义的位变量数目很多,在最后内存分配上将占居多个字节。

有时为了编程方便,位变量需单独定义和操作但又希望一次整个字节一起初始化(清零或赋值),这时我们可以定义字节(或字)和位域结构的联合体:

978-7-111-50417-7-Chapter03-36.jpg

整字节操作可以

myFlag.flagByte=0;

单独某一个位操作可以

978-7-111-50417-7-Chapter03-37.jpg

若这样的位变量名称太长,也可以在用户自己的头文件里用#define预定义,用更简洁易懂的名称进行替换。最后要提醒的是,在定义位变量时尽量将它们指定分配到内存空间的第0页(地址范围0x00-0xff),这样对位变量操作的C代码将直接被编译成对应的汇编位操作指令,代码效率最高。

2.变量的特殊修饰

上面介绍的各类基本变量和由其合成的高级变量(如数组、结构和联合体)可以满足95%以上的嵌入式程序设计工作,但由于单片机资源的有限性和特殊性,还有一小部分因素需要在定义变量时加以考虑。

(1)变量的绝对定位

变量绝对定位是特别针对芯片内部的硬件寄存器定义的。所有的硬件寄存器在编写C程序时均被视为变量,它们都已在CodeWarrior给定的头文件中预先定义。由于是硬件资源,其地址是唯一且不可改的,所以在头文件中定义这些寄存器时都采用绝对定位的方式,如定义PORTA端口:

978-7-111-50417-7-Chapter03-38.jpg

在定义端口寄存器时用“@”给出其绝对地址为0x00。

理论上用户自己定义的变量也可以用这种方式对其分配一个固定地址来绝对定位。但这样定义的变量其地址不被保留,完全可能被其他变量覆盖。所以用户可以采用上面介绍的方法来绝对定位自己的变量,但一定要合理规划好存储空间的地址分配,并随时关注其内容是否被其他变量覆盖,因而在此建议慎用该方法。

(2)变量volatile声明

volatile型变量顾名思义就是这些变量是易变的,声明方法如下。

978-7-111-50417-7-Chapter03-39.jpg

由于单片机片内部硬件寄存器值的变化是由内部硬件模块运作或外部信号输入决定而不受程序代码的控制,因而,对正常的代码运行流程来说对应的这些变量也就是易变的。volatile类型定义在单片机的C语言编程中比较重要,是因为它可以告诉编译器的优化处理器这些变量是实实在在存在的,在优化过程中不能无故消除。假定用户的程序定义了一个变量并对其进行了一次赋值,但随后就再也没有对其进行任何读写操作,如果是非volatile型变量,优化后的结果是这个变量将有可能被彻底删除以节约存储空间。另外一种情形是在使用某一个变量进行连续的运算操作时,这个变量的值将在第一次操作时被复制到中间临时变量中,如果它是非volatile型变量,则紧接其后的其他操作将有可能直接从临时变量中取数以提高运行效率,显然这样做后对于那些随机变化的参数就会出问题。所以只要将其定义成volatile类型后,编译后的代码就可以保证每次操作时直接从变量地址处取数。在程序中任何类型的变量,都可以冠以volatile声明。

(3)const声明

const用以声明变量为永不变化的常数。一般来说这些变量都应该被放在ROM区(也就是Flash程序空间)以节约宝贵的RAM内存,但简单的一个const声明并不能保证变量最后会被分配到ROM区,安全的做法必须配合#pragma声明的CONST_SEG数据段或INTO_ROM一起实现,这将在稍后介绍。任何类型的变量,都可以冠以const声明。下面为const声明的一个范例。

978-7-111-50417-7-Chapter03-40.jpg

3.#pragma声明

#pragma声明是基于单片机开发的特点而对标准C语法的一个扩充。它对充分利用单片机内各类有限的资源起到不可或缺的关键作用。下面简单介绍几个最常用的#pragma声明。

(1)#pragma DATA_SEG

定义变量所处的数据段。其语法形式为:

#pragma DATA_SEG<属性>名称

数据段的“名称”可以自己任意命名,但习惯上有些约定的名称,其作用分别为:

●DEFAULT,默认的数据段,在08系列单片机中的地址为0x100以上,一般的变量定义可以放在这一区域。

●MY_ZEROPAGE,特指第0页数据段,地址范围0x00-0xff,但实际用户可用的空间不到256B,因为前面的一些地址空间已经分配给了片内寄存器。需要频繁或快速存取的变量应该指定放在这一特殊区域,特别是位变量。(www.xing528.com)

数据段名称必须和prm文件中的数据段配置说明相关联才能真正发挥其定位作用。如果自己命名的数据段在prm文件中没有特别说明,此数据段的性质等同于“DEFAULT”。

数据段的“属性”可以默认,它主要的目的是告诉编译器此段数据可适用的寻址模式。不同的寻址模式所耗费的指令数量和运行时间都不同。对于08系列单片机,关键的是第0页数据段可以用8位地址进行直接快速寻址,故对应此数据段应尽量指明其属性为“__SHORT_SEG”,对于一般数据段没有属性描述,其默认是“__FAR_SEG”将用16位地址间接寻址。举例如下。

978-7-111-50417-7-Chapter03-41.jpg

(2)#pragma CONST_SEG

定义一个常数数据段,必须和变量的const修饰关键词配合使用。其语法形式如下。

#pragma CONST_SEG名称

该数据段下定义的所有数据将被放置在程序只读的ROM区,也就是08系列单片机内的Flash程序空间区。常数段名称可以用户自由定义,但一般都用DEFAULT,让连接器按可用的ROM区域自由分配变量位置。举例如下。

978-7-111-50417-7-Chapter03-42.jpg

(3)#pragma INTO_ROM

功能类似于CONST_SEG,和变量修饰词const配合使用,但它只定义一个常数变量到ROM区,且只作用于紧接着的下一行定义。例如:

#pragma INTO_ROM

const byte prjName[]=This is a demo;//变量将被放置在ROM区

word verData=0x0301;//变量将被放置在默认RAM区

(4)#pragma CODE_SEG

用以定义程序段并赋以特定的段名,语法形式如下:

#pragma CODE_SEG<属性>名称

一般的程序设计是无需对代码段做特殊处理的。因为所有传统的08系列单片机其程序空间都不超过64KB(16位寻址最大范围)且在内存地址中呈线性连续分布,且对于项目中所有的代码文件或库文件,连接器会在最后按程序模块出现的先后顺序挨个自动安排所有程序函数在内存中所处的实际位置,用户不必太关心某一个函数的具体位置。但当单片机程序将超过64KB时,就必须在内存空间中以页面形式映射到首64KB地址范围,其对应的程序段属性要特殊声明。

某些特殊的设计需要将不同部分的程序分别定位到不同的地址空间,例如实现程序代码下载自动更新。这样的设计需要把负责应用程序下载更新的驱动代码固定放置在一个保留区域内,而把一般的应用程序放置在另外一个区域以便在需要时整体擦除后更新。这时就需要用CODE_SEG来分别指明不同的程序段,但还必须配合prm文件对程序空间进行分配和指派。

在该声明中,代码段的属性一般都用默认的__FAR_SEG,表明所有的函数调用都是长调用。但S08系列单片机支持效率更高的函数短调用,如果某一个功能模块含有多个相互调用的小函数,且函数调用间距不超过+127或-128B,则可以将这部分代码段声明为短调用属性__NEAR_SEG。以几个实例进一步说明。

978-7-111-50417-7-Chapter03-43.jpg

978-7-111-50417-7-Chapter03-44.jpg

(5)#pragma TRAP_PROC

用于定义一个函数为中断服务类型。此类型的函数编译器在将C代码编译成汇编指令时会在代码前后增加必要的现场保护和恢复汇编代码,同时函数的最后返回用汇编指令RTI而不是针对普通函数的RTS。例如:

978-7-111-50417-7-Chapter03-45.jpg

注意:用TRAP_PROC定义的中断服务函数其实际中断矢量地址必须通过prm文件指派。

4.中断服务函数编写

编写中断函数几乎是每一个嵌入式项目开发必需的一个内容。CodeWarrior针对08系列单片机的中断函数编写有3种方式可以实现。

1)用关键词interrupt和中断矢量编号定义中断函数,这种方式最直观也最简单,但缺点是程序的可移植性稍差。关键词interrupt告诉编译器此函数为中断服务函数,数字中断矢量编号告诉连接器该中断矢量的偏移位置(以复位矢量偏移为0计)。某一个中断响应对应的矢量入口编号可以在该芯片的数据手册中查到,例如使用定时器溢出终端,其中断矢量编号为11。

978-7-111-50417-7-Chapter03-46.jpg

2)用关键词interrupt定义中断函数,中断矢量入口由prm文件指定,此处仍以上面的中断服务函数为例,定义方式为

978-7-111-50417-7-Chapter03-47.jpg

然后在项目对应的prm文件中添加一行矢量位置定义:

VECTOR 0_Startup //系统默认的复位矢量入口

VECTOR 17 SCI1_Receive_ISR //指定的中断服务矢量入口

3)用#pragma TRAP_PROC定义中断函数,中断矢量入口由prm文件指定实际上就是用前面介绍的#pragma TRAP_PROC定义中断函数,再按照和interrupt相同的方法在prm文件中指定矢量入口,不再重复介绍。

5.prm文件

prm文件是飞思卡尔单片机编程中一个非常重要的文件,可用于描述存储器映像,该文件由CodeWarrior编译器生产,其内容较为复杂,按所含的信息,prm文件由5个组成部分构成,在此仅简要介绍prm文件的各组成结构,详细设置请参考相关技术手册。

●“NAMES—END”部分用以指定在连接时加入除本项目文件列表之外的额外的目标代码模块文件,这些文件都是事先经C编译器或汇编器编译好的机器码目标文件而不是源代码文件。不过这种用法比较少见,因为我们可以在图3-15所示项目文件列表的Libs一栏中添加这些目标代码文件来实现同样的任务,而且由项目列表管理这些模块文件比较直观方便。

●“SEGMENTS—END”部分定义和划分芯片所有可用的内存资源,包括程序空间和数据空间。一般我们将程序空间定义成ROM,把数据空间划分成第0页的Z_RAM和普通区域的RAM,但实际上这些名字都不是系统保留的关键词,可以由用户随意修改。用户也可以把内存空间按地址和属性随意分割成大小不同的块,每块可以自由命名。

●“PLACEMENT—END”部分将指派源程序中所定义的各种段,例如数据段DATA_SEG、CONST_SEG和代码段CODE_SEG被具体放置到哪一个内存块中,它是将源程序中的定义描述和实际物理内存挂钩的桥梁

●“STACKSIZE”定义系统堆栈长度,其后给出的长度字节数可以根据实际应用需要进行修改。堆栈的实际定位取决于RAM内存的划分和使用情况,在常见的RAM线性划分变量连续分配的情况下,堆栈将紧挨在用户所定义的所有变量区域的高端。但如果用户将RAM区分成几个不同的块,请确保其中至少有一个块能容纳已经定义的堆栈长度。

●“VECTOR”定义所有矢量入口地址。模板在生成prm文件时已经定义了复位矢量的入口地址。对于各类中断矢量用户必须自己按矢量编号和中断服务函数名相关联。如果中断函数的定义是用interrupt加上矢量号,则无须在这里重复定义。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈