特别说明:
1. 本教程是安富莱电子原创。
2. 安富莱STM32F407开发板资料已经全部开源,开源地址:地址链接
3. 当前共配套300多个实例,4套用户手册。
第10章
uCOS-III在高版本MDK开启FPU方案
由于官方提供的μCOS-III移植工程中对于浮点寄存器的入栈和出栈处理是错误的,所以网上就流传了各种修正版本。但是这些修正的代码只能在MDK4.7以下版本中可以正常的运行,MDK4.7及其以上的版本无法正常运行。本期教程为此而生。本期教程提供的方案只有任务使用了浮点寄存器(也就是做了浮点运算)才需要将其入栈,没有使用浮点寄存器的任务不需要进行入栈,认识到这点很重要。此方案在MDK4.54、4.73、5.10以及IAR6.3、6.7上面测试均通过。
10.1 官方移植方案
10.2 开启FPU解决方案
10.3 开启FPU的优劣
10.4 总结
10.1
官方移植方案
官方提供的移植工程里面,只有IAR工程里面才有浮点寄存器的入栈和出栈处理函数,MDK工程里面是没有的。下面这个是os_cpu_c.c文件夹中的函数:
OS_STK *OSTaskStkInit (void
(*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U
opt)
{
OS_STK *p_stk;
p_stk
= ptos +
1u;
p_stk
= (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);
*(--p_stk) =
(OS_STK)0x01000000uL;
*(--p_stk) =
(OS_STK)task;
*(--p_stk) =
(OS_STK)OS_TaskReturn;
*(--p_stk) =
(OS_STK)0x12121212uL;
*(--p_stk) =
(OS_STK)0x03030303uL;
*(--p_stk) =
(OS_STK)0x02020202uL;
*(--p_stk) =
(OS_STK)0x01010101uL;
*(--p_stk) =
(OS_STK)p_arg;
*(--p_stk) =
(OS_STK)0x11111111uL;
*(--p_stk) =
(OS_STK)0x10101010uL;
*(--p_stk) =
(OS_STK)0x09090909uL;
*(--p_stk) =
(OS_STK)0x08080808uL;
*(--p_stk) =
(OS_STK)0x07070707uL;
*(--p_stk) =
(OS_STK)0x06060606uL;
*(--p_stk) =
(OS_STK)0x05050505uL;
*(--p_stk) =
(OS_STK)0x04040404uL;
#if (OS_CPU_ARM_FP_EN >
0u)
if ((opt & OS_TASK_OPT_SAVE_FP) != (INT16U)0) {
*--p_stk =
(OS_STK)0x02000000u;
*--p_stk =
(OS_STK)0x41F80000u;
*--p_stk =
(OS_STK)0x41F00000u;
*--p_stk =
(OS_STK)0x41E80000u;
*--p_stk =
(OS_STK)0x41E00000u;
*--p_stk =
(OS_STK)0x41D80000u;
*--p_stk =
(OS_STK)0x41D00000u;
*--p_stk =
(OS_STK)0x41C80000u;
*--p_stk =
(OS_STK)0x41C00000u;
*--p_stk = (OS_STK)0x41B80000u;
*--p_stk =
(OS_STK)0x41B00000u;
*--p_stk =
(OS_STK)0x41A80000u;
*--p_stk =
(OS_STK)0x41A00000u;
*--p_stk =
(OS_STK)0x41980000u;
*--p_stk =
(OS_STK)0x41900000u;
*--p_stk =
(OS_STK)0x41880000u;
*--p_stk =
(OS_STK)0x41800000u;
*--p_stk =
(OS_STK)0x41700000u;
*--p_stk =
(OS_STK)0x41600000u;
*--p_stk =
(OS_STK)0x41500000u;
*--p_stk =
(OS_STK)0x41400000u;
*--p_stk =
(OS_STK)0x41300000u;
*--p_stk =
(OS_STK)0x41200000u;
*--p_stk =
(OS_STK)0x41100000u;
*--p_stk =
(OS_STK)0x41000000u;
*--p_stk =
(OS_STK)0x40E00000u;
*--p_stk =
(OS_STK)0x40C00000u;
*--p_stk =
(OS_STK)0x40A00000u;
*--p_stk =
(OS_STK)0x40800000u;
*--p_stk =
(OS_STK)0x40400000u;
*--p_stk =
(OS_STK)0x40000000u;
*--p_stk =
(OS_STK)0x3F800000u;
*--p_stk =
(OS_STK)0x00000000u;
}
#endif
return (p_stk);
}
官方提供的这个堆栈初始化是错误的的,为什么是错误的?因为这个不符合浮点寄存器的入栈和出栈顺序。还有一部分代码在os_cpu_a.asm文件中,内容如下:
#ifdef
__ARMVFP__
PUBLIC OS_CPU_FP_Reg_Push
PUBLIC OS_CPU_FP_Reg_Pop
#endif
;********************************************************************************************************
;
EQUATES
;********************************************************************************************************
NVIC_INT_CTRL
EQU
0xE000ED04
; Interrupt control state register.
NVIC_SYSPRI14
EQU
0xE000ED22
; System priority register (priority 14).
NVIC_PENDSV_PRI
EQU
0xFF
; PendSV priority value (lowest).
NVIC_PENDSVSET
EQU
0x10000000
; Value to trigger PendSV exception.
;********************************************************************************************************
;
CODE GENERATION DIRECTIVES
;********************************************************************************************************
RSEG CODE:CODE:NOROOT(2)
THUMB
#ifdef __ARMVFP__
;********************************************************************************************************
;
FLOATING POINT REGISTERS PUSH
;
void
OS_CPU_FP_Reg_Push (OS_STK *stkPtr)
;
; Note(s) : 1) This function
saves S0-S31, and FPSCR registers of the Floating Point
Unit.
;
;
2) Pseudo-code is:
;
a) Get FPSCR register value;
;
b) Push value on process stack;
;
c) Push remaining regs S0-S31 on process stack;
;
d) Update OSTCBCur->OSTCBStkPtr;
;********************************************************************************************************
OS_CPU_FP_Reg_Push
MRS
R1,
PSP
; PSP is process stack pointer
CBZ
R1,
OS_CPU_FP_nosave
; Skip FP register save the first time
VMRS R1,
FPSCR
STR R1, [R0, #-4]!
VSTMDB R0!, {S0-S31}
LDR
R1, =OSTCBCur
LDR
R2, [R1]
STR
R0, [R2]
OS_CPU_FP_nosave
BX
LR
;********************************************************************************************************
;
FLOATING
POINT REGISTERS POP
;
void OS_CPU_FP_Reg_Pop (OS_STK
*stkPtr)
;
; Note(s) : 1) This function
restores S0-S31, and FPSCR registers of the Floating Point
Unit.
;
;
2) Pseudo-code is:
;
a)
Restore regs S0-S31 of new process stack;
;
b) Restore FPSCR reg value
;
c) Update OSTCBHighRdy->OSTCBStkPtr pointer of new proces
stack;
;********************************************************************************************************
OS_CPU_FP_Reg_Pop
VLDMIA R0!, {S0-S31}
LDMIA R0!, {R1}
VMSR FPSCR,
R1
LDR
R1, =OSTCBHighRdy
LDR
R2, [R1]
STR
R0, [R2]
BX
LR
#endif
如果不理解为什么这个浮点寄存的入栈和出栈是错误的,需要认真学习一下第5章:任务切换设计。第5章对于这个问题有深入的讲解。
10.2
开启FPU解决方案
为了解决FPU的问题,有两个函数需要修改:一个是CPU_STK
*OSTaskStkInit(),另一个是PendSV中断。
10.2.1
修改函数CPU_STK
*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;
p_stk =
&p_stk_base[stk_size];
p_stk = (CPU_STK *)((CPU_STK)(p_stk) & 0xFFFFFFF8);
*--p_stk =
(CPU_STK)0x01000000u;
*--p_stk =
(CPU_STK)p_task;
*--p_stk =
(CPU_STK)OS_TaskReturn;
*--p_stk =
(CPU_STK)0x12121212u;
*--p_stk =
(CPU_STK)0x03030303u;
*--p_stk =
(CPU_STK)0x02020202u;
*--p_stk =
(CPU_STK)p_stk_limit;
*--p_stk =
(CPU_STK)p_arg;
*--p_stk =
(CPU_STK)0x11111111u;
*--p_stk =
(CPU_STK)0x10101010u;
*--p_stk =
(CPU_STK)0x09090909u;
*--p_stk =
(CPU_STK)0x08080808u;
*--p_stk =
(CPU_STK)0x07070707u;
*--p_stk =
(CPU_STK)0x06060606u;
*--p_stk =
(CPU_STK)0x05050505u;
*--p_stk =
(CPU_STK)0x04040404u;
*--p_stk =
(CPU_STK)0xFFFFFFFDUL;
(1)
return (p_stk);
}
1.
这句话最重要,这里是将EXC_RETURN也进行了入栈处理。关于EXC_RETURN在前面4.2.5
特殊功能寄存器讲解。这里要补充一点,对于M4内核,EXC_RETURN的bit4也是有意义的。
当bit4 =
1时,8个寄存器自动入栈,还有8个寄存器需要手动入栈
当bit4 =
0时,18个浮点寄存器+8个寄存器自动入栈,还有16个浮点寄存器+8个寄存器需要手动入栈。
这些寄存器在第4章和第5章有详细的讲解,这里就不再赘述了。

