驱动开发规范
API 规范
头文件
每一个外设驱动与 API 相关的头文件有三个:
bk_api_xxx.h
定义 xxx 外设的 API。
bk_api_xxx_types.h
定义 xxx 外设的公开数据类型与宏。外部组件与驱动层可直接访问,HAL 层与 SOC 层禁止访问。
xxx_types.h
定义 xxx 外设的公开数据类型与宏。外部组件,驱动层,HAL 层,SOC 层都可访问。
API 头文件中的接口,数据类型,宏应该是稳定的,抽象的,与具体的硬件无关。
备注
xxx_types.h
不应该包含内部数据类型,而应该放到内部头文件中:
如果是驱动层私有数据类型,则放到
xxx_driver.h
中。如果是 HAL 层私有数据类型,则放到
xxx_hal.h
中。如果是 SOC 层私有数据类型,则放到
xxx_ll.h
中。
设备 ID 从 0 开始
像 DMA/ADC/GPIO 等多通道外设,其通道号,或者标识号从 0 开始。
错误码
每个驱动都应该定义自己的错误码,错误码偏移在 bk_err.h 中定义。
可重入
Armino 驱动 API 默认可实现为不可重入,对于某些具有并行访问要求的外设,如,两个不同 任务可能同时竞争访问同一 ADC 通道时,需要在驱动层使用加锁的方式保证可重入。驱动 API 可重入性需要在 API 文档中说明。
文档
文档要求如下:
所有驱动 API 需要有 API 文档
对于复杂驱动 API,需要增加 API 使用指南
示例
对于使用复杂的驱动,需要在 project/examples/periperial
下增加相应的示例程序。
CLI 命令
每一个驱动 API 均需要在增加一组 CLI 命令,用于对驱动进行测试。
驱动层规范
芯片无关性
驱动层代码与具体芯片无关,不应该出现像 CONFIG_SOC_xxx
包起来的代码。
具体芯片相关的实现应该放在 HAL 层。
寄存器无关
驱动层代码不应该直接操作寄存器,必须通过 HAL/LL 层接口操作寄存器。
低功耗要求
请充分考虑外设的低功耗设计需求:
当外设操作需要等硬件状态时,在事务开始前需要投票阻止 CPU 进入``低压休眠模式``,在事务结束后再投赞成票。
在外设 API 在实现时,操作前打开时钟,在操作结束之后关闭时钟。
传输模式
建议实现下述外设读写模式:
模式 |
读/写 |
描述 |
---|---|---|
轮询 |
读/写 |
不使用中断机制,使用``忙等待``的方式等待传输结束后返回。 |
中断 + 接收缓存 |
读 |
驱动层定义一个接收缓存,缓存长度可配置。 在中断中将收到的数据存接收缓存。 API 从接收缓存中取数据,支持阻塞与非阻塞方式, 阻塞方式可设置阻塞的超时时间。 |
中断 + 0-copy |
写 |
中断中直接使用用户传入的数据指针进行写操作。 API 支持阻塞与非阻塞方式,阻塞方式可设置 超时时间。 使用``非阻塞``方式时有独立的 API 可以等外设传输完成。 |
接口完整性
实现具体驱动时,建议参考 CMSIS/FreeRTOS_IO
以及典型操作系统外设驱动在适配时的要求,
确保驱动外设已经实现了最常见的功能,方便后续驱动适配。
驱动层通用 API
Driver 层通常要求实现如下标准 API, 在实现 API 时即要充分考虑未来扩充的可能性, 同时也要考虑尽量降低驱动开发复杂度。
Driver API |
功能描述 |
---|---|
|
|
|
驱动框架卸载,释放所有软/硬件资源 |
|
|
|
|
|
启动设备, start 之后设备正常工作 |
|
关闭设备, 不会复位硬件中已有配置 |
HAL/LL/SOC 层规范
软/硬件寄存器定义一致性
驱动软件寄存器命名与芯片寄存器定义中名字保持一致,建议通过脚本生成相关代码:
SoC 层
xxx_reg.h
/xxx_struct.h
由脚本依据寄存器定义生成LL 层 xxx_ll.h 由脚本依据寄存器定义生成
寄存器访问
通过结构体访问寄存器
为保证代码可读性,驱动代码中应该优先使用 xxx_struct.h
中定义的结构体字段访问寄存器。
通过宏访问寄存器
对于某些多通道位操作,或者某些对性能敏感的操作可选择使用寄存器宏操作。
最常用的寄存器位操作宏如下,不般不建议自己另外定义一套寄存器访问宏,请 优先使用下述宏访问寄存器。
宏名 |
描述 |
---|---|
REG_WRITE(r, b) |
写 32 位寄存器 |
REG_READ(r) |
读 32 位寄存器 |
REG_SET_BIT(r, b) |
写一位或者多位, mask 为 0xffffffff |
REG_CLR_BIT(r, b) |
清一位或者多位, mask 为 0xffffffff |
REG_SET_BITS(r, b, m) |
写一位或者多位, mask 为 m |
REG_SET_FIELD(r, f, v) |
写指定多位, mask 为 _S & _v |
REG_GET_FIELD(r, f, v) |
读指定多位, mask 为 _S & _v |