收稿日期:2002 - 05 - 10。姜换新,硕士,主研领域:数字通信、嵌入 式及网络编程。
ARM 嵌入式系统C 语言编程
姜换新
(惠普中国软件研发中心 上海201206)
摘 要 无操作系统支持的嵌入式系统软件,包括系统引导(BOOT) 、驱动程序、动态内存管理、IPO、通信以及应用软件等方面。
本文详细介绍了嵌入式平台上用C 语言编写系统软件和应用软件的方法。虽然是针对ARM平台介绍的,但基本经验和算法也适
合于其他嵌入式平台的软件设计。
关键词 嵌入式系统 软件 C 语言 ARM
PROGRAMMING C ON ARM EMBEDDED PLATFORM
Jiang Huanxin
( China Software Solutions Center , Hewlett - Packard Company , Shanghai 201206)
Abstract Programming C on ARM embedded platform is a complicated project.Modules including system boot ,drivers ,dynamic memory manage2
ment ,IPO interface ,communications and applications should be considered carefully.With an excellent experience on ARM embedded system ,the au2
thor gives a detailed description in this paper on the methods and algorithms about programming ARM. Though ARM is the only discussed item ,this
paper is useful for programming on any other embedded platforms.
Keywords Embedded system Software C programming language ARM
1 引 言
无操作系统支持的嵌入式软件包括系统引导(BOOT) 、外 围驱动程序、存储管理、系统IPO、通信、应用程序等方面,需要 结合采用汇编语言(约占10 %) 和C 语言(约占90 %) 。本文结 合作者实践,详细介绍ARM嵌入式平台的C 编程方法。考虑 到通信软件涉及范围较大,本文不进行讨论。
2 系统引导与main 函数
通常C 语言是从main 函数开始的。main 函数的原型是:
int main(int argc ,char 3 3 argv)
其中argc 是参数的个数, argv 是指向各参数的指针的数组。
main 函数由操作系统内核启动,操作系统内核完成函数所需的
变量初始化工作,并在调用结束后检查main 函数的返回值,若 返回值为0 ,表明程序运行正常,否则表明程序运行出错。在嵌 入式系统中,由于没有操作系统内核存在,对main 函数的初始 化工作只能由系统引导(BOOT) 模块完成。
系统引导(BOOT) 部分完成系统初始化工作,用汇编语言 实现。它的工作包括硬件初始化、栈寄存器的设置、全局变量 的初始化或清0、RAM中运行的模块的加载、堆参数的初始化 等。完成这些工作后,再把控制权交给C 的main 函数。显然, 对嵌入式系统的main 而言,argc 和argv 这两个参数及返回值都
是没有意义的(如果返回,表明系统出现严重错误) 。另外,为 了避免产生混淆,我们还必须给main 函数另外取一个名字,比 如Main。否则,编译器将会给main 函数生成一大堆初始化代 码,导致C 程序的主入口与系统引导模块的接口错误。 系统引导模块完成各种初始化工作后,用一条跳转指令进 入C 的主入口Main ,控制权从此移交给了C 应用程序。
3 存储管理
存储管理是一个复杂的课题。从广义的角度来说,磁盘文 件系统、内存、片内高速Cache 等都属于这个范畴。嵌入式系统 中,较有意义的是内存的动态分配与释放及Flash 存储器管理 两方面。本文要介绍的是我们在嵌入式系统中实现的动态内 存管理。
C 语言中动态内存分配与释放主要由malloc 和free 两个标
准库函数实现。malloc 从系统空闲内存中分配合适的内存块,
free 函数完成内存块的回收。这两个函数一般需要操作系统内
核的支持,在ARM 裸平台上,不能直接调用。为此,我们编写 了m alloc 和m free 两个函数,实现动态存储管理的功能。 典型应用程序内存映象分成代码区、数据区和栈区,三个 区从低地址到高地址依次分布。代码区从最低地址开始,栈区
·15 ·
? 1995-2004 Tsinghua Tongfang Optical Disc Co., Ltd. All rights reserved. 则占据最高地址。代码区和数据区可以相连,也可以分开。嵌 入式系统里,代码区位于只读存储器(如Flash) 中,数据区和栈 区则位于RAM中,因此代码区和数据区一般并不相连1) 。数据 区和栈区是分开的,它们之间的空隙称作堆。
堆作为一个连续的可利用空间,是系统的初始可分配块。 每次应用程序申请内存,m alloc 便从堆中分割出一块(从低地 址开始) 给它。随着申请次数的增加,原来一个完整的内存块 便被分割为多个独立的块分配给应用程序。由于内存释放的 先后顺序是随机的,因此一定时间后,系统中将存在多个互不 相连的内存块。这就使得整个内存区呈现出占用块和空闲块 犬牙交错的状态,如图1 所示。图中灰色部分表示内存被占 用,白色部分表示未被占用。
图1 动态存储管理过程中的内存状态
为了进行内存动态管理,需要维护两张全局表,一张是可 利用空间表(avail list) ,管理空闲内存块的信息,另一张是已 分配空间表(used list) ,管理占用内存块。这两张表都用双向 循环链表实现。随着系统的运行,可利用空间表中往往会有多 个空闲块存在,究竟分配哪一块呢? 文[ 1 ]介绍了三种不同的 分配策略,即首次拟合法、最佳拟合法和最差拟合法,各有优缺 点。笔者实现的是首次拟合法。
可利用空间表和已分配空间表采用相同的“表元”数据结 构,定义如下:
struct mblock{
struct mblock 3 next ;
struct mblock 3 prev ; size t size ; char 3 space ; } ;
在系统初始化时,整个可分配内存块是一个连续的存储 区,可利用空间表的元素只有一个。m alloc 函数每次分配内 存时,先检查size (m alloc 的参数) 是否合法(如是否超出堆的 范围) ,若合法,再将其与32 - bit 字对齐,然后从avail list 中搜 索合适的内存块,并将其分配给应用程序。如果内存块的大小 比size 大得较多,则对内存块进行分裂,低地址的一块分配给 应用程序,高地址的一块仍然放入avail list 中。如果搜索不到 合适的空闲块,m alloc 返回(void 3 ) 0。
m free 函数释放内存时,根据参数addr 给定的地址,在 used list 中搜索相应的表元,找到后,将它标识的内存块释放,
并插入到avail list 中去。然后,在avail list 中检查是否有相 邻的空闲块,并进行空闲块的合并。有三种不同的情况要分别 处理: (1) 左相邻:相邻块在当前释放块的低地址端。(2) 右相 邻:相邻块在当前释放块的高地址端。(3) 左右相邻:当前释放 块的低地址端和高地址端都有相邻块。
在具体的分配算法上,文[1 ]介绍了边界标识法和伙伴系 统。前者直接将链表管理信息插入到内存块的前端和后端,回 收算法效率较高,但如果应用程序改写了超出它所申请范围的 内存区,则会破坏整个数据结构,鲁棒性差一些。后者是笔者 采用的算法之一,但使用下来发现它没有本文所描述的算法的 效率高,且容易形成很多内存碎片。
4 LCD 终端( 系统IPO)
LCD 终端软件是系统I/ O 范畴的重要内容,主要包括LCD
字符显示(英文8 ×16 点阵,汉字16 ×16 点阵) ,LCD 绘图(点、 线、圆、面、位图、图形旋转等) 。320 ×240 象素的LCD 显示器, 能显示15 行×40 列英文字符,或15 行×20 列汉字字符,并基 本实现有较好分辨率的图形/ 图像的显示。
LCD 显示的最基本程序是画点程序,其原型如下:
void LCDPixel (int x , int y ,char color)
其中,x 和y 是点的坐标,坐标原点在左上角,color 是点的灰度。 字符和位图的显示利用了点阵方式。线、圆和面则利用相 应的算法实现。图形旋转需要使用坐标变换函数。 这里要详细介绍的是把LCD 作为(英文) 字符型终端时的 相应软件设计。把LCD 作为字符型终端时,一个关键点是定义 好光标:
static unsigned CurrentLine ,CurrentColumn
这里CurrentLine 和CurrentColumn 分别定义了光标的横坐 标和纵坐标(坐标原点在左上角) ,取值范围分别是(0~39) 和
(0~14) ,对应于横行40 个字符和纵列15 个字符。