當前位置:網站首頁>[RT-Thread學習筆記] 定時器源碼淺析及其應用

[RT-Thread學習筆記] 定時器源碼淺析及其應用

2022-01-26 22:47:25 羅輯的孤獨

RT-Thread版本:4.0.5 MCU型號:STM32F103RCT6(ARM Cortex-M3 內核)

1 RT-Thread定時器基礎知識

RT-Thread 的定時器提供兩類定時器機制:

  • 單次觸發定時器:在啟動後只會觸發一次定時器事件,然後定時器自動停止
  • 周期觸發定時器:周期性的觸發定時器事件,直到用戶手動的停止,否則將永遠周期性執行下去

當系統時鐘節拍到達定時器設定的超時時間時會回調一個函數,此函數即為超時函數,根據超時函數執行的上下文環境不同,RT-Thread定時器分為硬件定時器與軟件定時器,如下圖所示: 在這裏插入圖片描述 可以通過下列宏配置定時器模式及其狀態:

#define RT_TIMER_FLAG_DEACTIVATED       0x0             /* 非活動狀態 */
#define RT_TIMER_FLAG_ACTIVATED         0x1             /* 活動狀態 */

#define RT_TIMER_FLAG_ONE_SHOT          0x0             /* 單次觸發定時器 */
#define RT_TIMER_FLAG_PERIODIC          0x2             /* 周期觸發定時器 */

#define RT_TIMER_FLAG_HARD_TIMER        0x0             /* 硬件定時器,超時函數在tick中斷服務例程中回調  */
#define RT_TIMER_FLAG_SOFT_TIMER        0x4             /* 軟件定時器,超時函數在timer線程中回調 */  

2 定時器控制塊與工作機制

2.1 定時器控制塊

struct rt_object
{

    char       name[RT_NAME_MAX];                       /* 內核對象名 */
    rt_uint8_t type;                                    /* 內核對象類型 */
    rt_uint8_t flag;                                    /* 內核對象標志比特 */

    rt_list_t  list;                                    /* 內核對象鏈錶結點 */
};
struct rt_timer
{

    struct rt_object parent;                      /* 繼承父類屬性與方法 */
    rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL];      /* 定時器鏈錶節點 */
    void (*timeout_func)(void *parameter);        /* 定時器超時調用的函數 */
    void      *parameter;                         /* 超時函數的參數 */
    rt_tick_t init_tick;                          /* 定時器初始超時節拍數 */
    rt_tick_t timeout_tick;                       /* 定時器實際超時時的節拍數 */
};
typedef struct rt_timer *rt_timer_t;

RT-Thread采用面向對象的思想設計的代碼,所有內核對象都繼承rt_object結構體,定時器模式及其狀態通過timer->parent.flag成員修改: 在這裏插入圖片描述

2.2 定時器工作機制

創建的定時器會以超時時間排序(昇序)的方式插入到定時器鏈錶中(軟件與硬件定時器采用不同的鏈錶),因此該鏈錶是一個有序鏈錶,因此RT-Thread采用跳變算法來加快搜索鏈錶元素的速度,使得查找元素時鏈錶的時間複雜度由O(n) -> O(logn)。 【官方舉例】 一個有序的鏈錶,如下圖所示,從該有序鏈錶中搜索元素 {13, 39},需要比較的次數分別為 {3, 5},總共比較的次數為 3 + 5 = 8 次。 在這裏插入圖片描述 使用跳錶算法後可以采用類似二叉搜索樹的方法,把一些節點提取出來作為索引,得到如下圖所示的結構: 在這裏插入圖片描述 在這個結構裏把 {3, 18,77} 提取出來作為一級索引,這樣搜索的時候就可以减少比較次數了。還可以再從一級索引提取一些元素出來,作為二級索引,這樣更能加快元素搜索: 在這裏插入圖片描述 可以看出,跳錶算法是一種“空間換時間”的算法,跳錶的最大層數為log2n - 1層(n為鏈錶長度),在 RT-Thread 中通過宏定義RT_TIMER_SKIP_LIST_LEVEL來配置跳錶的層數,默認為 1,錶示采用一級有序鏈錶圖的有序鏈錶算法,每增加一,錶示在原鏈錶基礎上增加一級索引。

