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-systickDT_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_SECCONFIG_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_clocksys_clock_cycle_get_64sys_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-systickDT_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,不会发生混乱。