RTOS中的延時叫阻塞延時,即線程需要延時的時候,線程會放棄CPU的使用權,CPU可以去干其他事情,當線程延時時間到,重新獲取CPU使用權,線程繼續運行,這樣就充分利用了CPU的使用權,而不是剛等着。
當線程需要延時,CPU進入阻塞狀態,那CPU又去干什么事情了?如果沒有其它線程可以運行,RTOS都會為CPU創建一個空閑線程,這個時候CPU就運行空閑線程。在RT-Thread中,空閑線程是系統在初始化的時候創建的優先級最低的線程。空閑線程主體主要做一些系統內存的清理工作。在實際應用中,當系統進入空閑線程的時候,可在空閑線程中讓單片機進入休眠或者低功耗等操作。
1、實現空閑線程
1.1定義空閑線程的棧
空閑線程的棧在idle.c文件中定義,
#include <rtthread.h> #include <rthw.h> #define IDLE_THREAD_STACK_SIZE 512 ALIGN(RT_ALIGN_SIZE) static rt_uint8_t rt_thread_stack[IDLE_THREAD_STACK_SIZE];
空閑線程的棧是一個定義好的數組,大小由IDLE_THREAD_STACK_SIZE 這個宏控制,默認為512,即128個字。
1.2空閑線程的線程控制塊
/* 空閑線程的線程控制塊 */ struct rt_thread idle;
2、實現阻塞延時
阻塞延時的阻塞是指線程調用該延時函數后,線程會被剝離CPU使用權,然后進入阻塞狀態,直到延時結束,線程重新獲取CPU使用權才可以繼續運行,在線程阻塞這段時間,CPU可以去執行其他的線程,如果其他的線程也在延時狀態,那么CPU就將運行空閑線程。阻塞延時函數在thread.c中定義。
void rt_thread_delay(rt_tick_t tick) { struct rt_thread *thread; /* 獲取當前線程的線程控制塊 */ thread = rt_current_thread; (1) /* 設置延時時間 */ thread->remaining_tick = tick; (2) /* 進行系統調度 */ rt_schedule(); (3) }
(1)獲取當前線程的線程控制塊。rt_current_thread 是一個在scheduler.c中定義的全局變量,用於指向當前正在運行的線程的線程控制塊。
(2)remaining_tick 是線程控制塊的一個成員,用於記錄線程需要延時的時間,單位為SysTick 的中斷周期。
3、SysTick_Handler中斷服務函數
在系統調度函數rt_schedule()中,會判斷每個線程的線程控制塊中的延時成員remaining_tick的值是否為0,如果為0,就要將對應的線程就緒,如果不為0,就繼續延時。如果一個線程要延時,一開始remaining_tick 肯定不為0,當remaining_tick為0就延時結束,那么remaining_tick是以什么周期在遞減?在哪里遞減?在RT-Thread中,這個周期由SysTick中斷提供,操作系統里面最小的時間單位就是SysTick的中斷周期,我們稱之為一個tick,SysTick中斷服務函數
/* 關中斷 */ rt_hw_interrupt_disable(); (1) /* SysTick 中斷頻率設置 */ SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); (2) void SysTick_Handler(void) (3) { /* 進入中斷 */ rt_interrupt_enter(); (3)-1 /* 時基更新 */ rt_tick_increase(); (3)-2 /* 離開中斷 */ rt_interrupt_leave(); (3)-3 }
(1)關中斷。在程序開始的時候把中斷關掉是一個好習慣,等系統初始化完畢,線程創建完畢,啟動系統調度的時候會重新打開中斷。
(2)初始化SysTick,調用固件庫函數SysTick_Config來實現,配置中斷周期為10ms,中斷優先級為最低(無論中斷優先級分組怎么分都是最低,因為這里把表示SysTick中斷優先級的四個位全部配置為1,即15,在Cortex-M內核中,優先級越低,邏輯優先級最低),RT_TICK_PER_SECOND是一個在rtconfig.h中定義的宏,目前等於100。
(3)更新系統時基,該函數在clock.c中實現。
進入和離開中斷,這兩個函數在irq.c 中實現。
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { /* 非法的重裝載寄存器值 */ if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* 設置重裝載寄存器的值 */ SysTick->LOAD = (uint32_t)(ticks - 1UL); /* 設置 SysTick 的中斷優先級 */ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 加載 SysTick 計數器值 */ SysTick->VAL = 0UL; /* 設置系統定時器的時鍾源為 AHBCLK 使能 SysTick 定時器中斷 使能 SysTick 定時器 */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0UL); }
3.1系統時基更新函數
#include <rtthread.h> #include <rthw.h> static rt_tick_t rt_tick = 0; /* 系統時基計數器 */ (1) extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX]; void rt_tick_increase(void) { rt_ubase_t i; struct rt_thread *thread; rt_tick ++; (2) /* 掃描就緒列表中所有線程的 remaining_tick,如果不為 0,則減 1 */ for(i=0; i<RT_THREAD_PRIORITY_MAX; i++) (3) { thread = rt_list_entry( rt_thread_priority_table[i].next,
struct rt_thread,
tlist); if(thread->remaining_tick > 0) { thread->remaining_tick --; } } /* 系統調度 */ rt_schedule(); (4) }
(1)系統時基計數器,是一個全局變量,用來記錄產生了多少次SysTick 中斷。
(2)系統時基計數器加1操作
(3)掃描就緒列表中的remaining_tick,如果不為0,則減 1。
(4)進行系統調度
進入和離開中斷函數
#include <rtthread.h> #include <rthw.h> /* 中斷計數器 */ volatile rt_uint8_t rt_interrupt_nest; (1)
/** * 當 BSP 文件的中斷服務函數進入時會調用該函數 * * @note 請不要在應用程序中調用該函數 * * @see rt_interrupt_leave */ void rt_interrupt_enter(void) (2) { rt_base_t level; /* 關中斷 */ level = rt_hw_interrupt_disable(); /* 中斷計數器 ++ */ rt_interrupt_nest ++; /* 開中斷 */ rt_hw_interrupt_enable(level); } /** * 當 BSP 文件的中斷服務函數離開時會調用該函數 * * @note 請不要在應用程序中調用該函數 * * @see rt_interrupt_enter */ void rt_interrupt_leave(void) (3) { rt_base_t level; /* 關中斷 */ level = rt_hw_interrupt_disable(); /* 中斷計數器-- */ rt_interrupt_nest --; /* 開中斷 */ rt_hw_interrupt_enable(level); }
(1)中斷計數器,是一個全局變量,用來記錄中斷嵌套次數。
(2)進入中斷函數,中斷計數器rt_interrupt_nest 加1 操作。當BSP文件的中斷服務函數進入時會調用該函數,應用程序不能調用,切記。
(3)離開中斷函數,中斷計數器rt_interrupt_nest 減1操作。當BSP文件的中斷服務函數進入時會調用該函數,應用程序不能調用,切記。