Zephyr 的内核时间是由 sys_clock_announce
驱动,无论哪种 SOC 的系统计时器都是通过对接该 API 对 Zephyr 内核进行驱动,详细可以参考 Zephyr Tick Clock 简介 .
Zephyr 所有的系统计时器驱动都放在 zephyr/drivers/timer
下。
Zephyr 如何知道使用哪种驱动 链接到标题
zephyr/drivers/timer
下放置了 Zephyr 支持所有的系统计时器驱动,Zephyr 构建的时候通过识别设备树中的计时器,将设备树转化成 Kconfig 配置项,zephyr/drivers/timer
下的 Kconfig 文件通过识别这些配置项选择将对应的驱动加入构建。
例如 Cortex-M 系列的内核可以选择使用 SOC 内核上的 systick 定时器作为系统计时器,在设备树中指定
&systick {
status = "okay";
};
对应的节点全部信息为
systick: timer@e000e010 {
compatible = "arm,armv7m-systick";
reg = <0xe000e010 0x10>;
};
构建时 Zephyr 的 devicetree/kconfig 构建系统将设备树转化为 build/Kconfig/Kconfig.dts
,其中包含了:
DT_COMPAT_ARM_ARMV7M_SYSTICK := arm,armv7m-systick
config DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
def_bool $( dt_compat_enabled,$ ( DT_COMPAT_ARM_ARMV7M_SYSTICK ))
由于有 arm,armv7m-systick
,DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
被设置为 true
在 zephyr/drivers/timer/Kconfig
中包含了 systick 的 kconfig
source "drivers/timer/Kconfig.cortex_m_systick"
在 zephyr/drivers/timer/Kconfig.cortex_m_systick
中由于有 DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
为 true,CORTEX_M_SYSTICK
被选中
config CORTEX_M_SYSTICK
bool "Cortex-M SYSTICK timer"
depends on CPU_CORTEX_M_HAS_SYSTICK
default y
depends on DT_HAS_ARM_ARMV6M_SYSTICK_ENABLED || \
DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED || \
DT_HAS_ARM_ARMV8M_SYSTICK_ENABLED || \
DT_HAS_ARM_ARMV8_1M_SYSTICK_ENABLED
在 zephyr/drivers/timer/CMakeLists.txt
中有如下内容
zephyr_library_sources_ifdef ( CONFIG_CORTEX_M_SYSTICK cortex_m_systick.c )
由于 CORTEX_M_SYSTICK
被选中,cortex_m_systick.c
将会加入构建,选择到了正确的系统时钟驱动。
系统时钟驱动的实现和配置 链接到标题
系统计时器的驱动依赖于硬件,但大体可以分为下面几部分
初始化 链接到标题
实现计时器初始化函数,根据芯片的特性初始化系统计时器,也就是写寄存器
static int sys_clock_driver_init ( void )
{
NVIC_SetPriority ( SysTick_IRQn, _IRQ_PRIO_OFFSET ) ;
last_load = CYC_PER_TICK - 1;
overflow_cyc = 0U;
SysTick->LOAD = last_load;
SysTick->VAL = 0; /* resets timer to last_load */
SysTick->CTRL |= ( SysTick_CTRL_ENABLE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_CLKSOURCE_Msk ) ;
return 0;
}
计时器用于产生 tick,计算出每个 tick 要多少个 cycle,
# define CYC_PER_TICK ( sys_clock_hw_cycles_per_sec ( ) \
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC )
sys_clock_hw_cycles_per_sec ( )
通常是返回 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
,由此可以见系统计时器的配置取决于 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
和 CONFIG_SYS_CLOCK_TICKS_PER_SEC
两个配置项。
Zephyr 的 soc 下 kconfig 会读取设备树中 system-clock
节点 clock-freqyency
作为 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
的默认值。例如 rt 系列的 soc 下 zephyr/soc/arm/nxp_imx/rt/Kconfig.defconfig.series
config SYS_CLOCK_HW_CYCLES_PER_SEC
default $( dt_node_int_prop_int,$ ( DT_SYSCLK_PATH ) ,clock-frequency ) if SOC_SERIES_IMX_RT10XX && CORTEX_M_SYSTICK
default 32768 if MCUX_GPT_TIMER
从设备树中读取默认的 system-clock
sysclk: system-clock {
compatible = "fixed-clock";
clock-frequency = <600000000>;
#clock-cells = <0>;
};
当然不同的板子如果对系统计时器的频率有调整,也可以通过在 board 下的配置文件修改 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
,同样的应用配置文件也运行修改 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC
.
每秒的 tick 数 CONFIG_SYS_CLOCK_TICKS_PER_SEC
属于操作系统层面的配置和硬件无直接关系,Zephyr 在 zephyr/kernel/Kconfig
会直接给一个默认值
config SYS_CLOCK_TICKS_PER_SEC
int "System tick frequency ( in ticks/second ) "
default 100 if QEMU_TARGET || SOC_POSIX
default 10000 if TICKLESS_KERNEL
default 100
配置了 tickless 的情况下使用 10K,一般情况下为了降低内核上下文切换的损耗都会启用 tickless,所以 Zephyr 每秒的 tick 数通常都是 10K。但不同的 soc 会根据自己的特性做调整,例如 nordic nrf 系列的 soc 就会在 zephyr/soc/arm/nordic_nrf/Kconfig.defconfig
中指定为其它值
config SYS_CLOCK_TICKS_PER_SEC
default 128 if !TICKLESS_KERNEL
default 32768
中断处理 链接到标题
中断中就是计算 tick,并使用 sys_clock_announce
进行更新,详细可以查看 Zephyr Tick Clock 简介 一文。
API 链接到标题
系统定时器的 API 定义在 zephyr/include/zephyr/drivers/timer/system_timer.h
中,需要根据软硬件特性进行实现。
extern void sys_clock_set_timeout ( int32_t ticks, bool idle ) ;
extern void sys_clock_idle_exit ( void ) ;
extern void sys_clock_announce ( int32_t ticks ) ;
extern uint32_t sys_clock_elapsed ( void ) ;
extern void sys_clock_disable ( void ) ;
uint32_t sys_clock_cycle_get_32 ( void ) ;
uint64_t sys_clock_cycle_get_64 ( void ) ;
除了 sys_clock_cycle_get_64
外其它 API 也在 Zephyr Tick Clock 简介 中说明过,只是该篇文章比较老,大家阅读的时候自行将 z_clock
换为 sys_clock
。sys_clock_cycle_get_64
和 sys_clock_cycle_get_32
功能相似,只是 64 的计数精度更高,有效使用时间内不会溢出。
设备树有两个系统时钟时 Zephyr 如何选择 链接到标题
对于 NXP 的 rt 系列来说,除了 systick 外,soc 上还有一个 gpt timer 在 Zephyr 中也可以作为系统计时器,只用在设备树中将其开启
&gpt_hw_timer{
status = "okay";
}
gtp 对应的节点为为
gpt_hw_timer: gpt@401ec000 {
compatible = "nxp,gpt-hw-timer";
reg = <0x401ec000 0x4000>;
interrupts = <100 0>;
status = "disabled";
};
构建时 Zephyr 的 devicetree/kconfig 构建系统将设备树转化为 build/Kconfig/Kconfig.dts
,其中包含了:
DT_COMPAT_ARM_ARMV7M_SYSTICK := arm,armv7m-systick
config DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
def_bool $( dt_compat_enabled,$ ( DT_COMPAT_ARM_ARMV7M_SYSTICK ))
由于有 arm,armv7m-systick
,DT_HAS_ARM_ARMV7M_SYSTICK_ENABLED
被设置为 true
在 zephyr/drivers/timer/Kconfig
中包含了 gpt 的 kconfig, 根据 gpt 设备树的状态将 DT_HAS_NXP_GPT_HW_TIMER_ENABLED
设置为 true
DT_COMPAT_NXP_GPT_HW_TIMER := nxp,gpt-hw-timer
config DT_HAS_NXP_GPT_HW_TIMER_ENABLED
def_bool $( dt_compat_enabled,$ ( DT_COMPAT_NXP_GPT_HW_TIMER ))
在 zephyr/drivers/timer/Kconfig.mcux_gpt
中对 DT_HAS_NXP_GPT_HW_TIMER_ENABLED
进行判断将 MCUX_GPT_TIMER
打开
config MCUX_GPT_TIMER
bool "MCUX GPT Event timer"
default y
depends on PM
depends on DT_HAS_NXP_GPT_HW_TIMER_ENABLED
在 zephyr/drivers/timer/CMakeLists.txt
中就将 mcux_gpt_timer.c 加入构建
zephyr_library_sources_ifdef ( CONFIG_MCUX_GPT_TIMER mcux_gpt_timer.c )
现在你也许有疑问如果 gpt_hw_timer 和 systick 都同时在设备树中开启,sys_clock_announce 被两个 timer 同时驱动,是否会发生混乱?, 这一点已经在 zephyr/soc/arm/nxp_imx/rt/Kconfig.defconfig.series
帮我们处理了:
config CORTEX_M_SYSTICK
default n if MCUX_GPT_TIMER
当 MCUX_GPT_TIMER
被选择开启的时候 CORTEX_M_SYSTICK
会被关闭,也就是说 cortex_m_systick.c
不会加入构建,也就只有 gpt 去驱动 sys_clock_announce,不会发生混乱。