如何开发自己的嵌入式系统
嵌入式系统的悠久历史
电脑用于控制设备或嵌入系统的历史几乎电脑自身的历史一样长。在通讯领域,六十年代晚期,电脑被用于电子电话交换机,称为“存储程序控制”系统。“电脑”这词那时并不普遍,存储程序指内存装有程序和例程信息。存储控制逻辑,而不是将其固化在硬件中,在当时确实是突破性的。今天,我们认为它本来就应如此。
那时的电脑是为每一个应用而定制的,按今天的标准,它们是一些不正常的、由奇怪的特殊指令和I/O设备集成在一部电脑中。
微处理器通过提供构建大系统模块的小型、低成本、CPU引擎改变了这一切。它提出了外设通过总线联接的固定硬件架构及称为编程的一般编程模型。
软件也随着硬件提出。最初,编写和测试软件只有简单的编程开发工具。每个项目实际运行的软件通常来自于草稿的修改。编程常用汇编语言或宏语言,因为编译器常常有缺陷和缺乏完善的调试工具。软件构建模块和标准化库只是到了七十年代才流行起来的概念。
嵌入式系统的商品化操作系统在1970年代后期才出现,许多是用汇编语言写成的,并且只能用于特定的微处理器,当微处理器被淘汰时,它的操作系统除非为新处理器重写,否则也要被淘汰。今天,许多这类早期的系统成了些模糊的记忆;还有谁记得MTOS吗?当C语言出现时,操作系统编写的效率、稳定性、可移植性都提高了很多。这一点在管理上立刻表现出来,它为微处理器被淘汰时保护软件投资带来了希望。对于市场来说这是一个好消息。用C语言写成的操作系统今天越来越普遍。一般来说,可重复使用的软件已经占主导并越做越好。
在八十年代早期,我最喜欢的操作系统是Wendon操作系统,大约150美元就可以得到一个C源码库。它是一个包,你可以通过选择部件建立自己的操作系统,类似在菜单上点菜。例如,你可以在库清单上点工作排程安排和内存管理方案。 很多嵌入式系统的商品化操作系统是在八十年代出现的。这一热潮持续到现在,今天,有很多可行的商品化操作系统可供选择。一些大佬出现了,如VxWorks, pSOS, Neculeus和Windows CE。
许多嵌入式系统根本没有操作系统,只有循环控制。对于一些简单设备这是足够的,但是随着系统越来越复杂,操作系统就很必要了或软件变得不可思议的复杂。不幸的是,有些复杂得可怕的嵌入式系统只因为设计者坚持不要操作系统才那么复杂。
渐渐地,更多嵌入式系统需要与各类网络联接,因此需要网络功能。即便是酒店的门把手也嵌入了微处理器与网络相联。 对于仅仅是编码控制循环的嵌入式系统,增加网络功能将导致系统复杂程度提高以致要求操作系统。
除了商品化操作系统,还有大量专用操作系统。其中大部分来自于草案,如CISCO的IOS;还有是从其他操作系统中派生出来的。例如,许多操作系统是从同一版本的Berkeley Unix系统派生,因为它有完整的网络功能。其他是基于主要操作系统的如KA9Q来自Phil Karn。
Linux作为嵌入式系统是一个带有很多优势的新成员。它对许多CPU和硬件平台都是可移植的、稳定、功能强大、易于开发。
工具包突破ICE的障碍
开发嵌入式系统的关键的是可用的工具包。像任何工作一样,好的工具使得工作更快更好。开发的不同阶段需要不同的工具。
传统上,首先用于开发嵌入式系统工具是内部电路仿真器(ICE),它是一个相对昂贵的部件,用于植入微处理器与总线之间的电路中,允许使用者监视和控制微处理器所有信号的进出。这有点难做,因为它是异体,可能会引起不稳定。但是它提供了总线工作的清晰状况,免了许多对硬件软件底层工作状况的猜测。
过去,一些工作依赖ICE为主要调试工具,用于整个开发过程。但是,一旦初始化软件对串口支持良好的话,多数的调试可以不用ICE而用其他方法进行。较新的嵌入式系统利用非常清晰的微处理器设计。有时,相应工作初始码已经有了能够快速获得串口工作。这意味着没有ICE人们也能够方便地工作。省去ICE降低了开发的成本。一旦串口开始工作,它可以支持各种专业开发工具。
Linux是基于GNU的C编译器,作为GNU工具链的一部分,与gdb源调试器一起工作。它提供了开发嵌入式Linux系统的所有软件工具。这有些典型的、用于在新硬件上开发嵌入式Linux系统的调试工具。
1.写入或植入引导码
2.向串口打印字符串的编码,如“Hello World”(事实上我更喜欢“Watson,Come hre I need you”,电话上常用的第一个词。)
3.将gdb目标码植入工作串口,这可与另一台运行gdb程序的Linux主机系统对话。只要简单地告诉gdb通过串口调试程序。它通过串口与测试机的gdb目标码对话,你可以进行C源代码调试,也可以用这个功能将更多的码载入RAM或Flash Memory中。
4.利用gdb让硬件和软件初始化码在Linux内核启动时工作。
5.一旦Linux内核启动,串口成为Linux控制口并可用于后续开发。利用kgdb,内核调试版的gdb,这步常常不作要求,如果你与网络联接,如10BaseT,下一步你可能要启动它。
6.如果在你的目标硬件上运行了完整的Linux内核,你可以调试你的应用进程。利用其他的gdb或覆盖gdb的图形如xgdb。
什么是实时系统?
嵌入式系统常常被错误地分为实时系统,尽管多数系统一般并不要求实时功能。实时是一个相对的词,纯化论者常常严格地定义实时为对一事件以预定的方式在极短的时间如微秒作出响应渐渐地,在如此短暂时间间隔内的严格实时功能在专用DSP芯片或ASIC上实现了。只有在设计低层硬件FIFO、分散/聚集DMA引擎和定制硬件时才会有这样的要求。
许多设计人员因为对真实的要求设有清晰的理解而对实时的要求焦虑不安。对于大多数的系统,在一至五微秒的近似实时响应已经足够。同样软需求也是可以接受的。如 Windows 98 已经崩溃的中断必须在4毫秒内(±98%)内、或20毫秒(±0)内进行处理。
这种软要求是比较容易满足的,包括环境转换时间、中断等待时间、任务优先级和排序。环境转换时间曾是操作系统的一个热门话题。总之,多数CPU这些要求处理得很好,而且CPU的速度现在已经快了很多,这个问题也就不重要了。
严格的实时要求通常由中断例程或其他内核环境驱动程序功能处理,以确保稳定的表现,等待时间,一旦请求出现要求服务的时间很大程度上取决于中断的优先及其他能暂时掩盖中断的软件。
中断必须进行处理和管理以确保时间要求能符合,如同许多其他的操作系统。在IntelX86处理器中,这工作很容易由Linux实时扩展处理。这是提供了一个以后台任务方式运行Linux的中断处理调度。关键的中断响应不必通知Linux。因此可以得到许多对于关键时钟的控制。在实时控制级和时间限制宽松的基本Linux级之间提供接口,这提供了与其他嵌入式操作系统相似的实时框架。因此,实时关键代码是隔开的、并“设计”成满足要求的。代码处理的结果是以更一般的方法也许只在应用任务级。
嵌入式系统定义
一个观点是如果一个应用没有用户界面,它必须是嵌入式的,因为用户不能直接与之交互。当然这是简单化的。一个电梯控制的电脑被认为是嵌入式的:按键选择楼层指示灯显示电梯的停层。对于联网的嵌入式系统,如果系统包含监视和控制的网络浏览器,这种界限就更加模糊了。更好些的定义注重系统的集中的功能和主要的目的。
因为Linux提供了完成嵌入功能的基本的内核和你所需要的所有用户界面,它是多面的。它能处理嵌入式任务和用户界面。将Linux看作是连续的统一体,从一个具有内存管理、任务切换和时间服务及其他的分拆的、微内核到完整的服务器,支持所有的文件系统和网络服务。
一个小型的嵌入式Linux系统只需要下面三个基本元素:
1.引导工具:Linux微内核,由内存管理、进程管理和事务处理构成
2.初始化进程:如果要让它能干点什么且继续保持小型化,还得加上:
3.硬件驱动程序:提供所需功能的一个或更多应用程序。
再增加功能,或许需要这些:一个文件系统(也许在ROM或RAM中)TCP/IP网络堆栈存储半过渡数据和交换用的磁盘。
硬件平台
选择最好的硬件是一个复杂的工作、充满了公司其他项目的政治、偏见、传统,缺乏完整或精确的信息。 成本经常是关键的议题。当考虑成本时、确信你在考虑产品的整个成本、不仅是CPU。有时快的、便宜的CPU一旦加上总线逻辑和时延使之与外设一起工作,能变成一个昂贵的狗的产品。如果你在寻找软件,首先是硬件已经有产品了。如果你是系统设计者,由你决定制定实时的预算及硬件的工作是否满意。
现实中需要多快的CPU来完成一项工作,然后放大三倍。奇怪,CPU理论上的速度竟与现实中一样,别忘了应用程序将会充分利用cache。
想象总线的速度需要多快,如果有其他总线如PCI总线,包括进来。慢的总线或产生DMA阻塞的总线会降低CPU的速度造成拥挤。 有集成设备的CPU是好的,因为只须调试很少的设备,并且支持通用CPU的驱动程序通常都很容易获得。在我的项目中,芯片与外设的联接经常出问题或不满足我们所需的兼容性。因为外设是集成的,不要认为这会便宜。
将10斤重的Linux塞入只能装5斤的袋中。对于Linux一个共同的认识是它用于嵌入式系统简直是神奇极了。这可能不大对,典型的PC上的Linux对PC用户来说功能有多,对初学者而言,可以将内核与任务分开,标准的Linux内核通常驻留在内存中,每一个应用程序都是从磁盘运到内存上执行。当程序结束后,它所占用的内存就被释放,程序就被下载了。
在一个嵌入式系统里,可能没有磁盘。有两种途径可以消除对磁盘的依赖,这要看系统的复杂性和硬件的设计,在一个简单的系统里,当系统启动后,内核和所有的应用程序都在内存里。这就是大多数传统的嵌入式系统工作模式,它同样可以被Linux支持。
有了Linux,就有了第二种可能性。因为Linux已经有能力“加载”和“卸载”程序,一个嵌入式系统就可以利用它来节省内存。试想一个典型的包括一个大概8MB到16MB的Flash Memory和8MB内存的系统。Flash Memory可以作为一个文件系统。Flash Memory驱动程序用来连接Flash Memory和文件系统。作为替代,可使用Flash Disk。这Flash部件用软件仿真磁盘。其中一个例是M-Systems的DiskOnChip,可以达到160MB。所有的程序都以文件形式存储在Flash文件中,需要时可以装入内存。这种动态的、“根据需要加载”的能力是支持其它一系列功能的重要特征:它使初始化代码在系统引导后被释放。Linux同样有很多内核外运行的公用程序。这些通常程序在初始化时运行一次,以后就不再运行。而且,这些公用程序可以用它们相互共有的方式,一个接一个按顺序运行。这样,相同内存空间可以被反复使用以“召入”每一个程序,就象系统引导一样。这的确可以节省内存,特别是那些配置一次以后就不再更改的网络堆栈 如果Linux可加载模块的功能包括在内核里,驱动程序和应用程序就都可以被加载。它可以检查硬件环境并且为硬件装上相应的软件。这就消除了用一个程序占用许多Flash Memory来处理多种硬件的复杂性。
软件的升级更模块化。你可以在系统运行的时候在Flash上升级应用程序和可加载驱动程序,配置信息和运行时间参数可以作为数据文件储存在Flash上。
非虚拟内存
标准Linux的另一个待征是虚拟内存的能力。正是这种神奇的特征使应用程序员可以狂热的编写代码而不计后果,不管程序有多大。程序溢出到了磁盘交换区。在没有磁盘的嵌入式系统里,通常不能这么做。
在嵌入式系统里不需要这种强大的功能。实际上,你可能不希望它在实时的关键系统里,因为它会带来无法控制的时间因素。这个软件必须设计得更加精悍,以适合市面上物理内存,就象其它嵌入式系统一样。
注意由于CPU的原因,通常在Linux中保存虚拟内存代码是明智的,因为将它清除很费事。而且还有另外一个原因是它支持共享文本,这样就可以使许多程序共享一个软件。没有这个,每一个程序都要有它自己的库,就象printf一样。
虚拟内存的调入功能可以被关掉,只要将交换空间的大小设置为零。然后,如果你写的程序比实际的内存大,系统就会当作你的运行用尽了交换空间来处理;这个程序将不会运行,或者malloc将会失灵。
在许多CPU上,虚拟内存提供的内存管理可以将不同程序分开,防止它们写到其它地址的空间上。这在嵌入式系统上通常不可能,因为它只支持一个简单、扁平的地址空间。Linux的这种功能有助于其发展。它减少了胡乱的编写程序造成系统崩溃的可能性。许多嵌入式系统基于效率方面的原因有意识使用程序间可以共享的“全局”数据。这也可以通过Linux共享内存功能来支持,共享的只是指定的内存部分。
文件系统
许多嵌入式系统没有磁盘或者文件系统。Linux不需要它们也能运行。如前所述,应用程序任务可以和内核一起编写,并且在引导时作为一个影像加载。对于简单的系统来说,这就够了。然而,它缺乏前面所说的灵活性。
实际上,许多商业性嵌入式系统,提供文件系统作为选项。许多或者是专用的文件系统或者是MS-DOS-Compatible文件系统。Linux提供MS-DOS-Compatible文件系统,同时还有其它多种选择。之所以提供其它选择是因为它们更加强大而且具有容错功能。Linux还具有检查和维护的功能,商业性供应商往往不提供这些。这对于Flash系统来说尤其重要,因为它是通过网络更新的。如果系统在升级过程中失去了能力,那它就没有用了。维护的功能通常可以解决这类问题。
文件系统可以被放在传统的磁盘驱动器、Flash Memory或其它这类的介质上。而且,用于暂时保存文件,一个小RAM盘就足够了。Flash Memories被分割成块。这些块中也许包括一个含有当CPU启动时运行的最初的软件的引导块。这可能包括Linux 引导代码。剩余的Flash可以用作文件系统。Linux的内核可以通过引导代码从Flash复制到RAM,或者还有一个选择,内核可以被存储在Flash的一个独立部分,并且直接从那里执行。
另外对于一些系统来说还有一个有趣的选择,那就是将一个便宜的CD-ROM包含在内。这比Flash Memory 便宜,而且通过交换CD-ROM支持简单的升级。有了这个,Linux 只要从 CD-ROM上引导,并且象从硬盘上一样从CD-ROM上获得所有的程序。
最后,对于联网的嵌入式系统来说,Linux 支持NFS(Network File System)。这为实现联网系统的许多增值功能打开了大门。第一,它允许通过网络上加载应用程序。这是控制软件修改的基础,因为每一个嵌入式系统的软件都可以在一个普通的服务器上加载。它在运行的时候也可以用来输入或输出大量的数据、配置和状态信息。这对用户监督和控制来说是一个非常强大的功能。举例来说,嵌入式系统可以建立一个小的RAM磁盘,包含的文件中有与当前状态信息同步的内容。其它系统可以简单的把这个RAM磁盘设置为基于网络的远程磁盘,并且空中存取状态文件。这就允许另一个机器上的Web服务器通过简单的CGI Script存取状态信息。在其它电脑上运行的其它应用程序包可以很容易的存取数据。对更复杂的监控,应用程序包如Matlab可以用来在操作员的PC或工作站的提供系统运行的图形展示。
引导LILO和BIOS在哪里
当一个微处理器第一次启动的时候,它开始在预先设置的地址上执行指令。通常在那里有一些只读内存,包括初始化或引导代码。在PC上,这是BIOS。它执行了一些低水平的CPU初始化和其它硬件的配置。BIOS继续辨认哪个磁盘里有操作系统,把操作系统复制到RAM并且转向它。实际上,这非常复杂,但对我们的目标来说也非常重要。在PC上运行的Linux依靠PC的BIOS来提供这些配置和OS加载功能。
在一个嵌入式系统里经常没有这种BIOS。这样你就要提供同等的启动代码。幸运的是,嵌入式系统并不需要PC BIOS引导程序那样的灵活性,因为它通常只需要处理一个硬件的配置。这个代码更简单也更枯燥。它只是一指令清单,将固定的数字塞到硬件寄存器中去。然而,这是关键的代码,因为这些数值要与你的硬件相符而且要按照特定的顺序进行。所以在大多数情况下,一个最小的通电自检模块,可以检查内存的正常运行、让LED闪烁,并且驱动其它必须的硬件以使主Linux OS启动和运行。这些启动代码完全根据硬件决定,不可随意移动。
幸运的是,许多系统都有为核心微处理器和内存所定制的菜单式硬件设计。典型的是,芯片制造商有一个样本主板,可以用来作为设计的参考或多或少与新设计相同。通常这些菜单式设计的启动代码是可以获得的,它可以根据你的需要轻易的修改。在少数情况下,启动代码需要重新编写。 为了测试这些代码,你可以使用一个包含‘模拟内存’的电路内置模拟器,它可以代替目标内存。你把代码装到模拟器上并通过模拟器调试。如果这样不行,你可以跳过这一步,但这样就要一个更长的调试周期。
这个代码最终要在较为稳定的内存上运行,通常是Flash或EPROM芯片。你需要使用一些方法将代码放在芯片上。怎么做,要根据“目标”硬件和工具来定。
一种流行的方法是把Flash或EPROM芯片插入EPROM或Flash烧制器。这将把你的程序“烧”(存)入芯片。然后,把芯片插入你的目标板的插座,打开电源。这个方法需要板上配有插座,但有些设备是不能配插座的。 另一个方法是通过一个JTAG界面。一些芯片有JTAG界面可以用来对芯片进行编程。这是最方便的方法。芯片可以永远被焊在主板上,一个小电缆从板上的JTAG连接器,通常是一个PC卡,联到JTAG界面。下面是PC运行JTAG界面所需的一些惯用程序。这个设备还可以用来小量生产。
稳定性是最可靠的
在PC硬件上运行时,Linux是非常可靠和稳定的,特别是和现在流行的一些操作系统相比。嵌入式内核本身有多稳定呢?对大多数微处理器来说,Linux非常好。移植到新微处理器家族的Linux内核运行起来与本微处理器一样稳定。它经常被移植到一个或多个特定的主板上。这些板包括特定的外围设备和CPU。
幸运的是,许多代码是与处理器的,所以移植集中在差异上。其中大多数是在内存管理和中断控制领域。一旦成功移植,它们就非常稳定。前面我们讨论过,引导策略广泛依赖于硬件要求,而且你必须有计划地做一些定制的工作。
设备驱动程序更加混乱:有些稳定有些不稳定。而且选择很有限;一旦你离开了通用的PC平台,你需要自己编写。幸运的是,周围有许多驱动程序,你可能可以找到一个与你的需求相近的修改一下。这种驱动程序界面已定义好。许多类的驱动程序都非常相近,所以把磁盘、网络或一系列的端口驱动程序从一个设备移植到另一个设备上通常并不难。我发现许多驱动程序都写得很好,很容易理解,但你还是要准备一本关于内核结构的书在手头。 依我的经验,Linux至少和我用过的著名的商业性操作系统一样稳定。总之,这些操作系统和Linux的问题在于对工作过程微秒之处的误解,而不在于代码的难度或基本的设计错误。任何操作系统都有很多争论不休的故事,这里不需要重复。Linux的优势在于源代码是公开、注释清晰和文档齐全的。这样,你就可以控制和处理所出现的任何问题。
伴随着基本内核和驱动程序,还有其它问题。如果系统有一个硬盘,那么文件系统的可靠性就成问题。我们有用磁盘进行Linux系统设计超过两年的经验。这些系统几乎从未正常关闭过。电源随时都可能被中断。感觉非常好,使用的是标准(EXT2)文件系统。标准Linux初始化脚本运行fsck程序,它在检查和清除不稳定的inodes方面非常有效。将默认的每隔30秒运行更新程序改为每隔5或10秒运行是比较明智的。这样缩短了数据在进入磁盘之前,待在高速缓冲存储器内的时间,降低了丢失数据的可能性。
未来的发展
嵌入式Linux的确有它的缺陷。比如,虽然它并不比某些商业竞争对手差多少,但它的确是个贪婪的存储器。这可以通过减少一些不必要的功能来弥补,但这可能会花很长的时间,而且如果不仔细的话,还可能带来很大的困扰。
许多Linux的应用程序都要用到虚拟内存,在许多嵌入式系统中,是没有价值的,所以不要以为一个没有磁盘的嵌入式系统可以运行任何Linux应用程序。
内核调试工具都不怎么好,特别是在较底层的。kgdb可以使错误定位非常容易,你只要重新启动。不幸的是,打印语句更麻烦。
然而,对我来说最糟糕的是心理上的问题。Linux非常的灵活。嵌入式系统总的来说却不灵活;而且它们完全是为最有效实现预定功能而严格设计的。现在的趋势是保持灵活性、保持总体目标功能、尽量少做修改。这个目标是崇高的,但是,所付出的代价将是针对具体的工作做出巨大的调整。保持灵活性将导致额外的工作,带着额外的软件包,而且有时还要降低性能。一个反复出现的例子就是配置。考虑在一个网络界面配置IP地址,这通常是通过从启动script上运行ifconfig程序来完成的。这是一个28K的程序,从配置文件上调用数据,可以用几行代码代替,初始化合适的结构。然而,即使这非常合理,但它仍然有害,因为它用一种从未使用过的方法扭曲了软件。
Linux在嵌入式系统中的应用是可行的。它有用、可靠。它的发展成本和替代者一致。
评论
查看更多