freertos實時操作系統(tǒng)空閑任務(wù)阻塞延時示例解析
前言
阻塞態(tài):如果一個任務(wù)當(dāng)前正在等待某個外部事件,則稱它處于阻塞態(tài)。
rtos中的延時叫阻塞延時,即任務(wù)需要延時的時候,會放棄CPU的使用權(quán),進入阻塞狀態(tài)。在任務(wù)阻塞的這段時間,CPU可以去執(zhí)行其它的任務(wù)(如果其它的任務(wù)也在延時狀態(tài),那么 CPU 就將運行空閑任務(wù)),當(dāng)任務(wù)延時時間到,重新獲取 CPU 使用權(quán),任務(wù)繼續(xù)運行。
空閑任務(wù):處理器空閑的時候,運行的任務(wù)。當(dāng)系統(tǒng)中沒有其他就緒任務(wù)時,空閑任務(wù)開始運行,空閑任務(wù)的優(yōu)先級是最低的。
空閑任務(wù)
定義空閑任務(wù):
#define portSTACK_TYPE uint32_t typedef portSTACK_TYPE StackType_t; /*定義空閑任務(wù)的棧*/ #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; /*定義空閑任務(wù)的任務(wù)控制塊*/ TCB_t IdleTaskTCB;
創(chuàng)建空閑任務(wù):在vTaskStartScheduler調(diào)度器啟動函數(shù)中創(chuàng)建。
/*任務(wù)控制塊的結(jié)構(gòu)體 */
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 棧頂 */
ListItem_t xStateListItem; /* 任務(wù)節(jié)點 */
StackType_t *pxStack; /* 任務(wù)棧起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/* 任務(wù)名稱,字符串形式 */
TickType_t xTicksToDelay; /* 用于延時 */
} tskTCB;
typedef tskTCB TCB_t;
/*獲取獲取空閑任務(wù)的內(nèi)存:任務(wù)控制塊、任務(wù)棧起始地址、任務(wù)棧大小*/
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;//空閑任務(wù)的任務(wù)控制塊
*ppxIdleTaskStackBuffer=IdleTaskStack; //空閑任務(wù)的任務(wù)棧
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;//棧的大小
}
void vTaskStartScheduler( void )
{
/*創(chuàng)建空閑任務(wù)start*/
TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空閑任務(wù)控制塊 */
StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空閑任務(wù)棧起始地址 */
uint32_t ulIdleTaskStackSize;
/* 獲?。喝蝿?wù)控制塊、任務(wù)棧起始地址、任務(wù)棧大小 */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
&pxIdleTaskStackBuffer,
&ulIdleTaskStackSize );
/*創(chuàng)建空閑任務(wù)*/
xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任務(wù)入口 */
(char *)"IDLE", /* 任務(wù)名稱,字符串形式 */
(uint32_t)ulIdleTaskStackSize , /* 任務(wù)棧大小,單位為字 */
(void *) NULL, /* 任務(wù)形參 */
(StackType_t *)pxIdleTaskStackBuffer, /* 任務(wù)棧起始地址 */
(TCB_t *)pxIdleTaskTCBBuffer ); /* 任務(wù)控制塊 */
/* 將任務(wù)添加到就緒列表 */
vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
/*創(chuàng)建空閑任務(wù)end*/
/* 手動指定第一個運行的任務(wù) */
pxCurrentTCB = &Task1TCB;
/* 初始化系統(tǒng)時基計數(shù)器 */
xTickCount = ( TickType_t ) 0U;
/* 啟動調(diào)度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 調(diào)度器啟動成功,則不會返回,即不會來到這里 */
}
}
//下面是空閑任務(wù)的任務(wù)入口,看到,里面什么都沒做
//這個我用debug發(fā)現(xiàn)一直卡到這個for不動了。
//通過單步運行,發(fā)生了中斷,程序也無法進入中斷。
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
/* 防止編譯器的警告 */
( void ) pvParameters;
for(;;)
{
/* 空閑任務(wù)暫時什么都不做 */
}
}
阻塞延時
任務(wù)函數(shù)如下:延時函數(shù)由軟件延時替代為阻塞延時。
void Task1_Entry( void *p_arg )
{
for( ;; )
{
#if 0
flag1 = 1;
delay( 100 );/*軟件延時*/
flag1 = 0;
delay( 100 );
/* 線程切換,這里是手動切換 */
portYIELD();
#else
flag1 = 1;
vTaskDelay( 2 );/*阻塞延時*/
flag1 = 0;
vTaskDelay( 2 );
#endif
}
}
任務(wù)函數(shù)里面調(diào)用了vTaskDelay阻塞延時函數(shù),如下。
/*阻塞延時函數(shù)的定義 */
void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
/* 獲取當(dāng)前任務(wù)的任務(wù)控制塊 */
pxTCB = pxCurrentTCB;
/* 設(shè)置延時時間:xTicksToDelay個SysTick延時周期 */
pxTCB->xTicksToDelay = xTicksToDelay;
/* 任務(wù)切換 */
taskYIELD();
}
然后vTaskDelay里面調(diào)用了taskYIELD函數(shù),如下。目的是產(chǎn)生PendSV中斷,進入PendSV中斷服務(wù)函數(shù)。
/* Interrupt control and state register (SCB_ICSR):0xe000ed04
* Bit 28 PENDSVSET: PendSV set-pending bit
*/
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
#define portSY_FULL_READ_WRITE ( 15 )
/* Scheduler utilities. */
#define portYIELD() \
{ \
/* 設(shè)置 PendSV 的中斷掛起位,產(chǎn)生上下文切換 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ \
/* Barriers are normally not required but do ensure the code is completely \
within the specified behaviour for the architecture. */ \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
PendSV中斷服務(wù)函數(shù)如下,里面調(diào)用了vTaskSwitchContext上下文切換函數(shù),目的是尋找最高優(yōu)先級的就緒任務(wù),然后更新pxCurrentTCB。
__asm void xPortPendSVHandler( void )
{
// extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
/* 當(dāng)進入PendSVC Handler時,上一個任務(wù)運行的環(huán)境即:
xPSR,PC(任務(wù)入口地址),R14,R12,R3,R2,R1,R0(任務(wù)的形參)
這些CPU寄存器的值會自動保存到任務(wù)的棧中,剩下的r4~r11需要手動保存 */
/* 獲取任務(wù)棧指針到r0 */
mrs r0, psp
isb
ldr r3, =pxCurrentTCB /* 加載pxCurrentTCB的地址到r3 */
ldr r2, [r3] /* 加載pxCurrentTCB到r2 */
stmdb r0!, {r4-r11} /* 將CPU寄存器r4~r11的值存儲到r0指向的地址 */
str r0, [r2] /* 將任務(wù)棧的新的棧頂指針存儲到當(dāng)前任務(wù)TCB的第一個成員,即棧頂指針 */
stmdb sp!, {r3, r14}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 進入臨界段 */
msr basepri, r0
dsb
isb
bl vTaskSwitchContext /* 調(diào)用函數(shù)vTaskSwitchContext,尋找新的任務(wù)運行,通過使變量pxCurrentTCB指向新的任務(wù)來實現(xiàn)任務(wù)切換 */
mov r0, #0 /* 退出臨界段 */
msr basepri, r0
ldmia sp!, {r3, r14} /* 恢復(fù)r3和r14 */
ldr r1, [r3]
ldr r0, [r1] /* 當(dāng)前激活的任務(wù)TCB第一項保存了任務(wù)堆棧的棧頂,現(xiàn)在棧頂值存入R0*/
ldmia r0!, {r4-r11} /* 出棧 */
msr psp, r0
isb
bx r14
nop
}
vTaskSwitchContext上下文切換函數(shù)如下。
任務(wù)需要延時的時候,會放棄CPU的使用權(quán),進入阻塞狀態(tài)。在任務(wù)阻塞的這段時間,CPU可以去執(zhí)行其它的任務(wù)(如果其它的任務(wù)也在延時狀態(tài),那么 CPU 就將運行空閑任務(wù)),當(dāng)任務(wù)延時時間到,重新獲取 CPU 使用權(quán),任務(wù)繼續(xù)運行。
void vTaskSwitchContext( void )
{
if( pxCurrentTCB == &IdleTaskTCB )//如果當(dāng)前線程是空閑線程
{
if(Task1TCB.xTicksToDelay == 0)//如果線程1延時時間結(jié)束
{
pxCurrentTCB =&Task1TCB;//切換到線程1
}
else if(Task2TCB.xTicksToDelay == 0)//如果線程2延時時間結(jié)束(線程1在延時中)
{
pxCurrentTCB =&Task2TCB;//切換到線程2
}
else
{
return; /* 線程延時均沒有到期則返回,繼續(xù)執(zhí)行空閑線程 */
}
}
else//當(dāng)前任務(wù)不是空閑任務(wù)
{
if(pxCurrentTCB == &Task1TCB)//如果當(dāng)前線程是線程1
{
if(Task2TCB.xTicksToDelay == 0)//如果線程2不在延時中
{
pxCurrentTCB =&Task2TCB;//切換到線程2
}
else if(pxCurrentTCB->xTicksToDelay != 0)//如果線程1進入延時狀態(tài)(線程2也在延時中)
{
pxCurrentTCB = &IdleTaskTCB;//切換到空閑線程
}
else
{
return; /* 返回,不進行切換 */
}
}
else if(pxCurrentTCB == &Task2TCB)//如果當(dāng)前線程是線程2
{
if(Task1TCB.xTicksToDelay == 0)//如果線程1不在延時中
{
pxCurrentTCB =&Task1TCB;//切換到線程1
}
else if(pxCurrentTCB->xTicksToDelay != 0)//如果線程2進入延時狀態(tài)(線程1也在延時中)
{
pxCurrentTCB = &IdleTaskTCB;//切換到空閑線程
}
else
{
return; /* 返回,不進行切換*/
}
}
}
}
由上面代碼可知,vTaskSwitchContext上下文切換函數(shù)通過看xTicksToDelay是否為零,來判斷任務(wù)已經(jīng)就緒or繼續(xù)延時。
xTicksToDelay以什么周期遞減,在哪遞減。這個周期由SysTick中斷提供。
SysTick
SysTick是系統(tǒng)定時器,重裝載數(shù)值寄存器的值遞減到0的時候,系統(tǒng)定時器就產(chǎn)生一次中斷,以此循環(huán)往復(fù)。
下面是SysTick的初始化。
//main函數(shù)里面
/* 啟動調(diào)度器,開始多任務(wù)調(diào)度,啟動成功則不返回 */
vTaskStartScheduler();
//task.c里面調(diào)用了xPortStartScheduler函數(shù)
void vTaskStartScheduler( void )
{
//.....省略部分代碼
/* 啟動調(diào)度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 調(diào)度器啟動成功,則不會返回,即不會來到這里 */
}
}
//port.c里面
//xPortStartScheduler調(diào)度器啟動函數(shù),里面調(diào)用了vPortSetupTimerInterrupt函數(shù)初始化SysTick
BaseType_t xPortStartScheduler( void )
{
/* 配置PendSV 和 SysTick 的中斷優(yōu)先級為最低 */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 初始化SysTick */
vPortSetupTimerInterrupt();
/* 啟動第一個任務(wù),不再返回 */
prvStartFirstTask();
/* 不應(yīng)該運行到這里 */
return 0;
}
//system_ARMCM4.c文件
#define XTAL (50000000UL) /* Oscillator frequency */
#define SYSTEM_CLOCK (XTAL / 2U)
//FreeRTOSConfig.h文件
//系統(tǒng)時鐘大小
#define configCPU_CLOCK_HZ ( ( unsigned long ) 25000000 )
//SysTick每秒中斷多少次,配置成100,10ms中斷一次
#define configTICK_RATE_HZ ( ( TickType_t ) 100 )
//下面初始化SysTick
/* SysTick 控制寄存器 */
#define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
/*SysTick 重裝載寄存器*/
#define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
/*SysTick時鐘源的選擇*/
#ifndef configSYSTICK_CLOCK_HZ
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ//configSYSTICK_CLOCK_HZ=configCPU_CLOCK_HZ
/* 確保SysTick的時鐘與內(nèi)核時鐘一致 */
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )//無符號長整形32位二進制,左移兩位
#else
#define portNVIC_SYSTICK_CLK_BIT ( 0 )
#endif
#define portNVIC_SYSTICK_INT_BIT ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL )
//初始化SysTick的函數(shù)如下
void vPortSetupTimerInterrupt( void )
{
/* 設(shè)置重裝載寄存器的值 */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
/* 設(shè)置系統(tǒng)定時器的時鐘等于內(nèi)核時鐘
使能SysTick 定時器中斷
使能SysTick 定時器 */
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT |
portNVIC_SYSTICK_INT_BIT |
portNVIC_SYSTICK_ENABLE_BIT );
}
初始化好SysTick,下面看看SysTick的中斷服務(wù)函數(shù)。
現(xiàn)在就明白了,xTicksToDelay是以SysTick的中斷周期遞減的。
// port.c文件,SysTick中斷服務(wù)函數(shù)
//里面調(diào)用了xTaskIncrementTick函數(shù)更新系統(tǒng)時基
void xPortSysTickHandler( void )
{
/* 關(guān)中斷 進入臨界段*/
vPortRaiseBASEPRI();
/* 更新系統(tǒng)時基 */
xTaskIncrementTick();
/* 開中斷 退出臨界段*/
vPortClearBASEPRIFromISR();
}
//task.c文件,
static volatile TickType_t xTickCount = ( TickType_t ) 0U;
void xTaskIncrementTick( void )
{
TCB_t *pxTCB = NULL;
BaseType_t i = 0;
/* 更新系統(tǒng)時基計數(shù)器xTickCount,xTickCount是一個在port.c中定義的全局變量 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;//把xTickCount加1
/* 掃描就緒列表中所有線程的xTicksToDelay,如果不為0,則減1 */
for(i=0; i<configMAX_PRIORITIES; i++)
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
if(pxTCB->xTicksToDelay > 0)
{
pxTCB->xTicksToDelay --;
}
}
/* 任務(wù)切換 */
portYIELD();
}
實驗現(xiàn)象
這個里面就可以看到,高電平時間是20ms,剛好是阻塞延時的20ms。而且兩個任務(wù)波形相同,好像是CPU在同時做兩件事。這就是阻塞延時的好處。
為什么呢,
一開始,所有任務(wù)都沒有進入延時。
當(dāng)一個任務(wù)放棄CPU后(進入延時),這一瞬間,CPU立即轉(zhuǎn)向運行另一個任務(wù)(另一個任務(wù)也立即進入延時)。這是因為uvTaskDelay阻塞延時函數(shù)里面調(diào)用了taskYIELD()任務(wù)切換函數(shù)。所以產(chǎn)生PendSV中斷,進入PendSV中斷服務(wù)函數(shù)xPortPendSVHandler。
在那個PendSV中斷服務(wù)函數(shù)里面,調(diào)用vTaskSwitchContext上下文切換函數(shù),由于現(xiàn)在兩個任務(wù)都在延時過程中,就開始切到空閑任務(wù)。
等到重裝載數(shù)值寄存器的值遞減到0的時候,系統(tǒng)定時器就產(chǎn)生一次中斷,進入系統(tǒng)定時器的中斷函數(shù)中,改變xTicksToDelay,然后再次調(diào)用任務(wù)切換函數(shù)portYIELD()。目的是產(chǎn)生PendSV中斷,進入PendSV中斷服務(wù)函數(shù)。
然后再次調(diào)用vTaskSwitchContext上下文切換函數(shù),判斷現(xiàn)在兩個任務(wù)是否還在延時,如果任務(wù)1不在延時,那么立即切到任務(wù)1,任務(wù)1里面又調(diào)用uvTaskDelay阻塞延時函數(shù),再次套娃重復(fù)上面的活動。
所以波形上幾乎同步。

