3.5.2.5 __driver_stm32_usart_early_driver_init 函数详解
3.5.2.5 __driver_stm32_usart_early_driver_init 函数详解
📚 本节导读
学习时长: 约 15 分钟
难度级别: ⭐⭐☆☆☆(中级)
前置知识:
- 3.5.2.4 device_core_init 函数详解
- 串口通信基础知识
🎯 学习目标
- 理解早期串口初始化的目的
- 掌握控制台设备的识别方法
- 理解
OS_DRIVER_DEFINE宏的展开机制 - 了解
os_kprintf的完整工作流程 - 理解为什么这个函数在 LOW 子级别
一、函数概述
1.1 函数定位
__driver_stm32_usart_early_driver_init(由 OS_DRIVER_DEFINE 宏自动生成)用于 STM32 串口早期初始化,在系统启动早期识别并保存控制台串口句柄。
1.2 基本信息
| 属性 | 值 |
|---|---|
| 生成的函数名 | __driver_stm32_usart_early_driver_init |
| 驱动变量名 | stm32_usart_early_driver |
| 源文件 | drivers/hal/st/drivers/drv_usart.c(第 563-576 行) |
| 初始化级别 | OS_INIT_LEVEL_PRE_KERNEL_1 |
| 子级别 | OS_INIT_SUBLEVEL_LOW |
| 地址(来自 project.map) | 0x08005e08 |
1.3 为什么放在 LOW 子级别?
- 依赖
device_core_init已经完成设备框架初始化(设备表已就绪) - 需要在尽可能早的阶段完成,以便后续代码可以使用
os_kprintf - 放在 LOW 子级别(值为 3)确保它在 HIGH(值为 1)、MIDDLE(值为 2)之后执行
STM32F103ZET6 实际配置
OS_USING_CONSOLE:已定义(在 projects/stm32f103zet6-atk-elite/oneos_config.h 第 194 行)OS_CONSOLE_DEVICE_NAME:"uart1"(在 projects/stm32f103zet6-atk-elite/oneos_config.h 第 195 行)
结论:整个代码块在 #ifdef OS_USING_CONSOLE 条件编译内,条件满足,这一步会执行。
二、函数详解
2.1 完整代码
来源: drivers/hal/st/drivers/drv_usart.c (第 551-577 行)
#ifdef OS_USING_CONSOLE
static UART_HandleTypeDef *console_uart = OS_NULL;
void __os_hw_console_output(char *str)
{
if (console_uart == OS_NULL)
return;
HAL_UART_Transmit(console_uart, (uint8_t *)str, strlen(str), STM32_UART_POLL_TIMEOUT_MS_PER_BYTE);
}
static int stm32_usart_early_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
if (!strcmp(dev->name, OS_CONSOLE_DEVICE_NAME))
console_uart = (UART_HandleTypeDef *)dev->info;
return OS_SUCCESS;
}
OS_DRIVER_INFO stm32_usart_early_driver = {
.name = "UART_HandleTypeDef",
.probe = stm32_usart_early_probe,
};
OS_DRIVER_DEFINE(stm32_usart_early_driver, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_LOW);
#endif2.2 宏展开分析
涉及三个关键宏,层层展开。下面逐一分析。
第一层:OS_DRIVER_INFO 宏
定义位置:drivers/bus/bus.h 第 49 行
#define OS_DRIVER_INFO static OS_USED OS_SECTION("driver_table") const os_driver_info_t展开 OS_DRIVER_INFO stm32_usart_early_driver = { ... };:
static __attribute__((used)) __attribute__((section("driver_table"))) const os_driver_info_t stm32_usart_early_driver = {
.name = "UART_HandleTypeDef",
.probe = stm32_usart_early_probe,
};效果:
__attribute__((used))(OS_USED):告诉编译器即使未显式引用也不要优化掉这个变量__attribute__((section("driver_table"))(OS_SECTION("driver_table")):将变量放入链接器的driver_table段中
链接器段的作用
链接器会将所有放在 driver_table 段的驱动信息收集在一起,形成一个驱动信息数组。driver_match_devices() 函数遍历这个数组来匹配设备。
来自 project.map 的实际例子:
driver_table 段在内存中是连续排列的,可以从 project.map 中看到所有驱动条目(来自 projects/stm32f103zet6-atk-elite/build/keil/List/project.map):
地址 大小 来源对象
─────────────────────────────────────────────────────────────
0x08022e38 8 soft_i2c_bus.o(driver_table) ← driver_table$$Base
0x08022e40 8 drv_adc.o(driver_table)
0x08022e48 8 drv_can.o(driver_table)
0x08022e50 8 drv_dac.o(driver_table)
0x08022e58 8 drv_flash_f1.o(driver_table)
0x08022e60 8 drv_hwtimer.o(driver_table)
0x08022e68 8 drv_iwdg.o(driver_table)
0x08022e70 8 drv_rtc.o(driver_table)
0x08022e78 8 drv_spi.o(driver_table)
0x08022e80 16 drv_usart.o(driver_table) ← 包含两个驱动
─────────────────────────────────────────────────────────────
0x08022e90 ← driver_table$$Limitdrv_usart.o 的 driver_table 段大小为 16 字节(包含 2 个 8 字节的条目):
0x08022e80 stm32_usart_driver (正式驱动,注册在 PRE_DEVICE 级别)
0x08022e88 stm32_usart_early_driver (早期驱动,注册在 PRE_KERNEL_1 级别)链接器导出的符号:
driver_table$$Base = 0x08022e38 (指向第一个驱动条目)
driver_table$$Limit = 0x08022e90 (指向最后一个驱动条目之后)driver_match_devices() 通过这两个符号确定 driver_table 的起始地址和大小,从而遍历所有驱动条目。
第二层:OS_DRIVER_DEFINE 宏
定义位置:drivers/bus/bus.h 第 52-57 行
#define OS_DRIVER_DEFINE(_driver_, sequence, subsequence) \
static os_err_t __driver_##_driver_##_init(void) \
{ \
return driver_match_devices(&_driver_); \
} \
OS_INIT_CALL(__driver_##_driver_##_init, sequence, subsequence)将 _driver_ 替换为 stm32_usart_early_driver,sequence 替换为 OS_INIT_LEVEL_PRE_KERNEL_1(值为 4),subsequence 替换为 OS_INIT_SUBLEVEL_LOW(值为 3):
// 展开结果:
static os_err_t __driver_stm32_usart_early_driver_init(void)
{
return driver_match_devices(&stm32_usart_early_driver);
}
OS_INIT_CALL(__driver_stm32_usart_early_driver_init, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_LOW)展开逻辑:
##是 C 预处理器的令牌拼接运算符,将__driver_+stm32_usart_early_driver+_init拼接为函数名__driver_stm32_usart_early_driver_init- 函数内部调用
driver_match_devices()进行设备匹配(详解见下方) - 最后调用
OS_INIT_CALL将该函数注册到指定的初始化级别
driver_match_devices() 详解
定义位置:drivers/bus/bus.c 第 27-57 行
int driver_match_devices(const os_driver_info_t *driver)
{
extern const int device_table$$Base;
extern const int device_table$$Limit;
const os_device_info_t *device_table = (os_device_info_t *)&device_table$$Base;
int device_num = (os_device_info_t *)&device_table$$Limit - device_table;
int dev_index;
for (dev_index = 0; dev_index < device_num; dev_index++)
{
if (!strcmp(driver->name, device_table[dev_index].driver))
{
driver->probe(driver, &device_table[dev_index]);
}
}
return 0;
}分步解析:
第一步:获取设备表
extern const int device_table$$Base;
extern const int device_table$$Limit;
const os_device_info_t *device_table = (os_device_info_t *)&device_table$$Base;
int device_num = (os_device_info_t *)&device_table$$Limit - device_table;| 代码 | 作用 |
|---|---|
device_table$$Base | 链接器导出的符号,指向 device_table 段的起始地址 |
device_table$$Limit | 链接器导出的符号,指向 device_table 段的结束地址 |
device_table | 将起始地址转为 os_device_info_t * 指针 |
device_num | 通过指针算术计算设备数量(结束地址 - 起始地址) |
设备表中的 UART 设备
在 STM32F103ZET6 项目中,board/peripherals.c 第 35 行定义了 uart1 设备:
extern UART_HandleTypeDef huart1;
OS_HAL_DEVICE_DEFINE("UART_HandleTypeDef", "uart1", huart1);OS_HAL_DEVICE_DEFINE 宏(定义在 drivers/driver.h 第 81-86 行)展开为:
OS_DEVICE_INFO huart1_info = {
.name = "uart1", // 设备名称
.driver = "UART_HandleTypeDef", // 驱动类型
.info = &huart1, // 指向 UART_HandleTypeDef 句柄
};OS_DEVICE_INFO 宏将该结构体放入 device_table 段,与 driver_table 段对应。
第二步:遍历匹配
for (dev_index = 0; dev_index < device_num; dev_index++)
{
if (!strcmp(driver->name, device_table[dev_index].driver))
{
driver->probe(driver, &device_table[dev_index]);
}
}| 代码 | 作用 |
|---|---|
driver->name | 驱动的匹配名称,即 "UART_HandleTypeDef" |
device_table[dev_index].driver | 设备的驱动类型,如 "UART_HandleTypeDef" |
!strcmp(...) | 字符串相同返回 0,! 取反后为真 |
driver->probe(driver, ...) | 匹配成功后调用驱动的 probe 回调 |
以 uart1 为例的匹配过程:
driver->name = "UART_HandleTypeDef"
遍历 device_table:
[0] huart1_info: .driver = "UART_HandleTypeDef" → strcmp 返回 0 → 匹配成功!
调用 stm32_usart_early_probe(driver, &huart1_info)
probe 中比较: dev->name("uart1") == OS_CONSOLE_DEVICE_NAME("uart1") → 匹配!
保存: console_uart = (UART_HandleTypeDef *)&huart1
[1] huart2_info: .driver = "UART_HandleTypeDef" → strcmp 返回 0 → 匹配成功!
调用 stm32_usart_early_probe(driver, &huart2_info)
probe 中比较: dev->name("uart2") != OS_CONSOLE_DEVICE_NAME("uart1") → 不匹配
console_uart 不变注意
driver_match_devices 会匹配所有 driver 类型相同的设备(本例中 uart1 和 uart2 都匹配)。但在 stm32_usart_early_probe 中,只有设备名称等于 OS_CONSOLE_DEVICE_NAME("uart1")的才会被保存到 console_uart。
第三层:OS_INIT_CALL 宏
定义位置:kernel/include/os_stddef.h 第 108-110 行
#define OS_INIT_CALL(fn, level, sublevel) \
OS_USED static const init_call_entry_t _os_init_call_##fn OS_SECTION( \
".init_call." OS_STRING(level) "." OS_STRING(sublevel)) = {#fn, fn}将 fn 替换为 __driver_stm32_usart_early_driver_init,level 替换为 4,subsection 替换为 3:
// 展开结果:
static __attribute__((used)) __attribute__((section(".init_call.4.3"))) const init_call_entry_t
_os_init_call___driver_stm32_usart_early_driver_init = {
"__driver_stm32_usart_early_driver_init", // #fn:函数名字符串
__driver_stm32_usart_early_driver_init // fn:函数指针
};其中 init_call_entry_t 结构体定义(os_stddef.h 第 101-106 行):
typedef struct _init_call_entry
{
const char *name; // 函数名(调试用)
init_call_fn_t func; // 函数指针
} init_call_entry_t;关键细节
OS_STRING(level)是字符串化宏(#s),将数字转为字符串,用于构造段名.init_call.4.3#fn是字符串化,将函数名转为字符串"__driver_stm32_usart_early_driver_init"fn是函数指针,即函数的入口地址- 段名
.init_call.4.3中4=OS_INIT_LEVEL_PRE_KERNEL_1,3=OS_INIT_SUBLEVEL_LOW
完整展开总结
原始代码:
OS_DRIVER_INFO stm32_usart_early_driver = {
.name = "UART_HandleTypeDef",
.probe = stm32_usart_early_probe,
};
OS_DRIVER_DEFINE(stm32_usart_early_driver, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_LOW);等价于:
// 1. 驱动信息放入 driver_table 段
static __attribute__((used)) __attribute__((section("driver_table")))
const os_driver_info_t stm32_usart_early_driver = {
.name = "UART_HandleTypeDef",
.probe = stm32_usart_early_probe,
};
// 2. 生成初始化函数
static os_err_t __driver_stm32_usart_early_driver_init(void)
{
return driver_match_devices(&stm32_usart_early_driver);
}
// 3. 初始化函数指针放入 .init_call.4.3 段
static __attribute__((used)) __attribute__((section(".init_call.4.3")))
const init_call_entry_t _os_init_call___driver_stm32_usart_early_driver_init = {
"__driver_stm32_usart_early_driver_init",
__driver_stm32_usart_early_driver_init
};链接器如何使用这些段?
链接器将 .init_call.4.3 段中的所有条目收集在一起。系统启动时,内核遍历该段,依次调用每个条目的 func 函数指针,从而按级别和子级别顺序执行所有初始化函数。这就是 OneOS 自动初始化机制的核心。
2.3 分步详解
第一步:声明控制台串口句柄
static UART_HandleTypeDef *console_uart = OS_NULL;详细解释:
- 类型:
UART_HandleTypeDef *,STM32 HAL 库的 UART 句柄指针 - 初始值:
OS_NULL(空指针),表示尚未找到控制台串口 - 作用域:
static,仅在本文件内可见
为什么需要这个变量?
console_uart 被 __os_hw_console_output 函数使用。当系统启动早期还未注册控制台设备时,os_kprintf 的输出会通过 __os_hw_console_output 直接发送到这个串口。
第二步:定义硬件控制台输出函数
void __os_hw_console_output(char *str)
{
if (console_uart == OS_NULL)
return;
HAL_UART_Transmit(console_uart, (uint8_t *)str, strlen(str), STM32_UART_POLL_TIMEOUT_MS_PER_BYTE);
}详细解释:
| 代码行 | 作用 |
|---|---|
if (console_uart == OS_NULL) return; | 如果控制台串口尚未初始化,直接返回 |
HAL_UART_Transmit(...) | 通过 HAL 库一次性发送整个字符串 |
注意
这个函数会覆盖 drivers/console/console.c 中的同名弱符号(OS_WEAK)默认空实现。
第三步:识别控制台串口
static int stm32_usart_early_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
if (!strcmp(dev->name, OS_CONSOLE_DEVICE_NAME))
console_uart = (UART_HandleTypeDef *)dev->info;
return OS_SUCCESS;
}详细解释:
| 代码 | 作用 |
|---|---|
strcmp(dev->name, OS_CONSOLE_DEVICE_NAME) | 比较设备名称与控制台设备名 |
!strcmp(...) | strcmp 返回 0 表示相同,! 取反后为真 |
console_uart = (UART_HandleTypeDef *)dev->info | 保存串口 HAL 句柄 |
关键宏:OS_CONSOLE_DEVICE_NAME
STM32F103ZET6 实际配置
OS_CONSOLE_DEVICE_NAME 定义为 "uart1"(在 projects/stm32f103zet6-atk-elite/oneos_config.h 第 195 行)。
这意味着只有设备名为 "uart1" 的设备才会被识别为控制台串口。
第四步:注册驱动
OS_DRIVER_INFO stm32_usart_early_driver = {
.name = "UART_HandleTypeDef",
.probe = stm32_usart_early_probe,
};
OS_DRIVER_DEFINE(stm32_usart_early_driver, OS_INIT_LEVEL_PRE_KERNEL_1, OS_INIT_SUBLEVEL_LOW);详细解释:
| 代码 | 作用 |
|---|---|
OS_DRIVER_INFO | 宏,将结构体放入 driver_table 段(定义在 drivers/bus/bus.h) |
.name = "UART_HandleTypeDef" | 驱动匹配名称,与设备信息中的类型匹配 |
.probe = stm32_usart_early_probe | 探测回调函数 |
OS_DRIVER_DEFINE(...) | 注册驱动到指定初始化级别 |
2.4 数据结构
2.4.1 驱动信息结构(drivers/bus/bus.h 第 36-40 行)
typedef struct os_driver_info
{
char *name; // 驱动匹配名称
int (*probe)(const struct os_driver_info *drv,
const struct os_device_info *dev); // 探测回调函数
} os_driver_info_t;2.4.2 设备信息结构(drivers/bus/bus.h 第 42-47 行)
typedef struct os_device_info
{
char *name; // 设备名称(如 "uart1")
char *driver; // 驱动类型(如 "UART_HandleTypeDef")
const void *info; // 设备私有信息(如 UART_HandleTypeDef 句柄)
} os_device_info_t;匹配机制
driver_match_devices 函数遍历设备表,将驱动的 .name("UART_HandleTypeDef")与设备的 .driver 字段进行匹配。匹配成功后调用驱动的 .probe 回调函数。
三、工作原理
3.1 完整调用链
OS_DRIVER_DEFINE() 宏注册
↓
系统启动时执行 __driver_stm32_usart_early_driver_init()
↓
driver_match_devices(&stm32_usart_early_driver)
↓
遍历设备表,匹配 driver 类型为 "UART_HandleTypeDef" 的设备
↓
对每个匹配的设备调用 stm32_usart_early_probe()
↓
比较设备名称是否等于 OS_CONSOLE_DEVICE_NAME ("uart1")
↓
如果是,保存 UART_HandleTypeDef 句柄到 console_uart3.2 os_kprintf 的完整工作流程
os_kprintf("Hello, OneOS!\n")
↓
os_vsnprintf() 格式化字符串到静态缓冲区 log_buff
↓
os_hw_console_output(log_buff) ← drivers/console/console.c
↓
检查 os_console_get_device() 是否返回有效设备
├─ 设备未注册(启动早期)→ __os_hw_console_output(log_buff)
│ ↓
│ HAL_UART_Transmit(console_uart, ...)
│ ↓
│ 通过串口发送
└─ 设备已注册 → os_device_write_nonblock() 通过设备框架发送关键区别
- 启动早期(本函数执行后、设备框架完全注册前):走
__os_hw_console_output路径,直接操作硬件 - 设备注册后:走设备框架路径,通过
os_device_write发送
3.3 为什么需要早期初始化?
在系统启动的 PRE_KERNEL_1 阶段,设备框架尚未完全初始化,os_console_get_device() 返回 OS_NULL。此时如果调用 os_kprintf,会走到 __os_hw_console_output 这条路径。
如果 console_uart 未被初始化(仍为 OS_NULL),__os_hw_console_output 会直接返回,导致启动信息丢失。
因此,本函数必须在 PRE_KERNEL_1 阶段完成 console_uart 的初始化,确保后续代码可以通过串口输出调试信息。
四、在启动流程中的位置
4.1 执行顺序
pre_kernel_1_start() ← 3.5.2.1
↓
cortex_m_set_vector() ← 3.5.2.2(HIGH 子级别)
↓
os_hw_board_init() ← 3.5.2.3
↓
device_core_init() ← 3.5.2.4(设备框架初始化)
↓
__driver_stm32_usart_early_driver_init() ← 这里(LOW 子级别)
↓
PRE_KERNEL_1 阶段完成4.2 子级别执行顺序
在 OS_INIT_LEVEL_PRE_KERNEL_1 级别内:
HIGH(值 1)→ MIDDLE(值 2)→ LOW(值 3)
↑ ↑
cortex_m_set_vector __driver_stm32_usart_early_driver_init💡 本节总结
重点回顾
- 函数作用:识别并保存控制台串口句柄到
console_uart全局变量 - 宏展开:
OS_DRIVER_DEFINE生成__driver_stm32_usart_early_driver_init函数,内部调用driver_match_devices - 条件编译:整个代码块在
#ifdef OS_USING_CONSOLE内 - 两条输出路径:启动早期走
__os_hw_console_output,设备注册后走设备框架 - 为什么在 LOW 子级别:依赖
device_core_init完成设备框架初始化
关键符号说明
| 符号 | 来源 | 说明 |
|---|---|---|
OS_CONSOLE_DEVICE_NAME | oneos_config.h | 控制台设备名,STM32F103ZET6 为 "uart1" |
console_uart | drv_usart.c | 控制台串口 HAL 句柄 |
OS_DRIVER_DEFINE | drivers/bus/bus.h | 驱动注册宏,生成 init 函数并注册 |
driver_match_devices | drivers/bus/bus.c | 设备匹配函数,遍历设备表调用 probe |
📚 扩展阅读
- cortex_m_set_vector函数详解 - 了解向量表设置
下一步
接下来请学习:
- 3.5.3 _k_show_sys_info 函数详解 - 版本信息输出