uCOS II 在 LPC11C14 上面的移植方法
电子文档: http://download.csdn.net/detail/icegoly/5449031
第一: OS_CPU.H 的移植 2
1.1 定义 与编译器有关的数据类型 2
1.2 定义中断的实现方式 3
1.3 定义栈的生长方式 5
1.4 宏定义 优先级任务切换 6
1.5 定义开、关中断的函数 6
1.6 声明 5 个函数 6
第二:OS_CPU_C.C的移植 6
2.1 OSTaskStkInt() 任务堆栈初始化函数 6
2.2 OSTaskStkInit() 10
2.3 OSTaskCreateHook() 11
2.4 OSTaskDelHook() 11
2.5 OSTaskSwHook() 11
2.6 OSTaskStatHook() 11
2.7 OSTimeTickHook() 11
第三: OS_CPU_A.S 的移植 11
3.0 说明: 11
3.0.1 NVIC_INT_CTRL:中断控制和状态寄存器 12
3.0.2 NVIC_SCB_SHPR3 系统处理程序优先级寄存器 14
3.0.3 : PendSV 15
3.1 实现 中断开、关的汇编 15
3.2 OSStartHighRdy 函数移植 16
A :设置 PendSV 异常优先级为最低 17
B :初始化 PSP 设置为0 17
C :设置 OSRunning=ture 17
D :触发 PendSV 异常 17
E :开中断 18
3.3 OSCtxSw 任务切换 18
3.4 OSIntCtxSw任务切换 18
3.5 OS_CPU_PendSVHandler pendsv 异常处理 18
A :获取任务的 SP ,如果为 0 的话则 直接跳到 OS_CPU_PendSVHandler_nosave,去执行 18
B :保存 R4-R11 和 SP 19
C :将当前的堆栈指针给当前进程的任务块 19
D :然后调用 OSTaskSwHook 19
E :获取当前的高优先级 20
F获取当前就绪线程 20
G 得到新任务的Sp和恢复从堆栈 r4-r11 20
H:载入新的SP和返回 21
typedef unsigned char BOOLEAN ; /* boolean */ typedef unsigned char INT8U ; /* Unsigned 8 bit quantity 无符号8位整型变量 */ typedef signed char INT8S ; /* Signed 8 bit quantity 有符号8位整型变量 */ typedef unsigned short INT16U ; /* Unsigned 16 bit quantity 无符号16位整型变量 */ typedef signed short INT16S ; /* Signed 16 bit quantity 有符号16位整型变量 */ typedef unsigned int INT32U ; /* Unsigned 32 bit quantity 无符号32位整型变量 */ typedef signed int INT32S ; /* Signed 32 bit quantity 有符号32位整型变量 */ typedef float FP32; /* Single precision floating point 单精度浮点数(32位长度) */ typedef double FP64; /* Double precision floating point 双精度浮点数(64位长度) */
typedef unsigned int OS_STK ; /* Each stack entry is 32-bit wide 堆栈是32位宽度 */ typedef unsigned int OS_CPU_SR ; /* Define size of CPU status register (PSR = 32 bits) */ |
为了处理临界区代码,必须关中断,等处理完毕后,再开中断。关中断可以避免其他任务或中断进入临界区代码。 uC/OS-II 定义了这两个宏来实现,但注意一条:调用 uC/OS-II 功能函数时,中断应该总是开着的。 1 )当 OS_CRITICAL_METHOD= = 1 时,简单实现如下: #define OS_ENTER_CRITICAL() disable_int() #define OS_EXIT_CRITICAL() enable_int() 但这样有一个问题,如果禁止中断的情况下调用 uC/OS-II 功能函数,那么从功能函数返回时,中断可能变成允许的了,而实际上还是希望是禁止的。 2 )当 OS_CRITICAL_METHOD= = 2 时,实现如下: #define OS_ENTER_CRITICAL() asm( “ PUSH PSW ” ); asm( “ DI” ); #define OS_EXIT_CRITICAL() asm( “ POP PSW ” ); 执行OS_ENTER_CRITICAL() 时,先将中断状态保存到堆栈,然后关中断;执行OS_EXIT_CRITICAL() 时,再从堆栈中恢复原来的中断开 / 关状态。这种方法不会改变中断状态,避免前面的问题。 3 )当 OS_CRITICAL_METHOD= = 3 时,实现如下: #define OS_ENTER_CRITICAL() cpu_sr = get_processor_psw(); disable_interrupts(); #define OS_EXIT_CRITICAL() set_ processor_psw(cpu_sr); ==================== 这里注意的是 OS_CRITICAL_METHOD , ucos 提供了 3 种方法实现,第一种方法是直接简单的开关中断方式,但是一旦嵌套会发生意外,比如: view plaincopy to clipboardprint? ········· 10 ········ 20 ········ 30 ········ 40 ········ 50········ 60 ········ 70 ········ 80 ········ 90 ········ 100 ······· 110 ······· 120 ······· 130······· 140 ······· 150 void Task (void *data) ......... OS_ENTER_CRITICAL(); // 进入临界区 1 // 调用了某一函数 , 该函数也需要进入临界区 : { OS_ENTER_CRITICAL(); ................ OS_EXIT_CRITICAL(); } // 这里就自然变成废墟了 ........... // 临界区切掉 OS_EXIT_CRITICAL(); } void Task (void *data) { ......... OS_ENTER_CRITICAL(); // 进入临界区 1 // 调用了某一函数 , 该函数也需要进入临界区 : { OS_ENTER_CRITICAL(); ................ OS_EXIT_CRITICAL(); } // 这里就自然变成废墟了 ........... // 临界区切掉 OS_EXIT_CRITICAL(); } 此方法太多弊端 , 所以新内核中看不到她的影子了 于是出现第二种方法 , 执行 OS_ENTER_CRITICAL() 时首先保存中断状态到堆栈中 , 然后关中断 , 执行 OS_EXIT_CRITICAL() 时,再从堆栈中恢复原来的中断开/ 关状态。这种方法不会改变中断状态 , 由于用到堆栈 , 这样会带来隐忧 , 看邵贝贝翻译的有这样说: “ 但是,用户在使用这种方法的时候还得十分小心,因为如果用户在调用象OSTimeDly() 之类的服务之前就禁止中断,很有可能用户的应用程序会崩溃。发生这种情况的原因是任务被挂起直到时间期满,而中断是禁止的,因而用户不可能获得节拍中断!很明显,所有的 PEND 调用都会涉及到这个问题,用户得十分小心。一个通用的办法是用户应该在中断允许的情况下调用 µ C/OS- Ⅱ 的系统服务! ” 第 3 种方法直接保存到任务局部变量中去 #if OS_CRITICAL_METHOD == 3#define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr)) #endif 避免了使用堆栈 |
#define OS_CRITICAL_METHOD 3
#if OS_CRITICAL_METHOD == 3 #define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();} #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);} #endif |
/* 堆栈是从上往下长的, 0 -从下往上的生长方式 */
#define OS_STK_GROWTH 1 /* Stack grows from HIGH to LOW memory on ARM */ |
就绪任务的堆栈初始化应该模拟一次中断发生后的样子,堆栈中应该按进栈次序设置好各个寄存器的内容。OS_TASK_SW()函数模拟一次中断过程,在中断返回的时候进行任务切换
#define OS_TASK_SW() OSCtxSw() |
#if OS_CRITICAL_METHOD == 3 /* See OS_CPU_A.ASM */ 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) //和 OSCtxSw 可以一致 void OSStartHighRdy(void) //创建任务之后 ,运行优先级最高的任务 OS_CPU_SR OS_CPU_SR_Save(void) //开中断 void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr) //关中断
|
实际需要修改的只有 OSTaskStkInit()函数,其他五个函数需要声明,但不一定有实际内容。这五个函数都是用户定义的,所以OS_CPU_C.C中没有给出代码。如果用户需要使用这些函数,请将文件 OS_CFG.H 中的 #define constant OS_CPU_HOOKS_EN 设为1,设为0表示不使用这些函数
OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt) { OS_STK *stk;opt = opt; /* 'opt' 没有使用。作用是避免编译器警告 */ stk = ptos; /* 获取堆栈指针 */ /* 建立任务环境, ADS1.2 使用满递减堆栈 */*stk = (OS_STK) task; /* pc */ *--stk = (OS_STK) task; /* lr */ *--stk = 0; /* r12 */ *--stk = 0; /* r11 */ *--stk = 0; /* r10 */ *--stk = 0; /* r9 */ *--stk = 0; /* r8 */ *--stk = 0; /* r7 */ *--stk = 0; /* r6 */ *--stk = 0; /* r5 */ *--stk = 0; /* r4 */ *--stk = 0; /* r3 */ *--stk = 0; /* r2 */ *--stk = 0; /* r1 */ *--stk = (unsigned int) pdata; /* r0, 第一个参数使用R0 传递 */ *--stk = (USER_USING_MODE|0x00); /* spsr ,允许 IRQ, FIQ 中断 */ *--stk = 0; /* 关中断计数器 OsEnterSum; */ return (stk); }================================== *stk = (OS_STK) task; /* pc */ *--stk = (OS_STK) task; /* lr */ 干什么用的?不是很明白================================== *--stk = 0; /* r8 */ *--stk = 0; /* r7 */ *--stk = 0; /* r6 */ *--stk = 0; /* r5 */ *--stk = 0; /* r4 */ *--stk = 0; /* r3 */ *--stk = 0; /* r2 */ *--stk = 0; /* r1 */ 好像跟绍贝贝翻译的那本书上说法的不太一样,不是要保存寄存器吗?怎么都置 0 了? 答 1: 初始化的时候当然要清零了 答 2: 置0并且压入堆栈了。等下弹出来就象复位后一样~~~~ 答 3: 嗯,也是这样也对 书上说的,要保存寄存器的值 就像刚发生过中断一样 仔细想想初始化一个任务没必要那样做 答 4: 还有 *--stk = 0; /* r12 */ *--stk = 0; /* r11 */ *--stk = 0; /* r10 */ *--stk = 0; /* r9 */*--stk = 0; /* r8 */ *--stk = 0; /* r7 */ *--stk = 0; /* r6 */ *--stk = 0; /* r5 */ *--stk = 0; /* r4 */*--stk = 0; /* r3 */ *--stk = 0; /* r2 */ *--stk = 0; /* r1 */+++++++++++++++++++++++++++++ 可是,那些语句和后面的注释怎么就一一对应了? 答 5: 这些数据现在是保存在的堆栈中的,等下任务开始执行时,就从堆栈中弹出,弹出来刚好放在后面注释中的寄存器中。因为这些是工作寄存器,全部要压栈或者从堆栈中恢复的。 答 6: 还有 ==================================*stk = (OS_STK) task; /* pc */ *--stk = (OS_STK) task; /* lr */ 干什么用的?不是很明白 答 7: 后面不是有注释么?分别对应着 pc 寄存器和 lr 寄存器而 lr 寄存器在函数返回时,就会返回到任务入口处了。至于 pc ,为何要设置,这个我就不清楚了,也许是随意弄上去的一句,并没什么实际意义,只是为了把 R15 (即 PC )也弄一下吧。
|
说明:用户创建任务时, OSTasKCreat() 会调用 OSTaskStkInt() 函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的 TCB 结构中的最前面的参数OSTCBStkPtr 中,当该任务要被恢复时,任务切换函数从其 TCB 块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的 CPSR 、 r0 、 r1 , … , r12,lr,pc的寄存器中,完成现场的恢复和程序指针 PC 的返回。 |
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt) { OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */ stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */ *(stk) = (INT32U)0x01000000L; /* xPSR */ *(--stk) = (INT32U)task; /* Entry Point */ *(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/ *(--stk) = (INT32U)0x12121212L; /* R12 */ *(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */ *(--stk) = (INT32U)0x01010101L; /* R1 */ *(--stk) = (INT32U)p_arg; /* R0 : argument */
/* Remaining registers saved on process stack */ *(--stk) = (INT32U)0x11111111L; /* R11 */ *(--stk) = (INT32U)0x10101010L; /* R10 */ *(--stk) = (INT32U)0x09090909L; /* R9 */ *(--stk) = (INT32U)0x08080808L; /* R8 */ *(--stk) = (INT32U)0x07070707L; /* R7 */ *(--stk) = (INT32U)0x06060606L; /* R6 */ *(--stk) = (INT32U)0x05050505L; /* R5 */ *(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk); } |
下面的任务可以只声明不编写
NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register. ;NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14). NVIC_SCB_SHPR3 EQU 0xE000ED20 ; PendSV priority value (lowest). NVIC_PENDSV_PRI EQU 0x00FF0000 NVIC_PENDSVSET EQU 0x10000000 UCOS-II 启动多任务环境的函数是 OSStart ();用户在调用 OSStart ()前,必须已经建立了一个或多个任务。 OSStart ()最终调用 OSStartHighRdy () 运行多任务启动前优先级最高的任务。
A :设置 PendSV 异常优先级为最低
B :初始化 PSP 设置为0
C :设置 OSRunning=ture
D :触发 PendSV 异常
E :开中断
3.3 OSCtxSw 任务切换当操作系统要切换任务的时候,就触发一个pendsv异常。
3.4 OSIntCtxSw任务切换和 3.3 用一样的代码
3.5 OS_CPU_PendSVHandler pendsv 异常处理) 1 、 pendsv 用于使上下文切换。这是一个值得推荐的方法进行 Cortex-M3 上下文切换 ,这是因为 Cortex-M3 自动保存在任何例外处理器上下文的一半,使相同的异常返回 ,所以保存R4-R11 和 SP 是必须的。 使用 pendsv 异常这种方式意味着上下文保存和恢复是相同的,无论是由一个线程或发生中断或异常。 ) 2 、代码实现步骤 A :获取任务的 SP ,如果为 0 的话则 直接跳到 OS_CPU_PendSVHandler_nosave,去执行
B :保存 R4-R11 和 SP
C :将当前的堆栈指针给当前进程的任务块
D :然后调用 OSTaskSwHook
E :获取当前的高优先级
F获取当前就绪线程
G 得到新任务的Sp和恢复从堆栈 r4-r11
H:载入新的SP和返回
; Value to trigger PendSV exception.
|
SHPR2-SHPR3寄存器设置优先级可配置的异常处理程序的优先级级别( 0~3)。
SHPR2-SHPR3是字可访问的。有关它们的属性请见表 22.31 的寄存器小结。
利用 CMSIS 访问系统异常的优先级级别要用到以下 CMSIS 函数:
[1] uint32_t NVIC_GetPriority(IRQn_Type IRQn)
[2] void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
输入参数 IRQn 是 IRQ 编号,更多信息请见表 22.10 。
系统故障处理程序、优先级域以及每个处理程序的寄存器如下所示:
PendSV 是为系统级服务提供的中断驱动。在一个操作系统环境中,当没有其他异常正在执行时,可以使用 PendSV 来进行上下文的切换。
在进入 PendSV 处理函数时:
( 1 ) xPSR 、 PC 、 LR 、 R12 、 R0 ~ R3 已经在处理栈中被保存。
( 2 )处理模式切换到线程模式。
( 3 )栈是主堆栈。
由于 PendSV 在系统中被设置为最低优先级,因此只有当没有其他异常或者中断在执行时才会被执行。
OS_CPU_SR_Save MRS R0, PRIMASK CPSID I BX LR
OS_CPU_SR_Restore MSR PRIMASK, R0 BX LR |
UCOS-II 启动多任务环境的函数是 OSStart ();用户在调用 OSStart ()前,必须已经建立了一个或多个任务。 OSStart ()最终调用 OSStartHighRdy ()运行多任务启动前优先级最高的任务。
OSStartHighRdy ldr r0, =NVIC_SCB_SHPR3 ldr r1, [r0] ldr r2, =NVIC_PENDSV_PRI orrs r1, r1, r2 str r1, [r0]
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call MSR PSP, R0
LDR R0, =OSRunning ; OSRunning = TRUE MOVS R1, #1 STRB R1, [R0]
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0]
CPSIE I ; Enable interrupts at processor level
OSStartHang B OSStartHang ; Should never get here
|
ldr r0, =NVIC_SCB_SHPR3 ldr r1, [r0] ldr r2, =NVIC_PENDSV_PRI orrs r1, r1, r2 str r1, [r0] |
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call MSR PSP, R0 |
LDR R0, =OSRunning ; OSRunning = TRUE MOVS R1, #1 STRB R1, [R0] |
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] |
CPSIE I |
当操作系统要切换任务的时候,就触发一个pendsv异常。
OSCtxSw LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] BX LR
|
和 3.3 用一样的代码
) 1 、 pendsv 用于使上下文切换。这是一个值得推荐的方法进行
Cortex-M3 上下文切换 ,这是因为 Cortex-M3 自动保存在任何例外处理器上下文的一半,使相同的异常返回 ,所以保存R4-R11 和 SP 是必须的。 使用 pendsv 异常这种方式意味着上下文保存和恢复是相同的,无论是由一个线程或发生中断或异常。
) 2 、代码实现步骤
CPSID I // 更改处理器状态,关闭中断 ; Prevent interruption during context switch MRS R0, PSP ; PSP is process stack pointer ;CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time cmp r0, #0 beq OS_CPU_PendSVHandler_nosave |
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack ;STM R0, {R4-R11} stm r0!, {r4-r7} mov r1, r8 mov r2, r9 mov r3, r10 mov r4, r11 stm r0!, {r1-r4} subs r0, r0, #0x20 |
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP; LDR R1, [R1] STR R0, [R1] ; R0 is SP of process being switched out |
mov r0, lr push {r0} ldr r0, =OSTaskSwHook blx r0 |
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy; LDR R1, =OSPrioHighRdy LDRB R2, [R1] STRB R2, [R0] |
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy; LDR R1, =OSTCBHighRdy LDR R2, [R1] STR R2, [R0]
|
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr; ;LDM R0, {R4-R11} ; Restore r4-11 from new process stack ldm r0!, {r4-r7} ldm r0!, {r1-r3} mov r8, r1 mov r9, r2 mov r10, r3 ldm r0!, {r1} mov r11, r1 |
;ADDS R0, R0, #0x20 MSR PSP, R0 ; Load PSP with new process SP ;ORR LR, LR, #0x04 ; Ensure exception return uses process stack mov r0, lr movs r1, #0x04 orrs r0, r0, r1 mov lr, r0 CPSIE I // 更改处理器状态,使能中断 BX LR ; Exception return will restore remaining c |