Solution: Multimode Mouse¶
重要
此例程仅存在于特殊版本的SDK中,如有需要请联系Panchip。
1 功能概述¶
此sample为pan107(32pin芯片)在实体鼠标上演示三模鼠标基础功能的EVB demo,为低功耗办公键鼠提供参考方案。
1.1 核心特性¶
通用功能:
三模切换:支持USB/2.4G PRF/BLE三种工作模式
三模切换不复位(
user_is_sleep_allow接口模式返回false较稳定,BLE模式连接功耗高0.2mA)三模切换复位(默认
CONFIG_CHANGE_MODE_RESET=1):拨动开关/ADC 检测到模式变化时sys_reboot()整片复位,上电后由硬件重新检测模式;不写入 Flash KV
光电传感:通过sensor获取鼠标坐标,支持DPI按键切换传感器DPI
QDEC滚轮:支持去抖的正交解码器,反馈鼠标滚轮变化
按键模块:基础按键(左/中/右/侧边),底部按键用于模式切换和功能测试
LED指示:顶部RGB灯及底部指示灯,显示不同模式和连接状态
电量检测:ADC采集电量信息及模式检测
供电管理:电池供电/USB供电/USB插入PC自动切换
USB模式:
最大上报率1000Hz
配合
Panchip DFU Tool可进行USB DFU升级USB插入时优先级最高,自动切换到USB模式
2.4G模式(PRF增强型):
需配合PRF Dongle使用(支持PAN101/PAN107/PAN2628等多种Dongle)
跳频机制:连接阶段 ACK 连续丢失达阈值后在 8 个私有频点间跳频(配对第一阶段当前代码不跳频,固定公共频点重传)
对码协议:单向配对策略,公共地址对码后切换到私有地址通信
重传机制:丢包时快速重传直到收到对端回复
动态发射功率(可选):连接后根据ACK质量实时升降TX功率,近距离省电、远距离保链路
ACK解析:可解析的ACK,支持自定义回传消息
性能指标:
USB/2.4G 默认上报率 1000Hz(
CONFIG_DEFAULT_REPORT_RATE=3,可切换 125/250/500/1000 Hz,索引写入 KVprf-usb/rate_index)BLE 模式 slptmr 固定 125Hz(
main.c中app_rate=125)T->R 转换时间约 25us(最快时序);配对过程全速运行
空闲时由 Idle 调度进入 deepsleep 休眠
Dongle支持:多pipe模式,多主设备可共用一个Dongle
BLE模式:
默认133Hz(7.5ms interval)
支持3台设备配对和分时连接
空闲时由Idle调度进入deepsleep休眠
连接建立约 2s 后请求
conn_latency=35(app_ble_conn_upd_timer);无活动约 428 个连接事件后启用 slave latency持续空闲 300s(
CONFIG_BLE_IDLE_TIMEOUT_S)触发 standby M1;配对/断连超时同为 300s(CONFIG_BLE_PAIR_TIMEOUT_S/CONFIG_BLE_DISCONNECT_TIMEOUT_S)唤醒后系统会reset(目前已知问题)
2 环境要求¶
开发板:
pan107(32pin芯片,MPC+版本,USB功能较稳定)开发板 × 2串口配置: Git 默认
CONFIG_UART_LOG_ENABLE=0(关闭);调试时可置 1,配合CONFIG_LOG_UART_PIN=2(P16)、CONFIG_LOG_UART_BAUDRATE=115200若改
CONFIG_LOG_UART_PIN=0(P05),部分板级 RGB 红灯同引脚会冲突,需按板级头文件board/*.h确认
蓝牙主机: PC或手机等支持BLE的设备
PRF Dongle:
prf_donglesolution用于演示配对和跳频控制(支持PAN101/PAN107/PAN2628等)测试工具:
PPK功耗测试工具
Panchip DFU Tool(USB升级)
KEYTEST.exe(键盘测试工具,可选)
3 编译和烧录¶
3.1 例程位置¶
BOOT程序:
<nimble>/samples/solutions_hid/bootloaderAPP程序:
<nimble>/samples/solutions_hid/mult_ms/keil_107x
3.2 编译步骤¶
使用Keil打开项目工程
根据目标芯片选择对应的board配置
编译BOOT和APP两个固件
先烧录BOOT固件,再烧录APP固件
3.3 注意事项¶
需要烧录两个固件(BOOT + APP)
蓝牙和2.4G模式默认空闲进入deepsleep,此时JLink烧录容易失败
可以运行Keil工具JLink erase或切换到其他模式下进行烧录
4 功能说明¶
本程序为实体鼠标固件(非EVB演示程序),支持完整的按键组合功能和三模切换。
4.1 按键定义¶
基础按键:
左键(Left): MS_LEFT_KEY
中键(Middle/Wheel): MS_MIDDLE_KEY
右键(Right): MS_RIGHT_KEY
前进键(Forward): MS_FORWARD_KEY (侧边)
后退键(Back): MS_BACK_KEY (侧边)
DPI键: MS_DPI_KEY (底部功能键)
模式切换:
通过底部拨动开关或ADC检测进行USB/2.4G/BLE三模切换
USB插入时自动优先切换到USB模式
4.2 组合键功能¶
组合键由周期事件 combine_key_period_event 调度,周期 20ms(KEY_HANDLE_TIME_MS,定义于 board/*.h)。逻辑在 app_common.c 的 app_combine_key_handle() 中实现,各功能可通过板级头文件中的 MS_*_EN / MS_*_KEY / MS_*_DELAY_MS 宏独立开关。
触发时间说明:
DPI: 单独按下 DPI 键,持续 ≥20ms 即触发(防抖短按,非长按2秒)
其余组合键: 需同时按住对应按键达到宏定义延迟(默认 2000ms)
下表时间为秒,代码中对应
MS_*_DELAY_MS(DPI 为 20ms,其余默认 2000ms):按键
检测时间
2.4G模式
USB模式
蓝牙模式
DPI
短按(≥20ms)
切换传感器DPI
切换传感器DPI
切换传感器DPI
L+M+R
2s
重新配对
无
重新配对
M+R
2s
自动画圈
自动画圈
自动画圈
M
2s
切换上报率
切换上报率
无
L+M
2s
无
无
切换蓝牙多设备
DPI+L
2s
恢复出厂设置
恢复出厂设置
恢复出厂设置
组合键处理流程:
combine_key_period_event (20ms)
|
v
app_combine_key_handle()
|
+-- 按键不匹配 --> 清零 long_press_cnt
|
+-- 按键匹配 --> 累加 long_press_cnt
|
+-- 未达 DELAY_MS --> 继续等待
|
+-- 已达 DELAY_MS --> 按功能分支:
|
+-- DPI --> sensor_cyclic_increment_dpi() + LED 闪烁
+-- 上报率 --> 投递 rate_change_event
+-- 画圈 --> 切换 test_mode_auto_circle
+-- 重新配对/多设备/出厂 --> app_combine_key_flag_set()
|
+-- 约 200ms 后投递 wireless_repair_event /
| ble_id_change_event,或 app_factory_reset()
|
+-- long_key_flag=1 期间屏蔽键鼠上报
重新配对与出厂重置细节:
左+中+右(BLE): 达标后
app_ble_send_start(),约 200ms 后投递wireless_repair_event→app_ble_repair()(不重启,重新广播配对)左+中+右(2.4G): 投递
wireless_repair_event→app_prf_repair()(重置配对状态并进入重新配对流程,不擦除 Flash 中prf/dg_type_addr)DPI+左: 约 200ms 后调用
app_factory_reset(),擦除 KV 分区并重新初始化应用重新配对/出厂重置执行期间
app_combine_key_flag_get()==1时,app_device_data_get()会屏蔽鼠标键值上报
DPI 档位与 LED 颜色(app_sensor.c 定义 CPI 表,app_common.c 按 sensor_dpi_index_get() 显示颜色):
索引 |
LED 颜色 |
PAW3220 CPI(默认板) |
PAW3311 CPI |
|---|---|---|---|
0 |
绿 |
~800 |
800 |
1 |
蓝 |
~1200 |
1600 |
2 |
紫 |
~2400 |
2400 |
3 |
黄(默认) |
~3200 |
3200 |
4 |
青 |
~4000 |
6500 |
5 |
红 |
~4953 |
12000 |
PAW3220 的 CPI 由寄存器值 × 39 换算(见
P3220_DPI[]注释)。文档中常见的「400/6400/26000」等档位不在当前代码中。
LED指示说明:
状态 |
LED表现 |
触发条件 |
|---|---|---|
DPI切换 |
对应颜色闪烁约2s(200ms间隔) |
按下DPI键 |
上报率切换 |
白色闪烁约2s(200ms间隔) |
长按中键 |
2.4G未配对 |
白色慢闪(400ms间隔) |
无配对信息 |
2.4G配对中 |
白色快闪(100ms间隔) |
正在对码 |
2.4G已连接 |
LED熄灭 |
配对成功并连接 |
BLE未配对 |
蓝色慢闪(400ms间隔) |
无配对信息 |
BLE配对中 |
蓝色快闪(100ms间隔) |
正在广播 |
BLE已连接 |
LED熄灭 |
配对成功并连接 |
USB模式 |
绿色常亮 |
USB枚举完成 |
低电量 |
红色闪烁(400ms间隔) |
电压低于阈值 |
4.3 操作流程说明¶
4.3.1 初始上电¶
首次上电:编译后全部擦除下载,根据硬件配置默认进入2.4G或BLE模式
模式切换:默认
CONFIG_CHANGE_MODE_RESET=1,拨码变化时整片复位,上电后硬件重新检测模式(不写 Flash)自动画圈:如果使能
CONFIG_POWER_ON_AUTO_CIRCLES,上电后自动进入画圈测试模式
4.3.2 2.4G配对流程¶
上电配对:烧录了PRF Dongle的接收器,鼠标自动开始配对
配对过程:
在公共地址2412MHz单频点进行
白色LED快闪表示配对中
配对成功后切换到私有地址和频点
LED熄灭表示连接成功
重新配对:长按
左+中+右键2秒进入重新配对流程稳定性:每秒打印收发情况,挂测稳定
4.3.3 BLE模式操作¶
广播配对:切换到BLE模式后自动广播,PC/手机可搜索到设备
多设备支持:支持3台设备配对(
CONFIG_BT_MAX_PAIRED=3)设备切换:长按
左+中键2秒循环切换设备0/1/2重新配对:长按
左+中+右键2秒进入重新配对(不重启)Latency:连接约2s后设
conn_latency=35;无活动约428个连接事件后启用 slave latencyStandby:持续空闲 300s(
CONFIG_BLE_IDLE_TIMEOUT_S)进入 standby M1 断开
已知问题:BLE唤醒后系统会reset,目前reset后广播异常发不出,需chip reset恢复
4.3.4 USB模式¶
自动切换:USB插入PC时自动切换到USB模式,优先级最高
DFU升级:配合
Panchip DFU Tool可进行boot内USB DFU升级上报率:支持125/250/500/1000 Hz四档切换
拔出恢复:拔出USB后恢复到之前的无线模式
4.4 Bootloader说明¶
4.4.1 Boot功能概述¶
当使能CONFIG_ENABLE_BOOTLOADER=y时,工程合并了支持2.4G和USB双模升级的Bootloader:
Boot位置: <nimble>/samples/solutions_hid/bootloader
支持的升级方式:
USB DFU升级 (Boot 工程
BOOT_ENABLE_USB_DFU=1):Boot 内 USB HID 厂商命令传输固件
支持 GPIO 按键或 Flash DFU 标志进入
固件直接写入 App 分区
VID: 0x046D, PID: 0xC055, 设备名: “boot dfu”
2.4G PRF OTA升级 (Boot 工程
BOOT_ENABLE_PRF_OTA=1,可选):Boot 内通过 2.4G 接收固件并直接写入 App 分区
完成后重启运行新 App
详细说明: 请参考 PanChip 2.4G OTA升级指南
4.4.2 Boot工作流程¶
int main(void)
{
APP_LOG("Bootloader in..\n\n");
// 1. 检查Backup分区是否有有效镜像
ss_connect(0, sig_back_up_is_completed_image, on_image_load_enter);
// 2. 检查是否进入UART DFU模式(可选)
#if BOOT_ENABLE_UART_DFU
ss_connect(1, sig_key1_push_down, on_uart_dfu_enter);
#endif
// 3. 检查是否进入USB DFU模式
#if BOOT_ENABLE_USB_DFU
#if BOOT_USB_DFU_MODE == 0x00
ss_connect(2, sig_key2_push_down, on_usb_dfu_enter);
#endif
ss_connect(3, sig_usb_dfu_enter_check, on_usb_dfu_enter);
#endif
#if BOOT_ENABLE_PRF_OTA
ss_connect(4, sig_ota_start_received, on_prf_ota_enter);
#endif
// 4. 执行信号槽处理
sig_process();
// 5. 如果没有进入DFU,跳转到App
jump_to_app();
}
升级流程:
[正常启动]
Boot 上电 --> 检查 Backup 是否有完整镜像 --> 有则搬运到 App --> jump_to_app
|
+-- 无有效镜像 --> jump_to_app 直接运行 App
[USB DFU]
App 调用 dfu_flag_set() 或按键触发 --> 重启进入 Boot USB DFU
--> 固件直接写入 App 分区 --> 重启 jump_to_app
[2.4G OTA (Boot 内)]
2.4G OTA 握手 --> Boot 接收固件 --> 直接写入 App 分区 --> 重启 jump_to_app
提示: 本方案 USB DFU 与 Boot 内 2.4G OTA 均直接升级 App,不走 Backup 暂存。2.4G 协议细节见官方 OTA 指南。
4.4.3 Boot配置说明¶
App 工程关键宏 (mult_ms/keil_107x/configuration/sdk_config.h):
// 使能Bootloader(合并Boot+App镜像,Git默认=1)
#define CONFIG_ENABLE_BOOTLOADER 1
#define CONFIG_MERGE_IMAGES 1
#define CONFIG_APP_USE_IMAGE_HEADER 1
Boot 工程关键宏 (bootloader/keil_107x/configuration/sdk_config.h,Git默认):
#define BOOT_ENABLE_USB_DFU 1
#define BOOT_USB_DFU_MODE 0x00 // GPIO按键触发进DFU
#define BOOT_ENABLE_UART_DFU 0
#define BOOT_ENABLE_PRF_OTA 1 // Boot内2.4G OTA
#define CFG_RF_MANUFACTOR_TEST 0
Flash分区布局(当 CONFIG_ENABLE_BOOTLOADER=1 且使用默认分区大小时):
分区 |
起始地址 |
大小 |
用途 |
|---|---|---|---|
Bootloader |
0x00000 |
40 KB |
Bootloader 程序 |
App |
0x0A000 |
220 KB |
主应用程序 |
App Backup |
0x41000 |
220 KB |
见下文「Backup 可复用说明」 |
KV Store |
0x78000 |
16 KB |
配对信息、上报率等键值存储 |
User Custom |
0x7C000 |
12 KB |
用户数据;末 4KB 保留 DFU 标志 |
总计 |
- |
508 KB |
|
起始地址由
sdk_config.h中CONFIG_FLASH_PARTITION_*宏自动计算,修改任一分区大小时需保证各分区之和不超过 Flash 总容量。
分区使用要点(Boot + App 配合)
重要
User Custom 末 4KB 为 DFU 升级标志保留区,禁止使用
存放位置: User Custom 最后一个 4KB 扇区,默认 0x7E000 ~ 0x7F000
(=CONFIG_FLASH_PARTITION_USER_CUSTOM_ADDR + CONFIG_FLASH_PARTITION_USER_CUSTOM_SIZE - 0x1000)标志值: 扇区首字节
0x5A(USB_DFU_ENTER_FLAG) 时,下次上电 Boot 进入 USB DFUApp:
dfu_flag_set()/dfu_flag_check_clear()(app_usb_protocol.c);设置时会擦除整个末扇区Boot:
is_dfu_flag_valid()(bootloader/src/usb_dfu.c)用户可用: 默认 User Custom 前 8KB(
0x7C000~0x7DFFF);末 4KB 禁止读写配置:
CONFIG_FLASH_PARTITION_USER_CUSTOM_SIZE_KB(默认 12)与 Boot/App 末扇区逻辑绑定,不建议修改
Backup 分区可复用说明
本方案 USB DFU 与 Boot 内 2.4G OTA 均直接写入 App 分区,升级不依赖 Backup 暂存。因此 220KB App Backup(0x41000)可供用户存储数据。
注意:
Boot 上电仍检查 Backup 是否有完整有效 App 镜像;若有会
fm_image_move()覆盖 App复用 Backup 时勿写入有效 App 镜像头,避免误触发搬运
保持
CONFIG_FLASH_PARTITION_APP_BACKUP_*默认即可;由应用层管理该地址范围内读写
区域 |
默认大小 |
建议用途 |
约束 |
|---|---|---|---|
User Custom 前段 |
8 KB |
小容量用户参数 |
避开末 4KB |
User Custom 末段 |
4 KB |
DFU 标志(系统保留) |
禁止用户访问 |
App Backup |
220 KB |
大容量用户数据 |
勿写有效 App 镜像头 |
4.4.4 使用Panchip DFU Tool升级¶
升级步骤:
准备工具:
安装
量产烧录工具\Panchip DFU Tool选择”Pan10xx NDK芯片平台”
进入DFU模式:
方式1: App 内设置 DFU 标志后重启
dfu_flag_set(); // 写入 User Custom 末扇区 0x5A sys_reboot(0); // 下次上电 Boot 进入 USB DFU
亦可通过 USB HID 厂商命令
USB_VENDOR_DFU_FORCE_UPGRADE由工具/App 触发方式2: 按住 Boot 配置的 GPIO 按键上电(
BOOT_USB_DFU_MODE=0x00)
加载固件:
点击”加载固件”,选择编译生成的
app.signed.bin工具会自动识别Boot DFU设备(VID:0x046D, PID:0xC055)
开始升级:
点击”开始升级”,等待进度条完成
升级成功后自动重启进入新App
验证升级:
查看串口Log确认版本号
测试基本功能正常
注意事项:
升级过程中不要断电或拔插USB
建议使用签名固件(
app.signed.bin)确保安全升级失败可重新进入DFU模式再次升级
Boot区本身也可通过特殊工具升级(谨慎操作)
4.5 测试模式¶
模式指示:
蓝牙:按键由底部往上看,下拨,配对时左一灯快闪,连接后熄灭,断连时慢闪
2.4G:按键由底部往上看,上拨,配对时中间灯快闪,连接后熄灭,断连时慢闪
EMI:按住L+M上电,进入EMI测试,可以连接USB上位机工具进行EMI对测
Log:开启log时,调试log可由最下方灯IO输出
4.6 2.4G对码协议详解¶
4.6.1 对码流程概述¶
mult_ms采用单向配对策略,基于公共地址进行初始配对,配对成功后切换到私有地址和私有频点进行通信。
对码三阶段:
PRF_PAIR_FIRST_STAGE:鼠标发送配对请求(包含鼠标MAC地址)
PRF_PAIR_SECOND_STAGE:Dongle回复ACK(包含Dongle MAC地址)
PRF_PAIR_THIRD_STAGE:鼠标确认并保存配对信息
PRF_PAIR_END_STAGE:切换到私有地址和频点,进入正常通信
4.6.2 2.4G配对流程详解¶
配对阶段划分:
代码中将配对过程分为5个阶段(enum prf_stage):
enum prf_stage {
PRF_IDLE_STAGE = 0, // 空闲状态
PRF_PAIR_INIT_STAGE, // 配对初始化
PRF_PAIR_FIRST_STAGE, // 第一阶段:发送鼠标MAC
PRF_PAIR_SECOND_STAGE, // 第二阶段:等待Dongle回复
PRF_PAIR_THIRD_STAGE, // 第三阶段:确认配对
PRF_PAIR_END_STAGE, // 配对结束,切换私有地址
PRF_CONNECT_STAGE // 正常通信阶段
};
详细配对时序:
阶段1: 配对初始化 (PRF_PAIR_INIT_STAGE)¶
// 设置公共地址和公共频点
void app_prf_set_public_adr_chn(void)
{
// 公共地址: {0x7b, 0x41, 0x29, 0x72}
uint8_t public_addr[4] = PAIR_PUBLIC_ADDR;
// 公共频点: 2412 MHz (channel_list_public[1])
panchip_prf_set_chn(PAIR_PUBLIC_CHANNEL); // 2412
// 设置RF地址(4字节)
panchip_prf_set_addr(public_addr, 4, PRF_PIPE0, PRI_RF_MODE_SEL_TRX);
// 配对时使用低功率(-30dBm)减少干扰
RF_SetPowerLevel(CONFIG_PRF_PAIR_RF_POWER); // -30 dBm
RF_RefreshPhySeqRAM(0);
}
阶段2: 发送配对请求 (PRF_PAIR_FIRST_STAGE)¶
鼠标发送包含自己MAC地址的配对包:
数据包格式 (7字节):
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Type │ MAC[0] │ MAC[1] │ MAC[2] │ MAC[3] │ MAC[4] │ MAC[5] │
│ 0x00 │ │ │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
关键字段说明:
Type = 0x00: 表示普通鼠标类型(PAIR_COMMON_MOUSE_TYPE)MAC[0-5]: 鼠标的6字节MAC地址
阶段3: Dongle回复 (PRF_PAIR_SECOND_STAGE)¶
Dongle收到配对请求后,回复包含自己信息的ACK:
Dongle回复ACK (7字节):
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ 0xFF │ DG_Type │ MAC[0] │ MAC[1] │ MAC[2] │ MAC[3] │ MAC[4] │
│ │ │ │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
关键字段说明:
0xFF: 固定标识,表示这是Dongle回复DG_Type: Dongle类型标识(用于区分不同Dongle)MAC[0-4]: Dongle的5字节MAC地址(实际是6字节,但只传5字节)
阶段4: 鼠标处理响应 (PRF_PAIR_END_STAGE)¶
鼠标收到Dongle回复后,保存配对信息并生成私有地址/频点:
// 接收Dongle回复的数据
if (rx_payload.data[0] == 0xff && rx_payload.data_length == 7) {
// 保存Dongle类型和MAC地址到 dg_type_addr[7]
prf_state.dg_type_addr[0] = rx_payload.data[1]; // DG_Type
prf_state.dg_type_addr[1] = rx_payload.data[2]; // Dongle MAC[0]
prf_state.dg_type_addr[2] = rx_payload.data[3]; // Dongle MAC[1]
prf_state.dg_type_addr[3] = rx_payload.data[4]; // Dongle MAC[2]
prf_state.dg_type_addr[4] = rx_payload.data[5]; // Dongle MAC[3]
prf_state.dg_type_addr[5] = rx_payload.data[6]; // Dongle MAC[4]
// 保存到Flash
common_kv_write("prf/dg_type_addr", prf_state.dg_type_addr, 7);
// 进入配对结束阶段
app_prf_stage_transform(PRF_PAIR_END_STAGE);
}
阶段5: 生成私有地址和频点¶
私有RF地址生成(4字节):
void app_prf_set_private_adr_chn(void)
{
// 私有地址由Dongle的MAC地址生成
prf_state.private_rf_addr[0] = PAIR_COMMON_MOUSE_TYPE; // 0x00 (鼠标类型)
prf_state.private_rf_addr[1] = prf_state.dg_type_addr[3]; // Dongle MAC[2]
prf_state.private_rf_addr[2] = prf_state.dg_type_addr[4]; // Dongle MAC[3]
prf_state.private_rf_addr[3] = prf_state.dg_type_addr[5]; // Dongle MAC[4]
// 示例:
// 如果 Dongle MAC = {AA, BB, CC, DD, EE, FF}
// 则 dg_type_addr = {Type, AA, BB, CC, DD, EE}
// 私有地址 = {0x00, CC, DD, EE}
}
关键点:
私有地址不是由鼠标MAC生成,而是由Dongle MAC的后3字节生成
这样可以确保同一个Dongle配对的多个设备使用相同的地址前缀
私有跳频频点生成(8个频点):
void calculate_combo_freq(uint8_t *addr)
{
// addr 指向 dg_type_addr[2],即 Dongle MAC[1]
// 使用 Dongle MAC[1] 和 Dongle MAC[3] 生成频点
uint8_t x = addr[0] % 5; // Dongle MAC[1] % 5
uint8_t y = addr[2] % 8; // Dongle MAC[3] % 8
APP_LOG_INFO("calculate_combo_freq use Dongle MAC[1]=%d, MAC[3]=%d\n",
addr[0], addr[2]);
APP_LOG_INFO("x = %d, y = %d\n", x, y);
// 生成8个频点
for (uint8_t i = 0; i < 8; i++) {
// 公式: frequency = 2402 + 2 * (8 * x + y)
prf_state.private_rf_channel[i] = 2402 + 2 * (8 * x + y);
// x和y递增,产生不同的频点
x = (x + 1) % 5;
y = (y + 1) % 8;
// 避开WiFi干扰频点 2432MHz 和 2464MHz
if ((prf_state.private_rf_channel[i] == 2432) ||
(prf_state.private_rf_channel[i] == 2464)) {
APP_LOG_INFO("Skip WiFi channel %d MHz\n",
prf_state.private_rf_channel[i]);
// 重新计算下一个频点
prf_state.private_rf_channel[i] = 2402 + 2 * (8 * x + y);
x = (x + 1) % 5;
}
}
APP_LOG_INFO("Private channels: ");
for (uint8_t i = 0; i < 8; i++) {
APP_LOG_INFO("%d ", prf_state.private_rf_channel[i]);
}
APP_LOG_INFO("\n");
}
频点生成算法说明:
输入: Dongle MAC的两个字节 (
MAC[1]和MAC[3])初始值:
x = MAC[1] % 5(范围0-4)y = MAC[3] % 8(范围0-7)
频点公式:
freq = 2402 + 2 * (8 * x + y)基础频率: 2402 MHz
步长: 2 MHz
范围: 2402 ~ 2480 MHz
迭代: 每次循环x和y递增,生成8个不同频点
避坑: 自动跳过2432和2464两个WiFi常用频点
示例计算:
假设 Dongle MAC = {0xAA, 0x12, 0x34, 0x56, 0x78, 0x9A}
addr[0] = MAC[1] = 0x12 = 18
addr[2] = MAC[3] = 0x56 = 86
x = 18 % 5 = 3
y = 86 % 8 = 6
频点计算:
i=0: freq = 2402 + 2*(8*3+6) = 2402 + 60 = 2462 MHz
x=4, y=7
i=1: freq = 2402 + 2*(8*4+7) = 2402 + 78 = 2480 MHz
x=0, y=0
i=2: freq = 2402 + 2*(8*0+0) = 2402 + 0 = 2402 MHz
x=1, y=1
... (继续计算8个频点)
为什么这样设计?
唯一性: 不同Dongle的MAC不同,生成的频点序列也不同,避免互相干扰
均匀分布: 通过模运算确保频点在2.4G频段内均匀分布
抗干扰: 自动避开WiFi频点,提高稳定性
可预测: 双方使用相同算法,无需额外协商
阶段6: 切换到私有模式 (PRF_CONNECT_STAGE)¶
// 配对完成后
app_prf_set_private_adr_chn(); // 设置私有地址和频点
// 切换到连接功率(+1dBm或+7dBm)
RF_SetPowerLevel(CONFIG_PRF_CONNECT_RF_POWER);
RF_RefreshPhySeqRAM(0);
#if CONFIG_PRF_DYN_PWR_EN
app_prf_dyn_pwr_on_connect(); // 启用动态功率,从 CONNECT_RF_POWER 起始
#endif
// 更新状态
prf_state.stored_pair = true; // 已存储配对
prf_state.pairing = false; // 配对结束
prf_state.connect = true; // 进入连接状态
APP_LOG_INFO("PRF pairing completed!\n");
配对完成后的通信:
4.6.3 配对流程图¶
上电
|
v
读取 Flash 配对信息 (prf/dg_type_addr)
|
+-- 有配对信息 --> 加载私有地址/频点 --> PRF_CONNECT_STAGE --> 正常 HID 通信
|
+-- 无配对信息 --> 设置公共地址/2412MHz/配对功率 -30dBm
|
v
PRF_PAIR_FIRST_STAGE: 发送鼠标 MAC 包
|
+-- 超时 --> 同频点重传 (当前代码配对阶段不跳公共频点)
|
+-- 收到 Dongle ACK --> 保存 dg_type_addr 到 Flash
|
v
生成私有地址与 8 个私有频点 --> 切换连接功率 +1dBm
|
+-- CONFIG_PRF_DYN_PWR_EN=1 --> app_prf_dyn_pwr_on_connect()
|
v
PRF_CONNECT_STAGE --> 正常 HID 通信
4.6.4 跳频机制¶
跳频触发条件:
不同阶段有不同的ACK丢失阈值:
// pair_with_common_dongle.c 中的定义
#define CONNECT_ACK_LOST_CNT_TRIGGER_HOP 20 // 连接阶段:20次
#define PAIR_FIRST_ACK_LOST_CNT_TRIGGER_HOP 5 // 配对第一阶段:5次
#define PAIR_SECOND_ACK_LOST_CNT_TRIGGER_HOP 15 // 配对第二阶段:15次
#define PAIR_THIRD_ACK_LOST_CNT_TRIGGER_HOP 15 // 配对第三阶段:15次
跳频逻辑实现
void app_prf_hop(void)
{
switch (g_prf_ctrl.work_stage) {
case PRF_PAIR_FIRST_STAGE:
/* 当前代码:配对第一阶段不跳频,固定公共频点重传 */
break;
case PRF_PAIR_THIRD_STAGE:
break;
case PRF_CONNECT_STAGE:
if (prf_state.ack_lost_cnt >= CONNECT_ACK_LOST_CNT_TRIGGER_HOP) {
prf_state.channel_index++;
prf_state.channel_index = prf_state.channel_index % 8;
panchip_prf_set_chn(prf_state.private_rf_channel[prf_state.channel_index]);
prf_state.hop_cnt++;
prf_state.hop_recover_pending = true;
} else {
prf_state.ack_lost_cnt++;
}
break;
default:
break;
}
}
PAIR_FIRST/SECOND/THIRD_*_TRIGGER_HOP宏已定义,但当前仅在连接阶段使用CONNECT_ACK_LOST_CNT_TRIGGER_HOP(20)。
跳频流程:
发送数据包 --> 等待 ACK
|
+-----------+-----------+
| |
收到有效 ACK 未收到 ACK
| |
ack_lost_cnt = 0 ack_lost_cnt++
hop_recover_pending |
则 hop_tms++ +-- 未达阈值 --> 重传
| |
继续当前频点 +-- 达阈值 --> channel_index = (index+1)%8
切换频点, hop_trig++, hop_recover_pending=true
--> 继续发送
关键点:
配对阶段: 当前
app_prf_hop()在PRF_PAIR_FIRST_STAGE不执行跳频,固定在PAIR_PUBLIC_CHANNEL(2412MHz)重传连接阶段: 在8个私有频点(根据 Dongle MAC 生成)之间跳频,阈值 20 次
跳频统计:
hop_trig(=hop_cnt) 记录跳频触发次数;hop_tms记录跳频后首次收到有效 ACK 的次数(用于链路恢复观测)循环跳频: 到达第8个频点后回到第1个
4.6.5 重传机制¶
增强型模式特性:
TX后自动转入RX等待ACK
未收到ACK时自动重传
重传次数无限制,直到收到ACK或触发跳频
重传控制:
// 使能/禁用重传
void app_prf_tx_retransmit_enable(bool state);
// PID管理(防止重复包)
void app_prf_tx_pid_enable(bool state);
4.6.6 数据包格式¶
鼠标发送数据帧:
前导码 |
接入地址 |
Signal |
Payload |
CRC |
|---|---|---|---|---|
3B |
4B |
10bit |
可变长度 |
2B |
0x550f71 |
公共/私有地址 |
增强型字段 |
鼠标数据 |
16bit CRC |
Payload结构(最小4字节):
Type |
Seq |
Oppo_Seq |
Data_Type |
Data |
|---|---|---|---|---|
1B |
1B |
1B |
1B |
N Bytes |
设备类型 |
序列号 |
对端序列号 |
数据类型 |
实际数据 |
数据类型定义:
#define DATA_TYPE_NULL 0x00 // 空数据
#define DATA_TYPE_USB_EP0 0x10 // USB EP0数据
#define DATA_TYPE_USB_EP1 0x11 // USB EP1数据(标准键)
#define DATA_TYPE_USB_EP2 0x12 // USB EP2数据(多媒体/全键/鼠标)
#define DATA_TYPE_USB_EP3 0x13 // USB EP3数据(厂商自定义)
#define DATA_TYPE_REGROUP 0x20 // 重组数据
#define DATA_TYPE_CUSTOM 0x30 // 自定义数据
鼠标数据包示例(12字节):
void app_prf_set_ms_data(uint8_t *origin, uint8_t *tx_data, uint8_t *length)
{
g_prf_pkt_seq++; // 序列号递增
tx_data[0] = PAIR_COMMON_MOUSE_TYPE; // 0x00
tx_data[1] = g_prf_pkt_seq; // 发送序列号
tx_data[2] = g_oppo_seq; // 对端序列号
tx_data[3] = DATA_TYPE_USB_EP2; // 鼠标数据类型
tx_data[4] = 0x05; // Report ID
// 复制鼠标数据 (7字节: Key + X + Y + Roll)
for (uint8_t i = 0; i < 7; i++) {
tx_data[5 + i] = origin[i];
}
*length = 12;
}
4.6.7 多Dongle支持¶
代码支持多种Dongle类型,通过宏配置选择:
#if (CONFIG_DONGLE_TYPE == PAIR_WITH_PAN2628_DONGLE)
#include "pair_with_pan2628_dongle.c"
#elif (CONFIG_DONGLE_TYPE == PAIR_WITH_PAN1010_DONGLE)
#include "pair_with_pan1010_dongle.c"
#elif (CONFIG_DONGLE_TYPE == PAIR_WITH_COMBO_DONGLE)
#include "pair_with_combo_dongle.c"
#elif (CONFIG_DONGLE_TYPE == PAIR_WITH_COMMON_DONGLE)
#include "pair_with_common_dongle.c"
#elif (CONFIG_DONGLE_TYPE == PAIR_WITH_BY_DONGLE)
#include "pair_with_by_dongle.c"
#endif
支持的Dongle类型:
PAN2628: 多pipe Dongle,支持键鼠套件
PAN1010: 低成本Dongle
COMMON: 通用Dongle,支持多设备共用
COMBO: 组合Dongle
BY: 自定义Dongle
4.7 PRF动态发射功率(概述)¶
使能 CONFIG_PRF_DYN_PWR_EN=1 后,鼠标在 PRF 已连接阶段根据每包 ACK 质量实时调节 TX 功率:近距离自动降功率省电,远距离升功率保链路。实现位于 src/app_prf_dyn_pwr.c / app_prf_dyn_pwr.h。
功率调节流程:
每包 TX/RX 完成 --> 根据该 seq 的 timeout 次数判定 ACK 质量
|
+-- timeout=0 (干净 ACK) --> consec_ok++, consec_miss=0
+-- timeout>=2 (弱 ACK) --> consec_miss++, consec_ok=0
+-- timeout=1 (一般 ACK) --> 不累计升降计数
|
v
app_prf_idle_is() 时 app_prf_dyn_pwr_process():
|
+-- consec_miss>=3 或当前 seq timeout>=2 --> 升功率 +STEP_DB
+-- consec_ok>=80 且冷却结束 --> 降功率 -STEP_DB
+-- 否则 --> 保持当前功率
|
v
RF_SetPowerLevel() + 冷却 COOLDOWN_ROUNDS 轮
默认配置: CONFIG_PRF_DYN_PWR_EN=0(关闭),量产无额外风险;配对/断连时自动复位动态功率状态。
完整配置项、调试日志字段与调参建议见 6.1.5.1 动态发射功率。
5 性能说明¶
5.1 功耗说明¶
关闭log,降低频率降低功率可以在使用场景下进行优化功耗。
2.4G模式功耗¶
2.4G配对后,test_mode_auto_circle开启情况下,关闭log,可以PPK测试当前上报率下满载数据通信条件下的功耗。
功耗特点:
空闲时间进入deepsleep模式6~7ms,对功耗降低有一定帮助
不同发射功率下功耗差异明显
测试结果:
发射功率 |
工作电流 |
备注 |
|---|---|---|
+9 dBm |
700uA+ |
高功率模式 |
0 dBm |
500uA+ |
标准功率模式 |