3 硬件定時器實現

RT-Thread 定時器默認的方式就是HARD_TIMER模式,線程對象都有一個內置定時器,其默認就是此模式。所謂的默認就是timer->parent.flag成員為0時的標志狀態。

3.1 超時函數執行環境

硬件定時器超時函數工作在中斷上下文環境,因此對超時函數的要求與中斷服務例程的要求相同:

  • 快進快出,執行時間盡量短
  • 執行時不應導致當前上下文掛起、等待
  • ==絕對不允許==試圖去申請動態內存、釋放動態內存

3.2 私有對象

3.2.1 硬件定時器管理列錶

struct rt_list_node
{

    struct rt_list_node *next;                          /**< point to next node. */
    struct rt_list_node *prev;                          /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t;                  /**< Type for lists. */

static rt_list_t  _timer_list[RT_TIMER_SKIP_LIST_LEVEL];

後期添加定時器,按定時器超時時間順序進行排序插入。

3.2.2 系統硬件定時器初始化

void rt_system_timer_init(void)
{
    int i;

    for (i = 0; i < sizeof(_timer_list) / sizeof(_timer_list[0]); i++)
    {
        rt_list_init(_timer_list + i);
    }
}

【功能】 初始化硬件定時器鏈錶結點,將其前驅指針和後繼指針均指向錶頭結點。

3.2.3 硬件定時器掃描函數

/**
 * @brief 該函數用於掃描系統定時器列錶,當有超時事件發生時
 *        就調用對應的超時函數
 *
 * @note 該函數在操作系統定時器中斷中被調用
 */

void rt_timer_check(void)
{
    struct rt_timer *t;
    rt_tick_t current_tick;
    register rt_base_t level;
    rt_list_t list;

    rt_list_init(&list);

    /* 獲取系統時基計數器 rt_tick 的值 */
    current_tick = rt_tick_get();

    /* 關中斷 */
    level = rt_hw_interrupt_disable();

    /* 系統定時器列錶不為空,則掃描定時器列錶 */
    while (!rt_list_isempty(&_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
    {
        /* 獲取第一個節點定時器的地址 */
        t = rt_list_entry(_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,   // 節點地址
                          struct rt_timer,                                  // 結點所在的父結構的數據類型
                          row[RT_TIMER_SKIP_LIST_LEVEL - 1]);               // 節點在父結構的成員名

        /* 獲取第一個節點定時器的地址 */
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        {
            RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));  // 回調進入定時器鉤子函數

            /* 將定時器從定時器列錶移除 */
            _timer_remove(t);
            if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
            {
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }
            /* 將定時器添加到臨時列錶  */
            rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            /* 調用超時函數 */
            t->timeout_func(t->parameter);

            /* 重新獲取 tick */
            current_tick = rt_tick_get();

            RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));   // 回調退出定時器鉤子函數

            /* 檢測timer對象處於分離還是啟動狀態 */
            if (rt_list_isempty(&list))
            {
                continue;
            }
            rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {
                /* 啟動定時器 */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
        }
        else break;
    }

    /* 開中斷 */
    rt_hw_interrupt_enable(level);
}

rt_timer_check()函數用於檢查系統硬件定時器鏈錶,如果有定時器超時,將調用相應的超時函數,且所有定時器在定時超時後都會從定時器鏈錶中被移除,若是周期性定時器則會在它再次啟動時重新加入到定時器鏈錶。該函數在rt_tick_increase()中被調用,具體查看[時鐘節拍的獲取與線程時間片輪轉調度的實現]一文。

4 軟件定時器實現

4.1 超時函數執行環境

