论坛元老
 
- 积分
- 11912
- 金钱
- 11912
- 注册时间
- 2013-3-8
- 在线时间
- 1269 小时
|

楼主 |
发表于 2015-2-5 15:53:56
|
显示全部楼层
本帖最后由 不要神话 于 2013-8-10 16:28 编辑
基于CORTEX-M4的STM32F407最大的特色就是加入了一个FPU浮点处理器,能支持DSP运算。但是UCOS III官方的移植版本竟然不支持FPU浮点运算,如果在MDK的设置选项中把使能FPU打上的话运行的时候会把芯片带到硬fault,我用STLINK逐步跟踪,发现在使能FPU后系统在进行任务堆栈切换的时候发生错误进入硬fault,遂在GOOGLE上搜索了半天,并查看了M4的参考手册,发现使能FPU之后系统在进入中断的时候会自动将部分FPU寄存器入栈,而不支持FPU的UCOS移植版本显然没有为FPU寄存器做堆栈设置,所以在堆栈进行还原的时候导致还原点不同,进入硬fault。下面逐一分析:
1. 在UCOS III for STM32F4 中,UCOS在进行任务切换的时候并不是直接在任务中进行堆栈切换,而是使用了CORTEX-M系统架构中的一个叫做PENDSV的异常,通过悬起一个PENDSV异常,PENDSV异常其实想当于一个SVC(系统高胜),但是跟SVC的在于PENDSV的悬起特性,即是说置位一个PSNDSV异常之后如果当前还有更高优先级的异常在进行,则PENDSV会被延迟执行,当更高优先级的异常执行完毕才轮到PENDSV例程执行,CORTEX-M的尾链技术能够确保在别的中断完毕退出之后能够快速进入PENDSV执行,在PENDSV中,UCOS才进行实际的任务切换操作,PS:STM32有两个堆栈,一个是PSP(线程堆栈),一个是MSP(管理堆栈),一般来说用户程序运行在PSP的线程模式中,所有中断例程运行在MSP的特权模式中。更多关于PENDSV的内容参见《Cortex-M3权威指南》中文版(宋岩译)P121-P124
2. 使能/失能 FPU时的中断入栈区别。这个就是修改FPU支持的重点,在STM32F4中,发生中断时会将部分寄存器入栈,再进入中断全程,但是使能和失能FPU这两两种情况的入栈流程是有区别的。
a). 首先是普通情况下,不使用FPU的时候,这个时候CORTEX-M4是跟CORTEX-M3一样的入栈方式,一个异常到来的时候,系统自动将R1-R3、R12、LR、PC、xPSR入栈,这个时候UCOS官方移植版会接着将剩下的R4-R11入栈,保证当前任务的执行上下文全部保存起来,在os_cpu_a.asm的OS_CPU_PendSVHandler中有如下语句:
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack STM R0, {R4-R11}
b). 在使用FPU的情况下,异常到来的时候,CORTEX-M4会将部分FPU的寄存器也一起自动入栈,包括一个未知的字,一个FPSCR寄存器,以及S15-S0,所以如果要使UCOS支持FPU,就必须配合M4的这种入栈/出栈行为相应地修改任务堆栈的结构以及任务切换时的入栈/出栈汇编代码
3. 要修改的有两个地方,一个是任务初始化时的堆栈结构设置,在os_cpu_c.c的OSTaskStkInit函数中,函数原型:
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; /*  revent compiler warning */
p_stk = &p_stk_base[stk_size]; /* Load stack pointer */
/* Align the stack to 8-bytes. */
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8);
/* Registers stacked as if auto-saved on exception */
*--p_stk = (CPU_STK)0x01000000u; /* xPSR */
*--p_stk = (CPU_STK)p_task; /* Entry  oint */
*--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 */
*--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);
}
需要在中间增加FPU寄存器的保存结构,修改成下面的代码,红色部分即是修改的,使用两人个宏来判断是否需要增加此结构,以便用户自由地在MDK设置中设置是否启用FPU,而不用亲自修改该部分代码,为些需要在该文件头中#include "stm32f4xx.h"
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; /*  revent compiler warning */
p_stk = &p_stk_base[stk_size]; /* Load stack pointer */
/* Align the stack to 8-bytes. */
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8);
/* Registers stacked as if auto-saved on exception */
#if(__FPU_PRESENT == 1)&&(__FPU_USED == 1)
*--p_stk = (CPU_STK)0x00000000u; /*unknow 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; /*S09 */
*--p_stk = (CPU_STK)0x08080808u; /*S08 */
*--p_stk = (CPU_STK)0x07070707u; /*S07 */
*--p_stk = (CPU_STK)0x06060606u; /*S06 */
*--p_stk = (CPU_STK)0x05050505u; /*S05 */
*--p_stk = (CPU_STK)0x04040404u; /*S04 */
*--p_stk = (CPU_STK)0x03030303u; /*S03 */
*--p_stk = (CPU_STK)0x02020202u; /*S02 */
*--p_stk = (CPU_STK)0x01010101u; /*S01 */
*--p_stk = (CPU_STK)0x00000000u; /*S00 */
#endif
*--p_stk = (CPU_STK)0x01000000u; /* xPSR */
*--p_stk = (CPU_STK)p_task; /* Entry  oint */
*--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);
}
第二个需要修改的地方是任务切换函数,上面说了UCOS使用PENDSV异常来执行任务切换动作,所以在源码中找出PENDSV异常例程的代码,在os_cpu_a.asm文件中,使用汇编编写,找到OS_CPU_PendSVHandler函数,原型如下:
OS_CPU_PendSVHandler
CPSID I ;  revent interruption during context switch
MRS R0,  SP ;  SP is process stack pointer
CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
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
  USH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
  OP {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
MSR   SP, 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
修改后为:
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 enable the FPU
SUBS R0, R0, #0X40
VSTM R0, {S16-S31}
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 enable FPU
VLDM R0, {S16-S31}
ADDS R0, R0, #0X40
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
这些还需要在程序一开始启动FPU,我找到cstartup.s启动文件的Reset_Handler中开头有一段被注释掉的FPU启动代码,将些注释释放出来即可
同时需要在Project->Options->Target右边的Code Generation中选择“Use FPU”.
至此,修改工作完成,测试一下跑了两个任务执行浮点运算,相互不干扰,正常运行!!
附修改后的工程源码,删除了一些不必要的代码。 |
|