摘 要: 本文介绍了使用 arm 公司提供的 ads 开发工具,进行移植 uc/os-ii 的工作。结合基于 strongarm 评估板的硬件结构,对移植工作中的若干要点做了详细分析。最后,给出了移植体会和程序技巧分析。
关键词: ads uc/os-ii strongarm 移植
一、 选择开发工具
在嵌入式系统设计中,开发工具的选取是一个重要的考虑因素,通常这是与开发项目的需求和应用背景相关。一般嵌入式开发工具包含用于目标系统的交叉编译器、连接器、调试器以及辅助处理用的二进制文件分析工具等。
目前可以用来编译链接产生 arm 处理器执行代码的开发工具主要有如下几类:
1. arm 公司提供的 arm developer suite 集成开发环境
主要工具有 armasm、armcc、armlink、fromelf 等。
2. gnu 组织提供的 tool chain for arm
主要工具有 arm-elf-gcc、arm-elf-gdb、arm-elf-objcopy 等
3. microsoft公司提供的 embedded visual tools
主要工具有 clarm、clthumb、c2_arm、link、lib等
这里我们选用 arm 公司提供的 ads 下的工具集来编译我们的程序和链接目标代码并最终生成可执行的二进制映像。这里介绍一下主要会用到的一些工具:
armasm.exe : 汇编文件编译器
armcc.exe : c 文件编译器
armlink.exe : 目标文件连接器
fromelf.exe : 用于将 axf 或者 elf 格式转换成其他格式的文件,例如二进制映像。
armprof.exe : 对调试过程中生成的 profiling 记录文件做分析用的工具软件
二、 存储空间分配
1. 存储器组织
strongarm 存储器组织主要分4个部分:
1) 0h0000 0000 ~ 0h3fff ffff
用于静态存储器 rom、sram、flash
2) 0h4000 0000 ~ 0h8fff ffff
用于静态存储器和各种 i/o 器件
3) 0h8000 0000 ~ 0hbfff ffff
包括所有片内寄存器,主要用于外围控制、系统控制、存储扩展、lcd和dma。
4) 0hb000 0000 ~ 0hffff ffff
用于动态存储器,dram、sdram等
2. 堆栈空间分配
| 0xc2000000 -------- 系统堆栈从 0xc2000000 开始向下增长
| 其中 0xc2000000 为 svc 态的堆栈栈底
| 0xc1000000 为 irq 态的堆栈栈底
| sdram (32m)
|
| 0xc0000000 -------- rw-base 这里是程序的 rw 段,包括 uc/os-ii 的任务堆栈空间
|
:
|
| 0x02000000 --------
|
| flash rom (32m)
| 从 0x00000000 开始依次放置跳转指令,即异常向量表
| 0x00000000 -------- ro-base 这里是程序的 text 段和 ro 段
三、 启动代码
由于板子的 0x0 地址处是 32m 的flash rom,因此在板子加电后,会从 flash 中顺序执行启动代码。为了能使得 uc/os-ii 运行,启动代码需要完成如下工作:
1. 设置 异常向量表,即在 0x0 – 0x1c 位置放置7条跳转指令(其中 0x14 为空)
2. 分别实现每种异常的处理程序,其中包括 reset_handler、undefined_handler、swi_handler、prefetch_handler、abort_handler、irq_handler、fiq_handler。
3. 程序从 reset_handler 进入后,需要首先进行相关硬件的初始化操作,例如 初始化sdram、cpu speed、interrupt controller、uart、timer 等。
4. 建立每种异常状态下的系统堆栈,为了简单起见可以只在 svc 态 和 irq 态下的建立堆栈:setup_svc_stack ,setup_irq_stack。
5. 强制 arm 处理器状态转换为 svc 管理态。
6. 跳转到uc/os-ii 代码的 main 入口,实际上是编译链接后产生的 __main 入口。
四、 时钟与中断处理
1. 时钟控制逻辑
在上图中,有4种和系统时钟相关寄存器,它们的含义如下:
l oscr: 一个自动递增计数的 32 位计数器。
l osmr3-0: 4 个 32 位的匹配寄存器,当 oscr 的值匹配时产生中断。
l ossr: 状态寄存器,当 oscr 和 osmr 匹配时,会对 ossr 做标志。
l oier: 使能寄存器,表示当匹配发生时,允许在 ossr 设置一个标识位。
oscr 在自动累加的过程中,与osmr里面设定的那些匹配寄存器进行匹配,发现有匹配的事件时,就会对 ossr 中的相应位置设一个标志位“1”,表示oscr与对应的osmr 发生了匹配。当然这个匹配发生的前提是发生匹配的那个osmr在oier中的相应位被使能,否则osmr中的设置将不起作用。
2. 系统时钟初始化流程
uc/os-ii 中创建的第一个任务将负责启动时钟节拍,时钟的初始化设置流程如下:
1) 设置 osmr0 = x ,表示 初始化 osmr0,即当计数器为x时发生匹配
2) 设置 ossr = 0xf ,表示 清除所有已经发生的匹配,写“1”清除
3) 设置 oier = oier_eo ,表示 使能 osmr0 来产生匹配
4) 设置 oscr = 0 ,表示 初始化计数器的开始值 为 0
3. 系统时钟中断复位
1) 清除 ossr 中的相应位,即向发生匹配的osmr的那个对应位写“1”
2) 设置 oscr = 0 ,表示 继续初始化计数器的值为 0
4. 中断控制器相关的寄存器
l icpr: 中断标示寄存器,表示了当前系统正处于激活状态的中断源。
l icmr: 中断屏蔽寄存器,用来屏蔽相应位的中断。
l iclr: 中断级别设置寄存器,设定报告中断的级别是 irq 或者是 fiq 。
l icip: irq 级别的中断源寄存器,用来标识 irq 中断发生的源设备。
l icfp: fiq 级别的中断源寄存器,用来标识 fiq 中断发生的源设备。
5. 中断控制器初始化流程
1) 设置 icmr 屏蔽位为不屏蔽时钟中断 osmr0 (相应位写“1”)
2) 设置 iclr 为都报告为 irq 级别(所有位写“0”)
五、 移植工作总结
1. 难点分析
移植 uc/os-ii 到 strongarm 的芯片上,基本上和移植到 arm7 的芯片例如s3c4510,at91x等工作类似,因为所有的arm处理器都共享arm通用的基础体系结构,这使得移植工作变得相对简单,其中绝大部分工作都集中在 os_cpu_a.s 文件的移植,这个文件的实现集中体现了所要移植到处理器的体系结构和uc/os-ii 的移植原理;在这个文件里,最困难的工作主要是在 osintctxsw 和 ostickisr 这两个函数的实现上。因为它们的实现是和移植者的移植思路以及相关硬件定时器、中断寄存器的设置有关。在实际的移植工作中,这两个地方也是比较容易出错的地方。
osintctxsw 最重要的作用就是它完成了在中断isr中直接进行任务切换,从而提高了实时响应的速度。它发生的时机是在 isr 执行到 osintexit 时,如果发现有高优先级的任务因为等待的 time tick 到来获得了执行的条件,这样就可以马上被调度执行,而不用返回被中断的那个任务之后再进行任务切换,因为那样的话就不够实时了。
实现 osintctxsw 的方法大致也有两种情况:一种是通过调整 sp 堆栈指针的方法,根据所用的编译器对于函数嵌套的处理,通过精确计算出所需要调整的 sp 位置来使得进入中断时所作的保存现场的工作可以被重用。这种方法的好处是直接在函数嵌套内部发生任务切换,使得高优先级的任务能够最快的被调度执行。但是这个办法需要和具体的编译器以及编译参数的设置相关,需要较多技巧。
另一种是设置需要切换标志位的方法,在 osintctxsw 里面不发生切换,而是设置一个需要切换的标志,等函数嵌套从进入 osintexit => os_enter_critical() => osintctxsw() => os_exit_critical() => osintexit退出后,再根据标志位来判断是否需要进行中断级的任务切换。这种方法的好处是不需要考虑编译器的因素,也不用做计算,但是从实时响应上不是最快,不过这种方法实现起来比较简单。
在中断态下进行任务切换,需要特别说明的一个问题是如何获得被中断任务的 lr_svc 。因为进入中断态后,lr 变成了lr_irq ,原来任务的 lr_svc 无法在中断态下获得,这样要得到 lr_svc ,就必须在中断 isr 里面进行一次 cpu mode 强制转换,即对 cpsr 赋值为0x000000d3 ,只有返回到 svc 态之后才能得到 原来任务的 lr ,这个对于任务切换很重要。还有一个需要留意的问题是在强制 cpsr 变成 svc 态之后,spsr 也会相应地变成 spsr_irq ,这样就需要在强制转变之前保存 spsr ,也就是被中断任务中断前的 cpsr 。
2. 移植中使用的编程技巧
ads 编译器在编译 c 语言的程序时,如果程序中使用了 main 函数,则编译器将自动添加如下代码,完成初始化堆栈和c库等工作,工作流程如下:
1> 将执行文件中的 ro 段和 rw 段从 load address 复制到 execution address
2> 初始化 zi 区域,用 0 来初始化变量
3> 跳转到 __rt_entry 执行如下 4 个调用
3.1> 调用 __rt_statckheap_init ,建立程序的堆和栈
3.2> 调用 __rt_lib_init ,初始化程序用到的 c 库,并为 main 传递参数
3.3> 调用 main ,即用户程序的入口
3.4> 调用 exit
因为系统复位后,在启动代码中已经设置了系统堆栈,同时也不需要使用c库,因此可以从 __rt_entry 处直接跳转到 uc/os-ii 的代码中,即直接执行 main 函数,可以用新的 __rt_entry 来作为链接的目标入口。
import main
export __rt_entry
__rt_entry
b main
这样在启动代码的最后,加入一条跳转语句:
bl __main
__main 入口是用户程序执行的真正入口,我们利用 armcc 编译 c 里面的 main 入口以求得到 1> 和 2> 的代码,使得可以支持全局变量。否则的话,必须自己来实现全局变量的初始化或者把这些初始化操作放到函数内部来实现。
另外一个非常有用的编程技巧是通过串口实现自己的 printf 输出。 如果使用armcc编译器的 semihosting 的话,会把 printf 通过 target 的 swi 0x123456 输出。如果已经实现的 serial_putchar 之类的函数,那么可以用它来实现 fputc 接口,也就是低级的输出函数,这样就可以使用 printf 来输出了,详细的做法在 ads 安装目录下面的文档里可以找到,这里就不再赘述。
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』