杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

设备外设抽象(按键/触摸/传感器/存储)

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,任何硬件变更都会导致应用层大范围修改。

设备抽象层因此承担三个职责:

  1. 统一接口:为同类外设定义统一的操作句柄与回调(如按键统一向系统事件总线发 sys_event,gSensor 统一通过 G_SENSOR_INTERFACE 句柄读写);
  2. 编译期选型:通过 TCFG_* 编译宏决定启用哪些驱动与哪颗传感器芯片,未启用的驱动代码不参与编译,不影响代码体积;
  3. 运行时解耦:驱动内部处理时序、去抖、中断/轮询、总线竞争(如 IIC 自旋锁),上层只消费抽象结果。

关键概念与术语

术语含义
sys_event系统事件结构体,按键等外设向系统事件总线投递的事件,应用层在 key_event_deal 等处统一处理
G_SENSOR_INTERFACEgSensor 驱动接口句柄,统一封装芯片初始化、睡眠/唤醒、数据读取等操作
G_SENSOR_INFOgSensor 运行时信息结构体,包含 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)

触摸子系统包含三条路线,按成本与应用场景取舍:

  1. 内部 CTMU 触摸按键 ctmu_touch_key.c:复用芯片自带的 CTMU(Charge Time Measurement Unit,电荷时间测量单元)检测电容变化,无需外部触摸 IC,成本最低,用于耳机触摸区等少量按键场景;
  2. 通用触摸按键 touch_key.c:作为 key_driver.c 聚合的按键类型之一,把触摸检测结果映射为标准按键事件,对应用层完全透明;
  3. 外部触摸板 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)

关键设计点:

  1. 去抖在驱动层完成,框架层只处理"去抖后的有效状态",避免事件总线被抖动噪声淹没;
  2. key_event_remap 是唯一允许改键值的位置,组合键逻辑集中在此,扫描与分发代码保持简单;
  3. 软关机唤醒场景下事件被缓存补发,保证"开机键"这类关键事件不丢失;
  4. 按键开机标志 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整型宏0IIC 实现选择: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整型宏0SPI LCD 使能,间接决定 ALL_KEY_EVENT_CLICK_ONLY 默认值
KEY_EVENT_CLICK_ONLY_SUPPORT整型宏1是否支持"某些按键只响应单击"
ALL_KEY_EVENT_CLICK_ONLY整型宏0(SPI LCD 开启时为 1)是否全部按键只响应单击事件
GSENSOR_PRINTF_ENABLE整型宏0gSensor 调试日志开关,关闭后 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)决定重试或上报,管理器本身不重试——重试策略属于上层业务。

软关机唤醒竞态(事件早于服务就绪)

系统从软关机唤醒时,按键驱动可能尚未初始化完成,但用户按下的"开机键"事件已经产生。若不处理,事件被丢弃,表现为"按开机键无反应"。框架的解决方案:

  1. 唤醒早期按键事件被 save_wakeup_key_notify() 缓存到静态 e_tmp;
  2. 驱动初始化完成后调用 set_key_wakeup_send_flag(1) 触发补发(sys_event_notify(&e_tmp))并清除缓存;
  3. 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

  1. 新增按键类型:实现按键驱动(参考 iokey.c / adkey.c 的扫描与上报模式),在 key_driver.c 顶部加入 #include,复用框架的事件投递、开机标志与唤醒补发机制,应用层零改动。
  2. 键值重映射 / 组合键:重写弱符号 key_event_remap(struct sys_event *e),在分发前改写键值或吞掉事件。
  3. 新增 gSensor 芯片:实现 G_SENSOR_INTERFACE 接口(初始化、控制、数据读取),在 gSensor_manage.c 的条件编译中加入新的 TCFG_xxx_EN 宏分支,管理器与上层算法(Motion_api)无需修改。
  4. 切换 IIC 实现:仅需改 TCFG_GSENOR_USER_IIC_TYPE(1=硬件 IIC,0=软件 IIC),宏层自动切换 hw_iic_* / soft_iic_*。
  5. 挂接外部触摸板 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 升级见对应存储/升级目录页。
Prev
音频应用组件
Next
蓝牙公共模块与消息联动