-------------序言
手里有一个STM32F407的小板,于是想把ucos-III跑一下,之前在Freescale的kinetis K10上跑过ucos-III,于是直接把移植代码(官方的K53移植范例)拿过来,发现有问题,一运行就跑到hard_fault中断里了,一开始以为是堆栈啥的,检查了一遍没问题。于是根据Cortex-M3权威指南最后的查找硬件fault的做法,发现原因是总线fault上访造成的,然后一步步调试,发现是在时钟节拍任务里跑飞的:我只建了一个任务,而内核在时钟节拍里却检查到有多个任务,然后进行任务切换 ··· 换句话说,在任务切换后,我建立的任务的OS_TCB数据被破坏了,任务的名称也变了,寄存器的值也不是初始化的值。百思不得其解,网上搜了下,发现有人说使能FPU的话其寄存器在中断发生时也入栈。一看,果然,我在建立IAR的工程时在配置里使能了FPU。而目前ucos-III官方的移植都没有考虑到使能FPU的情况。于是翻看M4的手册,再修改移植代码,果然就对了!现在做个小结,也算是给自己的备忘。
------------------------------------------------------------------------------------------
首先,如下图所示,FPU有32个32位的单精度寄存器S0-S31,也可以组成16个64位的双精度寄存器D0-D15。
当使能FPU的时候,中断入栈如下:其中第一个不知道是啥,没有名字,第二个是FPU的状态控制寄存器,后面是S15到S0,接下来才是和M3一样的入栈。如果没有使能FPU,那么入栈也和M3一样。
再看一下STM32驱动库的初始化函数SystemInit(void),里面第一行如下:
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL <<10*2)|(3UL <<11*2)); /* set CP10 and CP11 Full Access */
#endif
__FPU_PRESENT是在stm32f4xx.h里定义的,代表该型号带有FPU;__FPU_USED应该是对应工程配置属性里的是否使能FPU。如果使能,则在此进行初始化。因为复位后默认FPU是不使能的。
根据前面的讨论,几个移植过程中设计入栈出栈的函数应修改。首先是堆栈初始化函数:
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
{
CPU_STK *p_stk;
(void)opt; /* Prevent compiler warning */
p_stk = &p_stk_base[stk_size]; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
*--p_stk = (CPU_STK)0x00000000u; /* No Name Register */
*--p_stk = (CPU_STK)0x00000000u; /* FPSCR */
*--p_stk = (CPU_STK)0x15151515u; /* S15 */
*--p_stk = (CPU_STK)0x14141414u; /* S14 */
*--p_stk = (CPU_STK)0x13131313u; /* S13 */
*--p_stk = (CPU_STK)0x12121212u; /* S12 */
*--p_stk = (CPU_STK)0x11111111u; /* S11 */
*--p_stk = (CPU_STK)0x10101010u; /* S10 */
*--p_stk = (CPU_STK)0x09090909u; /* S9 */
*--p_stk = (CPU_STK)0x08080808u; /* S8 */
*--p_stk = (CPU_STK)0x07070707u; /* S7 */
*--p_stk = (CPU_STK)0x06060606u; /* S6 */
*--p_stk = (CPU_STK)0x05050505u; /* S5 */
*--p_stk = (CPU_STK)0x04040404u; /* S4 */
*--p_stk = (CPU_STK)0x03030303u; /* S3 */
*--p_stk = (CPU_STK)0x02020202u; /* S2 */
*--p_stk = (CPU_STK)0x01010101u; /* S1 */
*--p_stk = (CPU_STK)0x00000000u; /* S0 */
#endif
*--p_stk = (CPU_STK)0x01000000u; /* xPSR */
*--p_stk = (CPU_STK)p_task; /* Entry Point */
*--p_stk = (CPU_STK)OS_TaskReturn; /* R14 (LR) */
*--p_stk = (CPU_STK)0x12121212u; /* R12 */
*--p_stk = (CPU_STK)0x03030303u; /* R3 */
*--p_stk = (CPU_STK)0x02020202u; /* R2 */
*--p_stk = (CPU_STK)p_stk_limit; /* R1 */
*--p_stk = (CPU_STK)p_arg; /* R0 : argument */
/* Remaining registers saved on process stack */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
*--p_stk = (CPU_STK)0x31313131u; /* S31 */
*--p_stk = (CPU_STK)0x30303030u; /* S30 */
*--p_stk = (CPU_STK)0x29292929u; /* S29 */
*--p_stk = (CPU_STK)0x28282828u; /* S28 */
*--p_stk = (CPU_STK)0x27272727u; /* S27 */
*--p_stk = (CPU_STK)0x26262626u; /* S26 */
*--p_stk = (CPU_STK)0x25252525u; /* S25 */
*--p_stk = (CPU_STK)0x24242424u; /* S24 */
*--p_stk = (CPU_STK)0x23232323u; /* S23 */
*--p_stk = (CPU_STK)0x22222222u; /* S22 */
*--p_stk = (CPU_STK)0x21212121u; /* S21 */
*--p_stk = (CPU_STK)0x20202020u; /* S20 */
*--p_stk = (CPU_STK)0x19191919u; /* S19 */
*--p_stk = (CPU_STK)0x18181818u; /* S18 */
*--p_stk = (CPU_STK)0x17171717u; /* S17 */
*--p_stk = (CPU_STK)0x16161616u; /* S16 */
#endif
*--p_stk = (CPU_STK)0x11111111u; /* R11 */
*--p_stk = (CPU_STK)0x10101010u; /* R10 */
*--p_stk = (CPU_STK)0x09090909u; /* R9 */
*--p_stk = (CPU_STK)0x08080808u; /* R8 */
*--p_stk = (CPU_STK)0x07070707u; /* R7 */
*--p_stk = (CPU_STK)0x06060606u; /* R6 */
*--p_stk = (CPU_STK)0x05050505u; /* R5 */
*--p_stk = (CPU_STK)0x04040404u; /* R4 */
return (p_stk);
}
因为在汇编文件里没法include stm32f4xx.h头文件(里面有很多C的定义),所以用了另外一个宏__FPU_USED来配置是否使能FPU。而任务切换的函数修改如下:
OS_CPU_PendSVHandler
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
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SUBS R0, R0, #0x40 ; Save remaining regs S16-S31 on process stack
VSTM R0, {S16-S31}
#endif
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
LDR R1, =OSTCBCurPtr ; OSTCBCurPtr->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
OS_CPU_PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R1, =OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
VLDM R0, {S16-S31} ; Restore S16-S31 from new process stack
ADDS R0, R0, #0x40
#endif
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
END
----------------------------------------------------------------------------------------------------------------------------------------------------
进行上述修改后,代码就能成功运行了。在ucos-III里,任务OS_TCB带有一个扩展指针,可以指向扩展的数据,书中说可以用来保存FPU的寄存器。这样也可以,但得为每个任务再分配一个数据块,而且访问起来也麻烦,还不如直接压到栈里。任务的栈要开大一点,因为保存的寄存器比较多,考虑到嵌套什么的。幸好现在的单片机资源都很丰富,RAM一般不成什么问题。