基于FreeRTOS的程序, 在 vTaskStartScheduler() 启动前, 调用 FreeRTOS 的 API, 会屏蔽低于 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 的优先级的中断.

FreeRTOS 的调度器没有运行, 不要使用其API, 避免不必要的麻烦.

问题背景

FreeRTOS 的程序运行流程大概是这样的

常见的开发中, 需要确定硬件版本进行初始化, 我们需要读取ADC数值, 确定硬件版本, 在 BSP_Init 过程中, ADC初始化中使用了信号量的初始化API:

在使用 xSemaphoreCreateMutex() 函数之后, wait_Convert()会一直得不到对应的数据, 因为转换完成的中断出错, masked by BASEPRI = 0x10.

问题原因

问题主要出现在 xSemaphoreCreateMutex()函数中, 依次往下调用

  1. void * pvPortMalloc( size_t xWantedSize )

  2. xTaskResumeAll()

  3. taskENTER_CRITICAL()

  4. taskEXIT_CRITICAL()

其中, FreeRTOS 的 API 常见的临界区保护函数就是根本的原因

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;    //临界区嵌套计数

    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}
void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 ) //临界区嵌套计数为 0, 才打开中断屏蔽
    {
        portENABLE_INTERRUPTS();
    }
}

FreeRTOS 的 API 大多都会有临界区保护的代码, 为了处理中断调用API, 使用临界区嵌套计数变量 uxCriticalNesting, 用来计算临界区的嵌套.

在API中进入临界区, 嵌套层数加一, 退出临界区, 嵌套层数减一, 只有当 API 的嵌套层数为 0 的时候, 才能将中断屏蔽打开.

在调度器启动的代码里面,有一段操作系统默认接口初始化函数,里面初始化了各种参数,包括临界区嵌套计数变量。在调度器启动之后再调用信号量创建函数是没有任何问题的。

void vTaskStartScheduler( void )
{
    //code
    if( xPortStartScheduler() != pdFALSE ){}
    //code
}

BaseType_t xPortStartScheduler( void )
{
    //code
    uxCriticalNesting = 0;
    //code
}

由于uxCriticalNesting初始数值为0xaaaaaaaa, 导致临界区的中断屏蔽一直会持续, 也是就会一直屏蔽低于 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 的优先级的中断. 直到调度器运行, 将uxCriticalNesting清零, 才会取消中断屏蔽.

static UBaseType_t uxCriticalNesting = 0xaaaaaaaa;

同理, 其他的 API 也大多是有临界区保护的, 在调度器没有运行的时候使用, 也会有这个问题.

问题启发

FreeRTOS 的调度器没有运行, 不要使用其API, 避免不必要的麻烦.

发表评论