先搞懂它是什么
- 设备模型:SDK 通过 HOSAL 统一外设层把所有外设抽象成“设备结构体”(
hosal_uart_dev_t、hosal_gpio_dev_t等),应用直接定义一个结构体变量(如hosal_uart_dev_t uart0;),再通过&uart0把它传给该外设的 API——写法上所有外设都一致。 - 驱动分层:应用调用的
hosal_xxx_*是“驱动 API”,驱动内部再操作寄存器(LL 层)控制硬件。 - 三种数据通路:轮询(程序主动读)、中断(硬件通知)、DMA(硬件直接搬数据)——理解了它们就理解了外设实现的核心。
层层拆解原理
1. 统一设备模型
HOSAL 把每个外设抽象成一个设备结构体 hosal_xxx_dev_t(xxx 为 uart/gpio/i2c/spi/timer…),外设使用前先定义结构体变量并填入配置:
hosal_uart_dev_t uart0; /* 定义 UART 设备结构体 */
hosal_gpio_dev_t gpio; /* 定义 GPIO 设备结构体 */hosal_xxx_dev_t 里存着该外设的端口号(port)、引脚/模式/速率等配置(config)字段,填好之后所有 hosal_uart_*/hosal_gpio_* API 都以它的地址(&dev)为第一个参数——这是 WB2 SDK 所有外设例程的统一套路。
2. 驱动分层结构
- API 层:面向应用,提供
hosal_xxx_init、hosal_xxx_send/recv/output_set/input_get等。 - HAL/LL 层:直接读写寄存器(如时钟门控
GLB_CGEN_*,见 SDK 的bl602_glb.h;数据寄存器),不同芯片实现不同,但 API 层保持一致——这也是“换芯片不换代码风格”的原因。
3. 初始化与操作的统一流程
几乎每个外设例程都是这个流程:
例:UART 是定义 hosal_uart_dev_t uart0(config 里配波特率/数据位/校验/停止位)→ hosal_uart_init(&uart0) → 接收中断或轮询 hosal_uart_receive。
4. 三种数据通路对比
| 方式 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 轮询 | 程序循环查状态寄存器 | 简单、易调试 | 占用 CPU | 低频、简单收发 |
| 中断 | 硬件触发回调,CPU 现场处理 | 不阻塞主流程、响应快 | 中断里不能做耗时操作 | 按键、串口接收 |
| DMA | 硬件把数据直接搬进内存 | CPU 几乎不参与、速度快 | 配置复杂、要处理内存一致性 | 大数据量(Flash、音频、显示) |
结合 SDK 看实现
以 GPIO 为例,把“设备模型 + 初始化 + 操作”串起来:
hosal_gpio_dev_t gpio; /* 定义设备结构体 */
hosal_gpio_init(&gpio, GPIO_PIN_12, HOSAL_GPIO_OUTPUT_PUSH_PULL); /* 初始化模式:推挽输出 */
hosal_gpio_output_set(&gpio, 1); /* 操作:输出高电平 */所有基础外设页(GPIO/UART/SPI/I2C/ADC/PWM/Timer…)都遵循同一骨架,学会一个就能举一反三。
常见考点 / 面试题
为什么要用 HOSAL 设备结构体而不是直接操作寄存器?
统一设备模型让驱动 API 与具体芯片解耦:换芯片/换实例只需重新定义结构体并修改配置,应用代码风格不变;也方便驱动管理中断、时钟等资源。
中断和 DMA 有什么区别?
中断是“硬件叫 CPU 来处理数据”;DMA 是“硬件自己搬数据,搬完再通知 CPU”。DMA 让 CPU 从搬运中解放,适合大数据量。
为什么外设初始化都要先定义设备结构体?
结构体封装了端口号、引脚、模式、速率等配置,驱动 API 通过它访问具体外设;这也是“同一份代码操作多个同类型外设(如两路串口)”的基础——各定义一个结构体实例即可。
LL 层和 API 层的关系?
API 层面向应用、跨芯片稳定;LL/HAL 层面向寄存器、随芯片变化。改动硬件平台时通常只需换 LL 层实现,应用代码基本不动。
轮询方式什么时候不可接受?
当外设事件稀疏而程序任务繁重时(如同时收串口、扫按键、跑界面),轮询会占用大量 CPU 且响应延迟;此时应改用中断或 DMA。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

