1 uCOS移植到STM32中,代码移植的详细注释,参考了网络上的很多资料,我只是用我自己的思路整理了一下。
有不对的地方请大家多多指教,互相学习
2 需要准备两个资料用于随时查看一些重点,《Cortex-M3权威指南》和《Cortex-M3_技术参考手册》
在代码的注释过程中都注明了对应的知识点在哪页,可以找到对应的地方仔细阅读,重点需要阅读的是《Cortex- M3权威指南》
3 系统复位后默认使用的堆栈指针是MSP,复位后的状态是特权级线程状态
4 假如在用户模式下使用的是PSP,那么寄存器的数值被保存到任务堆栈的空间,进入中断程序后就开始使用MSP,
如果还有一个高优先级的中断那么就继续的使用MSP,在程序退出最后一级中断的时候就用用户堆栈恢复寄存器
5 uCOS-II的任务使用的进程堆栈指针(PSP),异常使用的是主堆栈指针(MSP)
所以在起始文件中设置的Stack_Mem SPACE Stack_Size是设置的主堆栈的空间大小
在中断嵌套不深的时候可以让主堆栈占用的空间小一些
os_cpu_c.c
只有OSTaskStkInit函数的注释,其他的hook函数没有包含进来
<span style="font-size:18px;">/*
*********************************************************************************************************
* INITIALIZE A TASK'S STACK
*
* Description: This function is called by either OSTaskCreate() or OSTaskCreateExt() to initialize the
* stack frame of the task being created. This function is highly processor specific.
*
* Arguments : task is a pointer to the task code
*
* p_arg is a pointer to a user supplied data area that will be passed to the task
* when the task first executes.
*
* ptos is a pointer to the top of stack. It is assumed that 'ptos' points to
* a 'free' entry on the task stack. If OS_STK_GROWTH is set to 1 then
* 'ptos' will contain the HIGHEST valid address of the stack. Similarly, if
* OS_STK_GROWTH is set to 0, the 'ptos' will contains the LOWEST valid address
* of the stack.
*
* opt specifies options that can be used to alter the behavior of OSTaskStkInit().
* (see uCOS_II.H for OS_TASK_OPT_xxx).
*
* Returns : Always returns the location of the new top-of-stack once the processor registers have
* been placed on the stack in the proper order.
*
* Note(s) : 1) Interrupts are enabled when your task starts executing.
* 2) All tasks run in Thread mode, using process stack.
*********************************************************************************************************
*/
//做出任务好像被中断过一样的假象,用于任务的第一次执行的时候来恢复寄存器的内容
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,<Cortex-M3技术参考手册P21>,当T为(第24位)0时,执行指令会引起INVSTATE异常*/
*(--stk) = (INT32U)task; /* 任务的入口地址 */
*(--stk) = (INT32U)0xFFFFFFFEL; /*任务第一次被调度从堆栈中恢复寄存器的值的时候LR的值会恢复为0xFFFFFFFEL
这个值也没什么用,因为在进入中断之后LR的意义发生改变,此时的值也是由硬件自动设置的*/
/*LR是连接寄存器,用于在执行BL等分支指令的时候自动保存PC的值,用于保存子程序的返回地址*/
/* R14 (LR) (init value will cause fault if ever used)*/
/* LR:进入异常时LR的用法将被重新解释,其值也被更新成一种特殊的值,称为“EXC_RETURN”,
并且在异常返回时使用。EXC_RETURN的二进制值除了最低4位外全为1,而其最低4
位则有另外的含义(见表9.3和表9.4)。<权威指南PDF136页和139页>
合法的EXC_RETURN值及其功能:
0xFFFF_FFF1 返回handler模式
0xFFFF_FFF9 返回线程模式,并使用主堆栈(SP=MSP)
0xFFFF_FFFD 返回线程模式,并使用线程堆栈(SP=PSP)
如果主程序在线程模式下运行, 并且在使用MSP 时被中断, 则在服务例程中
LR=0xFFFF_FFF9(主程序被打断前的LR已被自动入栈)。
如果主程序在线程模式下运行, 并且在使用PSP 时被中断, 则在服务例程中
LR=0xFFFF_FFFD(主程序被打断前的LR已被自动入栈)。
如果主程序在Handler模式下运行,则在服务例程中LR=0xFFFF_FFF1(主程序被打断前的
LR已被自动入栈)。这时的“主程序”,其实更可能是被抢占的服务例程。事实上,在嵌套
时,更深层ISR所看到的LR总是0xFFFF_FFF1,
无效返回时<权威指南PDF142页>
如果LR中的EXC_RETURN不是合法的值(合法值见表9.4,包括企图返回ARM状态),则
引起用法fault。如果用法fault被除能,也上访成硬fault。此时,用法Fault状态寄存器(UFSR,
地址:0xE000_ED2A)中的INVPC位(位偏移:2),或者是INVSTATE位(位偏移:1)置位。
*/
/*无效的中断返回(LR 中包含了无效/错误的值)会产生用法fault <权威指南PDF119>*/
*(--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);
}</span>
os_cpu_a.asm
<span style="font-size:18px;">;/*********************** (C) COPYRIGHT 2010 Libraworks *************************
;* File Name : os_cpu_a.asm
;* Author : Librae
;* Version : V1.0
;* Date : 06/10/2010
;* Description : μCOS-II asm port for STM32
;*******************************************************************************/
IMPORT OSRunning ; External references
IMPORT OSPrioCur
IMPORT OSPrioHighRdy
IMPORT OSTCBCur
IMPORT OSTCBHighRdy
IMPORT OSIntNesting
IMPORT OSIntExit
IMPORT OSTaskSwHook
EXPORT OSStartHighRdy
EXPORT OSCtxSw
EXPORT OSIntCtxSw
EXPORT OS_CPU_SR_Save
EXPORT OS_CPU_SR_Restore
EXPORT PendSV_Handler
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器,<权威指南P131>表8.5
NVIC_SYSPRI2 EQU 0xE000ED20 ; 系统异常优先级寄存器阵列 <权威指南P128> 表8.3B
; 0xE000ED20:调试监视器的优先级配置寄存器地址
; 0xE000ED21:一个没有使用的地址
<span style="white-space:pre"> </span> ; 0xE000ED22:PendSV的优先级配置寄存器地址
; 0xE000ED23:SysTick的优先级配置寄存器地址
NVIC_PENDSV_PRI EQU 0xFFFF0000 ; 软件中断和系统节拍中断
; (都为最低,0xff).
NVIC_PENDSVSET EQU 0x10000000 ; 触发软件中断的值.<权威指南P131>表8.5
PRESERVE8
AREA |.text|, CODE, READONLY
THUMB
;********************************************************************************************************
; CRITICAL SECTION METHOD 3 FUNCTIONS
;
; Description: Disable/Enable interrupts by preserving the state of interrupts. Generally speaking you
; would store the state of the interrupt disable flag in the local variable 'cpu_sr' and then
; disable interrupts. 'cpu_sr' is allocated in all of uC/OS-II's functions that need to
; disable interrupts. You would restore the interrupt disable state by copying back 'cpu_sr'
; into the CPU's status register.
;
; Prototypes : OS_CPU_SR OS_CPU_SR_Save(void);
; void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
;
;
; Note(s) : 1) These functions are used in general like this:
;
; void Task (void *p_arg)
; {
; #if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
; OS_CPU_SR cpu_sr;
; #endif
;
; :
; :
; OS_ENTER_CRITICAL(); /* cpu_sr = OS_CPU_SaveSR(); */
; :
; :
; OS_EXIT_CRITICAL(); /* OS_CPU_RestoreSR(cpu_sr); */
; :
; :
; }
;********************************************************************************************************
OS_CPU_SR_Save
MRS R0, PRIMASK ;Set prio int mask to mask all (except faults)
;PRIMASK是只有一个位的寄存器,置1的时候关闭所有的可屏蔽中断
;只有NMI和硬fault可以响应,缺省值为0,表示没有关中断
;MRS,加载特俗功能寄存器的值到Rn,这里使 R0=PRIMASK
CPSID I ;快速关闭中断的指令,将PRIMASK置1
BX LR ;BX reg 转移到由寄存器reg给出的地址,这个指令不保存下一条指令的PC值到LR寄存器
;reg 的最低位指示出在转移后,将进入的状态是ARM(LSB=0)还是Thumb(LSB=1)
;CM3只在Thumb中运行,就必须保证reg 的LSB=1,否则fault<权威指南P107>表7.1系统异常清单
;BL Label; 转移到Label 处对应的地址,并且把转移前的下条指令地址保存到LR
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;MSR,存储Rn 的值到特殊功能寄存器,这里使PRIMASK=R0
BX LR
;/**************************************************************************************
;* 函数名称: OSStartHighRdy
;*
;* 功能描述: 使用调度器运行第一个任务
;*
;* 参 数: None
;*
;* 返 回 值: None
;**************************************************************************************/
OSStartHighRdy
LDR R4, =NVIC_SYSPRI2 ;set the PendSV exception priority
;加载立即数NVIC_SYSPRI2到R4,这里LDR是伪指令
;<权威指南 P128>
;NVIC_SYSPRI2 EQU 0xE000ED20
;0xE000ED20:调试监视器的优先级配置寄存器地址
;0xE000ED21:一个没有使用的地址
;0xE000ED22:PendSV的优先级配置寄存器地址
;0xE000ED23:SysTick的优先级配置寄存器地址
LDR R5, =NVIC_PENDSV_PRI ;
STR R5, [R4] ;设置PendSV和SysTick异常的优先级为FF
;这样之后,就把PendSV,SysTick优先级设置为了FF
;调试监视器的优先级就设置为了00
MOV R4, #0 ;set the PSP to 0 for initial context switch call
MSR PSP, R4 ;设置线程堆栈指针为0,PSP=R4
;用于在PendSV_Handler函数中判断系统是否是进行的第一次任务调度
LDR R4, =OSRunning ;OSRunning = TRUE,表示系统开始运行
MOV R5, #1 ;在ucos-ii.h中OSRunning被定义为 OS_EXT BOOLEAN OSRunning; 就是一个unsigned char 类型
STRB R5, [R4] ;STRB:把一个寄存器的低字节存储到存储器中
;
LDR R4, =NVIC_INT_CTRL ;rigger the PendSV exception (causes context switch)
;NVIC_INT_CTRL EQU 0xE000ED04; 中断控制寄存器,<权威指南P131>表8.5
LDR R5, =NVIC_PENDSVSET ;NVIC_PENDSVSET EQU 0x10000000; 软件触发PendSV中断,在PendSV中进行任务切换
STR R5, [R4] ;
CPSIE I ;enable interrupts at processor level
;开中断
OSStartHang ;正常情况下不会执行到下面的死循环代码
B OSStartHang ;should never get here
;/**************************************************************************************
;* 函数名称: OSCtxSw
;*
;* 功能描述: 任务级上下文切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSCtxSw
PUSH {R4, R5} ;寄存器的PUSH 和POP 操作永远都是4 字节对齐的——也就是说他们的地址必须是
;0x4,0x8,0xc,……。这样一来,R13 的最低两位被硬线连接到0,并且总是读出0(Read As Zero
;堆栈指针的最低两位永远是0,这意味着堆栈总是4字节对齐的 <权威指南P27>
;这就是为什么 定义OS_STK为32位的,typedef unsigned int OS_STK;
;PUSH,POP的批量操作<权威指南P47>图3.10
;不管在寄存器列表中,寄存器的序号是以什么顺序给出的,汇编器都将把它们升
;序排序。然后PUSH 指令按照从大到小的顺序依次入栈,POP 则按从小到大的顺序依次出栈
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET ;
STR R5, [R4]
POP {R4, R5}
BX LR
;/**************************************************************************************
;* 函数名称: OSIntCtxSw
;*
;* 功能描述: 中断级任务切换
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET ;
STR R5, [R4]
POP {R4, R5}
BX LR
NOP
;/**************************************************************************************
;* 函数名称: OSPendSV
;*
;* 功能描述: OSPendSV is used to cause a context switch.
;*
;* 参 数: None
;*
;* 返 回 值: None
;***************************************************************************************/
PendSV_Handler
CPSID I ; 关中断,阻止任务切换被打断
MRS R0, PSP ; 如果在用PSP堆栈,则可以忽略保存寄存器,参考CM3权威中的双堆栈-白菜注
CBZ R0, PendSV_Handler_Nosave ; Skip register save the first time
; CBZ 比较,如果结果为0 就转移
; PendSV_Handler_Nosave这个函数只在第一次进行任务调度的时候调用一次
; 我的理解是
; 第一次会跳过执行下面的五句代码,以后每次任务切换的时候都会执行
; 第一次不执行是因为任务堆栈里面的寄存器数据都是手动填进去的,不需要再保存一次
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
; At this point, entire context of process has been saved
;
PendSV_Handler_Nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook()
BLX R0 <span style="white-space:pre"> </span>; 调用钩子函数
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
; OS_EXT INT8U OSPrioCur;
LDR R1, =OSPrioHighRdy ; OS_EXT INT8U OSPrioHighRdy;
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
;
; typedef struct os_tcb {OS_STK *OSTCBStkPtr;...}
; OS_EXT OS_TCB *OSTCBCur;
LDR R1, =OSTCBHighRdy ; OS_EXT OS_TCB *OSTCBHighRdy;
LDR R2, [R1] ; R2是最高优先级任务TCB管理块所在的地址
STR R2, [R0] ; 使OSTCBCur指向优先级最高的任务的TCB管理块
LDR R0, [R2] ; R0是新任务的堆栈指针SP,SP = OSTCBHighRdy->OSTCBStkPtr;
; OS_TCB结构体定义中第一个字节是一个堆栈指针,所以这里R0中存放的就是最高优先级任务的堆栈指针
LDM R0, {R4-R11} ; 从最高优先级的堆栈中恢复R4-R1寄存器的值,其他的寄存器由硬件自动恢复
; LDM:从一片连续的地址空间中加载多个字到若干寄存器
ADDS R0, R0, #0x20 ; 修改堆栈指针的指向,这里恢复了8个寄存器,每个寄存器占用4个字节,一共32个字节,也就是0X20个
MSR PSP, R0 ; 更新进程堆栈,用新任务的SP加载PSP
ORR LR, LR, #0x04 ; 确保LR位2为1,返回后使用进程堆栈,因为在第一次调度任务之前系统默认使用的是MSP且在线程模式下运<span style="white-space:pre"> </span>; 行,第一次进入异常的时候LR=FFFFFFF9
<span style="white-space:pre"> </span>; 为了确保第一次进入任务调度之后能正确的返回线程模式,并使用线程堆栈(SP=PSP),所以或上#0X04,
<span style="white-space:pre"> </span>; 第一次 0XFFFFFFF9 | 0X04 = 0XFFFFFFFD
; 改变处理器的模式也有其它的方式:在异常返回时,通过修改LR 的位2,也能实现模式切换 <权威指南P42>
; 通过修改CONTROL寄存器的CONTROL[1]的值也可以选择堆栈指针,0=主堆栈指针(MSP),1=进程堆栈指针(PSP)
; ORR.W Rd, Rn, #imm12 ; Rd = Rn | imm12 按
;<权威指南P139>
;在进入异常服务程序后,LR的值被自动更新为特殊的EXC_RETURN
;这是一个高28位全为1的值,只有[3:0]的值有特殊含义,如表9.3所示。当异常服务例程把这个
;值送往PC时,就会启动处理器的中断返回序列。因为LR的值是由CM3自动设置的,所以只要没有特殊需求,<span style="white-space:pre"> </span>;就不要改动它
;或上#0X04就是置位位2,表示从进程堆栈中做出栈操作,返回后使用PSP
CPSIE I
BX LR ; 中断返回的时候由硬件自动返回最高优先级堆栈中的其他寄存器的值
; 这里才是整个PendSV_Handler函数的返回点,前面的CBZ R0, PendSV_Handler_Nosave只是做一下判断
; 是否执行CBZ R0, PendSV_Handler_Nosave后面的五句代码
; 中断返回时,处理器会自动把R0-R3,R12,LR,PC,PSR 弹出堆栈
end </span>
os_cpu.h
在这个头文件就是一些声明,没有什么需要注释的