之前用軟件延時在任務(wù)函數(shù)里面寫delay(100),這就屬于cpu一直跑這個delay,跑完了才進行任務(wù)切換,如下圖所示,一個任務(wù)高低電平全搞完,才切到下一個任務(wù)。


以上就是freertos實時操作系統(tǒng)空閑任務(wù)阻塞延時示例解析的詳細內(nèi)容,更多關(guān)于freertos空閑任務(wù)阻塞延時的資料請關(guān)注腳本之家其它相關(guān)文章!
- FreeRTOS實時操作系統(tǒng)的任務(wù)通知方法
- FreeRTOS實時操作系統(tǒng)的任務(wù)應(yīng)用函數(shù)詳解
- FreeRTOS實時操作系統(tǒng)空閑任務(wù)的阻塞延時實現(xiàn)
- FreeRTOS實時操作系統(tǒng)的任務(wù)創(chuàng)建與任務(wù)切換
- FreeRTOS任務(wù)控制API函數(shù)的功能分析
- FreeRTOS實時操作系統(tǒng)的任務(wù)創(chuàng)建和刪除
- FreeRTOS實時操作系統(tǒng)的任務(wù)概要講解
- FreeRTOS實時操作系統(tǒng)多任務(wù)管理基礎(chǔ)知識
- FreeRTOS使用任務(wù)通知實現(xiàn)命令行解釋器
相關(guān)文章
FreeRTOS實時操作系統(tǒng)空閑任務(wù)的阻塞延時實現(xiàn)
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)空閑任務(wù)的阻塞延時實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS實時操作系統(tǒng)的內(nèi)存管理分析
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)的內(nèi)存管理的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS動態(tài)內(nèi)存分配管理heap_4示例
這篇文章主要為大家介紹了FreeRTOS動態(tài)內(nèi)存分配管理heap_4示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS實時操作系統(tǒng)的任務(wù)概要講解
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)的任務(wù)概要講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
freertos實時操作系統(tǒng)空閑任務(wù)阻塞延時示例解析
這篇文章主要為大家介紹了freertos實時操作系統(tǒng)的空閑任務(wù)及阻塞延時示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04
FreeRTOS實時操作系統(tǒng)多任務(wù)管理基礎(chǔ)知識
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)多任務(wù)管理的基礎(chǔ)知識,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04

