ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Zephyr内核时钟详解:节拍、周期计数与Tickless机制

Zephyr内核时钟详解:节拍、周期计数与Tickless机制 Zephyr的时钟体系初看起来无非就是“一个内核节拍加几个API”但真正去读源码、写驱动、调调度延迟的时候才知道这一层东西有多绕。尤其是“Kernel Clocks”这个标题它指的并不是你在设备树里配的那个外设总线时钟而是系统级的时间底座——内核拿它来做什么时间维护、调度器切换、超时管理。我最初也以为这东西不过是定时器封装直到亲手在三四个不同硬件上碰过壁才摸清里面的门道。这篇就把内核时钟讲透适合已经开始用Zephyr、想知道节拍怎么触发、超时为什么有时候差几个毫秒、以及如何准确拿到运行周期的开发者。1 内核时钟的整体框架1.1 系统时钟与硬件时钟的区别Zephyr里“Clock”这个词其实有两个完全不同层次的含义。硬件时钟指单片机内部实实在在的定时器外设、计数器外设比如常见的32位定时器、系统节拍定时器甚至是低功耗定时器。它们由晶振或PLL驱动在真实世界里产生脉冲。内核时钟则是建立在硬件计数器之上的逻辑概念——内核并不直接关心硬件的每一次脉冲而是通过驱动层维护一个“当前运行到第几个节拍”的整数这个整数是调度器安排任务、处理超时、记录启动时长的根据。我当时理解不深就犯过大错直接在应用层调用驱动读硬件计数器然后换算成毫秒再拿来跟k_uptime_get()返回的值作比较。两者有时差几个毫秒我一开始以为是bug最后才发现硬件计数器是底层真实时间内核节拍是经过驱动处理和累积的“逻辑时间”这两者之间有一个同步过程和量化误差不是随随便便能画等号的。用一个生活类比硬件时钟是心跳每一次跳动都真实存在内核时钟是日历它基于心跳次数累加出“今天是几号”但日历不会因为心跳偶尔快慢而立刻体现它只在每个节拍对齐点更新。真正理解这一点后面很多看似奇怪的现象就容易解释了。1.2 时钟在调度和超时中的角色Zephyr调度器依赖内核时钟完成两件核心事情任务切换时机和超时判定。以抢占式调度为例当高优先级线程就绪且低优先级线程正在运行时调度器并不需要时钟介入直接触发切换。但如果是时间片轮转那么每个线程能跑多久由节拍计数决定——时间片到了调度器才强占。超时机制是另一个大块。所有等待操作比如信号量获取、队列接收、线程sleep都需要一个超时参数。内核把这些超时请求挂在一个“超时队列”里队列按到期节拍数排序。当内核时钟推进到某个节拍对应等待线程被唤醒并重新参与调度。这个过程看似简单但设计上有几个关键决策谁负责检查超时是每个节拍中断都扫描队列还是等队列头部到期再处理Zephyr用的是后者配合tickless机制平时中断极少超时精准触发。1.3 时钟约定对多核场景的影响如果你用的是多核系统比如某些双核芯片内核时钟的维护又不一样了。Zephyr在SMP场景下会有“锚定核”的概念——通常由主核负责维护全局节拍计数次核发生超时后被唤醒但节拍累加操作只在主核的中断上下文执行。这么做是为了免去锁竞争次核读取节拍计数时不需要禁中断但代价是次核上的超时判断可能晚一个节拍。多核调试时钟问题时最容易遇到的坑是你在次核上跑一个精度要求高的任务以为k_uptime_get()足够准结果发现存在持续的、稳定的偏差。这不是内核时钟跑慢了而是由于跨核同步开销次核看到的时间更新时机略滞后。类似的系统间差异需要结合具体的SoC架构来调整调度策略而不是简单改个频率值就能解决。2 核心概念与关键API2.1 节拍、周期、超时的换算内核时钟里有三个关键单位周期、节拍、时间毫秒。周期指的是硬件计数器的一次计数它的频率是CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC节拍是内核累加的基础单位每CONFIG_SYS_CLOCK_TICKS_PER_SEC个节拍对应一秒也就是说每个节拍间隔是1000/ticks_per_sec毫秒毫秒就是应用最常用的时间单位。换算关系看上去简单ms ticks * 1000 / ticks_per_sec。但实际实现不是用浮点除法而是做了很多整数优化和预计算。以常见的ticks_per_sec 100为例一个节拍是10毫秒。k_uptime_get()返回毫秒数内核不是每毫秒更新一次而是每10毫秒整段更新所以这个函数的精度天然受限于节拍大小。如果你调用k_uptime_get()两次间隔不到一个节拍结果可能完全一样。真正高精度的“当前时刻”由k_cycle_get_32()获得它直接读硬件周期计数器精度取决于计数器频率。假设计数器跑在32MHz那就比10毫秒的节拍精细得多。我在日常开发里凡是对时间精度有要求的测量一律用周期计数不用节拍毫秒。2.2 必用API和它们的用途Zephyr为内核时钟提供了几组API应用层开发者最常用的有这么几个k_uptime_get()返回内核启动以来的毫秒数32位约49天溢出k_uptime_get_32()只取32位性能和存储更优k_uptime_get_64()返回64位毫秒数适合长时间运行统计k_uptime_ticks()返回原始的节拍计数不换算成毫秒k_cycle_get_32()返回硬件周期计数用于精确时间戳k_cycle_get_64()底层支持64位计数器时可用能避免32位翻转问题k_timeout_t是各种等待接口的超时参数类型支持毫秒、节拍、周期三种换算在设置超时时通常用K_MSEC(100)表示100毫秒也支持K_TICKS(10)、K_CYCLE(320000)等构造。这些宏看着简单背后其实做了溢出检查和类型转换。习惯用法是能描述业务时间用K_MSEC需要精确对齐节拍边界用K_TICKS需要跟硬件事件精确同步则用K_CYCLE。2.3 24位/32位/64位计数器的差异不同SoC上的硬件计数器宽度差异很大这是很多人低估的坑。一种典型的低功耗计数器只有24位跑在32.768kHz外部晶振下每秒计数32768次那么24位计数器在约512秒后就会翻转归零。内核需要处理这种翻转驱动层要判断“计数变小是因为翻转还是真的回退”这涉及符号差值运算一旦驱动没处理好时间会时不时出现跳变。32位计数器是最常见的比如以1MHz运行的计数器约71分钟后翻转一次。k_cycle_get_32()直接用无符号减法计算时间差因此即使发生翻转差值依然正确。但如果你把两次k_cycle_get_32()的结果直接相减而不转成有符号差超过2的31次方就会出现符号错误所以业务上测量短间隔时问题不大测量长间隔就得小心。64位计数器是少数高端SoC或外扩定时器才有的。Zephyr提供k_cycle_get_64()同时配置项CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER会告知内核这个能力。64位计数器几乎不可能翻转用于长期性能统计非常友好唯一的缺点是部分场景下读取高32位和低32位需要额外处理可能引入短暂的读取不一致硬件级锁存可以解决但软件驱动得正确使用锁存寄存器。下面是三种情况的对比计数器宽度典型频率翻转周期常见问题推荐用法24位32.768kHz约512秒频繁翻转、驱动需补偿低功耗唤醒、粗略计数32位1MHz约71分钟差值计算需注意符号常规性能测量、超时换算64位数十MHz数百年读取一致性长期统计、精确UTC对表2.4 时间戳的实现细节实际拿到精确时间戳的方式并不是直接调用一个现成的“获取当前时间戳”函数而是利用周期计数器组合节拍。常见做法是先用k_uptime_ticks()拿到当前节拍再用k_cycle_get_32()拿到当前周期位置两者结合得到一个精度远高于节拍的近似时间。Zephyr内部某些驱动就是这么干的比如网络子系统打时间戳、协议栈统计往返延迟。但这里有个坑节拍和周期计数不是一个时刻采样的数据它们之间存在微小偏移。普通场景可以忽略但做确定性验证时需要在同一个临界区里读取这两个值甚至要在读取周期计数前后各读一次节拍判断中间是否发生了节拍更新。我自己的做法是用一个短临界区先取节拍再取周期然后看周期读取后有没有跨越节拍边界若跨越则修正一拍的偏移。3 配置层面怎么展开3.1 关键Kconfig选项内核时钟的配置项看起来不多但每个都对系统行为有很大影响。重点说这么几个CONFIG_SYS_CLOCK_TICKS_PER_SEC每秒多少个节拍默认通常是100CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC底层硬件计数器的频率这个值必须跟硬件匹配不匹配会导致换算错误CONFIG_SYS_CLOCK_EXISTS指示系统时钟是否存在一般默认yCONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER底层是否支持64位周期计数CONFIG_TICKLESS_KERNEL启用tickless模式空闲时停止周期中断CONFIG_TIMER_NO_WAIT低功耗场景下不等待定时器硬件就绪其中CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC最容易配错。它不是一个指定“你想要多快”的选项而是一个描述“实际硬件跑多快”的事实选项。如果这个值设错所有从周期到毫秒的换算都会偏差而内核节拍却可能看起来正常——因为节拍是靠定时器中断驱动的跟这个值没有直接关系。我踩过一次板子的外部晶振实际是32.768kHz但配置里写成了32768分频后内核节拍是100表面上完全正常但周期换算出来的延迟数据普遍多了3%左右。排查很久才定位到是晶振实际频率在温度和电压变化下略有偏移而配置写死导致换算比例失真。3.2 节拍率到底选多少节拍率直接决定系统的响应粒度和开销。CONFIG_SYS_CLOCK_TICKS_PER_SEC设为100就是每10毫秒中断一次设为1000就是每1毫秒中断一次。中断越多处理器越频繁唤醒功耗越高但调度响应更快。实际项目里怎么选我的经验是分场景低功耗电池设备选50或100中断少省电代价是毫秒级任务响应会慢交互式设备选1000触摸、按键响应用户感知不出来延迟高性能音频或电机控制不只靠内核节拍还要用周期计数的PWM或专用定时器内核节拍只做辅助调度特别提醒不要为了“提高精度”而无限提高节拍率。当节拍率超过某个阈值中断上下文切换的耗时可能超过有效工作时间况且内核节拍本来有量化误差——即使节拍率1000也仍然只能精确到1毫秒的整数倍真正的亚毫秒精度必须靠周期计数器。3.3 与底层counter驱动协作内核时钟并不直接操作寄存器它调用一个定时器驱动接口。这个驱动通常注册在drivers/timer/目录下对应SoC的硬件定时器。从接口看驱动需要提供timer_start、timer_stop、timer_read等函数并负责在硬件中断里调用sys_clock_announce()来告知内核有多少节拍经过。底层还可以是Zephyr的计数器Counter设备通过counter API封装一层比如counter_start、counter_get_value、counter_set_alarm。定时器驱动调用counter API时中断由counter设备触发counter的回调再转到内核时钟层。多一层封装增加灵活性但也意味着要确认驱动和counter中断优先级配合是否恰当。如果定时器中断被更高优先级中断频繁抢占节拍延迟会累积导致系统时间越来越偏。我在某块SoC上遇到过默认配置下一个串口DMA中断优先级比系统节拍高且DMA中断处理耗时较长导致节拍中断被延迟几个毫秒。表面现象是任务调度偶尔抖动深挖发现是中断优先级没设计好。解决方式很简单将定时器中断设成比DMA中断更高的优先级或者将DMA中断处理拆成短临界区加底半部。3.4 设备树与时钟频率的关系内核时钟与设备树里的时钟频率设置也有关联尤其是在SoC内部定时器外设的时钟源可能由某个总线时钟分频而来。设备树里通常只指定时钟驱动和时钟频率而最终喂给定时器的实际频率由硬件时钟控制驱动计算后配置。如果设备树里标注的总线频率和真实PLL配置不一致定时器实际计数频率就和你预想的不同。做硬件抽象时别在应用代码里写死定时器频率一定要通过DEVICE_DT_GET和对应的clock_control_get_rate或者直接从设备树属性拿频率值再换算成周期。否则换个板子、换个PLL配置所有时间换算都错。4 实操驱动和超时实现4.1 从定时器驱动到内核的对接如果自己写一个定时器驱动对接内核时钟的流程可以这样拆解第一步驱动初始化时配置硬件计数器频率和中断然后调用sys_clock_set_phase()建立一个初始相位对齐。第二步硬件中断触发后读取当前计数器的值计算距离上一次中断经过了多少个计数再换算成节拍数调用sys_clock_announce(ticks)。如果启用tickless驱动还应支持动态设置下一个报警点而不是固定周期性中断。第三步驱动实现k_cycle_get_32()对应的周期读取函数返回当前硬件计数值。第四步驱动需要处理暂停、恢复和低功耗切换保证挂起期间丢失的节拍在恢复后补上。核心代码如下#include zephyr/kernel.h #include zephyr/drivers/timer/system_timer.h #include zephyr/sys_clock.h #define HW_FREQ CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC #define TICK_FREQ CONFIG_SYS_CLOCK_TICKS_PER_SEC static uint32_t last_count; void timer_isr(void *arg) { uint32_t now timer_hw_read_count(); uint32_t elapsed now - last_count; uint64_t ticks (elapsed * TICK_FREQ) / HW_FREQ; last_count now; sys_clock_announce((uint32_t)ticks); } uint32_t z_timer_cycle_get_32(void) { return timer_hw_read_count(); } void z_timer_init(void) { last_count timer_hw_read_count(); timer_hw_set_irq_callback(timer_isr, NULL); timer_hw_start(); sys_clock_set_phase(0); }这个例子里关键的坑是elapsed的计算——直接用无符号减法天然处理了计数器翻转。但如果计数器宽度不是32位比如24位就要在每次读取后做掩码操作否则差值可能巨大。4.2 tickless模式与单次报警机制传统内核每节拍唤醒一次系统空闲时也在白白耗电。tickless内核在空闲时关闭周期性中断只在下一个已知超时点设置单次报警。这个机制能大幅降低功耗但也引入两个需要注意的问题。第一个问题是“错过的节拍”。系统从空闲中醒来时实际经过的时间可能比预定报警点多出几个节拍驱动必须在中断里算出“实际经过了多少节拍”调用一次sys_clock_announce()来弥补。第二个问题是“精度漂移”报警点设定用的是硬件周期换算成节拍时会有整除余数这些余数如果一直忽略长时间跑下来时间会越来越偏。Zephyr底层的sys_clock_set_phase()和剩余周期补偿机制就是为处理这个误差而存在的。以低功耗系统的实际表现为例某个项目里设备大部分时间处于睡眠偶尔醒来处理事件。如果报警点补偿做得好k_uptime_get()长期误差能控制在几十毫秒如果驱动只是简单地丢弃余数跑一天可能偏出一两分钟。4.3 应用层的超时和定时器在内核时钟之上应用最常用的是k_timer。它的接口非常直观static void my_timeout_handler(struct k_timer *timer) { printk(timer expired, current uptime: %lld ms\n, k_uptime_get_64()); } void app_main(void) { struct k_timer my_timer; k_timer_init(my_timer, my_timeout_handler, NULL); k_timer_start(my_timer, K_MSEC(500), K_SECONDS(1)); }这里K_MSEC(500)是首次超时时间K_SECONDS(1)是后续周期。重点说一下超时参数的实际含义K_MSEC(500)在内部会换算成节拍数由于节拍率可能是100那么500毫秒正好是50个节拍没有量化损失但如果传K_MSEC(501)换算结果可能是50或51个节拍取决于向上还是向下取整。需要精确控制的场景直接传K_TICKS(50)最稳妥。k_timer_start会更新底层报警点。如果你频繁启停一个周期很短的定时器注意底层硬件是否支持不断重新编程下一个报警点。某些计数器硬件只能在当前报警触发后设置新值做不到“无缝更新”。这种情况下Zephyr会在驱动层做补偿——先停掉旧报警再立刻设置新报警但这之间会损失极短时间窗口。对大多数业务来说无所谓但做精准PWM或同步采样就要考虑。4.4 测量内核时钟是否准确判断系统时间是否准确我常用的办法是在基准GPIO上输出秒脉冲用示波器或者外部高精度定时器对比。代码层面直接读k_uptime_get_64()的前后差值来验证节拍推进是否均匀。两个方法结合能快速定位是驱动层计时错误还是应用层换算错误。具体做法是启动一个高优先级线程死循环记录k_uptime_get_64()和k_cycle_get_32()打印每次差值。如果节拍推进均匀每次间隔应该是严格的节拍间隔整数倍如果出现“偶尔多了一拍”或者“漏了一拍”问题基本出在中断抢占或驱动补偿逻辑。另外还有一个简单方法利用k_uptime_get_64()和底层RTC时间做对比。睡十分钟醒来后比较内核节拍和RTC的差值如果偏差超过预期就去查驱动。RTC一般有自己的晶振精确度更高可以作为系统时间漂移的参照。这样做有个前提内核时钟和RTC用的是同一个系统时间基准Zephyr可能通过外部时间同步服务校准需要确认是否启用了自动NTP类的同步机制。5 常见问题与排查技巧5.1 节拍精度和漂移最常见的现象是“系统时间比真实时间慢”或“快”排除硬件晶振本身误差后要检查几点第一驱动里有没有正确处理计数器翻转。24位计数器频繁翻符号运算稍不留神就会导致时间倒退一大截。第二中断处理时是否读取了正确的计数值。有些硬件定时器读取会引入额外等待周期要确认读数没有因为总线访问延迟而失真。第三tickless模式下报警补偿代码有没有真的执行。有些驱动只做了周期性中断的适配没做单次报警补偿表面能用实际时间越偏越离谱。我习惯在调试阶段把CONFIG_TICKLESS_KERNEL临时关掉用固定节拍中断做对照。如果固定中断模式下时间准确开tickless后不准就可以确定是报警补偿逻辑问题而不是硬件计数器本身的问题。5.2 低功耗和暂停时钟的影响进入深度睡眠时很多SoC会直接停掉系统定时器时钟或CPU时钟。Zephyr的电源管理模块会调用定时器驱动的暂停/恢复接口让驱动记录暂停前计数、暂停后重新对齐。如果驱动没有实现这套逻辑睡眠唤醒后节拍计数会出现跳变或倒退直接表现为应用层定时器全部错乱。还有一类特殊场景CPU睡眠但定时器仍在跑或者反过来定时器停了但CPU还在跑。两者都会造成内核时间和真实时间的偏差。要确认具体行为最直接的办法是读芯片手册中关于定时器在低功耗模式下是否可用的描述在设备树或驱动里根据这个行为做对应处理。我见过一个板子默认配置下CPU睡眠会连带停掉系统定时器导致每次唤醒后k_uptime_get()像是“冻结”在睡眠前的时间业务上很多程序都受此影响。后来在配置里选了一个独立的低频时钟源作为定时器时钟问题才解决。5.3 几个排查手段排查时钟问题我先说排查顺序再用表格整理。排查顺序是先确认硬件计数器的频率读数是否正确再确认中断是否按时触发再确认sys_clock_announce()传入的节拍数是否准确最后才怀疑应用层换算。现象可能原因验证手段时间均匀偏慢硬件频率配置偏高对比RTC一段时间误差时间均匀偏快硬件频率配置偏低或丢失计数对比RTC或用秒脉冲抓GPIO偶发跳变计数器翻转处理错误长时间跑打印每次差值睡眠唤醒后时间异常低功耗时钟暂停/恢复未处理开关睡眠测试对比唤醒后时间中断频繁抖动中断优先级抢占定时器中断优先级调整后对比调试时最实用的一个手段是启用内核的时钟统计功能通过查看sys_clock_elapsed()或相关宏来确认当前节拍数。另外直接阅读arch/arch/core/下与时钟相关的汇编代码也有帮助不过那是比较内核底层的活了一般在查中断路径时会用到。5.4 经验中断上下文的节拍处理我在写驱动时最深刻的经验是不要在高于定时器中断优先级的中断里调用任何可能触发调度的API。内核时钟的推进依赖定时器中断里调用sys_clock_announce()如果这个中断被更高优先级中断长期挡住调度器无法及时处理超时系统的实时性指标会瞬间崩坏。合理的做法是把高优先级中断里要做的事压缩到极致只读取必要寄存器、置标志或往一个无锁队列里丢数据然后立刻退出。真正的时间敏感任务交给普通优先级线程去处理。很多实时性问题的根子就在这里而不是调度器算法本身。另一个经验是精确测量中断响应时间这类业务不要依赖k_uptime_get()更不要依赖k_cycle_get_32()的差值去推算中断发生点因为中断入口本身和调用读取API之间也有延迟。精确做法是让中断在入口处立即读周期计数器并记录到专用变量之后再在业务线程里读取这个专用变量。这一下差别可能就有几十到上百个周期在高速系统里已经非常可观了。6 结合项目经验谈几个细节6.1 一个实际的时基设计例子我参与过一个某设备的同步控制项目要求所有节点基于同一个时间基准协同动作。我们没用内核默认的节拍作为控制基准而是引入了一个独立的高精度计数器外设用它做周期计数通过周期中断驱动控制循环。内核时钟依然存在但只用来做调度和管理面的超时。这样设计的好处是隔离控制循环的时间基准完全不受内核调度延迟影响而管理面的超时又能方便地利用内核定时器。坏处是必须自己维护两个时间源的同步。我们最后在驱动里实现了一个校准函数每秒钟读取内核周期计数和外部计数器的计数做一次差值校正。这个方案跑了一段时间时间一致性能控制在微秒级而单靠内核节拍绝对做不到。这种场景也说明Zephyr的“Kernel Clocks”并没有覆盖所有时间需求。做高精度控制或测量时你要深入理解它能给什么以及它和底层硬件的边界在哪里。这不是Zephyr的不足而是嵌入式系统设计里本来就存在的层次问题。6.2 时间片轮转的时间片与节拍关系用时间片轮转调度时时间片长度通常用CONFIG_TIMESLICE_SIZE_MS或CONFIG_TIMESLICE_PRIORITY配置。这里要注意时间片是用节拍数实现的不是用周期计数实现的。如果你设置时间片为5毫秒而节拍率是100那么实际生效的时间片可能是10毫秒或5毫秒取决于内核节拍边界的对齐。所以让时间片长度等于节拍间隔的整数倍是最稳妥的。我曾经把时间片设成7毫秒期望几种线程均匀轮流执行结果实际表现出现明显不均。后来查代码发现时间片到期判定是基于“节拍计数是否跨越了超时点”7毫秒并不是10毫秒整数倍导致每个线程的时间片长度在一个和两个节拍之间来回波动。改成10毫秒后一切正常。6.3 关于相位校准和纪元同步如果系统需要和其他设备保持时间同步比如通过无线电对时或者分布式节点同步那么内核时钟需要支持“调整”而不是“跳变”。Zephyr的sys_clock_set_phase()允许驱动校准周期与节拍之间的比例而k_uptime_get_64()返回的是单调递增时间外部同步后可以叠加一个偏移量实现逻辑时间校正避免时间倒退导致各种定时器混乱。工程实现上我一般不会直接修改内核节拍计数而是把外部同步的时间源封装成一个独立模块内部维护一个偏差值在读取时加上去。这样内核时钟仍然保持单调外部业务时间能够跟随标准时间源。如果强行改内核计数所有正在等待的超时队列都可能产生连锁反应轻则跑飞重则死锁。这是我的血泪教训。6.4 如何阅读定时器驱动源码想深挖某个平台的时钟实现阅读源码路径很有讲究。定时器驱动一般在drivers/timer/下面文件名与具体SoC相关。找到驱动后优先看三个部分初始化函数如何处理中断优先级和频率配置周期读取函数z_timer_cycle_get_32有没有做掩码或锁存sys_clock_announce()的调用路径是否涉及多核上的全局变量同步。读懂这三个部分基本就能判断这个平台补了哪些坑、还有哪些隐患。比如我看到过某个驱动的sys_clock_announce()是在中断里加锁的这在单核没问题但在SMP下会导致次核在等待锁的时候错过后续中断。这种隐患通常不会在功能测试里暴露但在重负载和高频率下会周期性造成延迟尖刺。7 从实战角度聊聊我对内核时钟的使用建议7.1 默认配置能用但别迷信Zephyr项目用默认配置跑起来90%的场景不会出大问题。可真正做产品默认的节拍率、时钟源选择、中断优先级配置往往不是最优解。我的建议是产品定义阶段就要明确时间精度要求然后倒推需要什么样的硬件计数器、多大频率、是否支持tickless、低功耗模式下时钟是否保持。如果这个环节没做后面出现时间问题再优化往往要动驱动层或者硬件选型代价比一开始做设计大得多。7.2 周期计数是嵌入式时间测量的一等公民内核节拍专注调度和超时周期计数则负责“真实时间”。一个熟练的嵌入式开发者应该在所有涉及性能测量、精确同步、事件标记的场景里习惯性使用k_cycle_get_32()或者底层周期计数API而不是毫秒级的k_uptime_get()。周期计数和节拍之间的转换用k_ms_to_cyc_floor32()、k_cyc_to_ms_floor64()这类函数处理不要自己写除法因为这些函数会处理溢出和取整规则。自己写的换算代码在边界条件下很容易因为溢出或舍入方向不符合预期而出错。7.3 内核时钟的扩展方向后续做系统时你可以在这个基础上扩展的内容包括周期计数器与外部时间源的对齐校准、跨核共享时钟的同步优化、基于多个计数器的无缝切换机制。这些都是内核时钟概念的延伸Zephyr本身提供了一些基础设施但具体策略需要根据产品需求定制。理解了最底层的节拍、周期、超时三者如何联动这些问题自然就有了入手点。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表