小猫爪:这些年遇过的Bug4-在FreeRTOS下进行浮点运行结果出错问题

小猫爪:这些年遇过的Bug4-在FreeRTOS下进行浮点运行结果出错问题

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多多,下期再见。

END

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猫爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值