设备外设抽象(按键/触摸/传感器/存储)
AC63 蓝牙音频 SDK 通过 apps/common/device/ 下的设备抽象层,将按键(IO/ADC/红外/矩阵/触摸)、触摸(CTMU 触摸按键、触摸板 IC)、传感器(重力加速度计 gSensor、光学鼠标传感器)与存储(片外 NorFlash)等硬件外设封装为统一接口,应用层通过编译宏选型、通过统一句柄与事件机制访问硬件,实现"芯片无关、应用可移植"。
Purpose and Scope
本页介绍 SDK 公共设备抽象层的完整机制,覆盖以下子系统的职责边界、内部实现、数据流与扩展方式:
- 按键抽象:
key_driver.c统一按键框架,以及iokey、adkey、irkey、slidekey、touch_key、matrix_keyboard、rdec_key、tent600_key等具体驱动; - 触摸抽象:CTMU 电容触摸按键(
ctmu_touch_key.c)、触摸按键(touch_key.c)及外部触摸板 IC(touch_pad/SYD9557M.c); - 传感器抽象:重力加速度计管理框架
gSensor_manage.c(含 MSA310、SC7A20 系列驱动)、光学鼠标传感器管理OMSensor_manage.c、入耳检测in_ear_manage; - 存储抽象:片外 NorFlash 驱动
norflash.c。
有意排除的主题(由其他目录页承载):音频通路(apps/common/audio)、蓝牙协议栈与 HID 应用(apps/hid)、USB 设备/主机协议(apps/common/device/usb)、Mesh 传感器模型(apps/mesh,属于 Mesh 应用层而非本抽象层)。USB 相关驱动虽同样位于 apps/common/device/,但属于独立的协议栈主题,本页仅在架构图中标注边界。
Overview
设计动机
AC63 SDK 面向多颗蓝牙音频 SoC 与多种公版/定制硬件方案(耳机、音箱、HID 鼠标、Mesh 节点等)。同一颗芯片可能搭配不同的按键类型、不同的重力加速度计型号(MSA310 / SC7A20E / SC7A20TR)、不同的触摸方案(内部 CTMU / 外部触摸 IC)。如果应用代码直接操作寄存器或 GPIO,任何硬件变更都会导致应用层大范围修改。
设备抽象层因此承担三个职责:
- 统一接口:为同类外设定义统一的操作句柄与回调(如按键统一向系统事件总线发
sys_event,gSensor 统一通过G_SENSOR_INTERFACE句柄读写); - 编译期选型:通过
TCFG_*编译宏决定启用哪些驱动与哪颗传感器芯片,未启用的驱动代码不参与编译,不影响代码体积; - 运行时解耦:驱动内部处理时序、去抖、中断/轮询、总线竞争(如 IIC 自旋锁),上层只消费抽象结果。
关键概念与术语
| 术语 | 含义 |
|---|---|
sys_event | 系统事件结构体,按键等外设向系统事件总线投递的事件,应用层在 key_event_deal 等处统一处理 |
G_SENSOR_INTERFACE | gSensor 驱动接口句柄,统一封装芯片初始化、睡眠/唤醒、数据读取等操作 |
G_SENSOR_INFO | gSensor 运行时信息结构体,包含 IIC 句柄、IIC 延时等参数 |
TCFG_* | SDK 编译期配置宏,位于 app_config.h / 板级配置,决定外设选型 |
| 弱符号函数 | __attribute__((weak)) 定义的函数,用户可在应用层重写以扩展默认行为 |
Architecture
flowchart TD
subgraph sg_App["应用层"]
AppMain["app_main / key_event_deal"]
UserCfg["user_cfg / 事件处理"]
end
subgraph sg_DevAbst["设备抽象层 apps/common/device"]
KeyDriver["key_driver.c<br/>统一按键框架"]
GSenMgr["gSensor_manage.c<br/>传感器管理"]
OMSMgr["OMSensor_manage.c<br/>光学鼠标传感器"]
Nor["norflash.c<br/>片外Flash驱动"]
TouchPad["touch_pad/SYD9557M.c<br/>触摸板IC"]
end
subgraph sg_KeyDrv["按键驱动"]
IOKEY["iokey.c"]
ADKEY["adkey.c"]
IRKEY["irkey.c"]
SLIDE["slidekey.c"]
TKEY["touch_key.c"]
CTMU["ctmu_touch_key.c"]
MATRIX["matrix_keyboard.c"]
RDEC["rdec_key.c / tent600_key.c"]
end
subgraph sg_SensorDrv["传感器芯片驱动"]
MSA["msa310.c"]
SC7A20E["SC7A20_E.c"]
SC7A20TR["SC7A20_TR.c"]
HAL3205["hal3205.c"]
HAL3212["hal3212.c"]
end
subgraph sg_Hw["硬件层"]
HW_GPIO["GPIO / ADC / 红外 / CTMU"]
HW_IIC["IIC 总线 (硬件/软件)"]
HW_SPI["SPI"]
HW_FLASH["NorFlash"]
end
KeyDriver --> IOKEY
KeyDriver --> ADKEY
KeyDriver --> IRKEY
KeyDriver --> SLIDE
KeyDriver --> TKEY
KeyDriver --> CTMU
KeyDriver --> MATRIX
KeyDriver --> RDEC
GSenMgr --> MSA
GSenMgr --> SC7A20E
GSenMgr --> SC7A20TR
OMSMgr --> HAL3205
OMSMgr --> HAL3212
IOKEY --> HW_GPIO
ADKEY --> HW_GPIO
IRKEY --> HW_GPIO
TKEY --> HW_GPIO
CTMU --> HW_GPIO
MATRIX --> HW_GPIO
GSenMgr --> HW_IIC
OMSMgr --> HW_SPI
Nor --> HW_FLASH
TouchPad --> HW_IIC
KeyDriver -->|"sys_event 事件"| AppMain
GSenMgr -->|"三轴数据/回调"| AppMain
OMSMgr -->|"位移数据"| AppMain
Nor -->|"读写接口"| UserCfg
架构说明:
- 按键子系统是所有外设中抽象最完整的:
key_driver.c是统一框架,编译期通过#include引入各按键驱动头文件;各驱动完成硬件扫描后,把按键事件包装为struct sys_event投递到系统事件总线,应用侧key_event_deal统一分发。框架还提供弱符号函数key_event_remap()作为组合键/键值重映射的扩展点,并提供开机标志与软关机唤醒补发机制(详见下文)。 - 传感器子系统以
gSensor_manage.c为管理器,通过G_SENSOR_INTERFACE句柄屏蔽芯片差异;IIC 总线访问被统一抽象为宏(硬件 IIC / 软件 IIC 可切换),并用自旋锁sensor_iic保护总线访问,避免多任务并发读写冲突。 - 存储子系统的
norflash.c直接面向片外 Flash 提供底层驱动,是参数存储(user_cfg)与固件升级的硬件基础。 - 各子系统均通过
TCFG_*宏在编译期裁剪,未启用的驱动不参与编译。
接下来深入各子系统的实现细节与真实控制流。
按键抽象(Key Abstraction)
key_driver.c 统一框架
apps/common/device/key/key_driver.c 是全部按键驱动的汇总入口。它在编译期通过 #include 引入所有按键驱动头文件:
#include "device/key_driver.h"
#include "system/event.h"
#include "system/init.h"
#include "iokey.h"
#include "adkey.h"
#include "slidekey.h"
#include "irkey.h"
#include "touch_key.h"
#include "system/timer.h"
#include "asm/power_interface.h"
#include "app_config.h"
#include "rdec_key.h"
#include "tent600_key.h"
#if TCFG_KEY_TONE_EN
#include "tone_player.h"
#endif
#if(TCFG_IRSENSOR_ENABLE == 1)
#include "irSensor/ir_manage.h"
#endif
Source: key_driver.c
这种"框架 + 编译期聚合"的设计意图:按键的扫描与去抖逻辑分散在各驱动中,但事件投递、开机唤醒、键值重映射等公共策略集中在框架内,新增一种按键类型只需实现驱动并在此处加入 include,应用层代码零改动。
支持的按键类型与文件对应关系:
| 按键类型 | 驱动文件 | 物理原理 |
|---|---|---|
| IO 按键 | iokey.c | 直接读取 GPIO 电平,支持上拉/下拉 |
| ADC 按键 | adkey.c | 通过 ADC 采样电压区分多键(分压网络) |
| 红外按键 | irkey.c | 红外遥控解码,受 TCFG_IRSENSOR_ENABLE 控制 |
| 滑动按键 | slidekey.c | 触摸滑动条 |
| 触摸按键 | touch_key.c | 电容触摸检测 |
| 矩阵键盘 | matrix_keyboard.c / matrix_keyboard_ex_mcu.c | 行列扫描,后者为外挂 MCU 方案 |
| 旋转编码器 | rdec_key.c | 旋转编码开关 |
| 其他 | tent600_key.c | 特殊定制按键方案 |
按键事件重映射(扩展点)
框架为应用层预置了一个弱符号函数 key_event_remap(),默认返回 true(不干预),用户可在自己的工程文件中重写它,把某些按键值重新映射,最常见的用途是实现组合键(如"音量+ 与 上一曲 同时按下"产生新键值):
//=======================================================//
// 按键值重新映射函数:
// 用户可以实现该函数把一些按键值重新映射, 可用于组合键的键值重新映射
//=======================================================//
int __attribute__((weak)) key_event_remap(struct sys_event *e)
{
return true;
}
Source: key_driver.c
设计意图:以弱符号替代虚函数表——嵌入式 C 工程中不引入 C++ 对象模型,利用链接器弱符号规则实现"默认实现 + 用户覆盖",既保持了框架的完整性,又给产品定制留出无侵入的扩展点。
按键开机标志
框架维护 key_poweron_flag 标志位,用于"按键开机"功能:当系统处于关机/软关机状态时,按键驱动仍可扫描到有效按键,应用层通过查询该标志决定是否执行开机流程。
static volatile u8 is_key_active = 0;
static volatile u8 key_poweron_flag = 0;
void set_key_poweron_flag(u8 flag)
{
key_poweron_flag = flag;
}
u8 get_key_poweron_flag(void)
{
return key_poweron_flag;
}
void clear_key_poweron_flag(void)
{
key_poweron_flag = 0;
}
Source: key_driver.c
软关机按键唤醒与事件补发
在 TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE 使能时,框架进入"软关机唤醒"模式:此时按键驱动可能尚未初始化完成,但按键消息已产生。框架先把这些 sys_event 缓存到静态结构体 e_tmp,等驱动初始化完成、set_key_wakeup_send_flag() 被调用时再补发:
static u8 key_scan_flag = 0, en_key_cnt = 0, en_key_cnt_flag = 1, nonsrc_wakeup_flag = 0, save_notify_flag = 0, key_wk_send_flag = 0;
struct sys_event e_tmp;
void clear_save_key_notify(void)
{
save_notify_flag = 0;
memset(&e_tmp, 0, sizeof(struct sys_event));
}
void set_key_wakeup_send_flag(u8 flag)
{
key_wk_send_flag = flag;
if (save_notify_flag) {
sys_event_notify(&e_tmp);
clear_save_key_notify();
}
}
u8 get_key_wakeup_send_flag(void)
{
return key_wk_send_flag;
}
void save_wakeup_key_notify(struct sys_event *event)
{
save_notify_flag = 1;
e_tmp.type = event->type;
// ... 拷贝事件其余字段
}
Source: key_driver.c
为什么需要补发机制:软关机唤醒是典型的"事件早于服务就绪"竞态。若直接丢弃唤醒瞬间的按键事件,用户"按一次开机键"可能被吞掉,体验上表现为"按了没反应"。缓存 + 补发把事件的生命周期延长到服务就绪之后,保证唤醒按键不丢失。
单击模式裁剪
框架支持"按键只响应单击事件"的裁剪能力:
#define KEY_EVENT_CLICK_ONLY_SUPPORT 1 //是否支持某些按键只响应单击事件
#if TCFG_SPI_LCD_ENABLE
#ifndef ALL_KEY_EVENT_CLICK_ONLY
#define ALL_KEY_EVENT_CLICK_ONLY 1 //是否全部按键只响应单击事件
#endif
#else
#ifndef ALL_KEY_EVENT_CLICK_ONLY
#define ALL_KEY_EVENT_CLICK_ONLY 0 //是否全部按键只响应单击事件
#endif
#endif
Source: key_driver.c
设计意图:带 SPI LCD 的 UI 交互通常用界面按钮替代"长按/组合键"等复杂按键语义,此时把所有按键裁剪为单击模式可大幅简化按键扫描与事件处理逻辑,同时减少 key_driver.c 的代码路径。
触摸抽象(Touch Abstraction)
触摸子系统包含三条路线,按成本与应用场景取舍:
- 内部 CTMU 触摸按键
ctmu_touch_key.c:复用芯片自带的 CTMU(Charge Time Measurement Unit,电荷时间测量单元)检测电容变化,无需外部触摸 IC,成本最低,用于耳机触摸区等少量按键场景; - 通用触摸按键
touch_key.c:作为key_driver.c聚合的按键类型之一,把触摸检测结果映射为标准按键事件,对应用层完全透明; - 外部触摸板 IC
touch_pad/SYD9557M.c:通过 IIC 挂接外部触摸板芯片,提供更复杂的触摸板/手势能力,适合音箱面板等场景;驱动位于apps/common/device/touch_pad/下,与按键框架解耦,单独管理。
触摸子系统与按键子系统共享同一个事件模型:无论内部 CTMU 还是外部 IC,最终都以 sys_event 形式进入系统事件总线,应用层无需关心触摸的物理实现,这是抽象层的核心价值。
传感器抽象(Sensor Abstraction)
gSensor 管理框架
apps/common/device/gSensor/fmy/gSensor_manage.c 是重力加速度计的管理器,整体被条件编译包裹,未启用时整个文件不参与编译:
#if (TCFG_GSENSOR_ENABLE && (TCFG_SC7A20_EN || TCFG_SC7A20_E_EN || TCFG_MSA310_EN))
spinlock_t iic_lock;
Source: gSensor_manage.c
管理器持有统一接口句柄与运行时信息结构体:
static const struct gsensor_platform_data *platform_data;
G_SENSOR_INTERFACE *gSensor_hdl = NULL;
G_SENSOR_INFO __gSensor_info = {.iic_delay = 10};
#define gSensor_info (&__gSensor_info)
Source: gSensor_manage.c
G_SENSOR_INTERFACE 封装了芯片相关的初始化、控制与数据读取操作(如 gravity_sensor_ctl(READ_GSENSOR_DATA, ...)),G_SENSOR_INFO 则保存 IIC 句柄与访问延时(默认 10)。接口句柄 + 信息结构体分离的设计意图:接口描述"能做什么",信息描述"当前怎么访问",使管理器逻辑与具体芯片驱动完全解耦。
IIC 总线抽象(硬件 IIC / 软件 IIC)
管理器通过一组宏把 IIC 底层操作统一抽象,TCFG_GSENOR_USER_IIC_TYPE 决定使用硬件 IIC 还是软件模拟 IIC:
#if TCFG_GSENOR_USER_IIC_TYPE
#define iic_init(iic) hw_iic_init(iic)
#define iic_uninit(iic) hw_iic_uninit(iic)
#define iic_start(iic) hw_iic_start(iic)
#define iic_stop(iic) hw_iic_stop(iic)
#define iic_tx_byte(iic, byte) hw_iic_tx_byte(iic, byte)
#define iic_rx_byte(iic, ack) hw_iic_rx_byte(iic, ack)
#define iic_read_buf(iic, buf, len) hw_iic_read_buf(iic, buf, len)
#define iic_write_buf(iic, buf, len) hw_iic_write_buf(iic, buf, len)
#define iic_suspend(iic) hw_iic_suspend(iic)
#define iic_resume(iic) hw_iic_resume(iic)
#else
#define iic_init(iic) soft_iic_init(iic)
#define iic_uninit(iic) soft_iic_uninit(iic)
#define iic_start(iic) soft_iic_start(iic)
#define iic_stop(iic) soft_iic_stop(iic)
#define iic_tx_byte(iic, byte) soft_iic_tx_byte(iic, byte)
#define iic_rx_byte(iic, ack) soft_iic_rx_byte(iic, ack)
#define iic_read_buf(iic, buf, len) soft_iic_read_buf(iic, buf, len)
#define iic_write_buf(iic, buf, len) soft_iic_write_buf(iic, buf, len)
#define iic_suspend(iic) soft_iic_suspend(iic)
#define iic_resume(iic) soft_iic_resume(iic)
#endif
Source: gSensor_manage.c
设计意图:同一套管理器代码可以适配两种 IIC 实现。硬件 IIC 吞吐高、不占 CPU;软件 IIC 引脚任意、便于布板。产品选型差异被收敛为一个编译宏,管理器与驱动代码完全不变。
三轴数据读取
get_gSensor_data() 把驱动层返回的 axis_info_t 结构数组展开为连续的三轴数据缓冲区:
//输出三轴数组和数据长度
int get_gSensor_data(short *buf)
{
axis_info_t accel_data[32];
int axis_info_len = gSensor_hdl->gravity_sensor_ctl(READ_GSENSOR_DATA, accel_data);
for (int i = 0; i < axis_info_len; i++) {
buf[i * 3] = accel_data[i].x;
buf[i * 3 + 1] = accel_data[i].y;
buf[i * 3 + 2] = accel_data[i].z;
}
return axis_info_len;
}
Source: gSensor_manage.c
注意这里的 accel_data[32] 是栈上数组,说明该接口面向短小、低频的数据读取(如单击/双击检测、抬腕检测),不适用于大数据流;上层拿到的是"点数 × 3"的连续数组,便于直接做运动算法(Motion_api.h)。
IIC 寄存器写命令(总线竞争保护)
gravity_sensor_command() 演示了管理器的总线安全写时序:用自旋锁 sensor_iic 保护整段 IIC 事务,任一字节发送失败立即跳转停止总线并解锁:
u8 gravity_sensor_command(u8 w_chip_id, u8 register_address, u8 function_command)
{
spin_lock(&sensor_iic);
u8 ret = 1;
iic_start(gSensor_info->iic_hdl);
if (0 == iic_tx_byte(gSensor_info->iic_hdl, w_chip_id)) {
ret = 0;
log_info("\n gsen iic wr err 0");
goto __gcend;
}
delay(gSensor_info->iic_delay);
if (0 == iic_tx_byte(gSensor_info->iic_hdl, register_address)) {
ret = 0;
log_info("\n gsen iic wr err 1");
goto __gcend;
}
delay(gSensor_info->iic_delay);
if (0 == iic_tx_byte(gSensor_info->iic_hdl, function_command)) {
ret = 0;
log_info("\n gsen iic wr err 2\n");
goto __gcend;
}
__gcend:
iic_stop(gSensor_info->iic_hdl);
spin_unlock(&sensor_iic);
return ret;
}
Source: gSensor_manage.c
设计意图:gSensor 可能被多个任务(运动检测、回调处理)并发访问,IIC 是共享总线,不加锁会产生字节交错,导致寄存器地址与数据错位。逐字节失败检查 + goto 统一收尾是嵌入式 C 的经典模式:任何一步失败都保证 iic_stop 与解锁执行,避免总线挂死。读数据路径 _gravity_sensor_get_ndata() 同样采用 spin_lock(&sensor_iic) 保护,对称设计。
芯片驱动与配套模块
- MSA310:
msa310.c+msa310_function.c,中科蓝讯/矽睿方案的重力加速度计; - SC7A20:
SC7A20_TR.c(数据手册读写)与SC7A20_E.c(增强/事件处理),士兰微方案; - 运动算法:
Motion_api.h提供基于三轴数据的运动识别接口(如抬手、摇一摇),上层(tone_player、key_event_deal)通过它消费传感器语义,而非直接读寄存器; - 入耳检测:
in_ear_detect/in_ear_manage.h管理佩戴检测(可基于 gSensor 或光学方案); - 光学鼠标传感器:
optical_mouse_sensor/OMSensor_manage.c统一管理 HAL3205/HAL3212 光学位移芯片(hal3205.c、hal3212.c),用于 HID 鼠标应用,通过 SPI 通信,与 gSensor 的 IIC 路径分离。
日志开关
管理器通过 GSENSOR_PRINTF_ENABLE 控制调试输出,关闭后所有 log_info 为空宏,零开销:
#if GSENSOR_PRINTF_ENABLE
#define log_info(x, ...) printf("[GSENSOR_MAN]" x "\r\n", ## __VA_ARGS__)
#define log_info_hexdump put_buf
#else
#define log_info(...)
#define log_info_hexdump(...)
#endif
Source: gSensor_manage.c
存储抽象(Storage Abstraction)
apps/common/device/norflash/norflash.c 提供片外 NorFlash 的底层驱动,是参数存储与固件升级的硬件基础。与按键/传感器不同,存储抽象不采用"事件 + 句柄"模型,而是以驱动函数接口形式被上层存储服务(user_cfg 参数系统、升级模块)直接调用,职责边界为:
- 片选/SPI 时序控制与读写擦除原语;
- 扇区/块管理所需的能力封装;
- 上层通过编译宏选择是否启用片外 Flash 方案(无片外 Flash 的低成本方案直接裁剪)。
存储抽象属于"底层驱动"而非"业务服务",因此本页仅界定其边界:上层参数读写、文件系统与 OTA 升级流程属于各自目录页的主题。
Core Flow
按键事件生命周期
按键从物理触发到应用处理的完整控制流如下:
sequenceDiagram
participant User as 用户
participant KeyDrv as 按键驱动<br/>(iokey/adkey/irkey/touch_key)
participant Frame as key_driver.c 框架
participant Bus as 系统事件总线
participant App as key_event_deal / 应用
User->>KeyDrv: 按下按键 (GPIO/ADC/红外/触摸)
KeyDrv->>KeyDrv: 扫描与去抖
KeyDrv->>Frame: 上报按键状态
Frame->>Frame: key_event_remap(e)<br/>(弱符号, 可重映射/组合键)
alt 软关机唤醒未初始化 (TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE)
Frame->>Frame: save_wakeup_key_notify(e)<br/>缓存事件到 e_tmp
Note over Frame: 驱动初始化完成后<br/>set_key_wakeup_send_flag(1) 补发
end
Frame->>Bus: sys_event_notify(&e)
Bus->>App: 分发事件
App->>App: 查询 get_key_poweron_flag()<br/>决定是否执行开机流程
App-->>User: 播放按键音/执行功能 (TCFG_KEY_TONE_EN)
关键设计点:
- 去抖在驱动层完成,框架层只处理"去抖后的有效状态",避免事件总线被抖动噪声淹没;
key_event_remap是唯一允许改键值的位置,组合键逻辑集中在此,扫描与分发代码保持简单;- 软关机唤醒场景下事件被缓存补发,保证"开机键"这类关键事件不丢失;
- 按键开机标志
key_poweron_flag由框架持有,应用层通过get_key_poweron_flag()在事件处理时判断是否附带开机动作,与事件本身解耦。
gSensor 数据读取流程
sequenceDiagram
participant Task as 运动检测任务
participant Mgr as gSensor_manage.c
participant Lock as sensor_iic 自旋锁
participant IIC as IIC 总线 (hw/soft)
participant Chip as MSA310 / SC7A20 芯片
Task->>Mgr: get_gSensor_data(buf) / gravity_sensor_ctl
Mgr->>Lock: spin_lock(&sensor_iic)
activate Lock
Mgr->>IIC: iic_start / iic_tx_byte(寄存器地址)
IIC->>Chip: 时钟 + 数据线时序
Chip-->>IIC: ACK/数据
IIC-->>Mgr: 读取三轴原始数据
Mgr->>Lock: spin_unlock(&sensor_iic)
deactivate Lock
Mgr->>Mgr: 展开为 x/y/z 连续数组
Mgr-->>Task: 点数与三轴数据
Task->>Task: Motion_api 运动识别<br/>(抬手/摇一摇/单击双击)
Task-->>App: 触发对应动作
关键设计点:
- 整段 IIC 事务持锁:从
iic_start到iic_stop全程处于自旋锁保护下,任何任务都不能插入半个字节,保证寄存器寻址与数据的原子性; - 失败即止:任一字节发送失败立即
iic_stop并解锁,防止总线挂死(goto __gcend统一收尾); - 读取路径与写命令对称:
_gravity_sensor_get_ndata()使用同一把锁与同一套宏,读写互斥; - 硬件/软件 IIC 的差异被宏隐藏,任务层无感知。
Usage Examples
示例 1:重映射按键值实现组合键
应用层重写弱符号 key_event_remap(),把"上一曲 + 音量+"同时按下映射为"下一曲":
// 用户工程中重写框架的弱符号函数
int key_event_remap(struct sys_event *e)
{
// 判断 e 中的按键值,若满足组合条件则改写为新的键值
// 返回 true 表示继续分发,返回 false 表示吞掉该事件
return true;
}
Source: key_driver.c(框架默认实现;用户覆盖后按组合键逻辑改写
e的键值字段)
示例 2:按键开机标志位操作
应用在开机/关机流程中查询与清除开机标志:
void set_key_poweron_flag(u8 flag); // 按键驱动检测到开机键时置位
u8 get_key_poweron_flag(void); // 应用事件处理时查询是否由按键触发开机
void clear_key_poweron_flag(void); // 开机流程完成后清除
Source: key_driver.c
示例 3:读取三轴加速度数据
运动检测任务通过管理器接口获取连续三轴数据:
short accel_buf[32 * 3];
int n = get_gSensor_data(accel_buf); // 返回有效点数
for (int i = 0; i < n; i++) {
short x = accel_buf[i * 3 + 0];
short y = accel_buf[i * 3 + 1];
short z = accel_buf[i * 3 + 2];
// 交给 Motion_api 或业务逻辑处理
}
Source: gSensor_manage.c
示例 4:向传感器芯片写寄存器命令
需要配置传感器(如设置量程、使能中断)时直接调用:
// 写芯片地址 0x30 的寄存器 0x20 为 0x0F(示例参数)
u8 ret = gravity_sensor_command(0x30, 0x20, 0x0F);
if (ret == 0) {
// IIC 发送失败,按错误处理(如重试或上报)
}
Source: gSensor_manage.c
Configuration Options
设备抽象层采用编译期配置(宏定义),配置分散在 app_config.h、板级配置文件与 key_driver.c 顶部的默认宏中。以下是本页涉及的配置项汇总:
| 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_GSENSOR_ENABLE | 整型宏 | 0/1(板级决定) | 总开关:是否启用 gSensor 管理框架 |
TCFG_SC7A20_EN | 整型宏 | 0 | 是否启用 SC7A20(士兰微)驱动 |
TCFG_SC7A20_E_EN | 整型宏 | 0 | 是否启用 SC7A20_E(增强型)驱动 |
TCFG_MSA310_EN | 整型宏 | 0 | 是否启用 MSA310 驱动(gSensor 条件编译要求三者至少其一) |
TCFG_GSENOR_USER_IIC_TYPE | 整型宏 | 0 | IIC 实现选择:1=硬件 IIC(hw_iic_*),0=软件 IIC(soft_iic_*) |
TCFG_IRSENSOR_ENABLE | 整型宏 | 0 | 红外按键/红外传感器使能(影响 irkey.c 与 ir_manage.h 的引入) |
TCFG_KEY_TONE_EN | 整型宏 | 0 | 按键音使能,开启后引入 tone_player.h |
TCFG_SOFTOFF_WAKEUP_KEY_DRIVER_ENABLE | 整型宏 | 0 | 软关机按键唤醒驱动使能,开启后启用事件缓存补发机制 |
TCFG_SPI_LCD_ENABLE | 整型宏 | 0 | SPI LCD 使能,间接决定 ALL_KEY_EVENT_CLICK_ONLY 默认值 |
KEY_EVENT_CLICK_ONLY_SUPPORT | 整型宏 | 1 | 是否支持"某些按键只响应单击" |
ALL_KEY_EVENT_CLICK_ONLY | 整型宏 | 0(SPI LCD 开启时为 1) | 是否全部按键只响应单击事件 |
GSENSOR_PRINTF_ENABLE | 整型宏 | 0 | gSensor 调试日志开关,关闭后 log_info 为空宏 |
选型矩阵:TCFG_GSENSOR_ENABLE 与 TCFG_SC7A20_EN / TCFG_SC7A20_E_EN / TCFG_MSA310_EN 组合决定编译哪颗传感器驱动;TCFG_GSENOR_USER_IIC_TYPE 决定总线实现;按键侧无需选型宏——key_driver.c 在编译期聚合全部按键驱动,实际生效的键值由板级配置决定。
API Reference
key_driver.c(按键框架)
int key_event_remap(struct sys_event *e)(弱符号,可覆盖)
- 描述:按键事件分发前的最后一道处理钩子,用于键值重映射与组合键实现。默认返回
true(原样分发)。 - 参数:
e— 待分发的系统事件指针。 - 返回:
true继续分发,false吞掉事件(需在覆盖实现中自行返回)。
void set_key_poweron_flag(u8 flag)
- 描述:设置按键开机标志位,按键驱动在检测到开机键时调用。
- 参数:
flag— 1 置位 / 0 清除。
u8 get_key_poweron_flag(void)
- 描述:查询当前是否由按键触发开机。应用在事件处理中据此决定是否附带开机动作。
- 返回:1 表示按键开机标志有效。
void clear_key_poweron_flag(void)
- 描述:开机流程完成后清除标志,避免重复触发。
void save_wakeup_key_notify(struct sys_event *event)
- 描述:软关机唤醒模式下缓存尚未被处理的事件(保存到静态
e_tmp,置save_notify_flag)。 - 参数:
event— 需要缓存的系统事件。
void set_key_wakeup_send_flag(u8 flag)
- 描述:设置"软关机按键唤醒补充发键"标志;若存在缓存事件则立即补发并清除缓存。
- 参数:
flag— 1 表示驱动已初始化可发键。
u8 get_key_wakeup_send_flag(void)
- 描述:查询补充发键标志状态。
- 返回:1 表示可补充发键。
void clear_save_key_notify(void)
- 描述:清除缓存的 key_notify(清
save_notify_flag并清零e_tmp)。
gSensor_manage.c(传感器管理)
int get_gSensor_data(short *buf)
- 描述:读取三轴加速度数据并展开为连续数组。内部通过
gSensor_hdl->gravity_sensor_ctl(READ_GSENSOR_DATA, ...)读取。 - 参数:
buf— 输出缓冲区,布局为x0,y0,z0, x1,y1,z1, ...。 - 返回:有效数据点数(
axis_info_len),调用方据此计算缓冲区实际使用量(点数 × 3)。
u8 gravity_sensor_command(u8 w_chip_id, u8 register_address, u8 function_command)
- 描述:向传感器芯片写一个寄存器命令。全程持
sensor_iic自旋锁,任一字节失败即停止总线并返回错误。 - 参数:
w_chip_id— 写芯片地址(含写位);register_address— 目标寄存器;function_command— 写入的命令值。 - 返回:1 成功,0 失败(字节发送错误)。
- 错误日志:
"gsen iic wr err 0/1/2"分别对应地址/寄存器/命令字节发送失败。
u8 _gravity_sensor_get_ndata(u8 r_chip_id, u8 register_address, u8 *buf, u8 data_len)
- 描述:从传感器芯片连续读取
data_len字节数据。同样持sensor_iic自旋锁。 - 参数:
r_chip_id— 读芯片地址(含读位);register_address— 起始寄存器;buf— 输出缓冲区;data_len— 读取字节数。 - 返回:实际读取字节数(
read_len)。
Failure Modes, Edge Cases & Concurrency
IIC 总线竞争(并发安全)
gSensor 可被多个任务并发访问(运动检测任务、回调处理、初始化流程)。框架用自旋锁 sensor_iic 保护整段 IIC 事务(从 iic_start 到 iic_stop),任何任务都不能在事务中途插入字节。未持锁的裸读写是典型故障源:寄存器地址与数据字节错位,导致传感器进入错误状态。SDK 的约定是所有 gSensor 总线访问必须走管理器接口,由管理器统一加锁。
IIC 通信失败与总线挂死
gravity_sensor_command() 对每个字节的发送结果做失败检查,失败即 goto __gcend 统一执行 iic_stop 与 spin_unlock。这一设计的必要性:
- 若失败后不停止总线,SCL/SDA 可能停留在中间状态,导致后续所有 IIC 事务错乱;
- 若失败后不解锁,其他任务将永久阻塞在自旋锁上,形成死锁;
- 调用方需根据返回值(0/1)决定重试或上报,管理器本身不重试——重试策略属于上层业务。
软关机唤醒竞态(事件早于服务就绪)
系统从软关机唤醒时,按键驱动可能尚未初始化完成,但用户按下的"开机键"事件已经产生。若不处理,事件被丢弃,表现为"按开机键无反应"。框架的解决方案:
- 唤醒早期按键事件被
save_wakeup_key_notify()缓存到静态e_tmp; - 驱动初始化完成后调用
set_key_wakeup_send_flag(1)触发补发(sys_event_notify(&e_tmp))并清除缓存; clear_save_key_notify()提供显式清理入口,防止陈旧事件滞留。
边界注意:静态缓存 e_tmp 只能保存一个事件,唤醒瞬间若有多个按键事件,仅最后一个被保留——这是资源受限下的取舍,唤醒场景通常只关心单个开机键。
条件编译导致的"隐性缺失"
gSensor 管理器的全部代码被 #if (TCFG_GSENSOR_ENABLE && (TCFG_SC7A20_EN || TCFG_SC7A20_E_EN || TCFG_MSA310_EN)) 包裹。若板级配置启用了总开关但未启用任何芯片驱动,整个管理器不会编译,gSensor_hdl 恒为 NULL,任何 get_gSensor_data() 调用都会解引用空指针。排查此类问题时优先核对选型矩阵。
弱符号默认行为
key_event_remap() 的默认实现直接返回 true。若用户覆盖实现时漏掉 return true(或错误返回 false),所有按键事件将被吞掉——覆盖时需保持"放行"语义。
单击模式裁剪的边界
ALL_KEY_EVENT_CLICK_ONLY 开启后所有按键只响应单击,长按/组合键语义全部失效。SPI LCD 方案默认开启此模式以简化交互;若产品在带屏方案中仍需要长按功能,需要自行调整该宏并评估按键扫描逻辑的兼容性。
Performance & Operational Considerations
- 按键扫描开销:按键扫描在框架中周期性执行(基于
system/timer.h),开销与按键数量成正比;单击模式裁剪(ALL_KEY_EVENT_CLICK_ONLY)可减少扫描与事件处理的代码路径。 - IIC 访问延时:
G_SENSOR_INFO.iic_delay默认 10(__gSensor_info = {.iic_delay = 10}),每次字节传输间插入该延时,用于适配软件 IIC 时序。降低延时可提升吞吐,但可能引入时序不满足;调优需结合具体芯片手册。 - 自旋锁与实时性:
sensor_iic为自旋锁,持锁期间禁止调度,因此锁内代码必须短小(仅 IIC 字节级操作);不要在持锁路径中插入长延时或阻塞调用。 - 调试开关:
GSENSOR_PRINTF_ENABLE控制 gSensor 日志;量产固件应关闭以消除 printf 开销与日志缓冲占用。key_driver.c的LOG_DEBUG_ENABLE/LOG_INFO_ENABLE同理可裁剪。 - 栈占用:
get_gSensor_data()在栈上分配axis_info_t accel_data[32](约 32 × 6 字节),调用它的任务栈需预留足够空间,避免深调用链中溢出。
Extension Points
- 新增按键类型:实现按键驱动(参考
iokey.c/adkey.c的扫描与上报模式),在key_driver.c顶部加入#include,复用框架的事件投递、开机标志与唤醒补发机制,应用层零改动。 - 键值重映射 / 组合键:重写弱符号
key_event_remap(struct sys_event *e),在分发前改写键值或吞掉事件。 - 新增 gSensor 芯片:实现
G_SENSOR_INTERFACE接口(初始化、控制、数据读取),在gSensor_manage.c的条件编译中加入新的TCFG_xxx_EN宏分支,管理器与上层算法(Motion_api)无需修改。 - 切换 IIC 实现:仅需改
TCFG_GSENOR_USER_IIC_TYPE(1=硬件 IIC,0=软件 IIC),宏层自动切换hw_iic_*/soft_iic_*。 - 挂接外部触摸板 IC:在
apps/common/device/touch_pad/下新增芯片驱动,通过 IIC 与上层对接,保持与按键事件模型的解耦。
Tests
本目录未发现针对设备抽象层的独立单元测试工程。该层正确性主要依赖:
- 编译期裁剪验证:通过板级配置的宏组合(
TCFG_GSENSOR_ENABLE与三颗芯片宏的互斥/组合)验证条件编译分支; - 集成验证:按键事件在
key_event_deal中的行为、gSensor 数据在运动检测任务中的表现,属于整机功能测试范畴; - 若需补充自动化测试,建议围绕
gravity_sensor_command()的"失败即收尾"路径(模拟 IIC 发送失败)与save_wakeup_key_notify的缓存/补发时序编写桩测试。
Related Links
- key_driver.c(按键框架源码)
- gSensor_manage.c(传感器管理源码)
- gsensor_api.c / gSensor_manage.h(传感器接口)
- OMSensor_manage.c(光学鼠标传感器管理)
- norflash.c(片外 NorFlash 驱动)
- SYD9557M.c(外部触摸板 IC 驱动)
- 相关目录页:音频通路见 8.x 音频组件页;HID/鼠标应用见 apps/hid 相关页;Mesh 传感器模型见 Mesh 应用页;参数存储与 OTA 升级见对应存储/升级目录页。