軟件定時器模式可通過配置宏定義RT_USING_TIMER_SOFT來决定是否啟用該模式,該模式被啟用後,系統會在初始化時創建一個 timer 線程,SOFT_TIMER模式的定時器超時函數在都會在 timer 線程的上下文環境中執行,因此與線程一下需要注意以下幾點:

  • 不允許使用任何可能引軟件定時器起線程掛起或者阻塞的 API 接口
  • 不允許在超時函數中有阻塞的情况出現,更不允許有死循環
  • 不應該影響到其他定時器執行超時函數或本定時器的下一次超時回調
  • 不應該申請動態內存、釋放動態內存,因為malloc、free 等內存相關的函數內部使用了信號量作為臨界區保護(如果沒有獲取到信號量會掛起當前線程)

4.2 私有對象

4.2.1 軟件定時器管理列錶

static rt_list_t  _soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL];

4.2.2 系統軟件定時器線程初始化

  • 定時器列錶與timer線程初始化
static rt_list_t _soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL]; // 軟件定時器
static struct rt_thread _timer_thread;  // 定義timer線程
ALIGN(RT_ALIGN_SIZE)    // 四字節排列
static rt_uint8_t _timer_thread_stack[RT_TIMER_THREAD_STACK_SIZE];  // 定義timer線程棧大小

/**
 * @brief 這個函數將初始化系統定時器線程
 */

void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT // 是否使用軟件定時器的宏開關
    int i;
    /* 初始化軟件定時器鏈錶 */ 
    for (i = 0;
         i < sizeof(_soft_timer_list) / sizeof(_soft_timer_list[0]);
         i++)
    {
        rt_list_init(_soft_timer_list + i);
    }

    /* 創建timer線程 */
    rt_thread_init(&_timer_thread,
                   "timer",
                   _timer_thread_entry,
                   RT_NULL,
                   &_timer_thread_stack[0],
                   sizeof(_timer_thread_stack),
                   RT_TIMER_THREAD_PRIO,
                   10);

    /* 啟動timer線程 */
    rt_thread_startup(&_timer_thread);
#endif /* RT_USING_TIMER_SOFT */
}

軟件定時器使用了系統的一個鏈錶和一個線程資源,軟件定時器線程的優先級通過宏RT_TIMER_THREAD_PRIO設置,默認為4,定時器線程的堆棧大小 通過宏RT_TIMER_THREAD_STACK_SIZE設置,默認為 512 個字節。

  • timer線程入口函數
static void _timer_thread_entry(void *parameter)
{
    rt_tick_t next_timeout;

    while (1)
    {
        /* 獲取軟件定時器列錶中下一個定時器的到達時間 */
        next_timeout = _timer_list_next_timeout(_soft_timer_list);
        if (next_timeout == RT_TICK_MAX)
        {
            /* 如果沒有軟件定時器,則掛起線程自身 */
            rt_thread_suspend(rt_thread_self());
            rt_schedule();
        }
        else
        {
            rt_tick_t current_tick;

            /* 獲取當前系統時間 */
            current_tick = rt_tick_get();

            if ((next_timeout - current_tick) < RT_TICK_MAX / 2)
            {
                /* 計算下一個定時器溢出時間與當前時間的間隔 */
                next_timeout = next_timeout - current_tick;
                rt_thread_delay(next_timeout);
            }
        }

        /* 檢查軟件定時器列錶 */
        rt_soft_timer_check();
    }
}

4.2.3 軟件定時器掃描函數

#define RT_SOFT_TIMER_IDLE              1
#define RT_SOFT_TIMER_BUSY              0
/* 軟件定時器狀態 */
static rt_uint8_t _soft_timer_status = RT_SOFT_TIMER_IDLE;

/**
 * @brief 函數會檢查軟件定時器列錶,如果出現超時事件,則會調用相應的超時函數
 */

