通用外设驱动
本文档介绍杰理 AD15N/AD1NN SDK 中 sdk/app/bsp/common 下的通用外设驱动层:按键驱动(IO/AD/矩阵/IR/触摸)、软件 I2C(iic_soft)、FM 收音驱动、NOR Flash 驱动与编码器驱动,说明其架构、接口、配置方式与工作机制。
Purpose and Scope
本页覆盖 SDK 中与应用无关、可复用的"通用外设驱动"能力,即 sdk/app/bsp/common 目录下各外设子模块:
- 按键驱动(
key/):IO 按键、AD 按键、矩阵按键、IR 按键、触摸按键的统一扫描与状态机; - 软件 I2C(
iic_soft/):用 GPIO 位操作模拟 I2C 时序的通用总线驱动; - FM 收音(
fm/):BK1080、QN8035、RDA5807 等收音芯片驱动及 I2C 读写适配; - NOR Flash(
norflash/):外部 SPI NOR Flash 与 NORFS 文件系统的设备驱动; - 编码器(
encoder/):旋转编码器 API。
以下内容不在本页展开,属于兄弟页面范畴:音频解码器(decoder/)、文件系统与虚拟文件层(fs/)、电源管理(power_manage/)、USB 主机/从机(usb/)、VM 参数管理(vm/)等。本页聚焦"驱动如何被配置、注册、调用"这一通用机制,以及各外设驱动的典型用法。
Overview
在 AD15N SDK 中,外设访问遵循"平台数据配置 → 驱动初始化 → 统一接口调用"的三层模式:
- 平台数据(Platform Data):每个外设驱动都定义一个配置结构体(如
struct soft_iic_config、struct norflash_dev_platform_data),由板级代码通过XXX_PLATFORM_DATA_BEGIN/END宏实例化,描述引脚、时钟、延时等硬件相关参数。这种"数据与逻辑分离"的设计让同一份驱动代码可以在不同硬件板上复用。 - 驱动层:每个外设模块提供
init/uninit/suspend/resume以及业务读写接口(如soft_iic_read_buf、key_scan),并通过key_interface_t之类的接口结构体暴露能力,便于上层做多态分发。 - 设备管理层(dev_mg):需要接入统一设备模型的外设(如 NOR Flash)通过
device_operations结构体向dev_mg/device.h注册,应用层用dev_open/dev_read/dev_write等统一接口访问,实现"换设备不改应用"。
按键驱动是本 SDK 中最典型的通用外设驱动:key.h 定义了 5 种按键类型(IO/AD/矩阵/IR/触摸)、6 种按键状态(短按/短按释放/长按/连按/长按释放/双击),并给出滤波与状态判定的门槛值。这些状态统一通过 key_emit_t 回调输出到应用,应用只需实现消息过滤与响应,无需关心底层是哪种按键物理实现。
Architecture
下图展示通用外设驱动在 SDK 中的位置与依赖关系:
flowchart TD
subgraph sg_App["应用层 (app)"]
AppTask["应用任务/消息处理"]
end
subgraph sg_Drv["通用外设驱动层 (bsp/common)"]
Key["key 按键驱动<br/>IO / AD / 矩阵 / IR / 触摸"]
IicSoft["iic_soft 软件 I2C"]
Fm["fm 收音驱动<br/>BK1080 / QN8035 / RDA5807"]
Norflash["norflash 驱动"]
Encoder["encoder 编码器"]
end
subgraph sg_DevMg["设备管理层 (dev_mg)"]
DevMg["device.h / device_operations"]
end
subgraph sg_Hw["硬件层"]
Gpio["GPIO"]
Adc["ADC"]
Spi["SPI1 / SPI2"]
Chip["外设芯片"]
end
AppTask -->|"按键消息"| Key
AppTask -->|"read/write 接口"| IicSoft
AppTask -->|"read/write 接口"| Fm
AppTask -->|"dev_open / dev_read"| DevMg
DevMg -->|"device_operations"| Norflash
Key --> Gpio
Key --> Adc
IicSoft --> Gpio
Fm --> IicSoft
Fm --> Chip
Norflash --> Spi
Encoder --> Gpio
各节点职责说明:
- key 按键驱动:
key.c为总控,key_drv_io.c/key_drv_ad.c/key_matrix.c/key_ir.c/key_touch.c为各类型底层采集,统一实现key_interface_t(key_init+key_get_value)。 - iic_soft 软件 I2C:完全由 GPIO 位操作实现,
soft_iic_config指定scl、sda、delay与是否内部上拉。FM 驱动等芯片驱动依赖它进行总线访问。 - fm 收音驱动:
fm_iic_dri.h提供读写适配,BK1080/QN8035/RDA5807 是具体芯片的寄存器级实现,彼此独立、可替换。 - norflash 驱动:通过
norflash_dev_ops/norfs_dev_ops两个device_operations实例接入设备管理层,支持 SPI1/SPI2 硬件接口,是"驱动接入统一设备模型"的范例。 - encoder 编码器:
encoder_api.c提供旋转编码器/正交解码的 API 封装。
设计意图:驱动层刻意保持"平台无关",所有硬件差异被收敛到平台数据结构体与回调函数指针中。这样应用层(解码、FM、按键响应)可以跨板移植,同时各驱动又可独立开启/关闭(见 key.h 中 KEY_XXX_EN 开关宏),有效控制代码体积与功耗。
按键驱动(key)
按键驱动位于 sdk/app/bsp/common/key/,是整个 SDK 中复用度最高、机制最完整的通用外设驱动。它把"物理按键采集"与"业务响应"彻底解耦:底层驱动只负责读取电平/ADC/矩阵/红外/触摸值,上层状态机负责滤波与事件判定,最终通过回调把事件投递给应用。
按键类型与使能开关
key.h 用一组编译期宏控制各类型按键是否参与编译,并用枚举定义按键类型:
#define KEY_IO_EN 0 ///<IO按键使能
#define KEY_AD_EN 1 ///<AD按键使能
#define KEY_MATRIX_EN 0 ///<矩阵按键使能
#define KEY_IR_EN 0 ///<IR按键使能
#define KEY_TOUCH_EN 0 ///<触摸按键使能
/*按键类型*/
typedef enum {
KEY_TYPE_IO,
KEY_TYPE_AD,
KEY_TYPE_MATRIX,
KEY_TYPE_IR,
KEY_TYPE_TOUCH,
} KEY_TYPE;
Source: key.h
设计意图:MCU 资源有限,用宏裁剪可以让未使用的按键驱动代码完全不参与链接,同时保留 KEY_TYPE_* 枚举作为运行时类型标签,供 key_table_sel() 分发时识别事件来源。KEY_DOUBLE_CLICK_EN 单独控制双击功能(默认关闭),避免无需求时增加状态机复杂度。
按键状态机与门槛值
key.h 定义了统一的按键事件集合,任何类型的按键最终都映射到这 6 种状态:
/*按键门槛值*/
#define KEY_BASE_CNT 2
#define KEY_LONG_CNT 75
#define KEY_HOLD_CNT 15
#define KEY_SHORT_CNT 3
/*按键状态*/
enum {
KEY_SHORT = 0x0,
KEY_SHORT_UP = 0x1,
KEY_LONG = 0x2,
KEY_HOLD = 0x3,
KEY_LONG_UP = 0x4,
KEY_DOUBLE = 0x5,
KEY_MAX_STATUS,
};
Source: key.h
各门槛值含义(以 10ms 扫描周期计):
| 宏 | 值 | 含义 |
|---|---|---|
KEY_BASE_CNT | 2 | 消抖计数:连续 2 次采样一致才确认按下,滤除机械抖动 |
KEY_SHORT_CNT | 3 | 短按判定基准 |
KEY_LONG_CNT | 75 | 长按触发阈值(约 750ms) |
KEY_HOLD_CNT | 15 | 长按后的连按(HOLD)周期(约 150ms 触发一次 HOLD 事件) |
KEY_DOUBLE_CLICK_CNT | 35 | 双击判定窗口(约 350ms),配合 KEY_DOUBLE_CLICK_EN 使用 |
状态设计说明:KEY_SHORT(按下即报)与 KEY_SHORT_UP(释放才报)并存,让应用既可以在按下瞬间立即响应(如音量键连点),也可以在释放时确认非长按再响应;KEY_LONG 之后每 KEY_HOLD_CNT 周期重复上报 KEY_HOLD,实现"长按连续调节"效果;KEY_LONG_UP 让应用在长按结束时收到收尾事件(如停止快进)。
接口抽象:key_interface_t
各按键类型驱动通过同一个函数指针结构对外暴露,实现多态采集:
typedef struct {
KEY_TYPE key_type;
void (*key_init)(void);
u8(*key_get_value)(void);
} key_interface_t;
Source: key.h
key_type:标识该接口实例属于哪类按键(KEY_TYPE_IO等),用于key_table_sel()的路由;key_init:初始化对应引脚/ADC 通道/定时器等资源;key_get_value:返回当前采到的键值(无键按下返回NO_KEY,即0xff)。
对应实现文件:key_drv_io.c(读 GPIO)、key_drv_ad.c(读 ADC 分压,支持最多 AD_KEY_MAX_NUM=10 键)、key_matrix.c(行列扫描)、key_ir.c(红外解码,支持最多 IR_KEY_MAX_NUM=21 键)、key_touch.c(触摸检测)。每种类型均有独立的按键数量上限宏(IO_KEY_MAX_NUM/AD_KEY_MAX_NUM/MATRIX_KEY_MAX_NUM/IR_KEY_MAX_NUM/TOUCH_KEY_MAX_NUM,均为 10,IR 为 21)。
事件输出与调试
/*按键输出注册接口*/
typedef int (*key_emit_t)(u8 key_status, u8 key_num, u8 key_type);
Source: key.h
key_emit_t 是按键事件的统一出口:key_status 为上述状态枚举值,key_num 为键位号,key_type 为类型。应用通过 key_table_sel(void *msg_filter) 注册过滤/映射表,把"键位号+状态"翻译成业务消息。key.h 还提供 KEY_UART_DEBUG 调试开关,开启后 key_puts/key_printf/key_buf 映射到 log_info 系列日志,便于在串口上观察扫描过程(默认关闭以减小代码体积)。
软件 I2C(iic_soft)
iic_soft 用两个 GPIO 位操作完整模拟 I2C 主机时序(START/STOP/ACK/字节收发),是 FM、触摸等外部芯片访问的基础总线。其优势是任意 GPIO 均可作为 SCL/SDA,不占用硬件 I2C 外设,代价是速率受 delay 与 CPU 频率影响。
typedef const int soft_iic_dev;
struct soft_iic_config {
int scl;
int sda;
u32 delay;
u8 io_pu;
};
Source: iic_soft.h
scl/sda:使用的 GPIO 引脚号;delay:每个位周期的延时(决定时钟频率,值越大速率越低);io_pu:是否使能内部上拉(接开漏/无上拉器件时置 1)。
soft_iic_dev 被定义为 const int,本质是一个设备实例索引,指向 soft_iic_cfg[] 配置表中的某一项——soft_iic_cfg 由板级代码定义(extern const struct soft_iic_config soft_iic_cfg[])。这样同一份驱动可管理多路软件 I2C(如一路接 FM、一路接触摸),彼此通过索引区分。驱动同时提供 soft_iic_suspend/resume,配合电源管理在低功耗前后保存/恢复 GPIO 状态。
FM 收音驱动(fm)
fm/ 目录下按芯片厂商拆分驱动,fm_iic_dri.h 提供统一的 I2C 读写适配层,各芯片驱动(BK1080.h、QN8035.h、RDA5807.h)通过宏定义芯片参数,例如 BK1080 的器件地址 CHIP_DEV_ID 0x80、晶振配置 XTAL_CLOCK 1:
#define CHIP_DEV_ID 0x80
#define XTAL_CLOCK 1
Source: BK1080.h
设计意图:FM 芯片型号由板级宏选择,驱动源码互不干扰;所有芯片共用 fm_iic_dri 的总线访问方式(底层走 iic_soft),换芯片时只需替换寄存器操作层,应用层的 FM 功能接口保持不变。这也体现了"驱动按芯片隔离、总线按平台抽象"的分层原则:AppTask -> Fm -> fm_iic_dri -> soft_iic。
NOR Flash 驱动(norflash)
norflash 是接入统一设备模型(dev_mg)的典型外设。norflash.h 定义平台数据结构与实例化宏,并把 device_operations 暴露给设备管理层:
struct norflash_dev_platform_data {
s8 spi_hw_num; //只支持SPI1或SPI2
...
};
#define NORFLASH_DEV_PLATFORM_DATA_BEGIN(data) \
const struct norflash_dev_platform_data data = {
#define NORFLASH_DEV_PLATFORM_DATA_END() \
};
extern const struct device_operations norflash_dev_ops;
extern const struct device_operations norfs_dev_ops;
Source: norflash.h
要点:
spi_hw_num限定只支持 SPI1 或 SPI2,即 NOR Flash 必须挂在硬件 SPI 上,不适用软件 SPI;NORFLASH_DEV_PLATFORM_DATA_BEGIN/END宏把"定义结构体变量 + 赋值"封装成声明式语法,板级代码只需在 BEGIN/END 之间填写字段即可实例化一份平台数据,降低书写错误;norflash_dev_ops(裸 Flash 块设备)与norfs_dev_ops(NORFS 文件系统设备)是两个独立device_operations,分别对应"原始读写"与"文件系统挂载"两种使用方式;- 驱动使能由
TCFG_NORFLASH_DEV_ENABLE控制(EXT_FLASH_EN打开时自动开启),并通过 dev_mg 的dev_open/dev_read/dev_write/ioctl统一访问,vfs_fat.h中vfs_format()等接口即基于设备名(如"sd0")操作这类块设备。
编码器驱动(encoder)
encoder_api.c 位于 sdk/app/bsp/common/encoder/,提供旋转编码器(正交脉冲输入)的应用层 API。编码器常见于音量旋钮、菜单旋钮等场景,驱动负责对 A/B 相脉冲计数并转换为相对步进值,供应用映射为音量加减、列表翻页等操作。它与按键驱动一样属于"采集 + 事件化"模式,底层同样依赖 GPIO 中断或轮询。
驱动层共性机制小结
| 机制 | 体现 | 目的 |
|---|---|---|
| 平台数据结构体 | soft_iic_config、norflash_dev_platform_data | 硬件差异配置化,驱动代码跨板复用 |
| 编译期裁剪 | KEY_IO_EN、TCFG_NORFLASH_DEV_ENABLE、EXT_FLASH_EN | 未用驱动不链接,节省 Flash/RAM |
| 回调/接口多态 | key_interface_t、key_emit_t、device_operations | 上层与具体实现解耦 |
| 声明式实例化宏 | NORFLASH_DEV_PLATFORM_DATA_BEGIN/END | 减少样板代码,防止结构体初始化漏项 |
| 统一设备模型 | norflash_dev_ops 注册进 dev_mg | 应用以 dev_open/dev_read 访问,换设备不改应用 |
| 电源管理挂钩 | soft_iic_suspend/resume | 低功耗切换时保存/恢复外设状态 |
Core Flow
按键扫描与事件分发流程
按键驱动由系统定时器周期驱动(KEY_DOUBLE_CLICK_CNT 35 注释表明扫描 tick 为 10ms),key_scan() 完成"采样 → 滤波 → 状态判定 → 事件输出"的完整链路:
sequenceDiagram
participant T as 系统定时器 (10ms tick)
participant K as key_scan()
participant D as key_drv_io / key_drv_ad / matrix / ir / touch
participant S as 按键状态机 (消抖/短按/长按/连按)
participant F as key_table_sel() 事件过滤表
participant A as 应用消息队列
loop 每个扫描周期
T->>K: 周期调用 key_scan()
K->>D: key_get_value() 采集键值
D-->>K: key_num / NO_KEY(0xff)
K->>S: 计数滤波 (KEY_BASE_CNT) 与状态迁移
S-->>K: key_status (KEY_SHORT/LONG/HOLD/DOUBLE...)
K->>F: key_emit_t(key_status, key_num, key_type)
F->>A: 映射为业务消息并投递
A-->>F: 消息已处理
end
按键状态迁移
stateDiagram-v2
[*] --> Idle: 初始化
Idle --> Press: 连续采样达到 KEY_BASE_CNT
Press --> Short: 短按释放 (KEY_SHORT / KEY_SHORT_UP)
Press --> Long: 达到 KEY_LONG_CNT 触发 KEY_LONG
Long --> Hold: 每 KEY_HOLD_CNT 周期上报 KEY_HOLD
Hold --> LongUp: 释放上报 KEY_LONG_UP
Short --> Double: 窗口内再次按下 (需 KEY_DOUBLE_CLICK_EN)
Double --> Idle: 上报 KEY_DOUBLE
LongUp --> Idle
Short --> Idle
软件 I2C 读写时序
flowchart LR
A["soft_iic_start()<br/>SCL高时SDA拉低"] --> B["soft_iic_tx_byte()<br/>发送地址+方向位"]
B --> C{"收到 ACK?"}
C -->|"是"| D["soft_iic_write_buf() /<br/>soft_iic_read_buf() 数据阶段"]
C -->|"否"| E["返回错误<br/>总线释放"]
D --> F["soft_iic_stop()<br/>SCL高时SDA拉高"]
E --> F
Usage Examples
按键驱动:配置开关与门槛值
#define KEY_IO_EN 0 ///<IO按键使能
#define KEY_AD_EN 1 ///<AD按键使能
#define KEY_MATRIX_EN 0 ///<矩阵按键使能
#define KEY_IR_EN 0 ///<IR按键使能
#define KEY_TOUCH_EN 0 ///<触摸按键使能
Source: key.h
上例为典型的板级配置:仅开启 AD 按键(ADC 分压按键),其余类型全部裁剪。应用侧随后调用:
void key_init(void);
void key_scan();
void key_table_sel(void *msg_filter);
Source: key.h
key_init() 初始化所有使能的按键类型;key_scan() 放入定时器/任务循环周期执行;key_table_sel() 在初始化早期注册事件过滤表(msg_filter 指向将 key_status/key_num 翻译为应用消息的映射)。
软件 I2C:总线初始化与读写
typedef const int soft_iic_dev;
struct soft_iic_config {
int scl;
int sda;
u32 delay;
u8 io_pu;
};
Source: iic_soft.h
板级代码先定义 soft_iic_cfg[](每项是一路 I2C 的引脚/延时/上拉配置),再以索引调用驱动接口:
int soft_iic_init(soft_iic_dev iic);
int soft_iic_write_buf(soft_iic_dev iic, const void *buf, int len);
int soft_iic_read_buf(soft_iic_dev iic, void *buf, int len);
void soft_iic_start(soft_iic_dev iic);
void soft_iic_stop(soft_iic_dev iic);
Source: iic_soft.h
典型的芯片寄存器写流程为:soft_iic_init(iic) → soft_iic_start(iic) → soft_iic_tx_byte(iic, 芯片地址|写) → soft_iic_tx_byte(iic, 寄存器地址) → soft_iic_tx_byte(iic, 数据) → soft_iic_stop(iic)。FM 驱动(fm_iic_dri.h)正是封装了这套流程供 BK1080/QN8035/RDA5807 复用。
NOR Flash:平台数据实例化与设备注册
#define NORFLASH_DEV_PLATFORM_DATA_BEGIN(data) \
const struct norflash_dev_platform_data data = {
#define NORFLASH_DEV_PLATFORM_DATA_END() \
};
extern const struct device_operations norflash_dev_ops;
extern const struct device_operations norfs_dev_ops;
Source: norflash.h
板级代码使用宏声明一个平台数据实例(如指定 spi_hw_num 为 SPI1 或 SPI2),驱动通过 norflash_dev_ops 接入 dev_mg;上层随后可用 vfs_format()/vfs_mount 等文件系统接口对设备名(如 "sd0")操作,实现"驱动-设备-文件系统"三级贯通。
Configuration Options
按键驱动(key.h)
| 宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
KEY_IO_EN | 编译期开关 | 0 | IO 按键使能 |
KEY_AD_EN | 编译期开关 | 1 | AD(ADC 分压)按键使能 |
KEY_MATRIX_EN | 编译期开关 | 0 | 矩阵按键使能 |
KEY_IR_EN | 编译期开关 | 0 | 红外按键使能 |
KEY_TOUCH_EN | 编译期开关 | 0 | 触摸按键使能 |
KEY_DOUBLE_CLICK_EN | 编译期开关 | 0 | 双击功能使能 |
KEY_BASE_CNT | 计数门槛 | 2 | 消抖采样次数 |
KEY_SHORT_CNT | 计数门槛 | 3 | 短按判定基准 |
KEY_LONG_CNT | 计数门槛 | 75 | 长按触发阈值(约 750ms @10ms tick) |
KEY_HOLD_CNT | 计数门槛 | 15 | 长按后连按上报周期 |
KEY_DOUBLE_CLICK_CNT | 计数门槛 | 35 | 双击判定窗口(约 350ms) |
IO_KEY_MAX_NUM / AD_KEY_MAX_NUM / MATRIX_KEY_MAX_NUM | 上限 | 10 | 各类型最大键位数 |
IR_KEY_MAX_NUM | 上限 | 21 | IR 最大键位数 |
NO_KEY | 常量 | 0xff | 无按键采样的返回值 |
Source: key.h
软件 I2C(iic_soft.h)
| 配置项 | 类型 | 说明 |
|---|---|---|
soft_iic_cfg[].scl | int | SCL GPIO 引脚号 |
soft_iic_cfg[].sda | int | SDA GPIO 引脚号 |
soft_iic_cfg[].delay | u32 | 位周期延时(决定总线速率) |
soft_iic_cfg[].io_pu | u8 | 是否使能内部上拉 |
soft_iic_dev | const int | 设备实例索引(指向 soft_iic_cfg[] 表项) |
Source: iic_soft.h
NOR Flash(norflash.h)
| 宏/字段 | 类型 | 说明 |
|---|---|---|
EXT_FLASH_EN | 开关 | 外部 Flash 总开关,开启后自动定义 TCFG_NORFLASH_DEV_ENABLE |
TCFG_NORFLASH_DEV_ENABLE | 开关 | 启动 NOR Flash 设备 |
norflash_dev_platform_data.spi_hw_num | s8 | 硬件 SPI 编号,只支持 SPI1/SPI2 |
norflash_dev_ops | device_operations | 裸 Flash 块设备操作集 |
norfs_dev_ops | device_operations | NORFS 文件系统设备操作集 |
Source: norflash.h, norflash.h
API Reference
按键驱动
void key_init(void)— 初始化所有使能的按键类型(对应key_interface_t.key_init实现)。void key_scan(void)— 周期扫描入口,完成采样、滤波、状态机判定与事件输出,须由定时器/任务以固定周期(10ms)调用。void key_table_sel(void *msg_filter)— 注册事件过滤/映射表,决定按键事件如何翻译为应用消息。
Source: key.h
typedef int (*key_emit_t)(u8 key_status, u8 key_num, u8 key_type)— 按键事件回调原型;key_status取值KEY_SHORT(0x0)、KEY_SHORT_UP(0x1)、KEY_LONG(0x2)、KEY_HOLD(0x3)、KEY_LONG_UP(0x4)、KEY_DOUBLE(0x5)。返回非 0 通常表示事件已被消费。
软件 I2C
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
soft_iic_init | iic 设备索引 | int | 初始化该路 I2C 的 GPIO 与上拉配置;失败返回错误码 |
soft_iic_uninit | iic | void | 释放该路 I2C 资源 |
soft_iic_suspend / soft_iic_resume | iic | void | 低功耗挂起/恢复(保存/恢复 GPIO 状态) |
soft_iic_start / soft_iic_stop | iic | void | 生成 START/STOP 时序 |
soft_iic_tx_byte | iic, byte | u8 | 发送一字节,返回 ACK 状态 |
soft_iic_rx_byte | iic, ack | u8 | 接收一字节,ack 控制应答位 |
soft_iic_read_buf / soft_iic_write_buf | iic, buf, len | int | 批量读写,返回实际传输长度或错误 |
Source: iic_soft.h
Failure Modes, Edge Cases & Concurrency
- 按键抖动与误触发:机械按键按下/释放瞬间会产生多次电平跳变,
KEY_BASE_CNT的连续采样计数在状态机入口做消抖;NO_KEY(0xff)作为"无键"哨兵值,避免 0 号键位与无键混淆。若抖动严重,应提高KEY_BASE_CNT,代价是响应延迟增加。 - 长按与短按竞争:
KEY_LONG_CNT(750ms)与短按窗口相互制约——用户按住超过长按阈值后只上报KEY_LONG/HOLD系列事件,不再上报KEY_SHORT;应用需在KEY_LONG_UP处做收尾,否则可能出现"快进未停止"的悬挂状态。 - 双击误判:
KEY_DOUBLE_CLICK_EN默认关闭;开启后若第二次按下发生在KEY_DOUBLE_CLICK_CNT(350ms)窗口之外,会被判定为两次独立短按,应用需容忍该边界。 - I2C 无应答(NACK):
soft_iic_tx_byte返回 ACK 状态、read/write_buf返回错误码,调用方(如 FM 驱动)应据此重试或上报芯片异常;delay过小会导致总线速率超限、从机无法响应,io_pu未开且外部无上拉时 SDA 无法拉高产生误读。 - 多路 I2C 并发:
soft_iic_dev是const int索引,多路设备共用同一份驱动代码;若应用多任务同时访问不同索引,需保证soft_iic_cfg[]表项与访问互斥(通常由上层任务模型保证单线程访问)。 - NOR Flash 写保护/擦写中断:擦写期间掉电会损坏数据,
norfs_dev_ops与文件系统层配合处理坏块与掉电恢复;spi_hw_num限定 SPI1/SPI2,挂错 SPI 通道会导致设备无法枚举。 - 扫描任务优先级:
key_scan()的周期抖动会影响按键计数的精确性(门槛值按 10ms 计算),若被高优先级任务长时间抢占,KEY_LONG_CNT等阈值需按实际周期重新标定。
Performance & Operational Considerations
- 扫描开销:按键扫描在 10ms 周期内完成所有使能类型的采集与状态机判定,IO/AD 采集为微秒级操作,整体 CPU 占用极低;未使能的类型不参与编译(
KEY_XXX_EN=0),进一步减少 RAM/Flash 占用。 - 软件 I2C 速率:
delay每增加一位周期的延时即降低总线速率;对速率敏感器件(如某些 FM 芯片)需在数据手册允许范围内调小delay,但过小会引入时序违例,建议预留余量。 - 低功耗配合:外设驱动提供
suspend/resume钩子(如soft_iic_suspend/resume),系统进入低功耗前必须调用,避免 GPIO 悬空漏电;恢复后需重新确认总线状态。 - 调试手段:
KEY_UART_DEBUG可将按键扫描过程输出到串口(log_info),是排查"按键没反应/误触发"的第一手段。
Extension Points
- 新增按键类型:实现
key_interface_t(提供key_type、key_init、key_get_value),在KEY_TYPE枚举中增加类型值,并在key.c的扫描分发中加入该接口实例即可,无需改动应用层。 - 新增事件形态:在
KEY_MAX_STATUS前追加状态枚举值,并在状态机中补充迁移逻辑;同时评估key_emit_t回调语义是否需要扩展。 - 替换 FM 芯片:新建芯片驱动目录,实现基于
fm_iic_dri的寄存器操作,板级宏切换芯片型号即可,应用层 FM 接口不变。 - 接入统一设备模型:仿照
norflash_dev_ops/norfs_dev_ops,实现device_operations并注册到 dev_mg,即可获得dev_open/dev_read/dev_write/ioctl与文件系统(vfs_format等)的通用访问能力。 - 多路软件 I2C:向
soft_iic_cfg[]增加表项即可扩展第二/第三路 I2C 总线,驱动代码零改动。
Tests
SDK 中未发现针对通用外设驱动的独立单元测试工程(嵌入式板级驱动通常依赖整机联调验证)。可验证的行为包括:按键各状态事件序列(短按/长按/连按/双击)、I2C 读写回环(对芯片寄存器写后读回)、NOR Flash 格式化挂载后读写文件。联调时可借助 KEY_UART_DEBUG 串口日志与 soft_iic_tx_byte 的 ACK 返回值作为第一层断言。
Related Links
- 按键驱动头文件 key.h
- 按键实现 key.c
- 软件 I2C 头文件 iic_soft.h
- NOR Flash 驱动 norflash.h
- BK1080 收音驱动 BK1080.h
- 编码器驱动 encoder_api.c
- 相关兄弟页面:音频解码器(decoder)、文件系统与 VFS(fs)、电源管理(power_manage)、USB 驱动(usb)、VM 参数管理(vm)