1 背景
芯片型号:AWR2944-R5核
操作系统:FreeRTOS
应用类型:浮点运算
错误现象:进行浮点运算时,会出现计算结果错误,都是浮点乘除法以及一些特殊数学库计算,如三角函数。
2 问题分析
在刚遇到这个问题时,就觉得特别离谱,这么多年就没遇到过这种类型的问题,就找啊找,找啊找,最后将注意到了FreeRTOS的一个配置参数configUSE_TASK_FPU_SUPPORT,最终经过测试果然就跟这个参数有关。
因为我的MCU平台为R5核,所以我的代码截取均来Source\portable\GCC\ARM_CR5F分支。
首先FreeRTOS官方对这个参数的描述为:
/* Set configUSE_TASK_FPU_SUPPORT to 0 to omit floating point support even
* if floating point hardware is otherwise supported by the FreeRTOS port in use.
* This constant is not supported by all FreeRTOS ports that include floating
* point support. */
#ifndef configUSE_TASK_FPU_SUPPORT
#define configUSE_TASK_FPU_SUPPORT 1
#endif
这个描述只告诉了我们,这个参数只对使用了硬件浮点单元FPU有用,那么这个参数到底起到了什么作用呢,我们继续往下找,最后在初始化任务栈的函数pxPortInitialiseStack中发现:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
/*.......... */
/*.......... */
/*.......... */
#if( configUSE_TASK_FPU_SUPPORT == 1 )
{
/* The task will start without a floating point context. A task that
uses the floating point hardware must call vPortTaskUsesFPU() before
executing any floating point instructions. */
pxTopOfStack--;
*pxTopOfStack = portNO_FLOATING_POINT_CONTEXT;
}
#elif( configUSE_TASK_FPU_SUPPORT == 2 )
{
/* The task will start with a floating point context. Leave enough
space for the registers - and ensure they are initialised to 0. */
pxTopOfStack -= portFPU_REGISTER_WORDS;
memset( pxTopOfStack, 0x00, portFPU_REGISTER_WORDS * sizeof( StackType_t ) );
pxTopOfStack--;
*pxTopOfStack = pdTRUE;
ulPortTaskHasFPUContext = pdTRUE;
}
#else
{
#error Invalid configUSE_TASK_FPU_SUPPORT setting - configUSE_TASK_FPU_SUPPORT must be set to 1, 2, or left undefined.
}
#endif
return pxTopOfStack;
}
在这里的介绍中就可以看出点关键性信息了,如果配置参数configUSE_TASK_FPU_SUPPORT 等于1,如果Task中出现浮点运算,并且使能了硬件FPU,则需要在该Task中调用一次vPortTaskUsesFPU();如果配置参数configUSE_TASK_FPU_SUPPORT 等于2,则该Task则默认支持浮点运算。我们再找到函数**vPortTaskUsesFPU()**的原型,如下:
#if( configUSE_TASK_FPU_SUPPORT != 2 )
void vPortTaskUsesFPU( void )
{
uint32_t ulInitialFPSCR = 0;
/* A task is registering the fact that it needs an FPU context. Set the
* FPU flag (which is saved as part of the task context). */
ulPortTaskHasFPUContext = pdTRUE;
/* Initialise the floating point status register. */
__asm volatile ( "FMXR FPSCR, %0" ::"r" ( ulInitialFPSCR ) : "memory" );
}
#endif /* configUSE_TASK_FPU_SUPPORT */
可以看出不管配置参数configUSE_TASK_FPU_SUPPORT等于1还是2,它们所作的事情都是一样的,都是在Task执行浮点运算前将变量ulPortTaskHasFPUContext 置1,那么将该变量置1有什么用呢?再找出哪里用到了这个变量,结果发现在发生任务切换时的保存现场函数portSAVE_CONTEXT和恢复现场函数portRESTORE_CONTEXT中发现了它:
.macro portSAVE_CONTEXT
/* Save the LR and SPSR onto the system mode stack before switching to
system mode to save the remaining system mode registers. */
SRSDB sp!, #SYS_MODE
CPS #SYS_MODE
PUSH {R0-R12, R14}
/* Push the critical nesting count. */
LDR R2, ulCriticalNestingConst
LDR R1, [R2]
PUSH {R1}
/* Does the task have a floating point context that needs saving? If
ulPortTaskHasFPUContext is 0 then no. */
LDR R2, ulPortTaskHasFPUContextConst
LDR R3, [R2]
CMP R3, #0
/* Save the floating point context, if any. */
FMRXNE R1, FPSCR
VPUSHNE {D0-D15}
/*VPUSHNE {D16-D31}*/
PUSHNE {R1}
/* Save ulPortTaskHasFPUContext itself. */
PUSH {R3}
/* Save the stack pointer in the TCB. */
LDR R0, pxCurrentTCBConst
LDR R1, [R0]
STR SP, [R1]
.endm
; /**********************************************************************/
.macro portRESTORE_CONTEXT
/* Set the SP to point to the stack of the task being restored. */
LDR R0, pxCurrentTCBConst
LDR R1, [R0]
LDR SP, [R1]
/* Is there a floating point context to restore? If the restored
ulPortTaskHasFPUContext is zero then no. */
LDR R0, ulPortTaskHasFPUContextConst
POP {R1}
STR R1, [R0]
CMP R1, #0
/* Restore the floating point context, if any. */
POPNE {R0}
/*VPOPNE {D16-D31}*/
VPOPNE {D0-D15}
VMSRNE FPSCR, R0
/* Restore the critical section nesting depth. */
LDR R0, ulCriticalNestingConst
POP {R1}
STR R1, [R0]
/* Ensure the priority mask is correct for the critical nesting depth. */
LDR R2, ulICCPMRConst
LDR R2, [R2]
CMP R1, #0
MOVEQ R4, #255
LDRNE R4, ulMaxAPIPriorityMaskConst
LDRNE R4, [R4]
STR R4, [R2]
/* Restore all system mode registers other than the SP (which is already
being used). */
POP {R0-R12, R14}
/* Return to the task code, loading CPSR on the way. */
RFEIA sp!
.endm
可以看出如果ulPortTaskHasFPUContext=1,则在出栈和入栈的时候,多了一步则是将寄存器FPSCR也进行出入栈操作。再继续往下查,FPSCR寄存器原来保存了当前硬件FPU的状态。到这,终于水落石出了,原来是因为当任务A进行浮点运算时,这个时候高优先级任务B抢占CPU,这个时候如果硬件FPU的状态没有保存,当再次回到任务B运行时,FPU状态已被破坏最终导致浮点运算出错。
3 解决方案
解决方案就是在具有浮点运算的Task中调用函数portTASK_USES_FLOATING_POINT(),即vPortTaskUsesFPU(),如下实例:
static portTASK_FUNCTION( vCompetingMathTask1, pvParameters )
{
volatile portDOUBLE d1, d2, d3, d4;
volatile uint16_t * pusTaskCheckVariable;
volatile portDOUBLE dAnswer;
short sError = pdFALSE;
/* Some ports require that tasks that use a hardware floating point unit
* tell the kernel that they require a floating point context before any
* floating point instructions are executed. */
portTASK_USES_FLOATING_POINT();
d1 = 123.4567;
d2 = 2345.6789;
d3 = -918.222;
dAnswer = ( d1 + d2 ) * d3;
/* The variable this task increments to show it is still running is passed in
* as the parameter. */
pusTaskCheckVariable = ( volatile uint16_t * ) pvParameters;
/* Keep performing a calculation and checking the result against a constant. */
for( ; ; )
{
d1 = 123.4567;
d2 = 2345.6789;
d3 = -918.222;
d4 = ( d1 + d2 ) * d3;
#if configUSE_PREEMPTION == 0
taskYIELD();
#endif
/* If the calculation does not match the expected constant, stop the
* increment of the check variable. */
if( fabs( d4 - dAnswer ) > 0.001 )
{
sError = pdTRUE;
}
if( sError == pdFALSE )
{
/* If the calculation has always been correct then set set the check
* variable. The check variable will get set to pdFALSE each time
* xAreMathsTaskStillRunning() is executed. */
( *pusTaskCheckVariable ) = pdTRUE;
}
#if configUSE_PREEMPTION == 0
taskYIELD();
#endif
}
}
该问题就到此为止了,祝大家bug多多,下期再见。