要独占式地访问临界资源,有3种方法:
公平竞争:比如使用互斥量,谁先获得互斥量谁就访问临界资源;
谁要跟我抢,我就“灭掉谁”:
- 中断要跟我抢?我屏蔽中断;
- 其他任务要跟我抢?我禁止调度器,不运行任务切换。
一、屏蔽/使能中断
1.1 屏蔽中断
屏蔽中断有两套宏:任务中使用、ISR 中使用:
在任务中使用 | 在ISR 中使用 |
---|---|
taskENTER_CRITICA() | taskENTER_CRITICAL_FROM_ISR() |
taskEXIT_CRITICAL() | taskEXIT_CRITICAL_FROM_ISR() |
示例:
/*
* 在任务中,当前时刻中断是使能的
* 执行这句代码后,屏蔽中断
*/
taskENTER_CRITICAL();
/* 省略 */
/* 重新使能中断 */
taskEXIT_CRITICAL();
在 taskENTER_CRITICA()/taskEXIT_CRITICAL()之间:
- 低优先级的中断被屏蔽了:优先级低于、等于configMAX_SYSCALL_INTERRUPT_PRIORITY;
- 高优先级的中断可以产生:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY。
任务调度依赖于中断、依赖于 API 函数,所以:这两段代码之间,不会有任务调度产生。
这套taskENTER_CRITICA()/taskEXIT_CRITICAL()宏,是可以递归使用的,它的 内部会记录嵌套的深度,只有嵌套深度变为0时,调用taskEXIT_CRITICAL()才会重新使能中断。
使用这两个函数的缺点:
- 中断无法正常运行;
- 任务调度无法进行。
所以在这两句函数调用的代码段要尽可能地快速执行。
1.2 在ISR中屏蔽中断
示例代码如下:
void vAnInterruptServiceRoutine( void )
{
/* 用来记录当前中断是否使能 */
UBaseType_t uxSavedInterruptStatus;
/* 在 ISR 中,当前时刻中断可能是使能的,也可能是禁止的
* 所以要记录当前状态, 后面要恢复为原先的状态
* 执行这句代码后,屏蔽中断
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 访问临界资源 */
/* 恢复中断状态 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 现在,当前 ISR 可以被更高优先级的中断打断了 */
}
在 taskENTER_CRITICA_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR() 之 间 :
- 低优先级的中断被屏蔽了:优先级低于、等于configMAX_SYSCALL_INTERRUPT_PRIORITY;
- 高优先级的中断可以产生:优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY。
任务调度依赖于中断、依赖于 API 函数,所以:这两段代码之间,不会有任务调度产生。
二、暂停/恢复调度器
使用这两个函数来暂停、恢复调度器:
/* 暂停调度器 */
void vTaskSuspendAll( void );
/* 恢复调度器
* 返回值: pdTRUE 表示在暂定期间有更高优先级的任务就绪了
* 可以不理会这个返回值
*/
BaseType_t xTaskResumeAll( void );
示例代码:
vTaskSuspendScheduler();
/* 访问临界资源 */
xTaskResumeScheduler();
这套vTaskSuspendScheduler()/**xTaskResumeScheduler()宏,是可以递归使用的, 它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用taskEXIT_CRITICAL()**才会重新使能中断。
三、示例
解决DHT11读取出错问题
void Dht11Timer_Func(TimerHandle_t xTimer)
{
int hum,temp;
int err;
char buff[16];
/* 读取传感器 */
vTaskSuspendAll();
err = DHT11_Read(&hum,&temp);
xTaskResumeAll();
/* 显示数值 */
if(err == 0)
{
sprintf_P(buff, "%dC,%d%%", temp,hum);
draw_string(buff, false, 40, 0);
}else
{
draw_string("error ", false, 40, 0);
}
}
void game1_task(void *params)
{
/* 省略 */
DHT11_Init();
/* 创建定时器 */
g_TimerDht11 = xTimerCreate(g_TimerDht11,2000,pdTRUE ,NULL,Dht11Timer_Func);
/* 启动定时器 */
xTimerStart(g_TimerDht11,portMAX_DELAY);
/* 省略 */
}
上机实验:
在OLED 屏幕上显示出读取到的DHT11的数值,并且使用其它功能也没有影响到DHT11读取数值。