基于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()函数中, 依次往下调用
-
void * pvPortMalloc( size_t xWantedSize ) -
xTaskResumeAll() -
taskENTER_CRITICAL() 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, 避免不必要的麻烦.