void rt_soft_timer_check(void)
{
    rt_tick_t current_tick;
    struct rt_timer *t;
    register rt_base_t level;
    rt_list_t list;

    rt_list_init(&list);

    /* 關中斷 */
    level = rt_hw_interrupt_disable();

    while (!rt_list_isempty(&_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
    {
         /* 獲取第一個節點定時器的地址 */
        t = rt_list_entry(_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                            struct rt_timer, 
                            row[RT_TIMER_SKIP_LIST_LEVEL - 1]);

        current_tick = rt_tick_get();

    
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        {
            RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));

            /* 獲取第一個節點定時器的地址 */
            _timer_remove(t);
            if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
            {
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }
            /* 將定時器添加到臨時列錶  */
            rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));

            _soft_timer_status = RT_SOFT_TIMER_BUSY;    // 將軟件定時器標記為忙碌狀態
            /* 開中斷 */
            rt_hw_interrupt_enable(level);

            /* 調用超時函數 */
            t->timeout_func(t->parameter);

            RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
            RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));

            /* 關中斷 */
            level = rt_hw_interrupt_disable();

            _soft_timer_status = RT_SOFT_TIMER_IDLE;    // 取消標記軟件定時器為忙碌狀態
            /* 檢測timer對象處於分離還是啟動狀態 */
            if (rt_list_isempty(&list))
            {
                continue;
            }
            rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {
                /* start it */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
        }
        else break
    }
    /* 開中斷 */
    rt_hw_interrupt_enable(level);
}

軟件定時器與硬件定時器掃描函數內部實現基本一致,注意:無論是軟件還是硬件定時器,其超時時間必須小於RT_TICK_MAX / 2,其中RT_TICK_MAX = 0xffff ffff

5 定時器通用函數接口

在這裏插入圖片描述
在這裏插入圖片描述

5.1 創建與删除函數

打開RT_USING_HEAP宏才能創建與删除動態對象。

  • 創建定時器
/**
 * @brief 該函數用於初始化一個定時器,通常該函數用於初始化一個動態的定時器
 *
 * @param name 定時器的名字
 * @param timeout 超時函數
 * @param parameter 超時函數形參
 * @param time 定時器的超時時間
 * @param flag 定時器的標志
 *
 * @return 成功創建返回定時器結構體指針
 */

rt_timer_t rt_timer_create(const char *name,
                           void (*timeout)(void *parameter),
                           void       *parameter,
                           rt_tick_t   time,
                           rt_uint8_t  flag)

{
    struct rt_timer *timer;

    /* 動態申請內存*/
    timer = (struct rt_timer *)rt_object_allocate(RT_Object_Class_Timer, name);
    if (timer == RT_NULL)
    {
        return RT_NULL;
    }

    /* 定時器初始化 */
    _timer_init(timer, timeout, parameter, time, flag);

    return timer;
}

rt_timer_init函數將定時器具體的初始化封裝在了一個內部函數_timer_init中:

static void _timer_init(rt_timer_t timer,
                           void (*timeout)(void *parameter),
                           void      *parameter,
                           rt_tick_t  time,
                           rt_uint8_t flag)
{
    int i;

    /* 設置標志 */
    timer->parent.flag  = flag;

    /* 先設置為非激活態 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    timer->timeout_func = timeout;
    timer->parameter    = parameter;

    /* 初始化 定時器實際超時時的系統節拍數 */
    timer->timeout_tick = 0;
    /* 初始化 定時器需要超時的節拍數 */
    timer->init_tick    = time;

    /* 初始化定時器鏈錶的內置節點 */
    for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
    {
        rt_list_init(&(timer->row[i]));
    }
}
  • 删除定時器
rt_err_t rt_timer_delete(rt_timer_t timer)
{
    register rt_base_t level;

    /* 檢查定時器 */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
    RT_ASSERT(rt_object_is_systemobject(&timer->parent) == RT_FALSE);

    level = rt_hw_interrupt_disable();

    /* 將定時器從鏈錶中移除 */
    _timer_remove(timer);
    /* 將狀態設為非激活態 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    rt_hw_interrupt_enable(level);
 /* 從內核對象鏈錶中移除並釋放內存 */
    rt_object_delete(&(timer->parent));

    return RT_EOK;
}

_timer_remove是內聯函數:

rt_inline void _timer_remove(rt_timer_t timer)
{
    int i;
 /* 將定時器自身的節點從系統定時器列錶 rt_timer_list 脫離 */    
 for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
    {
        rt_list_remove(&timer->row[i]);
    }
}

