news 2026/9/29 3:41:13

Zephyr BSP: 12-Zephyr UART初始化解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zephyr BSP: 12-Zephyr UART初始化解析

这一篇正好接着你前面的09 Driver API + Device Instance、10 Device Model、11 DT_INST_FOREACH_STATUS_OKAY() 多实例。

摘要:本文以 UART0/UART1/UART2 为例,讲透 Zephyr 设备初始化框架:DEVICE_DT_DEFINE()如何注册设备、Init Level + Priority两级排序如何决定启动顺序,以及device_is_ready()的本质——它检查的是设备初始化状态,而非硬件健康状态。读完你将彻底掌握 Device Tree → Device Instance → Init Framework → Application 的完整链路。

Zephyr 设备初始化框架:从 Device Tree 到 device_is_ready() 的完整链路

这一篇真正要解决的问题是:
Zephyr 是怎么决定「什么时候初始化 UART0、UART1、UART2」的?
以及:
device_is_ready() 到底检查了什么?

先记住一个非常重要的结论:

UART0 / UART1 / UART2 │ ▼ 每一个都是一个 struct device │ ▼ 每一个 device 都有自己的 init 函数 │ ▼ init 函数被 DEVICE_DT_DEFINE()注册 │ ▼ Zephyr 根据 init level + priority 排序 │ ▼ 系统启动时依次执行 │ ├── PRE_KERNEL_1 ├── PRE_KERNEL_2 ├── POST_KERNEL └── APPLICATION

而:

device_is_ready(dev)

不是重新初始化设备。

它主要是在问:
这个 struct device 是否已经完成初始化,并且被标记为 ready?

1. 先建立整个启动模型
假设你的 SoC 有三个 UART:

UART0 UART1 UART2

设备树:

&uart0{status="okay";};&uart1{status="okay";};&uart2{status="okay";};

经过 Zephyr Device Model 后,最终类似:

Device Tree │ ┌─────────┼─────────┐ ▼ ▼ ▼ uart0 uart1 uart2 │ │ │ ▼ ▼ ▼ device_0 device_1 device_2 │ │ │ ▼ ▼ ▼ init_uart init_uart init_uart

注意:
UART0、UART1、UART2 并不是一个 driver 只被初始化一次。
而是:

同一个 Driver Code │ ├── instance0→ 一个 struct device ├── instance1→ 一个 struct device └── instance2→ 一个 struct device

这就是你上一章讲的 DT_INST_FOREACH_STATUS_OKAY() 的意义。

2. DEVICE_DT_DEFINE() 是关键入口
例如你的 UART driver 可能最终生成类似:

DEVICE_DT_DEFINE(DT_NODELABEL(uart0),uart_init,NULL,&uart0_data,&uart0_config,PRE_KERNEL_1,50,&uart_api);

UART1:

DEVICE_DT_DEFINE(DT_NODELABEL(uart1),uart_init,NULL,&uart1_data,&uart1_config,PRE_KERNEL_1,51,&uart_api);

UART2:

DEVICE_DT_DEFINE(DT_NODELABEL(uart2),uart_init,NULL,&uart2_data,&uart2_config,PRE_KERNEL_1,52,&uart_api);

这里已经出现了两个非常重要的信息:

PRE_KERNEL_150

它们分别代表:

Init Level + Priority

3. Init Level 到底是什么?
Zephyr 并不是:

main()↓ 初始化 UART0 ↓ 初始化 UART1 ↓ 初始化 UART2

这么简单。
它有一个完整的系统初始化阶段。
可以把它理解成:

系统启动 │ ▼ PRE_KERNEL_1 │ ▼ PRE_KERNEL_2 │ ▼ POST_KERNEL │ ▼ APPLICATION │ ▼ main()

也就是:

Zephyr Boot │ ▼ ┌─────────────────┐ │ PRE_KERNEL_1 │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ PRE_KERNEL_2 │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ POST_KERNEL │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ APPLICATION │ └─────────────────┘ │ ▼main()

这就是 Zephyr 的system initialization framework。

4. 为什么要分成 PRE_KERNEL_1 和 PRE_KERNEL_2?
因为硬件之间存在依赖关系。
例如:

Clock │ ▼ UART │ ▼ Console │ ▼ Application

UART 不能在 clock 尚未准备好的情况下正常工作。
因此:

Clock driver ↓ UART driver ↓ Console ↓ Application

需要有顺序。

下面用一张 ASCII 依赖关系图,把这条初始化依赖链完整画出来,并标注每个环节对应的 Init Level 和典型 Priority 值:

┌─────────────────────────────────────────────────────────────┐ │ Zephyr 初始化依赖链 │ │ (Level 优先于 Priority 的两级排序) │ └─────────────────────────────────────────────────────────────┘ Clock driver UART driver ┌──────────────┐ ┌──────────────────┐ │ Init Level │ │ Init Level │ │ PRE_KERNEL_1 │ │ PRE_KERNEL_2 │ │ Priority10│ │ Priority50│ └──────┬───────┘ └────────┬─────────┘ │ │ │ ① clock 先就绪 │ ② 依赖 clock ▼ ▼ ┌───────────────────────────────────────────────────┐ │ Console driver │ │ Init Level: POST_KERNEL │ │ Priority:30│ └──────────────────────┬────────────────────────────┘ │ │ ③ 依赖 UART 输出 ▼ ┌───────────────────────────────────────────────────┐ │ Application │ │ Init Level: APPLICATION │ │ Priority:10│ └──────────────────────┬────────────────────────────┘ │ ▼ main()

