RT-Thread第4課,聽聽 RT-Thread 的心跳,再學(xué)習(xí)一下基于心跳的軟件定時器使用。
目錄
前言
一、RT-Thread 時鐘節(jié)拍
1.1 時鐘節(jié)拍的概念
1.2 時鐘節(jié)拍實現(xiàn)原理
1.3 時鐘節(jié)拍示例
二、RT-Thread 軟件定時器
2.1 軟件定時器基本介紹
2.2 系統(tǒng)定時器初始化
2.3 定時器工作機制
2.4 us 延時函數(shù)
2.5 軟件定時器 or 硬件定時器?
三、 RT-Thread 軟件定時器操作函數(shù)
3.1 動態(tài)創(chuàng)建和刪除定時器
3.2 靜態(tài)創(chuàng)建和刪除定時器
3.3 啟動和停止定時器
3.4 定時器控制函數(shù)
四、定時器使用示例
4.1 動態(tài)創(chuàng)建定時器示例
4.2 靜態(tài)創(chuàng)建定時器示例
結(jié)語
前言
學(xué)習(xí)RTOS,肯定接觸到軟件定時器,學(xué)會軟件定時器的使用能夠使得我們擺脫硬件定時器在某些地方的局限性,而軟件定時器的實現(xiàn),又是基于系統(tǒng)的時鐘節(jié)拍,本文除了了解 RT-Thread 軟件定時器API,學(xué)會使用 RT-Thread 軟件定時器,還需要先了解下 RT-Thread 時鐘管理相關(guān)知識。
本 RT-Thread 專欄記錄的開發(fā)環(huán)境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發(fā)環(huán)境 及 配合CubeMX開發(fā)快速上手)
RT-Thread記錄(二、RT-Thread內(nèi)核啟動流程 — 啟動文件和源碼分析
RT-Thread 內(nèi)核篇系列博文鏈接:
RT-Thread記錄(三、RT-Thread 線程操作函數(shù)及線程管理)
一、RT-Thread 時鐘節(jié)拍
1.1 時鐘節(jié)拍的概念
時鐘節(jié)拍 (OS Tick)是系統(tǒng)心跳!任何操作系統(tǒng)都需要提供一個時鐘節(jié)拍,以供系統(tǒng)處理所有和時間有關(guān)的事件。
操作系統(tǒng)中最小的時間單位是時鐘節(jié)拍,時鐘節(jié)拍是特定的周期性中斷,內(nèi)核在時鐘節(jié)拍到的時候進行上下文切換。
RT-Thread 中,時鐘節(jié)拍的長度可以根據(jù) RT_TICK_PER_SECOND 的定義來調(diào)整,等于1/RT_TICK_PER_SECOND 秒,在我們測試的STM32F上,默認的時鐘節(jié)拍為1ms,如下:
在上一節(jié)創(chuàng)建線程的時候最后一個參數(shù)是時間節(jié)拍數(shù),比如設(shè)置為50,那么線程的時間片就是50ms。
另外,rtconfig.h中有 RT-Thread 內(nèi)核配置,線程通訊配置,組件配置,shell配置,設(shè)備驅(qū)動配置等等的宏定義配置。
RT_TICK_PER_SECOND是可以修改的,比如我們修改成100。時鐘節(jié)拍就是10ms。
1.2 時鐘節(jié)拍實現(xiàn)原理
那么時間節(jié)拍是如何實現(xiàn)的?
前面說過:時鐘節(jié)拍是特定的周期性中斷,這個中斷一般由MCU硬件定時器決定,就是系統(tǒng)的時鐘節(jié)拍的產(chǎn)生還是基于MCU的硬件定時器!
對于我們使用的 Corex-M
芯片來說,就是由滴答定時器Systick 來實現(xiàn)系統(tǒng)時鐘節(jié)拍的。
既然與滴答定時器Systick有關(guān)系,那么我們可以通過工程代碼來看一看,如圖:
在滴答定時器的中斷處理函數(shù)中,我們可以看到如下操作:
上圖代碼中的 全局變量,rt_tick
的值表示了系統(tǒng)從啟動開始總共經(jīng)過的時鐘節(jié)拍數(shù),即系統(tǒng)時間。rt_tick 在每經(jīng)過一個時鐘節(jié)拍時,值就會加 1。
到這里又有一個問題了,STM32中滴答定時器Systick不是固定的嗎?
所以我們這里就要說說 RT_TICK_PER_SECOND
改變的是什么,這個宏定義是如何影響系統(tǒng)節(jié)拍的。
我們找到drv_common.c
文件中的rt_hw_systick_init
函數(shù),如下圖:
上圖就是 RT-Thread 初始化配置啟動 MCU 滴答定時器的函數(shù),里面的配置用到了我們的宏定義RT_TICK_PER_SECOND
,所以宏定義的改變可以直接改變 Systick 的頻率,直接使得系統(tǒng)的時鐘節(jié)拍不同。
1.3 時鐘節(jié)拍示例
在上文我們說到,全局變量 rt_tick
表示了系統(tǒng)從啟動開始總共經(jīng)過的時鐘節(jié)拍數(shù), RT-Thread 給我們提供了一個函數(shù)rt_tick_get
來查看當(dāng)前的時鐘節(jié)拍值:
/*
返回值:rt_tick 當(dāng)前時鐘節(jié)拍值
*/
rt_tick_t rt_tick_get(void);
為了鞏固一下上面的內(nèi)容,我們來簡單的做個測試,因為測試比較簡單,我就直接上圖:
當(dāng)RT_TICK_PER_SECOND為1000的時候,就表示我們設(shè)置系統(tǒng)節(jié)拍為 1ms,那么 tick 的值就是 1ms 加一次,所以延時 1000ms 以后,是增加1000。
當(dāng)RT_TICK_PER_SECOND為100 的時候,就表示我們設(shè)置系統(tǒng)節(jié)拍為 10ms,那么 tick 的值就是 10ms 加一次,所以延時 1000ms 以后,是增加100。
二、RT-Thread 軟件定時器
2.1 軟件定時器基本介紹
RT-Thread 操作系統(tǒng)提供軟件實現(xiàn)的定時器,以時鐘節(jié)拍(OS Tick)的時間長度為單位,即定時數(shù)值必須是 OS Tick 的整數(shù)倍。
定時器分為 單次觸發(fā)定時器,周期觸發(fā)定時器;
定時器不管使用和理解都是比較簡單的,對于軟件定時的的一些注意事項,我在介紹FreeRTOS軟件定時器的時候?qū)戇^,可以比較參考一下,如下:
與 FreeRTOS 不同的是,根據(jù)超時函數(shù)執(zhí)行時所處的上下文環(huán)境,RT-Thread 的定時器可以分為 HARD_TIMER 模式與 SOFT_TIMER 模式:
- HARD_TIMER 模式
HARD_TIMER 模式的定時器超時函數(shù)在中斷上下文環(huán)境中執(zhí)行,RT-Thread 定時器默認的方式是 HARD_TIMER 模式,即定時器超時后,超時函數(shù)是在系統(tǒng)時鐘中斷的上下文環(huán)境中運行的。
簡單來說就是要把 HARD_TIMER 模式的回調(diào)函數(shù)當(dāng)成 中斷函數(shù)處理,快進快出。
- SOFT_TIMER 模式
SOFT_TIMER 模式可配置,通過宏定義 RT_USING_TIMER_SOFT 來決定是否啟用該模式。該模式被啟用后,系統(tǒng)會在初始化時創(chuàng)建一個 timer 線程,然后 SOFT_TIMER 模式的定時器超時函數(shù)在都會在 timer 線程的上下文環(huán)境中執(zhí)行。可以在初始化 / 創(chuàng)建定時器時使用參數(shù)RT_TIMER_FLAG_SOFT_TIMER 來指定設(shè)置 SOFT_TIMER 模式。
個人的習(xí)慣是,應(yīng)用中還是定義 RT_USING_TIMER_SOFT
,然后使用 SOFT_TIMER 模式,個人感覺這樣才更“像”軟件定時器。
最后要給個建議,實際應(yīng)用,不管是 HARD_TIMER 模式,還是 SOFT_TIMER 模式,在超時函數(shù)中都要做到快進快出,不要有延時掛起等操作。
2.2 系統(tǒng)定時器初始化
在 RT-Thread 使用中,往往都會定義RT_USING_TIMER_SOFT ,使用軟件定時器并且啟動 SOFT_TIMER 模式 ,該模式被啟用后,系統(tǒng)會在初始化時創(chuàng)建一個 timer 線程,用來對軟件定時器經(jīng)常管理,那么我們就通過源碼來看看 RT-Thread 到底是如何操作的。
通過 《RT-Thread記錄(二、RT-Thread內(nèi)核啟動流程 — 啟動文件和源碼分析)》學(xué)習(xí),我們可以找到rtthread_startup
函數(shù):
先來看看第一個rt_system_timer_init
:
接下來看看第二個函數(shù)rt_system_timer_thread_init
:
我們繼續(xù)進入 timer
線程的入口函數(shù),來看看 timer
線程具體做了什么事情,這里我們就通過放源碼,看注釋來分析一下:
/* system timer thread entry */
static void rt_thread_timer_entry(void *parameter)
{
rt_tick_t next_timeout;
while (1)
{
/*
get the next timeout tick
獲取下一次超時時間
得到軟件定時器鏈表上的下一個定時器的超時時間點
*/
next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);
/*
如果超過范圍,表示沒有軟件定時器,
則掛起當(dāng)前線程,繼續(xù)線程調(diào)度
*/
if (next_timeout == RT_TICK_MAX)
{
/* no software timer exist, suspend self. */
rt_thread_suspend(rt_thread_self());
rt_schedule();
}
else
{
rt_tick_t current_tick;
/*
get current tick
獲取當(dāng)前時間點
*/
current_tick = rt_tick_get();
/*
離下個中斷時間點還差些時候
*/
if ((next_timeout - current_tick) < RT_TICK_MAX / 2)
{
/* get the delta timeout tick */
next_timeout = next_timeout - current_tick;//計算還差多長時間
rt_thread_delay(next_timeout);//休眠差的這段時間
}
}
/*
check software timer
檢查是否該產(chǎn)生超時事件
*/
rt_soft_timer_check();
}
}
#endif
如果要繼續(xù)往下面分析,就得繼續(xù)分析rt_soft_timer_check();的實現(xiàn)源碼了,這里我們就不繼續(xù)分析下去,因為到目前為止,我們對于 RT-Thread 系統(tǒng)定時器的初始化過程已經(jīng)有了一個全面的認識,對于我們理解定時器有了很大的幫助,但是喜歡研究的小伙伴可以繼續(xù)往下面分析,分析源碼是理解一個系統(tǒng)最直接最有效的方式!
2.3 定時器工作機制
和線程控制塊一樣,內(nèi)核對于定時器的管理是通過這個定時器控制塊結(jié)構(gòu)體,里面包括 RT-Thread 軟件定時器的所有的“屬性”,對這些屬性的查看,修改就可以對實現(xiàn)對這個軟件定時器的管理控制。
/**
* timer structure
*/
struct rt_timer
{
/**< inherit from rt_object */
struct rt_object parent;
/* 定時器鏈表節(jié)點 */
rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL];
/**< timeout function 定時器超時調(diào)用的函數(shù) */
void (*timeout_func)(void *parameter);
/**< timeout function's parameter 超時函數(shù)的參數(shù)*/
void *parameter;
/** < timer timeout tick 定時器初始超時節(jié)拍數(shù) */
rt_tick_t init_tick;
/**< timeout tick 定時器實際超時時的節(jié)拍數(shù)*/
rt_tick_t timeout_tick;
};
typedef struct rt_timer *rt_timer_t;
定時器控制塊由 struct rt_timer 結(jié)構(gòu)體定義并形成定時器內(nèi)核對象,再鏈接到內(nèi)核對象容器中進行管理,list 成員則用于把一個激活的(已經(jīng)啟動的)定時器鏈接到 rt_timer_list 鏈表中。
對于定時器的工作機制,在 RT-Thread 的介紹已經(jīng)很詳細了,我這里用官方一張圖表示一下:
對于定時器的工作機制,實際上對我們使用定時器并沒有直接的幫助(因為定時器的使用真的很簡單),但是他能夠讓我們更深的理解定時器。往往這些更深的理解在我們遇到問題的時候,對解決問題起著至關(guān)重要的作用。
2.4 us 延時函數(shù)
使用過STM32 HAL 庫的小伙伴都知道,HAL庫是沒有us延時的,在 FreeRTOS 中,也是沒有us延時函數(shù)的。但是我們在進行一些總線操作的時候,比如軟件 I2C 通訊,不得不用到 us 延時函數(shù)。
現(xiàn)在好了,在使用 RT-Thread 的時候,系統(tǒng)直接給了我們一個 us延時函數(shù),如下:
/**
* This function will delay for some us.
*
* @param us the delay time of us
*/
void rt_hw_us_delay(rt_uint32_t us)
{
rt_uint32_t start, now, delta, reload, us_tick;
start = SysTick->VAL;
reload = SysTick->LOAD;
/* 獲得延時經(jīng)過的 tick 數(shù) */
us_tick = SystemCoreClock / 1000000UL;
do {
now = SysTick->VAL; // 獲得當(dāng)前時間
delta = start > now ? start - now : reload + start - now;
} while(delta < us_tick * us);
}
注意,這個函數(shù)只能支持低于 1 OS Tick 的延時。比如我們默認的設(shè)置,1OS Tick 為 1ms,那么這個函數(shù)的參數(shù) us 必須小于 1000!
2.5 軟件定時器 or 硬件定時器?
那么在實際應(yīng)用中,是使用軟件定時器 還是硬件定時器?我在另外一篇博文有過說明,如下圖:
用我們常用的 STM32系列芯片打個比方,也要看什么系列的芯片,首先STM32的定時器其實已經(jīng)足夠多,不管是M0全系列,還是M3M4全系列,至少也有4個(有錯誤請指出),他的硬件定時器足夠多的情況下,你可以不用軟件定時器,對于一些系列信號,RAM比較小的情況,有的小于10KB,那么你在跑 RTOS 的時候都得特別注意內(nèi)存大小,這個時候如果對內(nèi)存管理,代碼優(yōu)化不是很了解的情況下,我是不建議用的。
但是一些特殊的應(yīng)用場合,軟件定時器還是比硬件定時器優(yōu)勢明顯,因為可以隨意更改延時時間。同時使用軟件定時器的代碼移植起來更快。
三、 RT-Thread 軟件定時器操作函數(shù)
上文的基礎(chǔ)知識,說來說去也有一大堆了,到了說說怎么使用的時候了。
(快點!快點!理論都快睡著了! 用起來!展示起來!)
3.1 動態(tài)創(chuàng)建和刪除定時器
動態(tài)創(chuàng)建定時器(函數(shù)介紹看注釋,以下函數(shù)介紹類似):
/*
參數(shù)的含義:
1、name 定時器的名稱
2、void (timeout) (void parameter) 定時器超時函數(shù)指針(當(dāng)定時器超時時,系統(tǒng)會調(diào)用這個函數(shù))
3、parameter 定時器超時函數(shù)的入口參數(shù)(當(dāng)定時器超時時,調(diào)用超時回調(diào)函數(shù)會把這個參數(shù)做為入口參數(shù)傳遞給超時函數(shù))
4、time 定時器的超時時間,單位是時鐘節(jié)拍
5、flag 定時器創(chuàng)建時的參數(shù),支持的值包括單次定時、周期定時、硬件定時器、軟件定時器等(可以用 “或” 關(guān)系取多個值)
返回值:
RT_NULL 創(chuàng)建失敗(通常會由于系統(tǒng)內(nèi)存不夠用而返回 RT_NULL)
定時器的句柄 定時器創(chuàng)建成功
*/
rt_timer_t rt_timer_create(const char *name,
void (*timeout)(void *parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
上面的函數(shù),其中第5個參數(shù) flag
,有2組值可以填寫(對于靜態(tài)創(chuàng)建定時器也是如此),如下圖:
上面的2組宏定義可以以 “或” 邏輯的方式賦給 flag
,比如:
動態(tài)刪除定時器:
/*
參數(shù):
timer 定時器句柄,指向要刪除的定時器控制塊
返回值:
RT_EOK 刪除成功
*/
rt_err_t rt_timer_delete(rt_timer_t timer);
3.2 靜態(tài)創(chuàng)建和刪除定時器
和線程一樣,官方的用語是 初始化 和 脫離 定時器(理由在我上一篇博文有分析)。
靜態(tài)創(chuàng)建定時器:
/*
參數(shù)的含義:
1、timer 定時器句柄,指向要初始化的定時器控制塊,用戶創(chuàng)建的定時器控制塊結(jié)構(gòu)體
2、name 定時器的名稱
3、void (timeout) (void parameter) 定時器超時函數(shù)指針(當(dāng)定時器超時時,系統(tǒng)會調(diào)用這個函數(shù))
4、parameter 定時器超時函數(shù)的入口參數(shù)(當(dāng)定時器超時時,調(diào)用超時回調(diào)函數(shù)會把這個參數(shù)做為入口參數(shù)傳遞給超時函數(shù))
5、time 定時器的超時時間,單位是時鐘節(jié)拍
6、flag 定時器創(chuàng)建時的參數(shù),支持的值包括單次定時、周期定時、硬件定時器、軟件定時器等(可以用 “或” 關(guān)系取多個值)
返回值:
無返回值
*/
void rt_timer_init(rt_timer_t timer,
const char *name,
void (*timeout)(void *parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
靜態(tài)刪除定時器:
/**
參數(shù):
timer 定時器句柄,指向要刪除的定時器控制塊
返回值:
RT_EOK 刪除成功
*/
rt_err_t rt_timer_detach(rt_timer_t timer)
3.3 啟動和停止定時器
啟動定時器:
當(dāng)定時器被創(chuàng)建或者初始化以后,并不會被立即啟動,必須在調(diào)用啟動定時器函數(shù)接口后,才開始工作,啟動定時器函數(shù)接口如下:
/**
參數(shù):
timer 定時器句柄,指向要啟動的定時器控制塊
返回值:
RT_EOK 啟動成功
*/
rt_err_t rt_timer_start(rt_timer_t timer)
停止定時器:
/**
參數(shù):
timer 定時器句柄,指向要啟動的定時器控制塊
返回值:
RT_EOK 成功停止定時器
- RT_ERROR timer 已經(jīng)處于停止?fàn)顟B(tài)
*/
rt_err_t rt_timer_stop(rt_timer_t timer)
調(diào)用定時器停止函數(shù)接口后,定時器狀態(tài)將更改為停止?fàn)顟B(tài),并從 rt_timer_list 鏈表中脫離出來不參與定時器超時檢查。
當(dāng)一個周期性定時器超時時,也可以調(diào)用這個函數(shù)接口停止這個定時器本身。
3.4 定時器控制函數(shù)
RT-Thread 提供了定時器控制函數(shù)接口,以獲取或設(shè)置更多定時器的信息。
/**
參數(shù)的含義:
1、timer 定時器句柄,指向要控制的定時器控制塊
2、cmd
用于控制定時器的命令,當(dāng)前支持5個命令,
分別是設(shè)置定時時間,查看定時時間,設(shè)置單次觸發(fā),設(shè)置周期觸發(fā),查看狀態(tài)
3、arg
與 cmd 相對應(yīng)的控制命令參數(shù)
比如,cmd 為設(shè)定超時時間時,就可以將超時時間參數(shù)通過 arg 進行設(shè)定
返回值:
RT_EOK 成功
*/
rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
在官網(wǎng)介紹,上面函數(shù)的第二個參數(shù)只有 4 個命令,但是實際上我通過自己的工程查看得知現(xiàn)在的版本已經(jīng)有5個命令了,如下:
四、定時器使用示例
定時器的使用還是比較簡單的,我們這里還是直接通過截圖說明的方式講解下示例。
4.1 動態(tài)創(chuàng)建定時器示例
4.2 靜態(tài)創(chuàng)建定時器示例
結(jié)語
本文的內(nèi)容其實還是比較簡單的,在使用示例,我們只展示了定時器的創(chuàng)建方法和使用效果,實際引用中已經(jīng)能夠滿足大部分場合的需求。小伙伴可以自己新建線程嘗試通過定時器的控制函數(shù)控制某個定時器,以便更加熟悉 RT-Thread 軟件定時器的使用。
還是希望懂的朋友看完能夠多多指教!不懂的朋友看完能懂!謝謝!
-
時鐘
+關(guān)注
關(guān)注
11文章
1736瀏覽量
131580 -
定時器
+關(guān)注
關(guān)注
23文章
3251瀏覽量
115007 -
RTOS
+關(guān)注
關(guān)注
22文章
817瀏覽量
119717 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1294瀏覽量
40230
發(fā)布評論請先 登錄
相關(guān)推薦
評論