- 易迪拓培训,专注于微波、射频、天线设计工程师的培养
基于嵌入式系统设计要点
用大容量EPROM来固化程序的专用系统,正在智能仪器和自动化等领域里 得到广泛应用。传统设计方法用汇编语言编写程序,这主要是从保证速度和节省存储 空间考虑,但编程费时,调试和排错很不容易。微电子技术的飞速发展,使高性能微 处理器和大容量存储器的价格变得十分便宜,速度和存储容量不再是困扰设计者的主 要问题。人们将ROMBIOS和CRT显示器等外设加进这类专用系统,并尝试用 高级语言来开发其软件,即把通用计算机上的软件和硬件“嵌入”专用系统,构成所 谓的嵌入式系统(EmbeddedSystem)。由于C语言容易编程、代码紧 凑、可移植性和可维护性好,因而被普遍用于嵌入式程序的设计。
大多数嵌入式系统无操作系统支持,要由设计者提供所有低级I/O功能。系统 I/O资源有限,程序必须固化在EPROM中,不能象在DOS下那样从磁盘装入 和由用户编程。设计者要编写一个定位程序(Locator),把EXE格式的应 用程序转换成可固化进EPROM的二进制文件(ROM图)。还要编写一个启动程 序(runtimeStartupCode),与ROM图一起嵌入EPROM, 先由它建立数据区和对系统硬件作必要初始化,然后调Main函数,执行应用程序。 若想发挥C语言之优势,使用一些标准I/O语句,如用printf驱动显示器等, 则要在嵌入式程序中加进经过修改的库函数。总之,C语言编程会使系统开发面临一 些新问题,要求设计人员具备软硬件方面的综合知识,才能正确进行系统调试和排错。
当然,如果拥有专用的嵌入式系统开发工具,设计工作便要省劲些。它们通常配 有定位程序和可供设计者修改的启动程序样板,有些还能通过串口或并口,在PC机 上联机调试程序,甚至有源级代码调试功能。利用工控机来设计系统,事情就更简单。 不过,专用开发工具和工控机价格昂贵,因此许多人在设计嵌入式系统时选择自己编 写定位程序和启动程序,甚至编写可嵌入的I/O库函数。本文就嵌入式系统的程序 设计方法及设计中可能遇到的问题作些讨论,供打算设计嵌入式程序的读者参考,有关编写定位程序和启动程序的具体方法将另文介绍。
2 嵌入式程序的定位
2.1 EXE文件格式和DOS重定位
DOS下的EXE文件是一种可重定位文件 (Re-locatableFile),它由重定位标头和装入模块组成。后者含 一段或几段程序代码,段数与类型取决于程序规模和编译时所用的内存模式,然后是 初始化与未初始化的数据及堆栈,还可能有程序排错信息。代码、数据和堆栈段地址 均是参考到程序开头的相对地址。标头放在装入模块之前,含若干定位控制信息和一 张定位表。控制信息包括EXE文件大小、标头长度、需要重定位的项数和位置、装 入模块的开头和堆栈的相对地址等。定位表是一组形式为段址:偏址的远指针,指示 装入模块中要重定位的那些段址相对于模块开头处的位置。装进RAM后,加载程序 建立起程序段前缀PSP,并根据系统当前可用RAM地址修改这些段址,对装入模 块重定位,使程序中所有参考绝对地址的量正确指向模块装入后的起始地址,然后执 行。因此它可在RAM中的任何位置上执行。
2.2 嵌入式程序的定位
嵌入式系统有ROM和RAM两类内存,程序被固化进ROM,而程序 变量和堆栈应设在RAM中。因此,对EXE文件的重定位过程与DOS下不一样。定位程序必须根据系统的ROM和RAM地址,对定位表中各远指针指向的字进行修改。定位程序最后以一种可加载进测试系统或
可烧入EPROM的形式输出程序,即ROM图,它可以是二进制或Intel的HEX格式,根据EPROM编程器、仿真器或调试程序的要求而定。
可用两种方法把EXE文件转换为ROM图:
一是使用EXE2BIN命令。若EXE文件定位表中不含有定位远指针,EXE2BIN便将它转换成COM文件,它是可固化进EPROM的二进制文件,否则便放弃转换。这仅适用于较小的单段程序。较新版本的EXE2BIN在发现EXE文件中含重定位项时,会提示用户提供一个基地址,进行重定位。若选用适当的内存模式,并限制使用远指针,它也可能用一个基地址进行定位。但对于规模较大的程序,EXE2BIN无能为力。
二是根据标头和MAP文件所提供的信息进行定位,适用于所有的EXE文件。若在连接时进行限定,可生成只含内存分配段表的简单MAP文件。段表的每行描述一个段,按代码段、数据段和堆栈段的次序排列。MicrosoftC和BorlandC的MAP文件每行长度略有区别,但行上各参数(段始址、段末址、段长、段名、段类)的位置是固定的。 定位程序根据第一个RAM段的段名,从MAP文件中抽取出它的起始地址,它就是数据区的相对始址。再从标头内容计算出装入模块大小,即要占用的ROM容量。将系统ROM始址加上代码段在装入模块中的
相对地址,便得到程序开始执行的地址。然后,对定位指针进行自小到大排序。根据各段的始末地址逐段析出段址,并从装入模块中读入该段代码或数据。接着按定位指针顺序考察待定位的段址,若它落在该段范围内,便进行定位操作,即把此段址修改成实际的ROM或RAM地址。直到将属于这个段的定位指针全部处理好后,便把这段内容写到输出文件。对所有段都进行定位后,便获得ROM图。
3 启动程序
ROM中程序执行前,先要在RAM中建立堆栈和数据区。串数据等常数与程序一起固化在ROM中,程序可以访问它们,但RAM的存取速度比E-PROM高,因此也被复制进RAM,以提高读出速度。还需要建立C程序运行的环境,如对段寄存器和堆栈指针初始化、对静态变量和RAM区清零、建立堆(heap)等。程序运行前还应设置必要的中断矢量,并让各未用中断指向一个只含RET指令的哑函数,以防止错误中断引起系统的混乱。此外,还要对系统硬件进行初始化,并根据具体的系统,加入出错时中止程序或重启动的程序段等。这些工作都由用汇编语言编写的启动程序完成。启动程序是嵌入式程序的开头部分,应与应用程序一起固化在EPROM 中,并首先在系统上运行。它应包含进各模块中可能出现的所有段类,并合理安排它们的次序。当它作为第一个模块和应用程序等一起连接时,LINK将按照该次序归并类名相同的段。
写好启动程序是设计好嵌入式程序的关键。各类C编译均提供自己的启动程序模块(C0.ASM),可以此为样板,经简化和修改形成适用本系统的启动程序,也可以先搭一个启动程序骨架,再逐步完善。
4 嵌入式程序的运行
嵌入式系统大多不能从键盘接受命令,而要在系统通电或复位时,自动执行ROM中的程序。各系统的复位地址不尽相同,以工作在实模式的80x86嵌入式系统为例,CPU复位后将执行F000:FFF0H处的代码。这是系统ROM的高址端,仅有16字节空间,设计者可用DEBUG命令在ROM图的这个位置上,放一条无条件远跳转指令JMPFARPTRstart,转到ROM开头,从那里执行启动程序(。启动程序完成上述的初始化后调main函数,执行应用程序。 80286以上的CPU复位时,CS:IP初值仍是F000:FFF0H。但
A20以上地址线在CS寄存器被第一次装进新的内容前,一直保持高电平,即开始地址指向最高地址端。如只要求系统工作在实模式,可由译码电路将开始的高地址反射到低端的1MB空间,复位矢量仍是F000:FFF0H。当上述JMP指令一 执行,CS被改写,A20以上地址线将变低而进入实模式。要是希望启动后进入保护模式,就不需要进行地址反射,但是相应的复位地址上只能放一条近跳转指令,保证不改变CS值。然后进行必要的初始化,尽快进入保护模式。
5 嵌入式程序的编译和连接
综上所述,设计嵌入式系统时要在PC机上编写三个程序:应用程序MYPROG.C;定位程序LO-CATOR.C;启动程序STARTUP.ASM。然后按以下步骤进行编译和连接,生成可编程的ROM图:
将应用程序编译成MYPROG.OBJ。
将定位程序编译和连接成可执行程序LO-CATOR.EXE。
将启动程序编译成STARTUP.OBJ。
对STARTUP.OBJ、MYPROG.OBJ及必要的库函数进行连接,生成EMBED.EXE和简化的MAP文件EMBED.MAP。STARTUP 必须是LINK行上的第一个模块,保证它先执行。
执行LOCATOR.EXE,以EMBED.EXE、EMBED.MAP、第一个RAM段的段类名、RAM和ROM始址为输入参数,实现定位,输出ROM图EMBED.BIN。
用DEBUG命令在EMBED.BIN的F000:FFF0H位置上加进指令JMPFARPTRstart,形成最终的ROM图。
6 其它几个问题
6.1 系统内存考虑
为确保正确复位,设计硬件时要让ROM地址空间覆盖复位矢量。例如8086系统的最高地址为F000:FFFFH,若采用128KB的ROM,其地址范围应取E000:0000-F000:FFFFH。 RAM地址则应从0开始,由于开头1KB字节RAM要保留给中断矢量表,通常如图2那样将RAM数据区设在地址0040:0000H处。
常数先固化在ROM中,然后被复制进RAM,因此占用的存储器空间是DOS下的两倍。可在程序设计中设法限制要复制进RAM的常数数量。例如,系统若支持CRT显示器,可能需要在屏幕上显示各种消息和菜单提示。这时,可把所有显示函数和有关文本串放进一个模块,再用指针来存取它们。比如,本来可用下面语句打印提示:
printf("PressStoStart");
printf("PerssQtoQuit");
若程序中有许多类似的语句,便可能存在较多重复串。要是对各子串都用指针访问,编译就会把其中重复的串(如Press,to等)合并,省下不少内存空间。即把上面语句改为:
printf("%s%s%s%s","Press","S","to","Start") ;
printf("%s%s%s%s","Perss","Q","to","Quit");
6.2 标准I/O函数的使用
用C语言编写嵌入式程序的过程与DOS下一样,只是要避免使用不能被固化到 ROM中的库函数。在DOS下,许多低级I/O函数(如putch,getch) 均通过DOS中断21与硬件接口,高级函数printf,scanf等也使用该中断。若希望在无ROMBIOS的系统上使用这些函数,应编写一个模仿 DOSINT21的函数,这样便能使用除磁盘I/O函之外的大多数标准I/O 函数,缩短程序开发时间,并保证较好的可移植性。当然,如果使用的是现成的嵌入式系统开发软件包,厂商将告知哪些函数可被固化进ROM,不必自己编写INT21函数。
编写嵌入式支持函数时要防止使用与DOS有关的库函数。比较起来,BC提供 的库比MSC的更独立于DOS。例如,MSC的printf函数要依赖几个低级 的DOS函数。所以在仿真INT21的控制台I/O函数时,建议用BC的 printf函数。
6.3 配置参数的保护
断电时,嵌入式系统应能保持那些用于系统恢复或外设配置的数据,可用电池供 电的RAM或EEPROM来存放它们。但在复位时,启动程序要把有初值的变量复 制进RAM,并对其余RAM区清0,结果会清除应保持的数据。为此,可把这类数据归入一个专门段,不包括在初始化和未初始化数据区中,不让启动程序修改它们。
射频工程师养成培训教程套装,助您快速成为一名优秀射频工程师...