读图要点:这条链的每一环都依赖上一环先完成初始化。Clock放在PRE_KERNEL_1 / 10,UART放在PRE_KERNEL_2 / 50,Console放在POST_KERNEL / 30,Application放在APPLICATION / 10。即使UART的 priority 50 比Console的 30 大,UART依然先执行——因为Level 优先于 Priority,PRE_KERNEL_2永远排在POST_KERNEL前面。

Init Level 就是在表达这种关系。

5. PRE_KERNEL_1:非常早期的初始化
这是系统初始化非常早的阶段。
常用于:

非常基础的硬件

例如:

CPU / interrupt controller clock timer 某些 SoC 基础设施

具体哪些 driver 放在这里,要看具体 Zephyr SoC / architecture / driver 实现。
你可以把:

PRE_KERNEL_1

理解成:
系统还没有完全起来之前,先准备最基础的硬件。

6. PRE_KERNEL_2:继续初始化基础设备
然后:

PRE_KERNEL_1 ↓ PRE_KERNEL_2

这个阶段仍然非常早。
很多低层硬件设备会在这里初始化。
例如某些:

GPIO UART I2C SPI Timer

具体 level 依赖 driver 的设计和依赖关系。
所以千万不要记成:
“UART 永远是 PRE_KERNEL_1。”
这是错误的。
正确理解是:
Driver 作者根据设备依赖关系选择 init level。

7. POST_KERNEL:kernel 已经起来了
接下来:

POST_KERNEL

这时 kernel 基础设施已经完成。
因此可以初始化一些依赖 kernel 服务的设备。
例如某些设备可能需要:

kernel object mutex semaphore work queue thread

那么就不能放在太早的:

PRE_KERNEL_1

阶段。

8. APPLICATION:最后才轮到应用级初始化
最后:

APPLICATION

这个阶段已经非常接近:

main()

通常用于:

application-level initialization

例如:

applicationserviceapplication subsystem

9. 所以真正的排序是两级排序
这是理解 Zephyr Init 最重要的一点。
不是只有:

PRE_KERNEL_1 PRE_KERNEL_2 POST_KERNEL APPLICATION

还存在:

priority

因此真正类似:

Init Level + Priority

排序。
例如:

PRE_KERNEL_1 priority10priority20priority50PRE_KERNEL_2 priority10priority30priority50POST_KERNEL priority10priority50APPLICATION priority10

因此:

PRE_KERNEL_1/10↓ PRE_KERNEL_1/20↓ PRE_KERNEL_1/50↓ PRE_KERNEL_2/10↓ PRE_KERNEL_2/30↓ PRE_KERNEL_2/50↓ POST_KERNEL/10↓...

下面用一个对比表格,把四个 Init Level 的关键差异整理清楚:

Init Level典型用途可访问的 kernel 服务常见设备示例
PRE_KERNEL_1系统最早期的基础硬件准备,kernel 尚未完全起来基本不可用(kernel 服务尚未就绪)CPU / interrupt controller、clock、timer、某些 SoC 基础设施
PRE_KERNEL_2继续初始化低层硬件设备,仍处于早期阶段基本不可用(kernel 服务尚未就绪)GPIO、UART、I2C、SPI、Timer 等低层外设
POST_KERNELkernel 基础设施已完成,可初始化依赖 kernel 服务的设备kernel object、mutex、semaphore、work queue、thread依赖 kernel 服务的设备驱动
APPLICATION应用级初始化,最接近main()完整 kernel 服务application service、application subsystem

排序规则:Level 优先于 Priority。
也就是说,Zephyr 先按 Init Level 分大阶段,再在同一 Level 内部按 Priority 从小到大排序。PRE_KERNEL_2 / 10永远不会跑到PRE_KERNEL_1 / 100前面——即使它的 priority 更小。Level 决定了设备初始化的"大阶段",Priority 只决定同一阶段内部的先后顺序。

Level 优先于 priority。
也就是说:

PRE_KERNEL_2/10

也不会跑到:

PRE_KERNEL_1/100

前面。

下面用一个组合排序示例表,把不同Level + Priority组合的实际执行顺序列出来,并标注哪些组合是合法的、哪些会产生歧义:

执行顺序Init LevelPriority示例设备合法性说明
1PRE_KERNEL
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 3:41:12

AI Agent Harness 冷启动优化:TaoToken 统一 Key 通道快速响应配置方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 3:41:06

HC32F460 ADC+DMA高效采集方案:AOS路由与双缓冲实战

1. 为什么HC32F460的ADCDMA组合值得单独拿出来讲如果你之前用过STM32的ADCDMA方案,转到华大HC32F460的时候可能会觉得"差不多嘛"。但实际调试下来,坑的数量和类型完全不一样。HC32F460是华大半导体推出的一款Cortex-M4内核MCU,主频…

作者头像 李华
网站建设 2026/9/29 3:40:23

SmartCall智能体使用指南:零代码搭建能办事的AI电话客服

前言 很多客服运营负责人都有同感:想上AI客服降本,但要么配置复杂要靠技术排期,要么只会机械问答解决不了实际问题,调优全靠猜,最终上线效果差、人工没少用。 SmartCall 的智能体功能,就是专门面向业务运…

作者头像 李华