調用rt_timer_delete函數後,系統會把傳入的定時器對象指針從 rt_timer_list 鏈錶中删除,然後釋放相應的定時器控制塊占有的內存

5.2 初始化與脫離函數

  • 初始化定時器
/**
 * @brief 該函數用於初始化一個定時器,通常該函數用於初始化一個靜態的定時器
 *
 * @param timer 靜態定時器對象
 * @param name 定時器的名字
 * @param timeout 超時函數
 * @param parameter 超時函數形參
 * @param time 定時器的超時時間
 * @param flag 定時器的標志
 */

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)

{
    RT_ASSERT(timer != RT_NULL);

    /* 定時器對象初始化,  將定時器插入到系統對象容器列錶 */
    rt_object_init(&(timer->parent), RT_Object_Class_Timer, name);

    /* 定時器初始化 */
    _timer_init(timer, timeout, parameter, time, flag);
}
  • 脫離定時器
rt_err_t rt_timer_detach(rt_timer_t timer)
{
    register rt_base_t level;

    /* 檢查定時器 */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
    RT_ASSERT(rt_object_is_systemobject(&timer->parent));

    level = rt_hw_interrupt_disable();

    _timer_remove(timer);
    /* 將狀態設為非激活態 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    rt_hw_interrupt_enable(level);
    /* 從內核對象鏈錶中移除,但不會釋放內存 */
    rt_object_detach(&(timer->parent));

    return RT_EOK;
}

調用rt_timer_detach函數會把定時器對象從內核對象容器中脫離,但是定時器對象所占有的內存不會被釋放(靜態對象也無法釋放)

5.3 啟動與停止函數

  • 啟動定時器
rt_err_t rt_timer_start(rt_timer_t timer)
{
    unsigned int row_lvl;
    rt_list_t *timer_list;
    register rt_base_t level;
    register rt_bool_t need_schedule;
    rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
    unsigned int tst_nr;
    static unsigned int random_nr;

    /* 檢查定時器 */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

    need_schedule = RT_FALSE;

    /* 關中斷 */
    level = rt_hw_interrupt_disable();
    /* 將定時器從系統定時器列錶移除 */
    _timer_remove(timer);
    /* 改變定時器的狀態為非激活態 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));

    /*
     * 獲取 timeout tick,
     * 最大的 timeout tick 不能大於 RT_TICK_MAX/2
     */

    RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
    timer->timeout_tick = rt_tick_get() + timer->init_tick;

#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* 將定時器插入到軟件定時器列錶 */
        timer_list = _soft_timer_list;
    }
    else    
#endif /* RT_USING_TIMER_SOFT */
    {
        /* 將定時器插入到系統(硬件)定時器列錶 */
        timer_list = _timer_list;
    }
    /* 獲取定時器列錶第一條鏈錶根節點地址 */
    row_head[0]  = &timer_list[0];
    /* T_TIMER_SKIP_LIST_LEVEL 等於 1,這個循環只會執行一次 */
    for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
    {
        for (; row_head[row_lvl] != timer_list[row_lvl].prev;   // 遍曆定時器鏈錶(雙向循環鏈錶)
             row_head[row_lvl]  = row_head[row_lvl]->next)
        {
            struct rt_timer *t;
            /* 獲取定時器列錶節點地址 */
            rt_list_t *p = row_head[row_lvl]->next;

            /* 根據節點地址獲取父結構的指針 */
            t = rt_list_entry(p, struct rt_timer, row[row_lvl]);

            /* 如果兩個定時器超時時間相同, 最好是最早插入的最先被調用
             * 因此將新定時器插入到該定時器列錶的尾部
             */

            if ((t->timeout_tick - timer->timeout_tick) == 0)
            {
                continue;
            }
            else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
            {
                break;
            }
        }
        /* T_TIMER_SKIP_LIST_LEVEL 等於 1, 判斷條件永不為真, 即不使用跳錶算法 */
        if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
            row_head[row_lvl + 1] = row_head[row_lvl] + 1;
    }

    /* random_nr 是一個靜態變量,用於記錄啟動了多少個定時器 */
    random_nr++;
    tst_nr = random_nr;
    /* 將定時器插入到系統定時器列錶 */
    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],        // 列錶根節點地址
                         &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));  // 要被插入的節點的地址
    /* RT_TIMER_SKIP_LIST_LEVEL 等於 1,該 for 循環永遠不會執行 */
    for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
    {
        if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
            rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],       
                                 &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl])); 
        else
            break;
        /* Shift over the bits we have tested. Works well with 1 bit and 2
         * bits. */

        tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
    }
    /* 設置定時器標志比特為激活態 */
    timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;