BLE模式功耗¶
BLE配对后,test_mode_auto_circle开启情况下,关闭log,可以PPK测试当前上报率下满载数据通信条件下的功耗。
功耗特点:
空闲时间进入deepsleep模式2ms+,对功耗降低有一定帮助
蓝牙写入数据的函数为flash function,执行速度与代码当前cache有一定关系
以下代码执行1.3ms~2.5ms级别对应芯片的普通运行状态电流
剩余时间由controller判断是否执行休眠,因此蓝牙功耗测试可能不同次复位的情况下,功耗略不同
om = ble_hs_mbuf_from_flat(&app_data, sizeof(app_data));
ble_gatts_notify_custom(peri_conn_handle, hid_app_input_handle, om);
测试结果:
发射功率 |
工作电流 |
备注 |
|---|---|---|
+9 dBm |
3mA+ |
高功率模式 |
0 dBm |
2mA+ |
标准功率模式 |

BLE不同状态功耗:
除了全速运行状态,还有仅连接不上报数据状态、slave latency 连接状态(当前代码 conn_latency=35):
状态 |
电流 |
说明 |
|---|---|---|
仅连接(0dBm) |
1mA+ |
保持连接但不上报数据 |
Slave latency(35) |
约100uA+ |
跳过连接事件降功耗(实测曲线可能因固件版本略有差异) |
Standby M1 |
uA级别 |
空闲300s后断连待机 |


