一、概述
在嵌入式系統開發中,定時器是實現任務調度、精確延時等功能的核心組件。Arch Timer 作為基于 Timer Driver 實現的間隔定時器,在系統調度中扮演著重要角色。本文將全面介紹 Arch Timer 驅動框架,從基本概念到實際應用,幫助開發者快速掌握其使用與開發技巧。
二、認識 Arch Timer
1. 什么是 Arch Timer
Arch Timer 是基于 Timer Driver 實現的間隔定時器,為操作系統的 sched 模塊提供了豐富的 timer 接口。它支持兩種工作模式,以滿足不同場景的需求:
Tickless 模式
:允許更靈活高效的系統調度,無周期性時鐘中斷,系統在無任務執行時進入空閑模式,有效降低功耗。Tick 模式
:提供固定時間間隔的調度機制,按照配置的固定周期運行,保證周期性任務的穩定執行。
Arch timer 在系統中的位置框架如下圖所示:
2. Arch Timer 的驅動框架
Arch Timer 的驅動框架分為 upper - half 和 lower - half 兩部分:
upper - half
部分:由 openvela 提供,其中up_timer_initialize
接口需由芯片廠商實現。lower - half
部分:需芯片廠商進行適配,實現硬件級別的控制。
應用程序可通過標準的 POSIX API 接口或 ioctl 接口,調用這兩部分的接口來完成相應功能。
三、Arch Timer 接口詳解
sched
模塊依賴于 arch
模塊提供的定時器接口,這些接口定義在 /include/nuttx/arch.h
頭文件中。在 Tickless 模式下,接口按時間單位分為基于微秒(us
)和基于計時單元(tick
)的兩組,開發者可通過配置選項 CONFIG_SCHED_TICKLESS_TICK_ARGUMENT
選擇啟用哪一組。默認支持 tick
接口以確保運行效率,部分主要接口如下:
Arch Timer 接口說明
接口名稱 | 功能描述 |
---|---|
up_timer_set_lowerhalf | 初始化 Arch Timer 定時器,設置一個 timer_lowerhalf_s 實例,并啟動定時器 |
up_timer_tick_start | 啟動定時器,僅在 Tickless 模式下使用,參數為超時時間(單位:tick) |
up_timer_tick_cancel | 停止 Arch Timer 定時器,僅在 Tickless 模式中使用,返回當前剩余 tick 數 |
up_timer_getmask | 獲取定時器支持的時間掩碼(mask)的值 |
up_timer_gettick | 獲取定時器當前已經經過的 tick 數值 |
up_udelay | 實現以微秒(us)為單位的延時操作 |
up_mdelay | 實現以毫秒(ms)為單位的延時操作 |
四、Timer Driver 深入了解
1. 配置說明
啟用和調整 Timer Driver 功能需配置三個關鍵選項:
CONFIG_TIMER
:啟用 Timer Driver 功能。
CONFIG_TIMER_ARCH
:啟用 arch timer 模塊。
CONFIG_SCHED_TICKLESS
:啟用 Tickless 模式。
相關配置文件路徑如下:
sched/Kconfig
:包含 SCHED_TICKLESS 等相關配置。
# sched/Kconfig
config SCHED_TICKLESSdepends on ARCH_HAVE_TICKLESSconfig SCHED_TICKLESS_TICK_ARGUMENT
config SCHED_TICKLESS_LIMIT_MAX_SLEEP
drivers/timers/Kconfig
:包含 TIMER 和 TIMER_ARCH 等配置。
# drivers/timers/Kconfig
config TIMER
......
if TIMER
config TIMER_ARCHselect ARCH_HAVE_TICKLESSselect ARCH_HAVE_TIMEKEEPINGselect SCHED_TICKLESS_LIMIT_MAX_SLEEP if SCHED_TICKLESSselect SCHED_TICKLESS_TICK_ARGUMENT if SCHED_TICKLESS
#endif
可通過以下命令檢查配置是否正確:
grep -rE "CONFIG_TIMER|CONFIG_TIMER_ARCH|CONFIG_ARCH_HAVE_TICKLESS|CONFIG_ARCH_HAVE_TIMEKEEPING|CONFIG_SCHED_TICKLESS_TICK_ARGUMENT|CONFIG_SCHED_TICKLESS_LIMIT_MAX_SLEEP" nuttx/.config
2. 初始化
在 board 初始化過程中,需調用具體廠商實現的 ***_timer_initialize
函數,完成分配并初始化 struct
timer_lowerhalf_s
結構實例、注冊 Timer 驅動等操作,注冊后會生成 /dev/timer
設備節點,并綁定相關結構實例。
/***************************************************************************** Name: timer_register** Description:* This function binds an instance of a "lower half" timer driver with the* "upper half" timer device and registers that device so that can be used* by application code.** When this function is called, the "lower half" driver should be in the* disabled state (as if the stop() method had already been called).** NOTE: Normally, this function would not be called by application code.* Rather it is called indirectly through the architecture-specific* initialization.** Input Parameters:* dev path - The full path to the driver to be registered in the NuttX* pseudo-filesystem. The recommended convention is to name all timer* drivers as "/dev/timer0", "/dev/timer1", etc. where the driver* path differs only in the "minor" number at the end of the device name.* lower - A pointer to an instance of lower half timer driver. This* instance is bound to the timer driver and must persists as long as* the driver persists.** Returned Value:* On success, a non-NULL handle is returned to the caller. In the event* of any failure, a NULL value is returned.*****************************************************************************/FAR void *timer_register(FAR const char *path,FAR struct timer_lowerhalf_s *lower);
3. 上下層接口
-
upper - half
接口:主要供 sched 調用,根據配置可選擇以struct timespec
或 tick 為單位的接口,減少時間轉換工作。 -
lower - half
接口:通過struct timer_ops_s
提供標準化接口,供 upper - half、ioctl 系統調用等使用,按時間單位分為兩組,開發者可根據需求選擇實現,未實現的接口有默認實現。
struct timer_ops_s
{/* Required methods *******************************************************/CODE int (*start)(FAR struct timer_lowerhalf_s *lower);CODE int (*stop)(FAR struct timer_lowerhalf_s *lower);CODE int (*getstatus)(FAR struct timer_lowerhalf_s *lower,FAR struct timer_status_s *status);CODE int (*settimeout)(FAR struct timer_lowerhalf_s *lower,uint32_t timeout);CODE void (*setcallback)(FAR struct timer_lowerhalf_s *lower,CODE tccb_t callback, FAR void *arg);CODE int (*maxtimeout)(FAR struct timer_lowerhalf_s *lower,FAR uint32_t *maxtimeout);CODE int (*ioctl)(FAR struct timer_lowerhalf_s *lower, int cmd,unsigned long arg);CODE int (*tick_getstatus)(FAR struct timer_lowerhalf_s *lower,FAR struct timer_status_s *status);CODE int (*tick_setttimeout)(FAR struct timer_lowerhalf_s *lower,uint32_t timeout);CODE int (*tick_maxtimeout)(FAR struct timer_lowerhalf_s *lower,FAR uint32_t *maxtimeout);
};
五、調用流程解析
- Tickless 模式
- 模式概述:sched 動態管理軟件定時器,選擇最短時長的定時器作為下一次超時時間,動態調用啟動或停止函數。
- 調用流程:初始化定時器后,sched 計算超時時間,配置并啟動定時器,超時后觸發回調函數通知 sched,隨后重新分配時長并啟動下一個定時器或任務。
以下為 Tickless 模式下的調用流程圖
:
- Tick 模式
- 模式概述:sched 啟用固定時間間隔的周期性定時器,間隔由 CONFIG_USEC_PER_TICK 配置,系統初始化時啟動,無需頻繁調用啟動和停止接口。
- 流程描述:初始化時啟動周期性定時器,按固定周期觸發事件,觸發調度器調度,保證周期性任務執行。
六、驅動適配實例
以 nrf52(基于 ARMv7 - M 架構)為例,驅動適配主要包括兩部分:
1. 實現 up_timer_initialize 接口
在平臺特定代碼中實現該接口,調用 timer_register
函數注冊定時器驅動,生成設備節點。初始化調用流程如下:
nx_start
-> clock_initialize-> up_timer_initialize #開發者實現-> systick_initialize #開發者實現-> timer_register-> up_timer_set_lowerhalf
以下為 systick_initialize
的實現參考:
struct timer_lowerhalf_s *systick_initialize(bool coreclk,unsigned int freq, int minor)
{struct systick_lowerhalf_s *lower =(struct systick_lowerhalf_s *)&g_systick_lower;.../* Register the timer driver if need */if (minor >= 0){char devname[32];sprintf(devname, "/dev/timer%d", minor);timer_register(devname, (struct timer_lowerhalf_s *)lower);}return (struct timer_lowerhalf_s *)lower;
}
2. 實現 lower - half 接口
通過定義 struct timer_ops_s
結構的實例,實現其中的方法,如 start
、stop
等,以控制硬件運行。
在 ARMv7-M 的 Arch Timer 適配中,lower-half
方法的出現形式如下:
文件路徑: arch/arm/src/armv7-m/arm_systick.c
/* "Lower half" driver methods */
static const struct timer_ops_s g_systick_ops =
{.start = systick_start,.stop = systick_stop,.getstatus = systick_getstatus,.settimeout = systick_settimeout,.setcallback = systick_setcallback,.maxtimeout = systick_maxtimeout,
};
七、POSIX API 與測試實例
1. POSIX API
包括 timer_create
、timer_delete
、timer_settime
等接口,用于創建、刪除、配置定時器等操作。
timer_create
/*
* 函數:timer_create
* 參數:clockid,定時類型;evp,sigevent結構體,用來指定定時器到期時如何相應
* timerid,返回一個timerid
* 返回:0 success | -1 error
* 說明:創建一個定時器
*/
int timer_create(clockid_t clockid, FAR struct sigevent *evp,FAR timer_t *timerid);
timer_delete
/*
* 函數:timer_delete
* 參數:timerid,執行timer_create返回的timerid
* 返回:0 success | -1 error
* 說明:刪除一個定時器
*/
int timer_delete(timer_t timerid);
timer_settime
/* 設置定時器
* 函數:timer_settime
* 參數:timerid:id
* flags:相對時間/絕對時間
* value:定時時間和間隔
* ovalue:若不為NULL,則返回上次定時的剩余到期時間
* 返回:0 success | -1 error
* 說明:設置定時
*/
int timer_settime(timer_t timerid, int flags,FAR const struct itimerspec *value,FAR struct itimerspec *ovalue);
2. IOCTL 接口
應用級別的程序可以通過 ioctl 函數直接操作定時器(前提是在 bringup 過程中已注冊 /dev/timer 設備節點)。
支持的 IOCTL 命令
IOCTL 命令 | 功能描述 | 參數類型 | 參數說明 |
---|---|---|---|
TCIOC_START | 啟動定時器 | 無 | 無 |
TCIOC_STOP | 停止定時器 | 無 | 無 |
TCIOC_GETSTATUS | 獲取當前定時器的狀態 | struct timer_status_s* | 用于存儲定時器狀態信息的結構體指針 |
TCIOC_SETTIMEOUT | 設置定時器間隔時間(單位:微秒) | 32位無符號整數 | 定時器的間隔時間值 |
TCIOC_NOTIFICATION | 設置定時器超時消息 | struct timer_notify_s* | 包含超時通知配置的結構體指針 |
TCIOC_MAXTIMEOUT | 獲取定時器支持的最大時延(單位:微秒) | uint32_t* | 用于存儲最大時延值的指針 |
3. 測試實例
- TIMER API 測試:通過
cmocka
測試框架驗證 POSIX API 的工作,包括創建、配置、以及刪除定時器的全過程。
此部分介紹如何測試定時器相關功能,通過 cmocka 測試框架(可參考文檔:OpenVela之開發自測試框架cmocka進行cmocka相關配置)驗證 POSIX API 的工作,包括創建、配置、以及刪除定時器的全過程。
- 代碼位置:
apps/testing/drivertest/drivertest_posix_timer.c
- 測試框架:
cmocka
- 依賴配置:
TESTING_CMOCKA
TESTING_DRIVER_TEST
CONFIG_SIG_EVTHREAD
- 運行步驟:
- 啟用上述配置,并構建固件。
- 在 NuttShell(NSH)中運行以下命令:
nsh> cmocka_posix_timer
- IOCTL 測試:通過 IOCTL 命令控制定時器設備,測試啟動、停止、設置間隔等功能。
- 代碼路徑:
apps/testing/drivertest/drivertest_timer.c
- 測試框架:
cmocka
- 依賴配置:
TESTING_CMOCKA
TESTING_DRIVER_TEST
- 測試步驟:
- 啟用依賴配置,并構建功能完整的固件。
- 在 NuttShell(NSH)中運行以下命令:
nsh> cmocka_driver_timer
總結
Arch Timer 驅動框架為嵌入式系統提供了靈活高效的定時器解決方案,支持兩種工作模式,滿足不同調度需求。通過本文的介紹,相信開發者對其原理、接口、配置和應用有了全面的了解,能夠根據實際需求進行開發和適配,充分發揮其在系統調度中的作用。