#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* 檢查timer線程是否准備好 */
        if ((_soft_timer_status == RT_SOFT_TIMER_IDLE) &&
           ((_timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND))   // 軟件定時器鏈錶為空時timer線程會掛起自身,讓出CPU資源
        {
            /* 喚醒timer線程去調用軟件定時器掃描函數 */
            rt_thread_resume(&_timer_thread);
            need_schedule = RT_TRUE;
        }
    }
#endif /* RT_USING_TIMER_SOFT */

    /* 開中斷 */
    rt_hw_interrupt_enable(level);

    if (need_schedule)
    {
        rt_schedule();
    }

    return RT_EOK;
}

調用rt_timer_start函數,將定時器的狀態將更改為激活狀態RT_TIMER_FLAG_ACTIVATED,並按超時昇序插入到定時器鏈錶中。

  • 停止定時器
rt_err_t rt_timer_stop(rt_timer_t timer)
{
    register rt_base_t level;

    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

    /* 只有 active 的定時器才能被停止,否則退出返回錯誤碼 */
    if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
        return -RT_ERROR;

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));

    /* 關中斷  */
    level = rt_hw_interrupt_disable();
    /* 將定時器從定時器列錶删除 */
    _timer_remove(timer);
    /* 改變定時器的狀態為非 active  */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    /* 開中斷  */
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

調用rt_timer_stop函數,將定時器狀態改為非激活態RT_TIMER_FLAG_DEACTIVATED,並將其從定時器鏈錶移除,不參與定時器超時檢查。

5.4 控制函數

/**
 * @brief 該函數將獲取或者設置定時器的一些選項
 *
 * @param timer 將要被設置或者獲取的定時器
 * @param cmd 控制命令
 * @param arg 形參
 *
 * @return 控制狀態
 */

rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
{
    register rt_base_t level;

    /* timer check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

    level = rt_hw_interrupt_disable();
    switch (cmd)
    {
    case RT_TIMER_CTRL_GET_TIME:
        *(rt_tick_t *)arg = timer->init_tick;
        break;

    case RT_TIMER_CTRL_SET_TIME:
        timer->init_tick = *(rt_tick_t *)arg;
        break;

    case RT_TIMER_CTRL_SET_ONESHOT:
        timer->parent.flag &= ~RT_TIMER_FLAG_PERIODIC;
        break;

    case RT_TIMER_CTRL_SET_PERIODIC:
        timer->parent.flag |= RT_TIMER_FLAG_PERIODIC;
        break;

    case RT_TIMER_CTRL_GET_STATE:
        if(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED)
        {
            /* 定時器處於運行或啟動狀態 */
            *(rt_tick_t *)arg = RT_TIMER_FLAG_ACTIVATED;
        }
        else
        {
            /* 定時器處於停止狀態 */
            *(rt_tick_t *)arg = RT_TIMER_FLAG_DEACTIVATED;
        }
        break;

    default:
        break;
    }
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

其中cmd有以下幾種選擇:

#define RT_TIMER_CTRL_SET_TIME      0x0     /* 設置定時器超時時間       */
#define RT_TIMER_CTRL_GET_TIME      0x1     /* 獲得定時器超時時間       */
#define RT_TIMER_CTRL_SET_ONESHOT   0x2     /* 設置定時器為單次定時器   */
#define RT_TIMER_CTRL_SET_PERIODIC  0x3     /* 設置定時器為周期型定時器 */
#define RT_TIMER_CTRL_GET_STATE     0x4     /* 獲取定時器狀態:active or deactive */ 

