杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目概述与能力地图
    • 构建系统与编译流程
    • 芯片系列与规格
  • 应用示例

    • SPP 与 BLE 双模透传
    • AT 指令串口协议
    • HID 设备应用
    • 蓝牙 Mesh 应用
    • 公共组件与第三方协议
  • 芯片平台支持

    • 外设驱动
    • 电源与充电管理
    • 启动与链接脚本
    • 配置工具与 OTA 资源
  • 协议栈与系统库

    • 蓝牙控制器
    • BTStack 协议栈接口
    • 系统内核与服务
    • OTA 升级机制
  • 文档与参考

    • 蓝牙 AT 协议参考
    • 开发文档与认证信息

公共组件与第三方协议

本页面向 AC630N 蓝牙 SDK 中 apps/common 目录下的公共组件(按键驱动、代码切换、光电鼠标传感器、升级组件)与第三方协议栈(杰理 JL RCSP、透传 demo、AT 命令、HOGP、SIG Mesh)的完整实现说明。

Purpose and Scope

本页覆盖 apps/common 中所有被各 apps/<APP_CASE>(如 hid)工程共享的可复用组件与第三方协议实现,包括:

  • 公共组件:key/(按键驱动框架)、code_switch/(代码切换/升级模式选择)、optical_mouse_sensor/(光电鼠标传感器管理)、update/(OTA 升级入口)。
  • 第三方协议:third_party_profile/jieli/(杰理 RCSP 蓝牙配对/升级协议、SPP/BLE 透传 demo、AT 命令、HOGP)、third_party_profile/sig_mesh/(蓝牙 Mesh)。

不在本页范围:各具体应用工程(如 apps/hid、apps/classic 等)的业务逻辑、apps/app_cfg 的板级配置与 apps/debug.c 的调试框架、SDK 底层(include_lib 中的蓝牙协议栈、VM/FS 驱动、系统事件/定时器内核)——这些属于各自独立页面的话题。本页聚焦于这些组件如何被组装、如何与底层交互、如何被配置裁剪。

Overview

在 AC630N 蓝牙 SDK 中,apps/common 是应用层与底层 SDK 之间的共享中间层。它解决三类问题:

  1. 硬件抽象复用:不同应用(HID 键盘、音频、遥控器)都使用同一套按键扫描、消抖、事件判定逻辑(key_driver),通过注册不同的 get_value() 实现(IO 口、ADC、滑动、红外、触摸、旋转编码)来适配不同硬件。
  2. 协议接入标准化:杰理私有协议(RCSP)与标准协议(SIG Mesh、HOGP)被封装成可裁剪的 third_party_profile 组件,通过 apps/hid/Makefile 等构建脚本按需编入,避免未用代码进入固件。
  3. 配置与升级通道:custom_cfg.c 通过 config.dat 文件 + VM 存储保存可远程修改的配置(广播包、设备名、PIN 码、链接密钥等),配合 RCSP 升级通道实现"空中改配置/升级"。

构建层面,apps/hid/Makefile 通过 -I$(ROOT)/apps/common/ 及 objs += 列表把上述组件逐个加入编译,因此裁剪组件的入口就是各应用工程的 Makefile,这也是理解整个 apps/common 的关键线索。

Architecture

下图展示了 apps/common 公共组件与第三方协议在整体软件栈中的位置及其与底层 SDK 的依赖关系:

flowchart TD
    subgraph sg_App["应用层 apps/&lt;APP_CASE&gt;"]
        App["app 工程 (如 hid)"]
    end

    subgraph sg_Common["公共组件 apps/common"]
        Key["key 按键驱动<br/>key_driver / iokey / adkey"]
        CodeSwitch["code_switch 代码切换"]
        OMS["optical_mouse_sensor<br/>OMSensor_manage / hal3205"]
        Update["update 升级组件"]
    end

    subgraph sg_Third["第三方协议 apps/common/third_party_profile"]
        Jieli["jieli 杰理协议族<br/>RCSP / 透传 / AT / HOGP"]
        Mesh["sig_mesh 蓝牙 Mesh"]
    end

    subgraph sg_SDK["SDK 底层"]
        Event["system/event 事件系统"]
        Timer["system/timer 定时器"]
        BT["蓝牙协议栈 (SPP/BLE)"]
        VM["VM 存储 / FS 文件系统"]
    end

    App --> Key
    App --> CodeSwitch
    App --> OMS
    App --> Update
    App --> Jieli
    App --> Mesh
    Key --> Event
    Key --> Timer
    Jieli --> BT
    Jieli --> VM
    Jieli --> Update
    Mesh --> BT
    Update --> VM

各节点职责与连线说明:

  • key 组件是纯定时器驱动的扫描框架:系统定时器周期性调用 key_driver_scan(),扫描结果通过 sys_event(system/event.h)上报给应用,不直接触碰蓝牙协议栈。
  • code_switch 与 update 服务于固件升级与多代码区切换,被 RCSP 升级流程(rcsp_user_update、rcsp_ch_loader_download)调用。
  • jieli 协议族直接依赖底层 SPP/BLE 协议栈与 VM/FS,是唯一横跨"应用 ⇄ 协议栈 ⇄ 存储"的组件;sig_mesh 则完全运行在 BLE 之上。
  • optical_mouse_sensor 是独立的外设驱动层(HAL3205 传感器),通过 OMSensor_manage 向应用提供鼠标位移/按键数据,典型用于 HID 鼠标应用。

构建证据见 apps/hid/Makefile,其中明确列出了 apps/common/、apps/common/include/、third_party_profile/jieli/JL_rcsp/ 等头文件搜索路径以及 code_switch/include/、optical_mouse_sensor/include/ 等子模块路径。

公共组件:按键驱动子系统

组件结构与设计意图

按键驱动位于 apps/common/key/,由多个文件组成:key_driver.c(扫描框架与事件判定)、iokey.c(IO 口按键)、adkey.c(ADC 按键)、slidekey.c(滑动按键)、irkey.c(红外按键)、touch_key.c(触摸按键)、rdec_key.c(旋转编码器按键)。构建时通过 apps/hid/Makefile 一并编入:

objs += \
	$(ROOT)/apps/common/key/iokey.o \
	$(ROOT)/apps/common/key/adkey.o \
	$(ROOT)/apps/common/key/key_driver.o

Source: apps/hid/Makefile

设计上采用策略模式:struct key_driver_para 描述一次扫描所需的全部状态与回调(get_value() 读键值、filter_time 消抖时间、long_time 长按阈值、press_cnt/click_cnt 计数等),key_driver_scan() 只负责"周期性读取 → 消抖 → 判定事件类型 → 上报"。具体按键类型(IO/ADC/红外/触摸)通过不同的 get_value() 实现注入,框架代码无需改动即可适配新硬件。

扫描与消抖机制

key_driver_scan() 由系统定时器按固定周期(约 10ms)调用,核心代码如下:

static void key_driver_scan(void *_scan_para)
{
    struct key_driver_para *scan_para = (struct key_driver_para *)_scan_para;

    u8 key_event = 0;
    u8 cur_key_value = NO_KEY;
    u8 key_value = 0;
    struct sys_event e;
    static u8 poweron_cnt = 0;

    cur_key_value = scan_para->get_value();
    ...
//===== 按键消抖处理
    if (cur_key_value != scan_para->filter_value && scan_para->filter_time) {	//当前按键值与上一次按键值如果不相等, 重新消抖处理, 注意filter_time != 0;
        scan_para->filter_cnt = 0; 		//消抖次数清0, 重新开始消抖
        scan_para->filter_value = cur_key_value;	//记录上一次的按键值
        return; 		//第一次检测, 返回不做处理
    } 		//当前按键值与上一次按键值相等, filter_cnt开始累加;
    if (scan_para->filter_cnt < scan_para->filter_time) {
        scan_para->filter_cnt++;
        return;
    }
//===== 按键消抖结束, 开始判断按键类型(单击, 双击, 长按, 多击, HOLD, (长按/HOLD)抬起)

Source: apps/common/key/key_driver.c

消抖算法要点:

  • 两段式消抖:第一段比较 cur_key_value 与 filter_value,不一致则重置 filter_cnt 并记录新值后立即返回;一致则进入第二段,累加 filter_cnt 直到达到 filter_time。这保证按键电平在连续 filter_time 个周期内稳定才被确认,滤除机械抖动。
  • 防误判开关:代码中保留了一段注释掉的"上电前 250 个周期不做按键处理"逻辑,用于滤掉 adkey 与 mic 连在一起时电容充放电导致的开机按键误判(典型 Type-C 耳机场景),说明该框架曾为特定硬件形态做过针对性处理。
  • 按下/抬起状态机:cur_key_value != scan_para->last_key 时判定状态翻转——从有效键值变为 NO_KEY 视为抬起(若 press_cnt >= long_time 则上报 KEY_EVENT_UP),从 NO_KEY 变为有效键值视为按下(重置 press_cnt,并通过 notify_value 判断是否同一按键以累加 click_cnt,实现单击/双击/多击)。

事件判定与上报流程

判定完成后,按键事件通过系统事件(sys_event)上报,上报前经过一个可重映射的弱函数钩子:

//=======================================================//
// 按键值重新映射函数:
// 用户可以实现该函数把一些按键值重新映射, 可用于组合键的键值重新映射
//=======================================================//
int __attribute__((weak)) key_event_remap(struct sys_event *e)
{
    return true;
}

Source: apps/common/key/key_driver.c

key_event_remap 是弱符号(weak symbol),SDK 默认实现直接返回 true(不重映射);应用工程若需实现组合键(如 FN+音量键映射为其他功能),只需在业务代码中定义同名强符号函数即可覆盖,无需修改公共组件——这是 SDK 典型的扩展点设计。

按键扫描核心流程

flowchart TD
    Start([定时器触发 key_driver_scan]) --> GetVal["scan_para->get_value() 读取当前按键值"]
    GetVal --> Active["更新 is_key_active 活跃计数"]
    Active --> Debounce{"cur != filter_value<br/>且 filter_time != 0 ?"}
    Debounce -->|"是 (电平变化)"| Reset["filter_cnt = 0<br/>记录 filter_value 后返回"]
    Debounce -->|"否 (电平稳定)"| Cnt{"filter_cnt < filter_time ?"}
    Cnt -->|"是"| Inc["filter_cnt++ 返回"]
    Cnt -->|"否 (消抖完成)"| Judge{"cur != last_key ?"}
    Judge -->|"抬起 (cur = NO_KEY)"| Up{"press_cnt >= long_time ?"}
    Up -->|"是"| EvtUp["上报 KEY_EVENT_UP"]
    Up -->|"否"| Click["click_delay_cnt = 1<br/>等待连击窗口"]
    Judge -->|"按下 (cur = 有效键)"| Press["press_cnt = 1<br/>同键 click_cnt++ / 异键重置"]
    Click --> Wait([等待连击延时])
    EvtUp --> Notify["判定 单击/双击/长按/HOLD 事件类型"]
    Press --> Notify
    Notify --> Remap["key_event_remap() 弱函数重映射"]
    Remap --> Post["sys_event_notify 发送按键事件"]
    Post --> End([进入下一扫描周期])
    Wait --> End

设计意图说明:

  • 消抖在框架内完成而非各驱动内完成,保证所有按键类型行为一致;filter_time == 0 时跳过消抖,供对响应速度敏感的场景使用。
  • is_key_active 声明为 volatile u8,因为它在定时器中断上下文中被读写,应用代码(如待机判断)也可能读取,volatile 防止编译器缓存优化导致读取到过期值。
  • 长按(press_cnt >= long_time)后抬起才上报 KEY_EVENT_UP,而 HOLD 事件在长按期间持续上报;KEY_EVENT_CLICK_ONLY_SUPPORT 与 ALL_KEY_EVENT_CLICK_ONLY 宏(见 key_driver.c)用于让某些按键(如 SPI LCD 场景下)只响应单击,简化事件处理。

公共组件:代码切换与光电鼠标传感器

code_switch(代码切换)

apps/common/code_switch/code_switch.c 实现多代码区/多固件切换能力,其头文件路径 code_switch/include/ 被显式加入各工程搜索路径(见 apps/hid/Makefile)。该组件与 update/ 升级组件配合:升级下载完成后,通过 code_switch 将启动标志/跳转地址写入指定区域,实现复位后从新固件启动;它也是 RCSP 远程升级(rcsp_user_update)的落地执行者。代码切换通常需要把关键代码段放入固定地址(#pragma code_seg 一类段重定位),保证切换逻辑在任意固件版本下都可执行。

optical_mouse_sensor(光电鼠标传感器)

该组件为 HID 鼠标类应用提供外设抽象,包含两层:

  • OMSensor_manage.c:传感器管理/调度层,负责初始化、周期性读取位移与按键、向应用层上报鼠标数据。
  • hal3205/hal3205.c:HAL3205 传感器芯片驱动(芯片级寄存器读写)。

构建证据见 apps/hid/Makefile 与对象列表:

objs += \
	$(ROOT)/apps/common/optical_mouse_sensor/OMSensor_manage.o \
	$(ROOT)/apps/common/optical_mouse_sensor/hal3205/hal3205.o \

Source: apps/hid/Makefile

分层意图:hal3205 只负责"如何操作这颗芯片",OMSensor_manage 负责"何时操作、数据给谁",替换传感器型号时只需新增一个 hal 层驱动并保持管理接口不变。

第三方协议族(third_party_profile)

apps/common/third_party_profile/ 按厂商/标准组织,分为 jieli/(杰理私有协议)与 sig_mesh/(蓝牙 SIG Mesh 标准协议)两大块。下图展示协议栈分层关系:

flowchart LR
    subgraph sg_App2["应用层"]
        User["用户 app 业务 (hid/classic 等)"]
    end

    subgraph sg_Proto2["第三方协议层 apps/common/third_party_profile"]
        RCSP["JL RCSP<br/>rcsp_bluetooth"]
        Trans["透传 demo<br/>spp_trans_data / le_trans_data"]
        AT["AT 命令<br/>spp_at_com / le_at_com"]
        HOGP["HOGP 键盘<br/>le_hogp"]
        Mesh["SIG Mesh<br/>access / adv_core"]
    end

    subgraph sg_Stack2["SDK 协议栈"]
        SPP["SPP 串口仿真"]
        BLE["BLE GATT"]
        HIDP["HID over GATT"]
    end

    User --> RCSP
    User --> Trans
    User --> AT
    User --> HOGP
    User --> Mesh
    RCSP --> SPP
    RCSP --> BLE
    RCSP --> Trans
    Trans --> SPP
    Trans --> BLE
    AT --> SPP
    AT --> BLE
    HOGP --> HIDP
    HIDP --> BLE
    Mesh --> BLE

JL RCSP(杰理遥控/配对/升级协议)

目录 jieli/JL_rcsp/ 是杰理私有 RCSP(Remote Control/SPP)协议实现,核心文件:

  • rcsp_bluetooth.c:RCSP 与蓝牙协议栈的桥接,管理 SPP/BLE 连接的建立、数据收发与协议分发。
  • rcsp_updata/rcsp_user_update.c:用户区固件升级,通过 RCSP 通道接收升级包并写入 Flash。
  • rcsp_updata/rcsp_ch_loader_download.c:芯片 Bootloader(loader)下载,用于引导区程序更新。
  • jieli/hid_user.c:HID 应用与 RCSP 的用户层适配。

编译入口(见 apps/hid/Makefile):

objs += \
	$(ROOT)/apps/common/third_party_profile/common/custom_cfg.o \
	$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_bluetooth.o \
	$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_updata/rcsp_user_update.o \
	$(ROOT)/apps/common/third_party_profile/jieli/JL_rcsp/rcsp_updata/rcsp_ch_loader_download.o \

Source: apps/hid/Makefile

RCSP 协议栈通过 RCSP_BTMATE_EN / RCSP_ADV_EN 宏选择 BTMATE 或 ADV 变体(见 custom_cfg.c),并据此引入不同的用户升级头文件(rcsp_user_update.h 或 rcsp_adv_user_update.h)——协议实现与配置存储紧耦合,custom_cfg 即为 RCSP 提供可空中修改的配置项。

SPP/BLE 透传与 AT 命令

trans_data_demo/ 提供最简数据透传示例:spp_trans_data.c(SPP 通道)、le_trans_data.c(BLE 通道)、spp_user.c(SPP 用户回调)。spp_at_com.c 与 le_at_com.c 则在透传基础上实现 AT 命令解析,允许手机/上位机通过串口仿真通道下发 AT+... 指令控制设备(如改名字、查电量)。这些 demo 是接入 RCSP 前理解"字节流如何从协议栈到业务"的最佳范例,其 include 路径 third_party_profile/jieli/trans_data_demo/ 已加入构建(见 apps/hid/Makefile)。

HOGP 与 SIG Mesh

  • le_hogp.c:HID over GATT Profile 实现,使设备可作 BLE 键盘/鼠标(与 hid_user.c 的 USB/HID 路径互补)。le_client_demo.c 提供 BLE 客户端示例。
  • sig_mesh/:SIG Mesh 标准协议实现,access.c 为模型访问层(model access),adaptation/ble_core/adv_core.c 将 Mesh 广播承载(PB-ADV)适配到底层 BLE 广播。

裁剪原则:sig_mesh 与 jieli 协议族互不依赖,应用工程只需在 Makefile 中删除对应 .o 与 -I 路径即可移除协议,减少固件体积——协议组件全部按"需要才编译"设计。

公共组件:自定义配置(custom_cfg)

apps/common/third_party_profile/common/custom_cfg.c 是 RCSP 生态的配置持久化层:它把"可被手机 App 远程修改的配置项"统一读写到 SD 卡文件 config.dat(配合 VM 存储),并自动附加 CRC 校验。配置文件路径与 API 族选择如下:

#define RES_CUSTOM_CFG_FILE	SDFILE_RES_ROOT_PATH"config.dat"

//配置:VM的接口采用692X还是693X
#define VM_API_AC692X	0
#define VM_API_AC693X	1
#define USE_VM_API_SEL VM_API_AC693X

//配置:是否在总是擦除EXIF区域然后重写,还是只有在EXIF信息有变化时才擦除
#define ALWAYS_ERASE_EXIF_AREA		0
#define ONLY_DIFF_ERASE_EXIF_AREA	1
#define EXIF_ERASE_CONFIG			ALWAYS_ERASE_EXIF_AREA

//配置:文件操作接口是用692X还是693X
#define SDFILE_API_AC692X	0
#define SDFILE_API_AC693X	1
#define USE_FS_API_SEL		SDFILE_API_AC693X

Source: apps/common/third_party_profile/common/custom_cfg.c

设计意图:SDK 同时兼容 AC692X/AC693X 两代芯片,VM 与文件系统 API 签名不同,因此用编译期宏 USE_VM_API_SEL / USE_FS_API_SEL 在编译期选择 API 族,避免运行期分支开销并保证链接正确性;EXIF_ERASE_CONFIG 则控制擦写策略(总是擦除 vs 仅差异擦除),在 Flash 寿命与实现复杂度之间取舍。

配置项的数据结构

所有配置项都遵循统一的 {u16 crc, u16 len, u8 data[]} 头+载荷 布局,读写时按结构体偏移量定位字段:

#define MEMBER_OFFSET_OF_STRUCT(type, member)       ((u32)&(((type *)0)->member))
#define MEMBER_SIZE_OF_STRUCT(type,member)			((u16)sizeof(((type *)0)->member))

typedef struct _adv_data_t {
    u16 crc;
    u16 len;
    u8 data[31];
} adv_data_cfg_t;

typedef struct _ble_name_t {
    u16 crc;
    u16 len;
    u8 data[31 - 2];
} ble_name_t;

typedef struct _bt_name_t {
    u16 crc;
    u16 len;
    u8 data[31];
} bt_name_t;

typedef struct _bt_pin_code_t {
    u16 crc;
    u16 len;
    char data[4];
} bt_pin_code_t;

Source: apps/common/third_party_profile/common/custom_cfg.c

同一文件中还有 ver_info_cfg_t(固件版本信息,载荷为 update_file_id_t)、reset_io_info_cfg_t(复位 IO)、pilot_lamp_io_info_cfg_t(指示灯 IO,4 字节)、link_key_info_cfg_t(链接密钥,载荷为 update_file_link_key_t)、last_device_connect_linkkey_cfg_t(上次连接设备密钥,16 字节)等结构(见 custom_cfg.c)。

模式分析:crc + len + data 是典型的"自描述记录"格式——len 使旧固件可跳过新版本 App 写入的更长载荷,crc 在读取时校验完整性,损坏则回退默认值;MEMBER_OFFSET_OF_STRUCT / MEMBER_SIZE_OF_STRUCT 让配置项可按字段增量读写(配合 EXIF_ERASE_CONFIG 的差异擦除策略),避免每次全量重写浪费 Flash 寿命。

配置选项

以下配置项均来自实际源码,按所属组件分组:

组件配置宏类型默认值说明
keyKEY_EVENT_CLICK_ONLY_SUPPORTint1是否支持某些按键只响应单击事件
keyALL_KEY_EVENT_CLICK_ONLYintTCFG_SPI_LCD_ENABLE 时 1,否则 0全部按键只响应单击(SPI LCD 场景)
keyMOUSE_KEY_SCAN_MODEint—鼠标扫描模式:按下即上报,不等待长按抬起
keyTCFG_IRSENSOR_ENABLEint—是否启用红外传感器(引入 irSensor/ir_manage.h)
keystruct key_driver_para.filter_timeu8由驱动注册时设定消抖周期数;0 表示不消抖
keystruct key_driver_para.long_timeu8由驱动注册时设定长按/HOLD 判定阈值(扫描周期数)
code_switchcode_switch 相关接口——多代码区切换,配合 update 组件
RCSPRCSP_BTMATE_ENint—启用 BTMATE 版 RCSP 协议与 rcsp_user_update.h
RCSPRCSP_ADV_ENint—启用 ADV 版 RCSP 协议与 rcsp_adv_user_update.h
custom_cfgRES_CUSTOM_CFG_FILEstringSDFILE_RES_ROOT_PATH"config.dat"自定义配置文件路径
custom_cfgUSE_VM_API_SELintVM_API_AC693X选择 AC692X 或 AC693X 的 VM API
custom_cfgUSE_FS_API_SELintSDFILE_API_AC693X选择 AC692X 或 AC693X 的文件系统 API
custom_cfgEXIF_ERASE_CONFIGintALWAYS_ERASE_EXIF_AREAEXIF 区域擦写策略(总是擦除/仅差异擦除)
custom_cfgCUSTOM_CFG_DEBUG_ENint1使能 cfg_puts/cfg_printf/cfg_printf_buf 调试输出

构建裁剪选项(各应用 Makefile):

配置点默认值说明
includes += -I$(ROOT)/apps/common/...按工程设定头文件搜索路径,决定组件是否可见
objs += $(ROOT)/apps/common/.../xxx.o按工程设定对象文件列表,未列入的组件不参与链接

API 参考

int key_event_remap(struct sys_event *e)(弱函数)

按键事件上报前调用的重映射钩子。

  • 参数:e — 待上报的系统事件(含按键值与事件类型)。
  • 返回:true 表示继续正常上报;返回 false 可吞掉事件。
  • 说明:__attribute__((weak)) 弱符号,默认实现直接返回 true;应用可定义同名强符号实现组合键重映射,覆盖公共组件行为。

static void key_driver_scan(void *_scan_para)

按键扫描主函数,由系统定时器周期性调用。

  • 参数:_scan_para — struct key_driver_para *,包含 get_value()、filter_value、filter_time、filter_cnt、last_key、press_cnt、click_cnt、notify_value、long_time、click_delay_cnt 等扫描状态。
  • 行为:读取键值 → 消抖 → 判定按下/抬起 → 判定单击/双击/长按/HOLD → 经 key_event_remap 后通过 sys_event 上报。
  • 返回:无(void)。

custom_cfg 配置结构体族

类型载荷字段用途
adv_data_cfg_tu8 data[31]BLE 广播数据包配置
ble_name_tu8 data[29]BLE 设备名配置
bt_name_tu8 data[31]经典蓝牙设备名配置
bt_pin_code_tchar data[4]蓝牙 PIN 码配置
ver_info_cfg_tupdate_file_id_t data固件版本信息
reset_io_info_cfg_tu8 data复位 IO 配置
pilot_lamp_io_info_cfg_tu8 data[4]指示灯 IO 配置
link_key_info_cfg_tupdate_file_link_key_t data配对链接密钥
last_device_connect_linkkey_cfg_tu8 data[16]上次连接设备链接密钥

所有结构统一为 {u16 crc; u16 len; ...} 头 + 载荷布局,供 RCSP 远程读写。

故障模式、边界情况与并发

按键抖动与误判

  • 消抖窗口不足:若 filter_time 设置过小,机械抖动会被当作真实按键,产生连发;框架要求 filter_time != 0 才启用消抖,0 值场景(快速响应)由调用方自行承担抖动风险。
  • 上电误判:adkey 与麦克风共用引脚时,电容充放电会导致开机瞬间误判按键;key_driver_scan 中保留的 poweron_cnt 注释代码正是该问题的修复预案(上电前 N 周期忽略按键)。
  • 长按抬起事件:只有 press_cnt >= long_time 后的抬起才上报 KEY_EVENT_UP(key_driver.c);短按抬起不直接发 UP,而是进入连击等待窗口(click_delay_cnt = 1),因此单击事件存在上报延迟(等待确认不是双击),交互设计需考虑该延迟。

配置损坏与 Flash 寿命

  • CRC 校验失败:custom_cfg 各配置项带 u16 crc 头,读取时校验失败应回退默认配置;config.dat 为外部文件,可能被拔卡/断电写坏,恢复策略依赖该 CRC 机制。
  • Flash 擦写:EXIF_ERASE_CONFIG 默认 ALWAYS_ERASE_EXIF_AREA(总是擦除后重写),频繁远程改配置会加速 Flash 磨损;ONLY_DIFF_ERASE_EXIF_AREA 选项通过 MEMBER_OFFSET_OF_STRUCT/MEMBER_SIZE_OF_STRUCT 按字段比较,仅在变化时擦写,是延长寿命的推荐选项。
  • 版本兼容:len 字段使旧固件可跳过新载荷;新 App 写入更长数据时旧固件只读自身认识的字段,避免结构体越界。

并发与中断上下文

  • 按键扫描运行在定时器中断上下文,与业务线程共享 struct key_driver_para 状态;is_key_active 被声明为 volatile 以保证跨上下文可见性。业务代码不应直接修改扫描状态,只能通过 key_event_remap 或事件处理回调介入。
  • RCSP 升级(rcsp_user_update/rcsp_ch_loader_download)涉及擦写 Flash 的长时间临界区,升级期间蓝牙连接与按键扫描必须暂停或降级处理,避免擦写过程中被中断破坏数据;具体协调逻辑位于 RCSP 升级模块内部。
  • code_switch 涉及代码段跳转,必须在关闭中断、确认新固件 CRC 有效后执行,防止跳到损坏固件导致变砖。

协议冲突

  • jieli 协议族(RCSP/透传/AT)与 sig_mesh 均占用 BLE/SPP 链路,若同时使能需确认通道与 UUID 不冲突;le_at_com 与 spp_at_com 的 AT 解析器若与 RCSP 数据流混用同一连接,需在协议分发层(rcsp_bluetooth.c)区分帧头,否则会产生串包。

性能与运维考量

  • 扫描周期:按键扫描约 10ms 一次,每次仅做比较与计数,CPU 开销极低;filter_time 与 long_time 均以扫描周期为单位(如 35 对应 350ms),调参即改变手感与响应。
  • 编译期裁剪:apps/hid/Makefile 的 includes/objs 列表是固件体积的主要开关——不用的协议(Mesh、AT、透传)从 Makefile 移除即可显著减小 ROM 占用;custom_cfg 的 CUSTOM_CFG_DEBUG_EN 关闭后,cfg_printf 系列退化为空宏,减少调试输出开销。
  • 日志分级:key 组件通过 LOG_TAG "[KEY]" 与 LOG_ERROR_ENABLE/LOG_DEBUG_ENABLE/LOG_INFO_ENABLE 控制输出级别(key_driver.c),量产固件应关闭 DEBUG/DUMP 级别以降低串口与时间开销。
  • 升级运维:RCSP 提供 rcsp_user_update(用户区)与 rcsp_ch_loader_download(Bootloader)两条升级通道,配合 update 组件与 config.dat 可实现"远程改配置 + 远程升级固件"的完整运维闭环。

扩展点

  1. 新增按键类型:实现 get_value() 并注册 struct key_driver_para(参照 iokey.c/adkey.c 等既有驱动),框架自动获得消抖与事件判定能力。
  2. 组合键/键值重映射:覆盖弱函数 key_event_remap(struct sys_event *e),在事件上报前改写键值或吞掉事件。
  3. 新增传感器型号:仿照 hal3205/ 增加 hal 层驱动,保持 OMSensor_manage 接口不变。
  4. 新协议接入:在 third_party_profile/ 下新增目录并仿照 jieli/sig_mesh 提供 Makefile 片段(-I + .o),由各应用工程选择性编入。
  5. 配置项扩展:在 custom_cfg.c 中按 {crc, len, data} 模板新增结构体并注册到读写表中,App 侧即可通过 RCSP 远程读写。

测试情况说明

在本次探索范围内(apps/common 源码与 apps/hid/Makefile),未发现独立的单元测试工程;按键与协议组件的验证主要依赖目标板运行验证(如 HID 键盘的按键手感、RCSP 升级流程的实机测试)。key_driver.c 中保留的条件编译与注释代码(poweron_cnt 防误判、MOUSE_KEY_SCAN_MODE)表明其验证方式为实机迭代调参,测试覆盖需由各应用工程自行补充。

Related Links

  • apps/hid/Makefile — 公共组件与第三方协议的编译裁剪入口
  • apps/common/key/key_driver.c — 按键驱动框架核心实现
  • apps/common/third_party_profile/common/custom_cfg.c — RCSP 配置持久化实现
  • 相关目录:apps/common/code_switch/、apps/common/optical_mouse_sensor/、apps/common/third_party_profile/jieli/JL_rcsp/、apps/common/third_party_profile/sig_mesh/
  • 相邻页面:应用工程(apps/hid 等)的业务逻辑、SDK 底层协议栈与系统服务(event/timer/VM/FS)请参见对应目录的独立文档
Prev
蓝牙 Mesh 应用