首先到ucos的官网上下载ucosii的源码(实际上是很多已经移植好的目标板,寻找下你说需要的板子是否在其上),找到一个相似的板子的源码。我们的设备是开发板Help2416;采用的源码是Micrium-uCOS-II-V290,参考源码Micrium_STM32xxx_uCOS-II。在Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\这个路径下的AN-1018.pdf非常重要,这文档详细的介绍了文件夹结构,关系,以及移植的细节。下面我们来慢慢分析。
首先参考STM32工程建立Ports\arm2416\Generic\MDK路径,此路径主要存放移植相关的代码。新建
- OS_CPU.H
- OS_CPU_C.C
- OS_CPU_A.ASM
- OS_DBG.C //needless
OS_CPU.h中一般声明了常量,宏和基本类型定义。先按照AN1018.pdf中的步骤来一步一步制作:
先定义全局变量与外部定义符号
- #ifdef OS_CPU_GLOBALS
- #define OS_CPU_EXT
- #else
- #define OS_CPU_EXT extern
- #endif
这是一个很智能的宏,在一些必要函数前加上,宏的定义位置就取决于文件中是否定义了OS_CPU_GLOBALS.
2.定义了基本数据
- typedef unsigned char BOOLEAN;
- typedef unsigned char INT8U;
- typedef signed char INT8S;
- typedef unsigned short INT16U;
- typedef signed short INT16S;
- typedef unsigned int INT32U;
- typedef signed int INT32S;
- typedef float FP32;
- typedef double FP64;
- typedef unsigned int OS_STK;
- typedef unsigned int OS_CPU_SR;
由于都是32位小端,基本上基本类型都没有什么可修改的,继续往下看。(在实际的工作中,需要按照目标CPU的位宽修改基本类型,以满足有/无符号及位宽。
1.设置临界区模式
这里我们用常用的模式3,具体为什么不用模式1, 模式2,还不是很清楚,求了解的大大告之:[email protected]。
这里的实质就是将CPSR暂存于cpu_sr这个unsigned long型的临时变量里面(内核使用到的时候已经自动添加了定义)。
- #define OS_CRITICAL_METHOD 3
- #define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
- #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
2.设置堆栈增长方向
1表示从高地址到低地址(实际上,除了个别8位单片机及特殊用途的芯片,大多数都是这样的)。 3.定义上下文切换函数
- #define OS_STK_GROWTH 1
OSCtxSw()稍后我们会在os_cpu_a.s中实现。
- #define OS_TASK_SW() OSCtxSw()
4.定义函数原型
- #if OS_CRITICAL_METHOD == 3
- OS_CPU_SR OS_CPU_SR_Save(void);
- void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
- #endif
- void OSCtxSw(void);
- void OSIntCtxSw(void);
- void OSStartHighRdy(void);
接下来是OS_CPU_C.C这个文件的编写
- OSInitHookBegin()
- OSInitHookEnd()
- OSTaskCreateHook()
- OSTaskDelHook()
- OSTaskIdleHook()
- OSTaskStatHook()
- OSTaskStkInit()
- OSTaskSwHook()
- OSTCBInitHook()
- OSTimeTickHook()
看似有很多需要编写,实际上最主要的只需要一个 OSTaskStkInit(),那我们首先来编写他
- OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
- {
- INT32U *stk;
- opt = opt; /* 'opt' is not used, prevent warning */
- stk = (INT32U *)ptos; /* Load stack pointer */
- *(--stk) = (INT32U)task; /* Entry Point .. -> PC */
- *(--stk) = (INT32U)0; /* r14 */
- *(--stk) = (INT32U)0; /* r12 */
- *(--stk) = (INT32U)0; /* r11 */
- *(--stk) = (INT32U)0; /* r10 */
- *(--stk) = (INT32U)0; /* r9 */
- *(--stk) = (INT32U)0; /* r8 */
- *(--stk) = (INT32U)0; /* r7 */
- *(--stk) = (INT32U)0; /* r6 */
- *(--stk) = (INT32U)0; /* r5 */
- *(--stk) = (INT32U)0; /* r4 */
- *(--stk) = (INT32U)0; /* r3 */
- *(--stk) = (INT32U)0; /* r2 */
- *(--stk) = (INT32U)0; /* r1 */
- *(--stk) = (INT32U)pdata; /* r0 : argument */
- *(--stk) = (INT32U)0x0; /* CPSR model tel here 0x00000013L SVC mode */
- return ((void *)stk);
- }
注意:为了演示各种各样有可能的错误,我会在代码中将正确选项标识或注解的方式给出。
在网上搜索这个压栈顺序,说是在arm核心pdf上有,但是实际上我只在cortexM3上看到过,其他芯片上没有具体说,但是,我们只需要了解这里的压栈顺序必须和OSCtxSw()的弹栈顺序一一对应就OK了。
其他都是些预留的暂时不用的钩子函数,只需要定义一个空的执行体就行了。
文件OS_CPU_A.S
需要我们编写的代码:
OS_CPU_SR_Save()
OS_CPU_SR_Restore()
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
- OSStartHighRdy
- MSR CPSR_cxsf, #SVCMODE:OR:NOINT ;//switch to svcmode& disable irq&fiq
- BL OSTaskSwHook
- LDR R0, =OSRunning
- MOV R1, #1
- STRB R1, [R0]
- LDR R0, =OSTCBHighRdy
- LDR R0, [R0]
- LDR SP, [R0]
- LDMFD SP!, {R0}
- MSR SPSR_cxsf, R0
- LDMFD SP!, {R0-R12, LR, PC}^
这里就只是将系统运行打开,然后将最高优先级任务的栈指针赋值给SP,然后弹栈。
- OSCtxSw
- STMFD SP!, {LR}
- STMFD SP!, {R0-R12, LR}
- MRS R0, CPSR
- STMFD SP!, {R0}
- LDR R0, =OSTCBCur
- LDR R0, [R0]
- STR SP, [R0]
- OSIntCtxSw
- BL OSTaskSwHook
- LDR R0, =OSTCBHighRdy
- LDR R1, =OSTCBCur
- LDR R0, [R0]
- STR R0, [R1]
- LDR R0, =OSPrioHighRdy
- LDR R1, =OSPrioCur
- LDRB R0, [R0]
- STRB R0, [R1]
- LDR R0, =OSTCBCur
- LDR R0, [R0]
- LDR SP, [R0]
- LDMFD SP!, {R0}
- MSR SPSR_cxsf, R0
- LDMFD SP!, {R0-R12, LR, PC}^
这里我偷了一个懒,将OSCtxSw的下半部同时给OSIntCtxSw使用。
上下文切换的实质就是OSCurTCB中开始地址存放了当前将要压栈的任务的栈地址。OSTCBHighRdy中存放的是将要运行的任务的张地址。我们要先将当前的寄存器 PC CPSR压栈进去,就完成了对原任务的保护工作;然后调用用户自定义钩子函数OSTaskSwHook,完成后再把当前最高优先级的任务赋值给OSTCBCur, 将当前优先级改为新的最高优先级任务的优先级。然后读取出TCB结构体中的栈地址给SP,完成弹栈操作。
因为OSIntCtxSw是在中断处理完成后调用的,进入中断时已经完成了压栈保护工作,所以可以偷懒将上述代码的后半部给它。
当然 还有一个可选的函数,可选是指你可以在这个汇编文件中实现,也可以在C中去实现,这里给出汇编的实现,C的实现就对应很简单了。
- OS_CPU_IRQ_ISR
- STMFD SP!, {R1-R3}
- ;//----------------------------------------------------------------------------
- ;// R1--SP (IRQ MODE)
- ;// R2--PC (TASK POINTER)
- ;// R3--SPSR (TASK CPSR)
- ;//----------------------------------------------------------------------------
- MOV R1, SP
- ADD SP, SP, #12
- SUB R2, LR, #4
- MRS R3, SPSR
- MSR CPSR_cxsf, #SVCMODE:OR:NOINT
- ;//take care! this sp is not the one before!svc mode's SP
- STMFD SP!, {R2}
- STMFD SP!, {R4-R12, LR}
- LDMFD R1!, {R4-R6}
- STMFD SP!, {R4-R6}
- STMFD SP!, {R0}
- STMFD SP!, {R3}
- ;LDR R0, =OSIntNesting
- ;LDRB R1, [R0]
- ;ADD R1, R1, #1
- ;STRB R1, [R0]
- ;CMP R1, #1
- ;BNE %F1
- LDR R4, =OSTCBCur
- LDR R5, [R4]
- LDR SP, [R5]
- BL OSIntEnter
- MSR CPSR_c, #IRQMODE:OR:NOINT
- ;/**
- ;LDR R0, =INTOFFSET
- ;LDR R0, [R0]
- ;LDR R1, IRQIsrVect
- ;MOV LR, PC
- ;LDR PC, [R1, R0, LSL #2]
- ;*/
- BL irq_process
- MSR CPSR_c, #SVCMODE:OR:NOINT
- BL OSIntExit
- LDMFD SP!, {R4}
- MSR SPSR_cxsf, R4
- LDMFD SP!, {R0-R12, LR, PC}^
其大致过程是:SVC模式下原环境保护压栈,调用OSIntEnter, 进入中断模式,调用irq_process, 调用OSIntExt。
上述步骤大家可以在所有ucos的移植教程或步骤中找到。
接下来的一些列步骤有点类似于一直uboot,希望大家先行阅读相关的板子启动介绍文档。
除去nand与SD启动的BL0 与BL1这些初始化步骤,我们ucosii的引导步骤实际上是在uboot已经为我们初始化好了一些必要设备后进行的,所以相对简单,我们可以参考uboot的start.S文件来制作我们自己的start.S
IMPORT 与EXPORT 符号
- ;******************************************************
- PRESERVE8 ;//字节对齐
- CODE32 ;//ARM代码
- AREA RESET,CODE,READONLY ;//reset名,代码段,只读
- ENTRY ;//程序入口
这里的erea 取名是根据2416.sct中的名字来的,这个文件存储了链接时各个文件的存储位置与顺序等,详细的介绍请看uboot的相关介绍,如果想要一个最纯净的,请到u-boot/arch/arm/cpu对应的路径下找u-boot.lds文件。
在entry后紧跟
- b HANDLE_ResetInit
这个就是我们的第一个函数,也是我们的第一个初始化函数。这个函数里面的很多操作都可以直接拷贝armv5.pdf上的例子,因为涉及到太多的协处理器的操作。
- mov r0, #0
- mcr p15, 0, r0, c7, c7, 0 ;flush v3/v4 cache
- mcr p15, 0, r0, c8, c7, 0 ; flush v4 TLB
1.关闭Icache与Dcache
- mov r1, #0xd3 ;//svc mode
- msr cpsr_cxsf,r1
- ldr sp,=0x31000000
2.关闭IRQ FIQ, 设置工作模式SVC模式, 初始化堆栈地址。
PS:说实话,这个start.S也是我在2440中找到拿来改的,但是建议参考u-boot的顺序来写,比如这里应该先修改运行模式,再设置cache相关的东西。
- ;set to high vector address
- ;read c1 to r5
- MRC p15,0,r5,c1,c0,0
- ;set bit 13 of c1
- orr r5, r5, #0x2000
- ;write r5 to c1
- mcr p15, 0, r5, c1, c0, 0
这几步是将中断向量表的入口地址改为高地址 0xFFFF0000开始,因为我使用的是SD卡启动模式,低地址0被物理映射到了BL0所在的IROM中,这里面是只读的,所以我们系统启动之后,无法将中断向量表写入这个地址,所以将中断向量表的地址改为高地址以便写入。实际上u_boot与linux都是做了类似的操作。用nand启动方式似乎可以写入(未进行测试,有朋友测试后求告知结果)。
接下来就是建立mmu映射表,原因同上
- bl make_mmu_table
- ;enable domain access
- ldr r5, =0x0000ffff
- mcr p15, 0, r5, c3, c0, 0 ;load domain access register
- ldr r1,=0x37ff4000
- mcr p15, 0, r1, c2, c0, 0
这几句实际上是打开MMU寄存器的访问许可权,同时将内存映射表的开始地址赋给MMU的寄存器。
- mrc p15, 0, r0, c1, c0, 0
- orr r0, r0, #1 ;Set CR_M to enable MMU
- mcr p15, 0, r0, c1, c0, 0
- nop
- nop
- nop
- nop
使能MMU
接下来针对每个模式初始化设置他们的栈地址
- mov r1, #0x11 ;//FIQ mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3ff4000
- mov r1, #0x12 ;//IRQ mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3ff0000
- mov r1, #0x1f ;//sys mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fec000
- mov r1, #0x17 ;//abt mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe8000
- mov r1, #0x1b ;//undef mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe0000
- mov r1, #0xd3 ;//svc mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe4000
最后将bss段清零然后跳转到主函数
- BL InitRORWZI
- LDR PC,=main
在app.c这个文件中添加一个
Int main(void) 函数
{
Timer_init();
Uart_init();
OSinit();
App();
OSstart();
}
接下来就是编译调试错误, 像缺少头文件这种错误,请自行修改头文件名称。
有2点直接说,工程文件中不能加入ucos_ii.c这个文件;os_cfg_r.h与os_dbg_r.c
要修改为os_cfg.h, os_dbg.c
编译
错误
.\output\obj\ucos2416prob.axf: Warning: L6305W: Image does not have an entry point. (Not specified or not set due to multiple choices.)
引起原因 app.c文件中出现了main被当作默认C入口,而start.S中又指定了ENTRY
将start.S中的mian都改为Main或者其他什么名字。
还有一个错误是我自己引起的,将软定时器当作了系统定时器。