【@.1 移植准备】
目标CPU:LPC2119。ARM7TDMI,最高主频60MHz,Flash128KB,RAM20KB,ARM7系列的中断机制可以参考我的这篇文章<uCOS-II的中断-ARM7实现中断嵌套的方法探究>。
OS:uCOS-II,具体内核版本参考官网。应该已经出到2.90以上了,不过移植时的版本可能没这么高。对于uCOS-II的任务切换机制可以参考我的这篇文章<uCOS-II中的任务切换机制>。
Toolchain:Keil,目前我使用的版本是MDK4.60,移植时也用MDK4.60来建立。
官网移植工程下载:不能仅仅下载内核,需要下载整个移植工程进行移植工作。参考官网移植工程时需要注意的是,移植目标CPU内核与自己的CPU一样或相近,uCOS版本不要太旧,编译器跟自己需要使用的编译器一样(比如Keil)。移植时我参考的是
NXP LPC2148(μC/OS-II μC/OS-II V2.85 μC/Probe V1.30, IAR (EWARM) V4.x IAR (EWARM) V5.x Keil MDK V3.x 2012/12/06)
由于官网不断更新,所以可能以后我参考的移植工程就不在网上了。
希望在移植系统时要仔细了解目标CPU的内核机制和对应OS的一些基本原理,即使自己不编写底层移植代码也不能照着下面的步骤,最后能用了事。这样是得不到锻炼的,如果需要移植到其他的CPU,或者移植其他OS时就又不会了,又上网找别人的工程伸手要。
【@.2 工程模板建立】
解压下载的移植工程,仔细阅读解压目录下的ReadMe.pdf,会有详细的目录结构讲解,这里我就不重复了。
uCOS的通用源代码在Micrium\Software下,此文件夹下的各个子文件夹就是uCOS的各个模块。其中uCOS-II是主要源代码,uC-CPU,uC-LIB是必要的模块,uCOSView和uC-Probe可以不用。若要使用别的模块也可以参考这里,一个模块一个文件夹,比如shell和uCGUI。
在这个文件夹下有示范工程例程,用MDK3.xx版本写的,路径这里就不列出来了,因为每次下载的移植工程可能不一样。
先不打开MDK,首先工程文件夹目录如下:
[根目录文件名取工程名字]
+---App
| app.c //拷贝自下载的移植工程同名文件,此文件为用户应用程序代码。此文件还需要修改
| app_cfg.h //拷贝自下载的移植工程同名文件,自己的任务相关配置(如优先级,堆栈大小)
| includes.h //拷贝自移植工程同名文件,源程序都需包含它作为include入口
| os_cfg.h //拷贝自移植工程同名文件,uCOS的功能配置文件,只需根据自己需要的功能修改宏定义
|
+---Bsp
| bsp.c //拷贝自移植工程,但是需要修改
| bsp.h //拷贝自移植工程,但是需要修改
| LPC21xx.h //拷贝自MDK安装目录下\ARM\INC\Philips\ 的头文件
| Startup.s //拷贝自MDK安装目录下\ARM\Startup\Philips 的Startup.s。需要修改
|
+---Libraries //公司或自己的通用函数库。现在没有可以不放文件
+---Micrium //uCOS的核心代码,主要拷贝自uCOS的各个模块
| +---uC-CPU //拷贝移植工程下整个文件夹。由于LPC2119跟移植CPU差不多所以不需要修改
| | cpu.h
| | cpu_a.s
| | cpu_def.h
| |
| +---uC-Lib //拷贝移植工程下整个文件夹,uCOS的常用预定义和内存、字符串库,一般不需要修改
| | lib_def.h
| | lib_mem.c
| | lib_mem.h
| | lib_str.c
| | lib_str.h
| |
| \---uCOS-II //拷贝移植工程下整个文件夹,
| +---Port //移植uCOS时按手册要求需要编写的代码。LPC2119跟移植CPU差不多,所以不需要修改
| | os_cpu.h
| | os_cpu_a.asm
| | os_cpu_c.c
| | os_dbg.c
| |
| \---Source //uCOS的内核代码,千万不要修改
| os_core.c
| os_flag.c
| os_mbox.c
| os_mem.c
| os_mutex.c
| os_q.c
| os_sem.c
| os_task.c
| os_time.c
| os_tmr.c
| ucos_ii.h
|
\---Solution //与MDK工程相关的文件都放在这里
|
|
+---LIST //MDK中的List输出文件夹,待MDK中修改
+---MDK //MDK的工程文件就放在这里,如下面文件名
|
| LPC2119_uCOS-II.uvproj
|
\---OBJ //MDK的目标输出文件夹,待MDK中修改
这个文件结构建好之后如图所示。不要忘了其中的需要拷贝/建立源代码。
之后打开MDK新建工程。打开MDK->新建工程,选择保存路径在刚才新建的文件夹下的\Solution\MDK\ 中,名字自己随便取。CPU选择NXP->LPC2119,当提示是否需要拷贝启动代码到工程文件夹下时选择否,因为我们自己拷贝过了。左侧的Project右击Target 1 -> Manage Components,之后需添加刚才所拷贝/新建的源文件。Project Targets改名随便,比如LPC2119_Target.将中间Group一栏改名为App,点击右侧Add Files可以将源文件添加到这一个Group中。按照之前的文件结构,添加Group的文件如下
可以看到MDK中的工程目录跟实际硬盘上的文件结构是基本一致的,这也便于对MDK工程的理解。右键工程,点击Options for ... 编辑工程属性。
Output选项卡点击Select folder for object,选为刚才新建的目录\Solution\OBJ,选中Create HEX file,Name for Excutable修改为自己需要的hex文件名。最好不要包含'-'等字符。
Listing选项卡点击Select folder for listing,选为刚才新建的目录\Solution\LIST
C/C++选项卡中的Including Path可以选择工程中所用到的头文件搜索路径,点击右侧可添加。这里添加如下
..\..\App
..\..\Bsp
..\..\Libraries
..\..\Micrium\uC-Lib
..\..\Micrium\uCOS-II\Port
..\..\Micrium\uCOS-II\Source
..\..\Micrium\uC-CPU
均为刚才所建工程目录下的文件。以后若新添加的头文件在别的目录也需要在这里添加。
Utilities选项卡 ->J-Link Settings->Add 找到LPC2000 IAP 128KB Flash即可用J-Link即可debug和下载
至此MDK工程设置结束,下面修改代码
【@.3 移植代码修改】
Startup.s是主要修改的文件。在MDK中的Startup.s比较简单,我们需要修改ISR中断入口函数使之与uCOS的中断通用处理函数连接起来即可。
找到 AREA RESET, CODE, READONLY 端, 这一段是此汇编的户口,下面的Vectors标签即所有的中断入口偏移表。在Vectors前面,添加如下代码
IMPORT OS_CPU_ARM_ExceptUndefInstrHndlr
IMPORT OS_CPU_ARM_ExceptSwiHndlr
IMPORT OS_CPU_ARM_ExceptPrefetchAbortHndlr
IMPORT OS_CPU_ARM_ExceptDataAbortHndlr
IMPORT OS_CPU_ARM_ExceptIrqHndlr
IMPORT OS_CPU_ARM_ExceptFiqHndlr
这表示以上IMPORT的函数在别的文件中有定义。而在uCOS中这些函数是在uCOS-II/Port中的os_cpu_a.asm中用汇编写好的。本来这些代码是需要移植时参考uCOS手册的要求和移植的芯片手册自己编写,但是我们下载的移植工程已经写好,我们只需要将这些函数与启动代码中的中断入口链接起来即可。
在Vectors中的IRQ中断一列,Keil可能是下面这样写的:
;LDR PC, IRQ_Addr
LDR PC, [PC, #-0x0FF0] ; Vector from VicVectAddr
其实第一句被注释掉了,所以起作用的是第二句,这句话直接将IRQ的中断跳转到PC, #-0x0FF0,也就是地址位于0xFFFFF030的VICVectAddr寄存器,这是LPC的中断控制器,中断向量的入口地址,按照LPC2000系列的规定这里存放了我们在程序中注册的中断服务程序地址。因此采用第二句话实际上是将中断直接交给LPC的中断控制器进行中断控制。但是我们需要将IRQ中断交给uCOS一并管理所以需要修改,应该采用第一句的LDR PC, IRQ_Addr进行偏移,之后再进行如下程序的链接。
Reset_Addr DCD Reset_Handler
Undef_Addr DCD OS_CPU_ARM_ExceptUndefInstrHndlr
SWI_Addr DCD OS_CPU_ARM_ExceptSwiHndlr
PAbt_Addr DCD OS_CPU_ARM_ExceptPrefetchAbortHndlr
DAbt_Addr DCD OS_CPU_ARM_ExceptDataAbortHndlr
DCD 0 ; Reserved Address
IRQ_Addr DCD OS_CPU_ARM_ExceptIrqHndlr
FIQ_Addr DCD OS_CPU_ARM_ExceptFiqHndlr
删掉源文件中Keil对中断地址的定义,修改为如上的uCOS的中断入口。其中IRQ_Addr将跳转到OS_CPU_ARM_ExceptIrqHndlr,也就是交给uCOS对IRQ中断进行控制。所以不管移植到哪种CPU上,所有的异常和中断的入口都要像上面一样进行重新定位,到自己编写的os_cpu_a.asm中的对应函数。最终的RESET代码段的代码修改如下:
另外,由于uCOS要求在LCP2xxx上应该运行在Supervisor模式,而Keil默认的启动代码在结束startup后是进入的User模式,权限太低不能进行一些特定的汇编指令操作,因此在Startup.s的下面,初始化堆栈中Enter User Mode and set its Stack Pointer下面的代码全部删除或注释掉,需要删除或注释的代码如下
; MSR CPSR_c, #Mode_USR
; IF :DEF:__MICROLIB
; EXPORT __initial_sp
; ELSE
; MOV SP, R0
; SUB SL, SP, #USR_Stack_Size
; ENDIF
这里我们不再进入用户模式对堆栈初始化了,因为进入了用户模式之后要切换到Supervisor模式需要使用软件中断,不能简单的进行切换,比较麻烦,所以干脆舍弃用户模式,我们永远不进入用户模式也不需要对其堆栈初始化,这里只需要保证堆栈初始化时最后一个是对Supervisor模式进行的堆栈初始化即可保证进入main函数之后不在其他模式下进行的。所以像STM32这样没有那么多复杂的模式的CPU移植起来就简单得多。
下面不是具体的代码操作,但是还是说一些注意事项
@->app.c中保留下列函数用于application hook 功能,里面空即可,否则编译时找不到函数定义,
#if (OS_APP_HOOKS_EN > 0)
void App_TaskCreateHook (OS_TCB *ptcb) { ;}
void App_TaskDelHook (OS_TCB *ptcb){ ;}
#if OS_VERSION >= 251
void App_TaskIdleHook (void){ ;}
#endif
void App_TaskStatHook (void){ ;}
#if OS_TASK_SW_HOOK_EN > 0
void App_TaskSwHook (void){ ;}
#endif
#if OS_VERSION >= 204
void App_TCBInitHook (OS_TCB *ptcb){ (void)ptcb; }
#endif
#if OS_TIME_TICK_HOOK_EN > 0
void App_TimeTickHook (void){ ;}
#endif
#endif
所以可以看到其实也可以在os_cfg.h中将OS_APP_HOOKS_EN设置为0关掉这个功能。不过为了了解uCOS-II的所有特性以免井底之蛙,现在暂时留着。
主函数和任务函数现在先保留,可以根据自己的任务需要另作修改
@->app_cfg.h中包含的是任务堆栈大小和优先级,以及一些其他应用程序相关的定义。
@->bsp.c中的Tmr_TickISR_Handler(void)需要保留,这是uCOS ticker的服务代码,这个ticker使用的是LPC中的Timer0中断,因此这个服务函数必须保留。
OS_CPU_ExceptHndlr(CPU_INT32U)这个函数必须保留,这是所有异常发生时都将进入的函数,这里可能只写了IRQ发生时的代码,其他异常发生时都将进入死循环(除了Reset)。也可以自己添加其他异常代码,不过小的应用也不需要。
【@.4 移植工程测试代码】
下面仅仅列出app.c中的用户代码,注意,实际对硬件操作的功能函数都在bsp.c中,所以每个移植工程的实际bsp.c是不一样的。
主函数中需首先对OS初始化,再新建至少一个任务,最后调用OSStart()之后则完成整个OS的启动过程。一般新建的一个任务叫做启动任务(Task Starter),其中需进行OS所需的系统时钟中断的启动(这里包含在了BSP_Init()硬件初始化函数中),之后再新建其他任务。这里的启动任务中不建立新的任务,仅仅进行通过延时对小灯进行亮、灭的控制。将整个工程编译后下载即可观察到运行效果。
最后附工程模板的下载链接
LPC2119_uCOSPrj.7z
另外添加一个实现了中断机制的工程模板,利用两个外部中断控制流水灯的启停。可以参考这个源文件看看uCOS-II中的中断是怎么编写的。
LPC2119_uCOSPrj_LEDSample.7z
@.[FIN] @.date->Mar 28, 2013 @.author->apollius