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

