基于ARM的嵌入式系统程序开发要点(一)
与传统的4/8位单片机相比,ARM的性能和处理能力当然是遥遥领先的,但与之相应,ARM的系统设计复杂度和难度,较之传统的设计方法也大大提升了。本文旨在通过讨论系统程序设计中的几个基本方面,来说明基于ARM的嵌入式系统程序开发的一些特点,并提出和解决了一些常见的问题。
本文分成几个相对独立的专题陆续刊载。
(一) 嵌入式程序开发基本概念
(二) 系统的初始化过程
(三) 如何满足嵌入式系统的灵活需求
(四) 异常处理机制的设计
(五) ARM/Thumb的交互工作
(六) 开发高效程序的技巧
1 嵌入式程序开发过程
不同于通用计算机和工作站上的软件开发工程,一个嵌入式程序的开发过程具有很多特点和不确定性。其中最重要的一点是软件跟硬件的紧密耦合特性。
图1是两类简化的嵌入式系统层次结构图。由于嵌入式系统的灵活性和多样性,图1中各个层次之间缺乏统一的标准,几乎每一个独立的系统都不一样。这样就给上层的软件设计人员带来了极大地困难。第一,在软件设计过程中过多地考虑硬件,给开发和调试都带来了很多不便;第二,如果所有的软件工作都需要在硬件平台就绪之后进行,自然就延长了整个的系统开发周期。这些都是应该从方法上加以改进和避免的问题。
图1 两类不同的嵌入式系统结构模型
为了解决这个问题,工程和设计人员提出了许多对策。首先在应用与驱动(或API)这一层接口,可以设计成相对统一的一些接口函数,这对于具体的某一个开发平台或在某个公司内部,是完全做得到的。这样一来,就大大提高了应用层软件设计的标准化程度,方便了应用程序在跨平台之间的复用和移植。
对于驱动/硬件抽象这一层,因为直接驱动硬件,其标准化变得非常困难甚至不太可能。但是为了简化程序的调试和缩短开发周期,我们可以在特定的EDA工具环境下面进行开发,通过后再移植到硬件平台上。这样既可以保证程序逻辑设计的正确性,同时使得软件开发可行甚至超前于硬件开发进程。
我们把脱离于硬件的嵌入式软件开发阶段称之为“PC软件”的开发,可以用图2来示意一个嵌入式系统程序的开发过程。
图2 嵌入式系统程序的开发过程
在“PC软件”开发阶段,可以用软件仿真,即指令集模拟的方法,来对用户程序进行验证。在ARM公司的开发工具中,ADS内嵌的ARMulator和RealView开发工具中的ISS,都提供了这项功能。在模拟环境下,用户可以设置ARM处理器的型号、时钟频率等,同时还可以配置存储器访问接口的时序参数。程序在模拟环境下运行,不但能够进行程序的运行流程和逻辑测试,还能够统计系统运行的时钟周期数、存储器访问周期数、处理器运行时的流水线状态(有效周期、等待周期、连续和非连续访问周期)等信息。这些宝贵的信息是在硬件调试阶段都无法取得的,对于程序的性能评估非常有价值。
为了更加完整和真实地模拟一个目标系统,ARMulator和ISS还提供了一个开放的API编程环境。用户可以用标准C来描述各种各样的硬件模块,连同工具提供的内核模块一起,组成一个完整的“软”硬件环境。在这个环境下面开发的软件,可以更大程度地接近最终的目标。
利用这种先进的EDA工具环境,极大地方便了程序开发人员进行嵌入式开发的工作。当完成一个“PC软件”的开发之后,只要进行正确的移植,一个真正的嵌入式软件就开发成功了。而移植过程是相对比较容易形成一套规范的流程的,其中三个最重要的方面是:
◆ 考虑硬件对库函数的支持
◆ 符合目标系统上的存储器资源分布
◆ 应用程序运行环境的初始化
2 开发工具环境里面的库函数
如果用户程序里调用了跟目标相关的一些库函数,则在应用前需要裁减这些函数以适合在目标上允许的要求。主要需要考虑以下三类函数:
◆ 访问静态数据的函数
◆ 访问目标存储器的函数
◆ 使用semihosting(半主机)机制实现的函数
这里所指的C库函数,除了ISO C标准里面定义的函数以外,还包括由编译工具提供的另外一些扩展函数和编译辅助函数。
2.1 裁减访问静态数据的函数
库函数里面的静态数据,基本上都是在头文件里面加以定义的。比如CTYPE类库函数,其返回值都是通过预定义好的CTYPE属性表来获得的。比如,想要改变isalpha() 函数的缺省判断,则需要修改对应CTYPE属性表里对字符属性的定义。
2.2 裁减访问目标存储器的函数
有一类动态内存管理函数,如malloc() 等,其本身是独立于目标系统而运行的;但是它所使用的存储器空间需要根据目标来确定。所以malloc() 函数本身并不需要裁减或移植,但那些设置动态内存区(地址和空间)的函数则是跟目标系统的存储器分布直接相关的,需要进行移植。例如堆栈的初始化函数__user_initial_stackheap(),是用来设置堆(heap)和栈(stack)地址的函数。显然,针对每一个具体的目标平台,该函数都需要根据具体的目标存储器资源进行正确移植。
下面是对示例函数__user_initial_stackheap() 进行移植的一个例子:
__value_in_regs struct __initial_stackheap __user_initial_stackheap(
unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
struct __initial_stackheap config;
config.heap_base = (unsigned int) 0x11110000;
// config.stack_base = SP; // optional
return config;
}
请注意上面的函数体并不完全遵循标准C的关键字和语法规范,使用了ARM公司编译器(ADS或RealView Compilation tool) 里的C语言扩展特性。关于编译器特定的C语言扩展,请参考相关的编译器说明,这里简单介绍函数__user_initial_stackheap() 的功能,它主要是返回堆和栈的基地址。上面的程序中只对堆(heap) 的基地址进行了设置(设成了0x11110000),也就是说用户把0x11110000开始的存储器地址用作了动态内存分配区(heap区)。具体地址的确定是要由用户根据自己的目标系统和应用情况来确定的,至少要满足以下条件:
◆ 0x11110000开始的地址空间有效且可写(是RAM)
◆ 该存储器空间不与其它功能区冲突(比如代码区、数据区、stack区等)
因为__user_initial_stackheap() 函数的全部执行效果就是返回一些数值,所以只要符合接口的调用标准,直接用汇编来实现看起来更加直观一些:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR r0,0x11110000
MOV pc,lr
如果不对这个函数进行移植,编译过程中将使用缺省的设置,这个设置适用于ARM公司的Integrator系列平台。
2.3 裁减使用半主机机制实现的函数
库函数里有一大部分函数是涉及到输入/输出流设备的,比如文件操作函数需要访问磁盘I/O,打印函数需要访问字符输出设备等。在嵌入式调试环境下,所有的标准C库函数都是有效且有其缺省行为的,很多目标系统硬件不能支持的操作,都通过调试工具来完成了。比如printf() 函数,缺省的输出设备是调试器里面的信息输出窗口。
但是一个真实的系统是需要脱离调试工具而独立运行的,所以在程序的移植过程当中,需先对这些库函数的运行机制作一了解。
图3说明了在ADS下面这类C库函数的结构。
图3 C库函数实现过程中的层次调用
如图4中例子所示,函数printf() 最终是调用了底层的输入/输出函数_sys_write() 来实现输出操作的,而_sys_write() 使用了调试工具的内部机制来把信息输出到调试器。
图4 printf() 的调试过程
显然这样的函数调用过程在一个真实的嵌入式系统里是无法实现的,因为独立运行的嵌入式系统将不会有调试器的参与。如果在最终系统中仍然要保留printf() 函数,而且在系统硬件中具备正确的输出设备(如LCD等),则在移植过程中,需要把printf() 调用的输出设备进行重新定向。
单纯考虑printf() 的输出重新定向,可以有三种途径实现:
◆ 改写printf() 本身
◆ 改写 fput()
◆ 改写 _sys_write()
需要注意的是,越底层的函数,被其他上层函数调用的可能性越大,改变了一个底层函数的实现,则所有调用该函数的上层函数的行为都被改变了。
以fputc() 的重新实现为例,下面是改变fputc() 输出设备到系统串行通信端口的实例:
int fputc(int ch, FILE *f)
{ /* e.g. write a character to an UART */
char tempch = ch;
sendchar(&tempch); // UART driver
return ch; }
代码中的函数sendchar() 假定是系统的串口设备驱动函数。只要新建函数fput() 的接口符合标准,经过编译链接后,该函数实现就覆盖了原来缺省的函数体,所有对该函数的调用,其行为都被新实现的函数所重新定向了。
3 Semihosting 机制
上面提到许多库函数在调试环境下的实现都调用了一种叫作semihosting(半主机)的机制。Semihosting具体来讲是指一种让代码在ARM 目标上运行,但使用运行了ARM 调试器的主机上I/O 设备;也就是让ARM 目标将输入/ 输出请求从应用程序代码传递到运行调试器的主机的一种机制。通常这些输入/输出设备包括键盘、屏幕和磁盘I/O。
半主机由一组已定义的SWI 操作来实现,如图5所示。库函数调用相应的SWI(软件中断),然后调试代理程序处理SWI 异常,并提供所需的与主机之间的通讯。多数情况下,半主机SWI 是由库函数内的代码调用的。但是应用程序也可以直接调用半主机SWI。半主机SWI 的接口函数是通用的。当半主机操作在硬件仿真器、指令集仿真器、RealMonitor或Angel下执行时,不需要进行移植处理。
图5 semihosting的实现过程
使用单个SWI 编号请求半主机操作。其它的SWI 编号可供应用程序或操作系统使用。用于半主机的SWI号是:
在ARM 状态下:0x123456
在Thumb 状态下:0xAB
SWI 编号向调试代理程序指示该SWI 请求是半主机请求。要辨别具体的操作类型,用寄存器r0 作为参数传递。r0 传递的可用半主机操作编号分配如下:
◆ 0x00~0x31:这些编号由ARM 公司使用,分别对应32个具体的执行函数。
◆ 0x32~0xFF:这些编号由ARM 公司保留,以备将来用作函数扩展。
◆ 0x100~0x1FF:这些编号保留给用户应用程序。但是,如果编写自己的SWI 操作,建议直接使用SWI指令和SWI编号,而不要使用半主机SWI 编号加这些操作类型编号的方法。
◆ 0x200~0xFFFFFFFF:这些编号未定义。当前未使用并且不推荐使用这些编号。半主机SWI使用的软件中断编号也可以由用户自定义,但若是改变了缺省的软中断编号,需要:
◆ 更改系统中所有代码(包括库代码)的半主机SWI 调用
◆ 重新配置调试器对半主机请求的捕捉与响应这样才能使用新的SWI 编号。
共5条
1/1 1 跳转至页
如果需要的话可以找我哦!
ARM9开发板淘宝专营店
http://shop35220357.taobao.com/
本店真诚代理广州广嵌和广州友善之臂的ARM9(S3C2410和S3C2440)开发板
倾情打造全国同类品牌的最低价格,为广大嵌入式爱好者服务
同时凡在本店购买开发板者均可获赠一套4DVD的嵌入式linux培训视频教程
欢迎咨询 QQ:510094305 手机:13750361226
友善之臂QQ2440技术支持Q群:45341996(新群,可接受有购买意向的顾客及已购买顾客)
广嵌GEC技术支持Q群:41124889(群将满,现仅接受已购买顾客)
广嵌GEC2410和GEC2440开发板的技术支持论坛:http://bbs.gd-emb.org/?
友善之臂QQ2440技术支持论坛:http://www.study-bbs.com/index.asp?boardid=44
(所有问题,24小时内肯定有答复,我们有专人负责回答客户在论坛上的提问)
蓝宇PC104产品业界领先特色
1、 完全自主版权的BIOS
我公司完全自行编制的BIOS,不需要支付版费,降低了成本,蓝宇BIOS具有如下特点:
A、支持多种DOS操作系统,用户可以自由选择免费的操作系统如DR-DOS、FREE-DOS,一般的ROM-DOS 和MS DOS 都是需要付费的。
B、BIOS不需要用户配置参数,启动时间短,1.8秒可启动小的应用程序,复位=0.5S,BIOS=1.2S,DOS=0.1,与专用嵌入式系统相当,实现了快速启动特性。
C、BIOS启动配置参数存储在FLASH中,不需要电池,电池掉电仅仅影响RTC时间,因此不会出现传统的工控板掉电后无法启动的问题
D、双BIOS备份,主要用于升级bios或者写入参数时意外断电,参数意外损坏后,仍然可以从主bios启动,无需返厂维修,进一步提高了产品可靠性和现场可维护性。
E、有开机LOGO,提供工具,客户可以自行自定开机画面,开机不显示自检画面,于专用系统相同。
由于编制BIOS 的技术复杂,不易掌握,国内真正自己编制的其他公司的BIOS 在市场上还没有见到。
2、 独创的虚拟显示技术
虚拟显示是我公司独特的一项技术,用串口把BBPC4X86 与一台普通电脑连接起来,在电脑上使用键盘和屏幕显示,就像BBPC4X86 是一台完整的PC一样,开发测试简单,不干扰应用显示设备,生产维护方便。一般嵌入式应用,在开发阶段需要完整的开发环境,系统交付使用后,这些环境就没有用了,如果为了开发方便买一个带显示和键盘的X86主板,实际上就增加了不必要的成本。一般嵌入式开发,经常会需要直接调试硬件设备,很多用户习惯使用DEBUG 等工具,直接操作端口,甚至编一段小的测试程序,还有用户喜欢使用一些直接写屏的软件进行开发,比如Q 编辑器,TUBROC2.0,DEBUG 等,对用户开发非常方便。用户开发完成后,需要对电子盘进行格式化、传系统、拷贝文件等等,可以自由使用免费的DOS 操作系统,方便生产,降低成本。
3、 大常规内存
一般X86 体系,在DOS 下只能直接寻址640KB 内存,高端内存只能用作数据,无法直接作为程序区使用,使用ROM-DOS 的系统只有512KB 可用内存。BBPC4X86 系列产品独有大内存技术,在大内存模式下可以直接使用896KB 内存,使用Borland C 开发时不需要任何特殊操作,您的天空一下子大了很多!注意此模式下,不能使用直接写屏的软件,如Q 编辑器、TC2.0,但是DEBUG 可以正常使用。小内存模式下可以直接使用736KB 内存,直接写屏的软件如Q 编辑器、TC2.0 都可以使用。
4、 支持标准的IDE硬盘、软驱和CF 卡
直接支持低价格、大容量的ide硬盘/高速中等容量CF卡和小容量软驱,在需要大量数据存储的应用中使用方便,成本低廉,一般相同容量的CF 卡比DOC 电子盘价格低得多,而标准硬盘的价格之低就更不需要说了。嵌入式应用有时会需要使用大容量存储介质,CF卡价格便宜,接口标准,运行无噪音,速度快,抗震,容量大,是一种理想的解决方案,IDE 接口也可以挂接标准硬盘,但是一般电子盘在嵌入式应用中使用较为普遍。
5、 内置大容量电子盘
同类内置电子盘的产品,一般只有300KB 的电子盘,与PC 系统的寻址能力(1MB)不匹配,用户程序和数据稍微大一点就不够用了,使用汉字显示的时候,汉字库就有256KB,用户基本无法使用,只能去购买昂贵的DOC,成本增加100 多元,这时的300KB 电子盘形同虚设。我公司产品板载360KB~7MB电子盘,存放操作系统和应用程序,不需要频繁修改数据,抗震动,可靠性高,与PC 系统的寻址能力(1MB)相匹配,绝大多数情况下,用户不必另外购置电子盘,节约了成本。
6、 内置2级汉字库
在汉字应用中,一般的300K 电子盘不能满足用户要求,因为16 点阵汉字库256KB,再加上操作系统64KB,应用程序和数据空间所剩无几,为此使用电子盘需要增加100 元成本,而且字库加载到内存后需要占用256KB 内存,减少了用户内存。BBPC4X86 内置二级16 点阵汉字库和字符库,不占用用户内存和电子盘空间,还可以作为掉电保持存储器使用,用于存放小时级别更新的用户数据,节约了系统成本。
7、 实时时钟
内置的RTC 实时时钟完全与DOS 和BIOS 兼容,使用系统函数很容易访问,对于需要日历时间的应用是必不可少的。
8、 支持标准PC键盘
标准的PS/2 键盘,编程简单,价格便宜量又足,很多用户都非常喜欢使用,我公司提供了标准的驱动程序,可以直接支持标准键盘(包括大键盘和小键盘),节约了成本。对于需要矩阵键盘的用户,我公司也提供完整软硬件方案,使用方法和标准键盘一样。
9、多种显示方案
虽然BBPC4X86 系列产品可以直接支持液晶显示模块,但是有一些高端用户需要动态的画面显示或者使用真彩液晶屏,此时液晶模块就不能满足要求了。我公司配套的液晶显示卡,可以直接支持128*64~1204*768 的单色、彩色液晶屏,显示速度快,适合动态画面的显示,同时提供图形库,用户编程极为方便。
共5条
1/1 1 跳转至页
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
【换取逻辑分析仪】自制底板并驱动ArduinoNanoRP2040ConnectLCD扩展板被打赏47分 | |
【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用被打赏38分 | |
【换取逻辑分析仪】-基于ADI单片机MAX78000的简易MP3音乐播放器被打赏48分 | |
我想要一部加热台+树莓派PICO驱动AHT10被打赏38分 | |
【换取逻辑分析仪】-硬件SPI驱动OLED屏幕被打赏36分 | |
换逻辑分析仪+上下拉与多路选择器被打赏29分 | |
Let'sdo第3期任务合集被打赏50分 | |
换逻辑分析仪+Verilog三态门被打赏27分 | |
换逻辑分析仪+Verilog多输出门被打赏24分 | |
【分享评测,赢取加热台】使用8051单片机驱动WS2812被打赏40分 |