6 开发说明¶
6.1 SDK配置宏详解¶
以下列出mult_ms工程的关键配置宏,按功能模块分类说明。
重要提示:
所有配置默认值均基于库上的原始
sdk_config.h文件(git HEAD版本)实际项目中可根据需求修改这些配置,但需充分测试
配置文件位置:
keil_107x/configuration/sdk_config.h可通过Keil IDE的 “Configuration Wizard” 图形化界面进行配置
6.1.1 硬件与板级配置¶
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
1 |
鼠标硬件类型选择: |
0-2 |
|
1 |
使能RAM代码执行 |
0/1 |
|
1 |
电源模式选择: |
0/1 |
注意: CONFIG_BOARD_TYPE 决定按键映射、传感器驱动、LED引脚等硬件相关配置,必须与实际PCB设计匹配。
6.1.2 工作模式配置¶
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
1 |
模式切换时是否复位芯片 |
0/1 |
|
0 |
上电固定进入2.4G模式 |
0/1 |
|
0 |
上电固定进入BLE模式 |
0/1 |
|
0 |
上电固定进入USB模式 |
0/1 |
|
0 |
上电自动开启画圈测试 |
0/1 |
注意: 以上四个POWER_ON_FIXED_*宏互斥,最多只能使能一个。如果都不使能,则通过硬件(拨动开关/ADC)检测模式。
6.1.3 应用层功能配置¶
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
1 |
快速连接测试模式 |
0/1 |
|
0 |
锁定上报率不可切换 |
0/1 |
|
3 |
默认上报率索引: |
0-3 |
|
0x07D10006 |
客户ID(Customer ID) |
32bit值 |
上报率配置建议:
办公场景:
DEFAULT_REPORT_RATE=0(125Hz), 省电游戏场景:
DEFAULT_REPORT_RATE=3(1000Hz), 低延迟通用场景:
DEFAULT_REPORT_RATE=2(500Hz), 平衡
6.1.4 时间参数配置¶
宏定义 |
默认值 |
单位 |
说明 |
|---|---|---|---|
|
300 |
秒 |
BLE断连后超时时间(5分钟) |
|
300 |
秒 |
BLE配对超时时间 |
|
300 |
秒 |
BLE空闲超时时间 |
|
60 |
秒 |
2.4G配对超时时间 |
|
3 |
秒 |
2.4G空闲进入deepsleep时间 |
|
200 |
毫秒 |
2.4G连接状态下发包间隔 |
|
1 |
秒 |
2.4G连接建立超时 |
|
60 |
秒 |
2.4G断连后重新扫描时间 |
时间参数调优建议:
低功耗优先: 减小
PRF_IDLE_TIMEOUT_S(更快休眠)响应速度优先: 减小
PRF_CONNECTION_INTERVALS_MS(更快上报)稳定性优先: 增大
PRF_DISCONNECT_TIMEOUT_S(更长重连等待)
6.1.5 2.4G PRF射频配置¶
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
3 |
Dongle类型选择: |
0-4 |
|
-30 |
配对时发射功率(dBm) |
-30 ~ +9 |
|
+1 |
连接时起始发射功率(dBm) |
-30 ~ +9 |
|
0 |
使能动态发射功率 |
0/1 |
|
1 |
使能独立PRF线程 |
0/1 |
|
0 |
每次上电强制重新配对 |
0/1 |
|
0 |
PRF收发调试信息打印 |
0/1 |
6.1.5.1 动态发射功率¶
使能CONFIG_PRF_DYN_PWR_EN后,鼠标在PRF已连接阶段根据每包ACK质量逐包实时调节TX功率,实现近距离降功率省电、远距离升功率保链路。实现位于src/app_prf_dyn_pwr.c / app_prf_dyn_pwr.h(由app_prf.c include)。
生效条件(须同时满足):
CONFIG_PRF_DYN_PWR_EN = 1处于
PRF_CONNECT_STAGE且prf_state.connect == true(非配对阶段)连接后已完成
CONFIG_PRF_DYN_WARMUP_ACKS次ACK预热(预热期间不调功率)
配对/断连时调用app_prf_dyn_pwr_on_pair()复位;连接成功时app_prf_dyn_pwr_on_connect()将当前功率设为CONFIG_PRF_CONNECT_RF_POWER。
调制逻辑
每个逻辑seq(一包HID数据对应一轮TX/RX)统计该轮内RX timeout次数timeouts_in_round。收到ACK时:
该seq收到ACK前的timeout次数 |
判定 |
计数器影响 |
|---|---|---|
0 |
干净ACK |
|
≥ |
弱ACK |
|
1(低于弱ACK阈值) |
一般ACK |
升降功率均不累计,仅 |
PRF任务空闲时(app_prf_idle_is())执行app_prf_dyn_pwr_process()决策:
条件 |
动作 |
|---|---|
|
升功率 |
当前seq的 |
升功率(重传过程中即可响应,不等ACK) |
|
降功率 |
功率变化后调用RF_SetPowerLevel() + RF_RefreshPhySeqRAM(0),同步tx_config.tx_power,并进入CONFIG_PRF_DYN_COOLDOWN_ROUNDS轮冷却。实际功率限制在[CONFIG_PRF_DYN_PWR_MIN, CONFIG_PRF_DYN_PWR_MAX]。
动态功率相关配置
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
-20 |
动态调制功率下限(dBm) |
-30 ~ +9 |
|
+8 |
动态调制功率上限(dBm) |
-30 ~ +9 |
|
1 |
每次升降步进(dBm) |
1 ~ 9 |
|
3 |
连续弱ACK次数触发升功率 |
1 ~ 20 |
|
80 |
连续干净ACK次数触发降功率 |
10 ~ 500 |
|
2 |
单seq timeout达到此次数,该ACK计为弱ACK |
1 ~ 15 |
|
80 |
每次改功率后的冷却轮数 |
10 ~ 500 |
|
100 |
连接后预热ACK数,期间不调节 |
0 ~ 500 |
调试日志
开启 CONFIG_PRF_RF_TRANSFE_DEBUG=1 时,每秒通过 prf_stats_print_event 异步打印(不阻塞 PRF 热路径):
1s sensor N tx N new_seq N retx N ack_rx N ... hop_trig N hop_tms N
ch[0] 2462MHz ack N
...
pwr cur -1 ok 12 miss 0 tmo 0 cd 0 wu 0 up 2 down 0 // 仅 CONFIG_PRF_DYN_PWR_EN=1
字段 |
含义 |
|---|---|
|
跳频触发次数(= |
|
跳频后首次收到有效 ACK 的次数 |
|
当前 TX 功率(dBm),需 |
|
连续干净/弱 ACK 计数 |
|
当前 seq 已累计的 timeout 次数 |
|
冷却/预热剩余 |
|
过去 1 秒内升/降功率次数 |
调参建议
现象 |
建议 |
|---|---|
拉远后功率升得慢 |
减小 |
功率来回抖动 |
增大 |
近距离功耗仍偏高 |
确认干净ACK能稳定累计;适当降低 |
拉远上报率仍差 |
提高 |
量产不需要统计 |
|
功率配置建议:
办公场景(近距离<2米,固定功率):
PAIR=-30,CONNECT=0,DYN_PWR_EN=0→ 功耗~500uA办公场景(近距离,动态功率):
PAIR=-30,CONNECT=+1,DYN_PWR_EN=1,PWR_MIN=-20→ 近距离自动降功率通用场景(3-5米):
PAIR=-30,CONNECT=+7,DYN_PWR_EN=1→ 远近自适应远距离(>5米,固定功率):
PAIR=-30,CONNECT=+9,DYN_PWR_EN=0→ 功耗~900uA
Dongle类型选择指南:
COMMON_DONGLE(推荐): 通用型,支持多设备共用,兼容性好
PAN2628_DONGLE: 多pipe Dongle,支持键鼠套件同时连接
PAN1010_DONGLE: 低成本方案,适用于价格敏感产品
COMBO_DONGLE: 组合型Dongle,特殊应用场景
BY_DONGLE: 自定义协议,需自行实现配对逻辑
6.1.6 BLE蓝牙配置¶
BLE基础配置:
宏定义 |
默认值 |
说明 |
取值范围 |
|---|---|---|---|
|
1 |
BLE延迟优化 |
0/1 |
|
0 |
重新配对时刷新绑定信息 |
0/1 |
|
1 |
广播响应包中包含设备名称 |
0/1 |
|
“PanMS-LP” |
BLE完整设备名称(最大31字节) |
字符串 |
|
“PanMS-LP” |
BLE短设备名称 |
字符串 |
|
3 |
最大可切换BLE设备数 |
1-8 |
BLE DIS(Device Information Service)配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
2 |
PnP Vendor ID Source: |
|
0x07D1 |
Vendor ID (厂商ID) |
|
0x0006 |
Product ID (产品ID) |
|
1 |
Version (版本号) |
BLE连接参数:
宏定义 |
默认值 |
说明 |
计算方式 |
|---|---|---|---|
|
6 |
最小连接间隔 |
6 × 1.25ms = 7.5ms |
|
6 |
最大连接间隔 |
6 × 1.25ms = 7.5ms |
|
30 |
偏好 Latency(协商前) |
连接后约2s改为 35 |
|
200 |
Supervision Timeout |
200 × 10ms = 2000ms |
BLE低功耗管理(app_ble.c 运行时逻辑):
宏/行为 |
默认值 |
说明 |
|---|---|---|
|
300 s |
连接空闲超时后进 standby M1 |
|
300 s |
配对广播超时 |
|
300 s |
已连接但无 notify 超时 |
|
2000 ms |
连接后定时请求 |
|
428 事件 |
无活动约 428 个连接事件后启用 slave latency |
BLE功耗优化策略:
Latency: 连接约 2s 后设
conn_latency=35;无活动约 428 个连接事件(约 3.2s @7.5ms)后启用Standby: 持续空闲 300s →
standby_m1_event断开连接(uA 级)连接间隔:
MIN_INT=MAX_INT=6(7.5ms,约 133Hz 事件率)
6.1.7 USB HID配置¶
USB VID/PID配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0x07 |
USB Vendor ID 高字节 |
|
0xD1 |
USB Vendor ID 低字节 |
|
0x00 |
USB Product ID 高字节 |
|
0x06 |
USB Product ID 低字节 |
USB描述符配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
“panchip” |
制造商字符串(第1段) |
|
“fs_ms_107x” |
制造商字符串(第2段) |
|
“1234” |
制造商字符串(第3段) |
USB低功耗:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
USB模式下使能deepsleep |
注意: VID/PID需要向USB-IF申请,或使用PanChip提供的默认值。
6.1.8 EMI测试配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
EMI测试类型: |
|
0 |
EMI载波发射功率(dBm) |
|
2402 |
EMI载波频点(MHz) |
EMI测试说明:
Carrier Test: 持续发射单频载波,用于频谱分析仪测试辐射
USB Test: 通过USB发送特定数据包,测试USB通信时的EMI
量产前必测: 确保产品通过FCC/CE等电磁兼容认证
6.1.9 ARGB灯效配置(可选)¶
ARGB功能使能:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
使能ARGB灯效功能 |
SPI接口配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
SPI模块选择: |
|
1 |
SPI分频系数 |
引脚配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能ARGB数据输出 |
|
3 |
SPI MOSI(DOUT)引脚: |
|
0 |
使能ARGB时钟输出测试 |
|
2 |
SPI CLK引脚: |
LED数量:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
8 |
ARGB LED总数 |
注意: ARGB功能会增加功耗(~5-10mA),电池供电产品建议禁用或仅在充电时使能。
6.1.10 调试与测试配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
上电自动开启画圈测试 |
|
0 |
上电固定进入2.4G模式 |
|
0 |
上电固定进入BLE模式 |
|
0 |
上电固定进入USB模式 |
|
0 |
上电固定进入EMI测试模式 |
|
0 |
每次上电强制重新配对 |
|
0 |
BLE每次上电强制重新配对 |
|
0 |
PRF收发数据调试打印 |
|
0 |
GPIO调试信息打印 |
|
0 |
PRF调试IO输出(P10-T/P04-R/P05-ACC/P07-RxData) |
|
0 |
ADC电量采样日志打印 |
调试建议:
开发阶段: 使能相关DEBUG宏,方便定位问题
量产前: 关闭所有DEBUG宏,节省Flash/RAM资源
自动化测试: 使用
POWER_ON_*固定模式宏,简化测试流程
6.1.11 Bootloader配置¶
App工程Boot配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能Bootloader功能 |
|
40 |
Bootloader分区大小(KB) |
|
“../../bootloader/keil_107x” |
Bootloader工程相对路径 |
|
0 |
强制重新编译Bootloader |
|
1 |
合并Boot+App为一个镜像文件 |
|
1 |
使能App镜像头 |
App版本信息:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
主版本号 |
|
0 |
次版本号 |
|
1 |
修订版本号 |
|
0 |
构建号 |
Flash分区布局(当CONFIG_ENABLE_BOOTLOADER=1时):
分区名称 |
起始地址 |
大小 |
说明 |
|---|---|---|---|
Bootloader |
0x00000 |
40KB |
Boot程序,支持USB DFU + 2.4G OTA |
App |
0x0A000 |
220KB |
应用程序主分区 |
App Backup |
0x41000 |
220KB |
见§4.4.3 Backup 可复用说明 |
KV Store |
0x78000 |
16KB |
键值存储(配对信息/配置) |
User Custom |
0x7C000 |
12KB |
用户数据;末4KB保留DFU标志 |
总计 |
- |
508KB |
PAN1070 Flash总容量 |
注意:
Boot 相关
BOOT_*宏在 bootloader 工程sdk_config.h配置;App 工程用CONFIG_ENABLE_BOOTLOADER等User Custom 末 4KB 为 DFU 标志保留区;Backup 在本方案升级中可复作用户存储(见§4.4.3)
CONFIG_APP_CID用于DFU防误升级,不同客户应设置不同CID2.4G OTA详细说明: 请参考官方文档 PanChip 2.4G OTA升级指南
6.1.12 上报率配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
3 |
默认上报率索引: |
|
0 |
锁定上报率不可切换 |
上报率配置建议:
办公场景:
DEFAULT_REPORT_RATE=0(125Hz), 省电游戏场景:
DEFAULT_REPORT_RATE=3(1000Hz), 低延迟通用场景:
DEFAULT_REPORT_RATE=2(500Hz), 平衡
6.1.13 平台与时钟配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
48 |
系统主时钟(MHz): |
|
2 |
APB1总线分频系数 |
|
12 |
APB2总线分频系数 |
|
0 |
32K低速时钟源: |
|
1 |
强制校准RCL时钟 |
|
1 |
使能RAM函数 |
|
1 |
使能Flash LDO(1.8V) |
|
1 |
中断向量表重映射到RAM |
|
1536 |
中断栈大小(字节) |
|
0 |
C库堆大小(字节) |
时钟配置建议:
高性能:
SYS_CLOCK=48,APB1_DIV=2,APB2_DIV=2低功耗:
SYS_CLOCK=32,APB1_DIV=2,APB2_DIV=12时钟源: 无外部晶振时用RCL,有晶振时用XTL(精度更高)
6.1.14 低功耗管理配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能电源管理(deepsleep/standby) |
|
0 |
使能系统看门狗 |
|
1 |
低功耗模式下保持Flash供电 |
|
1 |
DeepSleep模式选择: |
|
500 |
上电后延迟进入deepsleep时间(ms) |
|
0 |
增加LPLDOH电压校准值 |
|
0 |
Standby M1唤醒后是否复位: |
DeepSleep模式对比:
特性 |
Mode 1 |
Mode 2 |
|---|---|---|
功耗 |
~4uA |
~8uA |
唤醒源 |
GPIO, SleepTimer CH0/1/2 |
GPIO, SleepTimer, HW Timer0/1/2 |
PWM输出 |
不支持 |
支持 |
适用场景 |
纯低功耗 |
需要PWM/定时器 |
6.1.15 LL RAM使用配置¶
说明: LL RAM是BLE Link Layer专用的RAM区域,可被App借用存储数据以节省SRAM。
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能App访问LL RAM |
|
1 |
BLE Host任务使用LL RAM |
|
1 |
OS msys缓冲区使用LL RAM |
|
1 |
BLE传输缓冲区使用LL RAM |
|
0 |
BLE存储使用LL RAM |
|
1 |
BLE安全管理器使用LL RAM |
|
0 |
BLE HCI使用LL RAM |
|
0 |
BLE GATT Client使用LL RAM |
|
0 |
BLE ATT Server使用LL RAM |
|
4096 |
LL Data RAM剩余大小(字节) |
LL RAM优化建议:
内存紧张时: 使能更多LL RAM选项,释放SRAM
稳定性优先: 保留足够
LL_DATA_FREE_SIZE,避免BLE异常推荐配置: 保持默认值,已经过充分测试
6.1.16 RTOS(FreeRTOS)配置¶
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能FreeRTOS操作系统 |
|
8 |
最大任务优先级数 |
|
9000 |
OS总堆大小(字节) |
|
500 |
Main任务栈大小(word) |
|
3 |
Main任务优先级 |
|
400 |
BLE Host任务栈大小(word) |
|
6 |
BLE Host任务优先级 |
|
1 |
使能软件定时器 |
|
192 |
Timer任务栈深度(word) |
|
2 |
Timer任务优先级 |
|
12 |
Timer命令队列长度 |
|
0 |
使能Idle Hook回调 |
|
0 |
使能Tick Hook回调 |
|
1 |
使能malloc失败Hook |
|
2 |
栈溢出检测模式: |
|
1 |
支持静态内存分配 |
|
84 |
Idle任务最小栈大小(word) |
任务优先级规划:
优先级 |
任务名称 |
说明 |
|---|---|---|
7 (最高) |
- |
保留给紧急中断处理 |
6 |
BLE Host |
BLE协议栈,高优先级保证实时性 |
5 |
- |
预留 |
4 |
- |
预留 |
3 |
Main Task |
主任务,处理事件队列 |
2 |
Timer |
软件定时器任务 |
1 |
- |
预留 |
0 (最低) |
Idle |
空闲任务 |
内存优化建议:
RAM紧张: 减小
configTOTAL_HEAP_SIZE,关闭未使用的Hook稳定性优先: 增大任务栈大小,使能栈溢出检测Mode 2
调试阶段: 使能
configUSE_MALLOC_FAILED_HOOK,检测内存泄漏
6.1.17 日志与调试输出配置¶
日志系统总开关:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能日志系统总开关 |
|
0 |
使能断言(Assert) |
APP日志配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能APP层日志 |
|
4 |
日志级别: |
|
1 |
打印日志级别前缀([DEBUG]/[INFO]等) |
|
0 |
打印详细信息(文件/行号/函数名) |
|
1 |
日志后端: |
SYS日志配置:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
使能SYS层日志 |
|
3 |
SYS日志级别 |
|
1 |
打印SYS日志级别前缀 |
|
0 |
打印SYS详细信息 |
|
1 |
SYS日志后端(printf/printk) |
BLE Controller日志:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
2 |
BLE底层日志后端: |
日志输出通道:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
使能UART日志输出(Git默认关闭) |
|
2 |
日志UART TX引脚(Git默认): |
|
115200 |
日志UART波特率 |
|
0 |
使能RTT日志输出 |
|
512 |
RTT上行缓冲区大小(字节) |
IO时序追踪配置(高级调试):
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
使能IO时序追踪 |
|
0 |
BLE Link Layer内部时序追踪 |
|
0x22 |
DeepSleep模式追踪引脚(P22) |
|
0x23 |
Sleep模式追踪引脚(P23) |
|
0x99 |
BLE/PRF IRQ追踪引脚(禁用) |
… |
… |
其他IRQ追踪引脚配置(共30+个) |
日志配置建议:
阶段 |
推荐配置 |
说明 |
|---|---|---|
开发调试 |
|
最详细日志,方便定位问题 |
集成测试 |
|
正常日志,减少输出量 |
量产固件 |
|
关闭日志,节省~11KB Flash + 2KB RAM |
资源占用对比:
配置 |
Flash占用 |
RAM占用 |
性能影响 |
|---|---|---|---|
日志全开(DEBUG+Trace) |
~15KB |
~3KB |
-10% |
日志正常(INFO) |
~8KB |
~2KB |
-5% |
日志关闭 |
0 |
0 |
0% |
6.1.18 时钟输出与启动延迟配置¶
时钟 mux 到IO引脚(调试用):
宏定义 |
默认值 |
说明 |
|---|---|---|
|
0 |
使能时钟输出到IO引脚 |
|
1 |
32K低速时钟输出到P11 |
|
0 |
AHB(系统)时钟输出到P06 |
|
0 |
32M RCH时钟输出到P17 |
|
0 |
32M XTH时钟输出到P27 |
|
0 |
DPLL时钟(/8)输出到P23 |
启动延迟:
宏定义 |
默认值 |
说明 |
|---|---|---|
|
1 |
使能上电长延迟 |
使用场景:
时钟测量: 使能对应时钟mux,用示波器测量实际频率
J-Link连接困难: 使能
STARTUP_LONG_DELAY,给调试器更多连接时间量产固件: 关闭所有clock mux和startup delay
6.2 软件架构¶
6.2.1 目录结构¶
mult_ms/
├── keil_107x/ # Keil工程文件
├── src/ # 源代码目录
│ ├── main.c # 主程序入口,模式检测与初始化
│ ├── app_common.c/h # 通用功能(KV存储/事件队列/组合键)
│ ├── app_prf.c/h # 2.4G PRF通信模块
│ ├── app_prf_dyn_pwr.c/h # PRF动态发射功率(可选,CONFIG_PRF_DYN_PWR_EN)
│ ├── app_ble.c/h # BLE蓝牙通信模块
│ ├── app_ble_gatt_svr.c/h# BLE GATT服务实现
│ ├── app_usb.c/h # USB HID通信模块
│ ├── app_sensor.c/h # 光电传感器驱动
│ ├── app_mouse_key.c/h # 按键扫描与处理
│ ├── app_mouse_wheel.c/h # QDEC滚轮模块
│ ├── app_adc.c/h # ADC电量检测
│ ├── app_led.c/h # LED指示灯控制
│ ├── app_argb_led.c/h # ARGB灯效控制
│ ├── app_pm.c/h # 电源管理(deepsleep/standby)
│ ├── app_emi.c/h # EMI测试模式
│ ├── fragment_regroup_packet.c/h # 数据分片与重组
│ ├── dongle/ # 不同Dongle配对协议实现
│ │ ├── pair_with_common_dongle.c
│ │ ├── pair_with_pan2628_dongle.c
│ │ ├── pair_with_pan1010_dongle.c
│ │ └── ...
│ └── usb/ # USB协议栈
└── board/ # 板级配置文件
6.2.2 主程序流程¶
main函数启动流程:
int main(void)
{
// 1. 打印版本信息(如果使能BOOT)
#if CONFIG_BOOT_ENABLE
print_version_info();
#endif
// 2. 上电初始化(复位原因检测/GPIO清除)
power_on_init();
// 3. 应用层初始化
app_wdt_init(); // 看门狗初始化
app_deepsleep_init(); // 深睡初始化
app_run_time_init(); // 运行时间计数
app_event_queue_init(); // 事件队列初始化
// 4. 存储初始化
kv_init(); // KV存储系统
// 5. 外设初始化
#if !CONFIG_POWER_ON_AUTO_CIRCLES
app_mouse_key_init(); // 按键初始化
app_emi_mode_detect(); // EMI模式检测
app_usb_detect_init(); // USB检测初始化
app_mouse_wheel_init(); // 滚轮初始化
sensor_spi_init(); // 传感器SPI初始化
sensor_init(); // 传感器初始化
app_led_init(); // LED初始化
app_led_flicker_init(); // LED闪烁初始化
app_argb_init(); // ARGB初始化
app_battery_get(); // 电量检测
#endif
// 6. 模式检测与初始化
app_mode_detect(); // 检测当前工作模式
app_work_mode_init(); // 初始化对应模式
// 7. PRF IO调试(可选)
app_prf_io_debug();
// 8. 主循环:处理事件队列
while (1) {
app_event_queue_handle();
}
}
主循环与周期事件架构:
[sleep timer IRQ]
|
v
周期事件 (combine_key 20ms / led / battery / usb / switch / ble_period*)
|
+-- 可投递一次性事件 --> app_event_queue_put()
|
v
主循环 while(1): app_event_queue_handle()
|
+-- 队列有事件 --> 查 g_app_event_queue[] 执行 callback
+-- 队列空 --> 阻塞等待
* ble_period 在 PRF 模式下由 app_prf_mode_init() 停止, deinit 时恢复
PRF模式注意: app_prf_mode_init() 会 app_period_event_stop(ble_period_event),避免 PRF 模式下 BLE 周期任务占用事件队列;退出 PRF 时在 app_prf_mode_deinit() 中恢复。
模式切换逻辑:
void app_work_mode_deinit(void)
{
switch (orgin_app_work_mode) {
case app_usb_mode:
app_usb_mode_deinit();
app_slptmr_stop();
break;
case app_prf_mode:
app_slptmr_stop();
app_prf_mode_deinit();
break;
case app_ble_mode:
app_ble_mode_deinit();
app_slptmr_stop();
break;
// ...
}
orgin_app_work_mode = app_work_mode;
}
void app_work_mode_init(void)
{
switch (app_work_mode) {
case app_usb_mode:
app_usb_mode_init();
app_rate_init();
app_slptmr_init(app_rate);
break;
case app_prf_mode:
app_prf_mode_init();
app_prf_pair_init(); // 初始化配对状态
app_rate_init();
app_slptmr_init(app_rate);
break;
case app_ble_mode:
app_ble_mode_init();
app_ble_mode_start(); // 启动BLE广播
app_rate = 125;
app_slptmr_init(app_rate);
break;
// ...
}
}
6.2.3 事件驱动架构¶
mult_ms 采用事件队列 + 周期事件双层调度:一次性事件入 g_app_event_queue,周期任务由 sleep timer 驱动并在回调中可投递事件。
一次性事件类型(app_event_queue.h / app_event_table.c):
enum event_e {
mode_change_event = 0, // 模式切换
ble_id_change_event, // BLE 多设备 ID 切换
wireless_repair_event, // 重新配对(2.4G/BLE)
standby_m1_event, // Standby M1
deepsleep_event, // Deepsleep
rate_change_event, // 上报率切换
usb_detect_event, // USB 插拔检测
ble_hid_transfer_send_event, // BLE HID 发送
ble_pair_info_event, // BLE 配对信息处理
ble_reload_adv_event, // BLE 重新加载广播
#if CONFIG_PRF_RF_TRANSFE_DEBUG
prf_stats_print_event, // PRF 1s 统计打印(异步)
#endif
#if (CONFIG_DONGLE_TYPE == PAIR_WITH_COMMON_DONGLE)
prf_regroup_event, // 分片重组完成
prf_usb_ep3_handle_event, // Dongle 回传 EP3 数据
#endif
event_max // 队列深度 APP_EVENT_QUEUE_LEN_MAX=10
};
周期事件类型(enum period_event_e, ID 130+):
事件 |
周期 |
回调 |
说明 |
|---|---|---|---|
|
20ms |
|
组合键扫描 |
|
可配 |
|
电量更新 |
|
可配 |
|
LED 闪烁 |
|
可配 |
|
BLE 状态机;PRF 模式下停止 |
|
可配 |
|
USB HID |
|
可配 |
|
三模拨动检测 |
事件处理主循环:
void app_event_queue_handle(void)
{
uint8_t event;
if (app_event_queue_get(&event)) {
/* 在 g_app_event_queue[] 中查找 event 并调用 callback */
for (i = 0; i < event_max; i++) {
if (g_app_event_queue[i].event == event) {
g_app_event_queue[i].callback();
break;
}
}
}
}
典型事件链路示例:
组合键 左+中+右(重新配对) →
wireless_repair_event→app_wireless_repair()→app_prf_repair()/app_ble_repair()组合键 中键 2s →
rate_change_event→app_rate_change()PRF 调试开启时,sleep timer 每秒投递
prf_stats_print_event,打印含hop_trig/hop_tms/动态功率统计
优势:
单线程模型,避免资源竞争
事件优先级清晰,易于调试
低功耗友好,无事件时可进入休眠
6.2.4 低功耗管理¶
休眠控制接口:
用户可以使用idle线程控制vApplicationIdleHook,也可以使用API user_is_sleep_allow接口控制休眠状态为deepsleep或者wfi sleep。
默认使用API接口控制并且关闭了idle线程。
Deepsleep钩子函数:
deepsleep前后拉了IO可以debug睡眠时间状态:
CONFIG_RAM_CODE void vSocDeepSleepEnterHook(void)
{
// This hook function executes right before SoC entering DeepSleep Mode.
APP_TRACK_CHN0_LOW // 拉低IO,标记进入深睡
}
CONFIG_RAM_CODE void vSocDeepSleepExitHook(void)
{
// This hook function executes right after SoC waking up from DeepSleep Mode.
APP_TRACK_CHN0_HIGH // 拉高IO,标记退出深睡
#if !APP_CONN_EVEN_CB_EN
if (app_work_mode == app_ble_mode) {
app_ble_trigger_send_data(); // BLE模式下触发数据发送
}
#endif
}
低功耗状态机:
状态 |
描述 |
唤醒源 |
|---|---|---|
Active |
活跃工作状态 |
- |
Idle |
空闲状态(WFI) |
中断 |
DeepSleep |
深度睡眠(RAM保持) |
GPIO/WDT/LPTIMER |
Standby M1 |
待机模式(RAM不保持) |
GPIO |
6.2.5 2.4G PRF模块¶
初始化流程(app_prf_mode_init):
void app_prf_mode_init(void)
{
memset(&prf_state, 0, sizeof(prf_state));
app_period_event_stop(ble_period_event); // PRF 模式停止 BLE 周期任务
sem_prf_data = xSemaphoreCreateBinary();
app_prf_dongle_init();
/* 发送环形缓冲: 7字节 × 30 包 (PRF_PERIPHERAL_DATA_NUMBER=30) */
ring_buf_init(&g_prf_tx_ring_buf,
PRF_PERIPHERAL_DATA_SIZE * PRF_PERIPHERAL_DATA_NUMBER, &g_tx_buf[0][0]);
app_ble_phy_host_init_once();
mode_change_protect_reset();
PRI_RF_ChipModeSel(PRI_RF, PRI_RF_CHIP_MODE_297);
panchip_prf_init(&tx_config);
panchip_prf_set_chn(tx_config.rf_channel);
panchip_prf_pid_cfg(&tx_config, 0);
panchip_prf_rx_length_irq_cfg(ENABLE);
app_prf_thread_init(); // CONFIG_PRF_THREAD_EN=1 时创建独立 PRF 任务
}
反初始化(app_prf_mode_deinit): 等待 PRF idle → 删除 PRF 任务 → app_period_event_start(ble_period_event) 恢复 BLE 周期任务。
发送缓冲: g_prf_tx_ring_buf 容量 30 包(每包 7 字节 HID 采样);满时新采样丢弃,不覆盖旧数据。
RF 调试统计(CONFIG_PRF_RF_TRANSFE_DEBUG=1): 每秒异步打印一行,含 hop_trig(跳频触发次数)、hop_tms(跳频后首次 ACK 次数)及动态功率字段;详见 6.1.5.1。
PRF线程(可选):
void app_prf_thread_init(void)
{
static bool prf_thread_inited = false;
if (prf_thread_inited) {
return;
}
prf_thread_inited = true;
/* Create an PRF Task */
int r = xTaskCreate(task_prf_entry, // Task Function
"task_prf", // Task Name
300, // Task Stack Size
NULL, // Task Parameter
7, // Task Priority
&task_prf_hd // Task Handle
);
/* Check if task has been successfully created */
if (r != pdPASS) {
APP_LOG_INFO("Error, PRF Task created failed!\n");
while (1);
}
}
关键特性:
根据使用场景主要用到了sleep timer irq trigger组包
RF中断处理重传或者动态配置可以进入deepsleep
发包状态下始终控制不休眠状态稳定
6.2.6 BLE模块¶
初始化流程:
void app_ble_mode_init(void)
{
// 确保芯片模式为BLE
app_ble_ensure_chip_mode();
// 初始化BLE Host线程(内部创建)
pan_ble_stack_init(app_ble_pre_init_cb, app_ble_enabled_cb);
// 初始化BLE线程
app_ble_thread_init_once();
}
void app_ble_ensure_chip_mode(void)
{
APP_LOG_INFO("bleprph_on_sync chip mode1 %d\n",
PRI_RF_GetChipMode(PRI_RF));
// 切换到BLE芯片模式
PRI_RF_ChipModeSel(PRI_RF, PRI_RF_CHIP_MODE_BLE);
APP_LOG_INFO("bleprph_on_sync chip mode1 %d\n",
PRI_RF_GetChipMode(PRI_RF));
}
连接参数调整:
动态修改latency以降低功耗:
void app_ble_change_latency(uint16_t itvl_min, uint16_t itvl_max,
uint16_t latency, uint16_t supervision_timeout)
{
APP_LOG_INFO("latency %d change\n", latency);
struct ble_gap_upd_params upd_params = {
.itvl_min = itvl_min,
.itvl_max = itvl_max,
.latency = latency,
.supervision_timeout = supervision_timeout,
};
ble_gap_update_params(peri_conn_handle, &upd_params);
}
BLE线程:
static void app_ble_thread_init_once(void)
{
static bool app_ble_thread_inited = false;
if (app_ble_thread_inited) {
return;
}
app_ble_thread_inited = true;
// 注册连接事件关闭回调(trigger组包使用)
#if APP_CONN_EVEN_CB_EN
pan_misc_register_conn_evt_close_cb(
(pan_conn_evt_close_cb_t)app_conn_evt_close_cb);
#endif
}
关键接口:
广播接口:重新配对等处理参考代码调度
不复位切换需要注意处理好蓝牙状态,线程调度状态
动态改latency接口用于功耗优化
6.3 主要数据结构¶
6.3.1 工作状态枚举¶
enum app_work_mode_t {
app_null_mode, // 0: 无效模式
app_charge_mode, // 1: 充电模式
app_usb_mode, // 2: USB有线模式
app_prf_mode, // 3: 2.4G PRF模式(默认)
app_ble_mode, // 4: BLE蓝牙模式
app_emi_mode, // 5: EMI测试模式
};
6.3.2 PRF状态结构¶
struct prf_state_t {
volatile bool init; // 初始化标志
volatile bool stored_pair; // 已存储配对信息
volatile bool pairing; // 正在配对
volatile bool connect; // 已连接
volatile bool ack_done; // ACK完成
volatile bool trx_flag; // 收发标志
volatile uint16_t trx_wait_irq_cnt; // 收发等待中断计数
uint8_t own_addr[6]; // 本机MAC地址
uint8_t private_rf_addr[4]; // 私有RF地址
uint8_t dg_type_addr[7]; // Dongle类型+地址
uint16_t private_rf_channel[8]; // 私有跳频频点
volatile uint8_t channel_index; // 当前频点索引
uint16_t ack_lost_cnt; // ACK丢失计数
uint16_t hop_cnt; // 跳频触发计数(日志 hop_trig)
uint16_t hop_tms; // 跳频后首次有效ACK计数
volatile bool hop_recover_pending; // 跳频后等待首次ACK标志
uint16_t ch_ack_cnt[8]; // 各频点ACK统计
uint16_t tx_new_seq_cnt; // 新seq发送计数
uint16_t tx_retx_cnt; // 重传计数
uint16_t ack_rx_cnt; // ACK接收计数
uint16_t rx_test_cnt; // RX测试计数
uint16_t valid_rx_test_cnt; // 有效RX测试计数
uint16_t tx_test_cnt; // TX测试计数
uint16_t tx_irq_test_cnt; // TX中断测试计数
uint16_t timout_test_cnt; // 超时测试计数
uint16_t crc_test_cnt; // CRC错误计数
uint16_t pid_test_cnt; // PID错误计数
uint16_t rx_len_err_test_cnt; // RX长度错误计数
uint16_t sensor_test_cnt; // 传感器测试计数
uint16_t keep_work_time; // 保持工作时间
volatile uint32_t trx_idle_cnt; // 收发空闲计数
volatile uint32_t ack_err_cnt; // ACK错误计数
volatile uint32_t prf_trx_time; // PRF收发时间
};
6.3.3 配对控制结构¶
typedef struct {
volatile enum prf_stage work_stage; // 工作阶段
volatile bool prf_retransmit; // 重传使能
volatile bool prf_pid; // PID使能
volatile bool pad_data_flag; // 填充数据标志
volatile bool no_data_flag; // 无数据标志
volatile bool empty_flag; // 空数据标志
volatile bool dongle_read_flag; // Dongle读取标志
} prf_ctrl_t;
7 RAM/Flash资源使用情况¶
PAN107x资源配置:
Flash Total: 508 KB (0x7F000)
SRAM Total: 48 KB (0xC000)
实际使用情况(当前配置):
Memory Region |
Start Address |
Used Size |
Region Size |
%age Used |
|---|---|---|---|---|
FLASH_TEXT |
0x0000A200 |
182,254 B |
220.00 KB |
80.90% |
SRAM_DATA_TEXT |
0x20000000 |
47,280 B |
48.00 KB |
96.19% |
说明:
Flash占用: 182,254字节 ≈ 178.0 KB (包含Bootloader + App代码和数据)
RAM占用: 47,280字节 ≈ 46.2 KB (包含系统栈、堆、BLE协议栈和应用数据)
Flash分区: Bootloader(40KB) + App(220KB) + Backup(220KB) + KV Store(16KB) + User(12KB) = 508KB
RAM紧张: SRAM使用率已达96.19%,接近上限,需注意内存优化
资源优化建议:
Flash优化:
关闭日志(
PAN_LOG_ENABLE=0): 节省约11KB关闭调试信息(
APP_LOG_TRACE_OUTPUT_EN=0): 节省约3KB移除未使用的功能模块(如ARGB): 节省约5-10KB
RAM优化:
减小任务栈大小(谨慎操作,需测试稳定性)
关闭未使用的LL RAM选项
减小OS堆大小(
configTOTAL_HEAP_SIZE): 当前9000字节关闭malloc失败Hook(
configUSE_MALLOC_FAILED_HOOK=0): 节省少量RAM
当前配置特点:
使能Bootloader(CONFIG_ENABLE_BOOTLOADER=1)
使能独立PRF线程(CONFIG_PRF_THREAD_EN=1)
使能BLE Host(CONFIG_BT_MAX_NUM_OF_PERIPHERAL=1)
默认上报率1000Hz(CONFIG_DEFAULT_REPORT_RATE=3)
这些配置都会增加RAM占用
8 已知问题¶
实体鼠标中,已知问题(待进一步优化软件硬件):
No |
问题描述 |
影响 |
解决方案/状态 |
|---|---|---|---|
1 |
某些硬件测试到过蓝牙切USB导致跳线 |
模式切换异常 |
硬件改版后改善 |
2 |
某些2628硬件配对交互35B性能不一致 |
配对稳定性 |
软件优化中 |
3 |
2.4G强干扰下小概率CRC误判 |
数据传输错误 |
需软硬件协同优化(详见下文) |
4 |
传感器精确度需要进一步确认 |
定位精度 |
待测试验证 |
5 |
功耗仍有优化空间 |
电池续航 |
通过延长deepsleep方式为主要优化点 |
6 |
BLE唤醒后reset,广播异常发不出 |
重连失败 |
需chip reset恢复,临时方案 |
8.1 2.4G强干扰下CRC误判问题¶
8.1.1 问题现象¶
在以下极端条件下,有极小概率出现CRC16校验通过但数据内容错误的情况:
硬件收发质量略差:
RF前端匹配不佳(天线/电感/电容参数偏差)
PCB布局不合理(RF走线过长/阻抗不连续)
电源噪声较大(LDO纹波/DCDC开关噪声)
空中强干扰环境:
WiFi密集区域(多个AP同时工作,信道重叠)
蓝牙设备密集(办公区大量BLE设备)
微波炉/无线电话等2.4G干扰源附近
长时间挂测(数小时~数天持续运行)
典型表现:
[正常情况] TX: {0x05, 0x01, 0x02, 0x03} → CRC16 = 0xABCD RX: {0x05, 0x01, 0x02, 0x03} → CRC16 = 0xABCD ✓ 数据正确 [异常情况 - 极小概率] TX: {0x05, 0x01, 0x02, 0x03} → CRC16 = 0xABCD RX: {0x05, 0xFF, 0x02, 0x03} → CRC16 = 0xABCD ✗ 数据错误但CRC匹配!
8.1.2 根本原因分析¶
CRC16碰撞原理:
CRC16是16位循环冗余校验,理论上存在碰撞可能:
CRC16碰撞概率 ≈ 1 / 2^16 = 1 / 65536 ≈ 0.0015%
但在实际场景中,碰撞概率会显著增加:
干扰导致比特翻转:
原始数据: 0x05 0x01 0x02 0x03 ↓ ↓ ↓ ↓ 干扰翻转: 0x05 0xFF 0x02 0x03 (第2字节从0x01翻转为0xFF)特定翻转模式可能导致CRC巧合匹配:
多比特同时翻转
翻转位置恰好使CRC多项式计算结果相同
RF解调错误导致的系统性偏差
长时间运行累积效应:
假设每秒1000个数据包,运行10小时: 总包数 = 1000 × 3600 × 10 = 36,000,000 包 理论碰撞次数 ≈ 36,000,000 / 65536 ≈ 549 次 但实际上,由于干扰不是随机的,碰撞概率会更高
8.1.3 当前状态¶
问题影响范围:
发生概率: 极低(< 0.001%)
触发条件: 需要硬件收发质量较差 + 强干扰环境 + 长时间运行
影响程度: 偶发鼠标指针跳跃或按键误触发
已采取的措施:
协议层已有CRC16校验,可拦截大部分传输错误
重传机制可在丢包时自动恢复
跳频机制可避开干扰严重的频点
后续优化方向:
应用层增加额外校验机制(如累加和/XOR校验)
优化硬件RF前端设计和PCB布局
增强抗干扰能力(ACK确认/FEC等)
启用crc24校验(已测试挂测3天正常,而实际增大需要考虑和其他设备兼容性及包载荷变长引起上报率略微下降)
优化建议:
功耗优化:
延长deepsleep时间为主要优化点
使能动态发射功率(
CONFIG_PRF_DYN_PWR_EN=1)按距离自动降功率,或手动降低CONFIG_PRF_CONNECT_RF_POWER关闭不必要的log输出
优化BLE latency参数
稳定性优化:
统一硬件版本后进行充分测试
优化BLE重连逻辑
改进传感器滤波算法
性能优化:
调整PRF重传策略
优化跳频阈值
改进数据包调度