10.2.2
修改函数OS_CPU_PendSVHandler
函数所在的位置如下:
PendSV中断需要修改的地方如下:
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
TST
LR, #0x10
(1)
IT
EQ
VSTMDBEQ R0!,
{S16-S31}
MOV
R3,
LR
(2)
STMDB
R0!,{R3-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;
LDMIA
R0!,{R3-R11}
(3)
MOV
LR, R3
TST
LR, #0x10
(4)
IT
EQ
VLDMIAEQ R0!,
{S16-S31}
MSR
PSP,
R0
; Load PSP with new process SP
CPSIE I
BX
LR
; Exception return will restore remaining context
END
1.
通过检测EXC_RETURN(LR)的bit4来看这个任务是否使用了浮点寄存器,如果使用了需要将剩余的16个浮点寄存器入栈。
TST
LR, #0x10 :
TST指令通常与EQ,NE条件码配合使用。当所有测试位均为0时,EQ有效。而只要有一个测试位不为0,则NE有效。这里LR和0x10的值按位作逻辑“与”操作。
IT
EQ :
这里IT指令就是IF-THEN的缩写。IF‐THEN(IT)指令围起一个块,里面最多有4条指令,它里面的指令可以条件执行。
IT已经带了一个“T”,因此还可以最多再带3个“T”或者“E”。并且对T和E的顺序没有要求。其中T对应条件成立时执行的语句,E对应条件不成立时执行的语句。在If‐Then块中的指令必须加上条件后缀,且T对应的指令必须使用和IT指令中相同的条件,E对应的指令必须使用和IT指令中相反的条件。
IT的使用形式总结如下:
IT
;围起1条指令的IF-THEN块
IT
;围起2条指令的IF-THEN块
IT
;围起3条指令的IF-THEN块
IT
;围起4条指令的IF-THEN块
其中,
,
的取值可以是“T”或者“E”。而则是在下表中列出的条件(AL除外)。
这么说还不够形象,下面举一个简单的例子,用IT指令优化C伪代码:
if
(R0==R1)
{
R3 = R4 +
R5;
R3 = R3 /
2;
}
else
{
R3 = R6 +
R7;
R3 = R3 /
2;
}
可以写作:
CMP R0,
R1
;
比较R0和R1
ITTEE
EQ
; 如果R0 == R1,
Then-Then-Else-Else
ADDEQR3, R4,
R5 ; 相等时加法
ASREQR3, R3,
#1 ; 相等时算术右移
ADDNER3, R6,
R7 ; 不等时加法
ASRNER3, R3,
#1 ; 不等时算术右移
有了上面这些基础知识后再看下面的指令。
VSTMDBEQ
R0!, {S16-S31}:
结合上面的IT
EQ,这里的意思就是 if LR & 0x10 == 0 then
VSTMDB R0!,
{S16-S31}。也就是咱们前面所说的如果EXC_RETURN(LR)的bit4
= 0就表示使用浮点寄存器了,这里需要入栈。
2.
这里比较好理解,只不过也将EXC_RETURN进行了入栈。
3.
参考上面的第二条,只不过这里是出栈。
4.
参考上面的第一条,只不过这里是出栈。
10.2.3
开启FPU
修改了上面的两个地方后别忘了开启FPU:
通过上面这三部就完成了相关的修改。
10.3
开启FPU的优劣
开启FPU的好处就是加快浮点运算的执行速度,缺点就是增加任务堆栈的大小,因为34个浮点寄存器也需要入栈。同时也增加了任务的切换时间。下面是在μCOS-III的GUI任务和启动任务中使用了浮点运算的对比(App
Task GUI和App
Task Start)。
第一个截图是开启了FPU:
第二个截图是没有开启FPU:
特别对比下使用了浮点运行的两个任务。
10.4
总结
本期教程提供的方案在MDK4.54、4.73、5.10以及IAR6.3、6.7上面测试均通过,用户只需按照上面讲的三个地方做修改即可。
加载中,请稍候......