注意:使用控制函數並且需要傳入arg時,定義的參數變量大小必須是4字節,否則可能會直接卡死或者得到錯誤的數據。(個人猜測與字節對齊有關)

5.5 定時器鉤子函數

定時器鉤子函數在掃描函數中調用。

  • 設置定時器入口鉤子
void rt_timer_enter_sethook(void (*hook)(struct rt_timer *timer))
{
    rt_timer_enter_hook = hook;
}
  • 設置定時器出口鉤子
void rt_timer_exit_sethook(void (*hook)(struct rt_timer *timer))
{
    rt_timer_exit_hook = hook;
}

6 應用示例

static rt_timer_t timer1;
static struct rt_timer timer2;

static void timer1_callback(void *parameter)
{
    rt_uint32_t active_status = 0// 必須是四字節, 否則內存錯誤

    rt_timer_control(timer1, RT_TIMER_CTRL_GET_STATE, &active_status);
    rt_kprintf("[%u]tm1_flag: %u\n", rt_tick_get(), timer1->parent.flag);
    rt_kprintf("[%u]tm1_active_status: %u\n", rt_tick_get(), active_status);
    rt_kprintf("[%u]timer1_callback running...\n", rt_tick_get());
//        rt_timer_delete(timer1); // 絕對不允許在ISR中申請與釋放內存,否則斷言報錯卡在死循環
}

static void timer2_callback(void *parameter)
{
    static rt_uint8_t count = 0;

    if(++count == 10)
    {
        rt_timer_detach(&timer2);     // 將狀態設為非active,同時從定時器鏈錶中脫離
//            rt_timer_stop(&timer2);     // 僅將狀態改為非active
    }
    rt_kprintf("[%u]timer2_callback running...\n", rt_tick_get());
    rt_thread_mdelay(10);        // 掛起timer線程, 同時會把timer線程的定時器加入到軟件定時器鏈錶(這裏僅做測試, 實際使用一般不允許調用延時函數)
}

static void tm_enter_hook(struct rt_timer *timer)
{
    rt_kprintf("[%u]%s enter hook\n", rt_tick_get(), timer->parent.name);
}

static void tm_exit_hook(struct rt_timer *timer)
{
    rt_kprintf("[%u]%s exit hook\n", rt_tick_get(), timer->parent.name);
}

static int tick_sample(void)
{
    rt_uint32_t tm2_timeout = 1000// 必須是四字節

    // 設置定時器鉤子
    rt_timer_enter_sethook(tm_enter_hook);
    rt_timer_exit_sethook(tm_exit_hook);

    // 動態創建定時器
    timer1 =  rt_timer_create("tm1",
                             timer1_callback,
                             RT_NULL,
                             3000,  // timeout 不能 >= RT_TICK_MAX / 2
                             RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_HARD_TIMER);
    if(timer1 == NULL)
    {
        LOG_E("rt_timer1_create failed...\n");
        return -RT_ENOMEM;
    }
    LOG_D("rt_timer1_create successed...\n");
    rt_timer_start(timer1);

    // 靜態創建定時器
    rt_timer_init(&timer2,
                  "tm2",
                  timer2_callback,
                  NULL,
                  5000,
                  RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
    rt_timer_start(&timer2);
    /* 重新設置tm2超時時間為1000 os_tick(第二次超時調用時生效, 因為只有當rt_soft_timer_check函數
     * 調用rt_timer_start重新獲取timer->init_tick時生效) */

    rt_timer_control(&timer2, RT_TIMER_CTRL_SET_TIME, (rt_tick_t*)&tm2_timeout);

    return RT_EOK;
}

INIT_APP_EXPORT(tick_sample);

部分打印結果: 在這裏插入圖片描述

END

版權聲明
本文為[羅輯的孤獨]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201262247246567.html

隨機推薦