传感器与编码器
AW31N BLE SDK 中的外设输入/输出能力封装:光学鼠标传感器(OMSensor)管理层负责将光电位移传感器数据转换为 HID 鼠标报告,IR 编码器负责红外载波信号的调制与发送。本文档以源码为依据,说明两者的抽象模型、注册机制、数据流与控制流程。
Purpose and Scope
本页面覆盖 AW31N BLE SDK 中与"传感器与编码器"相关的两块子系统:
- 光学鼠标传感器管理(OMSensor_manage):位于
apps/app/bsp/common/mouse_sensor/,负责光电鼠标传感器驱动的注册、轮询、位移读取、滤波、HID 报告打包与 CPI 调节,是无线鼠标/带光标控制外设方案的核心。 - IR 编码器(ir_encoder):位于
apps/app/bsp/common/ir/与apps/include_lib/common/ir_encoder.h,负责红外遥控载波信号的初始化、数据帧发送与重复码控制。
不在此页面范围内的相关主题:BLE HID 服务与上报链路的整体实现、GPIO/IOMUX 底层配置、系统定时器(sys_timer)机制,请参阅各自对应的目录页面。
Overview
在 AW31N 这样的单芯片 BLE 外设方案中,SDK 需要把"物理世界输入"(手指移动、滚轮滚动、按键按下)转化为"数字世界事件"(HID 报告、BLE 通知),并把数字指令重新转化为物理信号(红外载波)。
- 传感器侧:光学鼠标传感器(如 PAW 系列)通过 SPI 类接口输出 X/Y 位移增量。SDK 用
OMSENSOR_INTERFACE函数指针表抽象不同型号的差异,用REGISTER_OMSENSOR宏把驱动实例链接进.omsensor_dev段形成设备表,管理层通过list_for_each_omsensor遍历并选中可用设备。读取到的原始位移经过坐标交换、取反、均值滤波与饱和裁剪后,被打包进符合 HID 规范的 3 字节xymovement字段。 - 编码器侧:IR 编码器把地址码/命令码按载波频率与占空比调制为红外脉冲序列,支持带重复码与不带重复码(HX 协议)两种发送模式,并提供停止重复码的接口。
两个子系统都遵循"配置开关 + 平台数据 + 接口抽象"的 SDK 通用模式:通过 TCFG_OMSENSOR_ENABLE 等宏裁剪代码,通过 *_PLATFORM_DATA 结构体描述硬件引脚,通过函数指针表解耦具体器件型号。
Architecture
flowchart TD
subgraph sg_App["应用层 (App Task)"]
AppTask["鼠标/HID 应用任务"]
end
subgraph sg_Manager["传感器管理层 (OMSensor_manage)"]
Init["optical_mouse_sensor_init"]
MotionHandler["optical_mouse_sensor_read_motion_handler"]
HighHandler["optical_mouse_read_sensor_handler_high"]
SetCpi["optical_mouse_sensor_set_cpi"]
ForceWake["optical_mouse_sensor_force_wakeup"]
end
subgraph sg_DevTable["驱动设备表 (.omsensor_dev 段)"]
DevTable["OMSensor_dev_begin ... OMSensor_dev_end"]
DevA["OMSensor 驱动 A (OMSensor_init/read_motion...)"]
DevB["OMSensor 驱动 B"]
end
subgraph sg_Platform["平台配置"]
PlatData["OMSENSOR_PLATFORM_DATA<br/>(sclk/data/int IO + 型号 ID)"]
end
subgraph sg_IR["红外编码器 (ir_encoder)"]
IRInit["ir_encoder_init(gpio, freq, duty)"]
IRTx["ir_encoder_tx / ir_encoder_tx_hx"]
IRStop["ir_encoder_repeat_stop"]
end
AppTask -->|"TCFG_OMSENSOR_ENABLE"| Init
AppTask -->|"定时器回调"| MotionHandler
AppTask -->|"HID 上报"| HighHandler
AppTask --> SetCpi
Init --> DevTable
MotionHandler --> DevTable
HighHandler --> DevTable
Init --> PlatData
DevTable --> DevA
DevTable --> DevB
AppTask -->|"红外遥控"| IRInit
AppTask --> IRTx
AppTask --> IRStop
架构要点:
- 设备表(DevTable):
REGISTER_OMSENSOR宏通过SEC_USED(.omsensor_dev)把驱动实例放入专属链接段,OMSensor_dev_begin/OMSensor_dev_end为该段的起止符号。这是一种零运行时注册开销的静态设备表模式——驱动"存在即注册",管理层用指针区间遍历即可发现所有驱动,无需维护运行时链表。 - 接口抽象(OMSENSOR_INTERFACE):每个驱动只需实现
init / read_motion / data_ready / set_cpi / wakeup / led_switch / status_dump等函数指针,管理层对所有型号一视同仁;具体型号差异(寄存器序列、SPI 时序)被完全封装在驱动内部。 - 平台数据(PlatData):
OMSENSOR_PLATFORM_DATA描述传感器挂载的硬件资源(时钟脚、数据脚、中断脚、型号 ID),由板级配置通过OMSENSOR_PLATFORM_DATA_BEGIN/END宏声明,通过optical_mouse_sensor_init传入驱动。 - IR 编码器:与应用层松耦合,应用只需调用
ir_encoder_init配置发送脚与载波,再调用ir_encoder_tx/ir_encoder_tx_hx发送帧。
光学鼠标传感器管理(OMSensor_manage)深入分析
数据模型与类型定义
OMSensor_manage.h 定义了四个核心类型,它们共同构成传感器子系统的"契约":
// HID鼠标的报告格式
typedef struct {
uint8_t buttonMask; // 按钮状态,5位用于按钮,3位用于填充
uint8_t wheel; // 滚轮移动,8位
uint8_t xymovement[3]; // 数组来存放X和Y轴的移动量
} mouse_packet_data_t;
typedef struct {
uint8_t button_send_flag;
uint8_t wheel_send_flag;
uint8_t sensor_send_flag;
} mouse_send_flags_t;
Source: OMSensor_manage.h
mouse_packet_data_t是标准 HID 鼠标报告结构:buttonMask承载 5 个按键位,wheel为滚轮增量(8 位有符号),xymovement[3]按 HID 规范的低字节/高半字节交错方式存放 12 位 X/Y 增量(见下文打包算法)。mouse_send_flags_t是应用侧与传感器层之间的"节流"握手标志:sensor_send_flag置位表示上一帧尚未上报,新数据到达时先清零增量累加器,避免位移累积漂移。
平台数据与驱动接口是传感器层与具体器件解耦的关键:
typedef struct {
u8 OMSensor_id[20];
u8 OMSensor_sclk_io;
u8 OMSensor_data_io;
u8 OMSensor_int_io;
} OMSENSOR_PLATFORM_DATA;
typedef struct {
u8 OMSensor_id[20];
u8(*OMSensor_init)(OMSENSOR_PLATFORM_DATA *);
u8(*OMSensor_read_motion)(s16 *, s16 *);
u8(*OMSensor_data_ready)(void);
u8(*OMSensor_status_dump)(void);
void (*OMSensor_wakeup)(void);
void (*OMSensor_led_switch)(u8);
u16(*OMSensor_set_cpi)(u16 dst_cpi);
} OMSENSOR_INTERFACE;
Source: OMSensor_manage.h
设计意图: 把"驱动能力"抽象为纯函数指针集合,使管理层只依赖接口而不依赖型号。OMSensor_id 同时出现在平台数据与接口中,可用于运行时校验"这块板子的传感器型号"与"编译进固件的驱动"是否匹配;OMSensor_sclk_io/data_io/int_io 描述 SPI 时钟、数据与中断引脚,驱动据此完成 IO 初始化和中断使能。
设备注册机制(静态链接段设备表)
extern OMSENSOR_INTERFACE OMSensor_dev_begin[];
extern OMSENSOR_INTERFACE OMSensor_dev_end[];
#define REGISTER_OMSENSOR(OMSensor) \
static OMSENSOR_INTERFACE OMSensor SEC_USED(.omsensor_dev)
#define list_for_each_omsensor(c) \
for (c=OMSensor_dev_begin; c<OMSensor_dev_end; c++)
Source: OMSensor_manage.h
REGISTER_OMSENSOR 把驱动实例放入 .omsensor_dev 链接段:链接器将所有驱动实例连续排列,OMSensor_dev_begin 与 OMSensor_dev_end 自动界定区间。这样:
- 零运行时开销:没有注册表初始化代码,设备发现即指针区间遍历;
- 编译期裁剪:未编译进固件的驱动自然不会出现在区间内,
SEC_USED防止链接器把"看似无人引用"的驱动段丢弃; - 易扩展:新增传感器型号只需写一个
OMSENSOR_INTERFACE实例并调用REGISTER_OMSENSOR,管理层无需改动。
位移读取与坐标变换
传感器返回的原始位移在进入事件系统前会经过坐标变换,见 optical_mouse_sensor_read_motion_handler:
void optical_mouse_sensor_read_motion_handler(void *priv)
{
s16 x = 0, y = 0;
if (optical_mouse_sensor_data_ready()) {
s16 temp = 0;
if (OMSensor_hdl->OMSensor_read_motion) {
OMSensor_hdl->OMSensor_read_motion(&x, &y);
}
temp = x;
x = y;
y = temp;
VECTOR_REVERS(x);
VECTOR_REVERS(y);
mouse_optical_sensor_event(0, x, y);
}
}
Source: OMSensor_manage.c
其中 VECTOR_REVERS(vec) 定义为 vec = ~vec; vec++,即对 16 位增量按位取反后加一——等价于取相反数(-vec)。传感器芯片安装方向不同会导致 X/Y 轴方向反转,这里统一在管理层做一次轴交换(temp = x; x = y; y = temp)与方向取反,把"硬件安装差异"收敛到一处,应用层无需感知。
optical_mouse_sensor_data_ready() 是内部辅助函数:若接口提供了 OMSensor_data_ready 则调用之,否则返回 0。这体现了函数指针接口的"可选能力"设计——不是所有传感器都有硬件就绪中断。
HID 报告打包算法(12 位增量编码)
optical_mouse_read_sensor_handler_high 是高频路径,负责把位移增量编码进 HID 报告:
void optical_mouse_read_sensor_handler_high(mouse_packet_data_t *mouse_packet, mouse_send_flags_t *mouse_flags)
{
int16_t x = 0, y = 0;
static int16_t delta_x = 0, delta_y = 0;
if (optical_mouse_sensor_data_ready()) {
int16_t temp = 0;
if (OMSensor_hdl->OMSensor_read_motion) {
OMSensor_hdl->OMSensor_read_motion(&x, &y);
}
temp = x;
x = y;
y = temp;
VECTOR_REVERS(x);
VECTOR_REVERS(y);
if (mouse_flags->sensor_send_flag) {
delta_x = 0;
delta_y = 0;
}
if (((delta_x + x) >= -2047) && ((delta_x + x) <= 2047)) {
// x值在有效范围内
} else {
x = 0;
}
if (((delta_y + y) >= -2047) && ((delta_y + y) <= 2047)) {
// y值在有效范围内
} else {
y = 0;
}
// 坐标调整
delta_x += (-y);
delta_y += (x);
mouse_packet->xymovement[0] = delta_x & 0xFF;
mouse_packet->xymovement[1] = ((delta_y << 4) & 0xF0) | ((delta_x >> 8) & 0x0F);
mouse_packet->xymovement[2] = (delta_y >> 4) & 0xFF;
mouse_flags->sensor_send_flag = 0;
}
}
Source: OMSensor_manage.c
关键算法解读:
- 增量累加(delta_x/delta_y):传感器两次读取间隔内可能移动多格,累加器把多次采样合并为一帧,保证不丢失位移;同时
static变量跨调用保持状态。 - 饱和裁剪:HID 规范中 X/Y 增量为 12 位有符号数,范围 ±2047。若累加值超出范围,本帧丢弃该轴位移(置 0)而不是截断,避免产生方向错误的回跳。
- 轴旋转:
delta_x += (-y); delta_y += (x)等价于把原始位移旋转 90°(配合前面的轴交换与取反,完成传感器坐标系到屏幕坐标系的映射)。 - 12 位打包:
xymovement[0]存 X 低 8 位;xymovement[1]高 4 位是 Y 低 4 位、低 4 位是 X 高 4 位;xymovement[2]是 Y 高 8 位——正是 HID 鼠标报告对 12 位相对位移的标准字节序要求。 - 发送标志握手:打包完成后清
sensor_send_flag;应用侧若因 BLE 连接拥塞未能及时上报,会重新置位该标志,下一次采样时累加器清零,防止位移无限累积。
CPI 调节、唤醒与 LED 控制
u16 optical_mouse_sensor_set_cpi(u16 dst_cpi)
{
u16 cpi = 0;
log_info(">>>>>>>>>set cpi: %d\n", dst_cpi);
if (OMSensor_hdl->OMSensor_set_cpi) {
cpi = OMSensor_hdl->OMSensor_set_cpi(dst_cpi);
}
return cpi;
}
Source: OMSensor_manage.c
CPI(每英寸计数)决定鼠标灵敏度。管理层把目标值透传给驱动,驱动负责写入传感器寄存器并返回实际生效值——因为硬件可能只支持离散档位,返回值可能与目标值不同,应用层应使用返回值向用户反馈。optical_mouse_sensor_force_wakeup 与 optical_mouse_sensor_led_switch 同样通过 OMSensor_wakeup / OMSensor_led_switch 函数指针透传,分别用于低功耗模式下唤醒传感器和开关传感器内置 LED。
均值滤波
static s16 avg_filter(s16 *pdata, u8 num)
{
u8 i = 0;
s16 sum = 0;
for (i = 0; i < num; i++) {
sum += pdata[i];
}
return (sum / num);
}
Source: OMSensor_manage.c
avg_filter 对 num 个采样求算术平均,用于抑制传感器抖动噪声。它是 static 内部函数,说明滤波策略属于管理层私有实现——驱动只需要保证单次 read_motion 的原始准确性,平滑处理统一由管理层负责。
IR 编码器(ir_encoder)深入分析
IR 编码器把逻辑数据(地址码 + 命令码)调制为 38kHz 量级的红外载波脉冲序列,由 GPIO 驱动红外发射管。其公共接口定义在 apps/include_lib/common/ir_encoder.h,实现位于 apps/app/bsp/common/ir/ir_encoder.c,apps/app/bsp/cpu/periph_demo/ir_encoder_decoder.c 提供了发送/接收的演示用法。
公共 API
void ir_encoder_init(u32 gpio, u32 freq, u32 duty); //gpio:发送脚, freq:载波频率, duty:占空比,满量程10000
void ir_encoder_deinit();
u32 ir_encoder_tx(u8 ir_addr, u8 ir_cmd, u8 repeat_en); //addr:地址码, cmd:命令码, repeat_en:重复码发送使能
u32 ir_encoder_tx_hx(u8 ir_addr, u8 ir_cmd, u8 repeat_en);
void ir_encoder_repeat_stop();
Source: ir_encoder.h
参数语义:
ir_encoder_init(gpio, freq, duty):指定红外发射脚(GPIO)、载波频率(如 38000Hz)与占空比。duty满量程为 10000,即 50% 占空比传 5000。占空比影响发射功率与功耗的权衡——占空比越高,发射距离越远但平均功耗越大。ir_encoder_tx(addr, cmd, repeat_en):发送一帧 NEC 风格的红外遥控数据。repeat_en为真时发送完命令帧后继续周期性发送重复码(用于按键长按场景,如音量持续调节),为假时仅发送单帧。ir_encoder_tx_hx(addr, cmd, repeat_en):HX 协议变体,时序参数与 NEC 不同,用于兼容 HX 系列遥控接收头。ir_encoder_repeat_stop():停止重复码发送,通常与按键释放事件绑定。ir_encoder_deinit():释放 GPIO 与定时资源,进入低功耗前调用。
设计意图: 协议差异(NEC vs HX)通过两个独立入口 ir_encoder_tx / ir_encoder_tx_hx 暴露,而不是通过参数开关,因为二者的引导码与位时序完全不同;重复码的启停分离,使应用层可以把"长按持续发送"与"松开停止"表达为两个明确动作。
与传感器层的对比
IR 编码器是传感器系统的"镜像":传感器把物理位移→数字事件,编码器把数字命令→物理脉冲。两者都遵循 SDK 的 GPIO 平台配置风格,但编码器 API 更直接(无设备表抽象),因为红外协议栈相对固定,无需多器件适配。
核心控制流
传感器数据流:采样 → 坐标变换 → HID 打包 → 上报
sequenceDiagram
participant T as 系统定时器 (sys_timer)
participant M as OMSensor_manage 管理层
participant D as 传感器驱动 (OMSENSOR_INTERFACE)
participant A as HID 应用任务
T->>M: 周期回调 optical_mouse_sensor_read_motion_handler
M->>D: OMSensor_data_ready()
D-->>M: 数据就绪标志
M->>D: OMSensor_read_motion(&x, &y)
D-->>M: 原始位移 x/y
M->>M: 轴交换 + VECTOR_REVERS 取反
M->>A: mouse_optical_sensor_event(0, x, y)
A->>M: optical_mouse_read_sensor_handler_high
M->>D: OMSensor_read_motion(&x, &y)
M->>M: 增量累加 + ±2047 饱和裁剪 + 12位打包
M-->>A: 填充 mouse_packet->xymovement[3]
A->>A: 通过 BLE HID 上报
流程说明: 位移读取与 HID 打包是两个独立入口,可由不同上下文调用——read_motion_handler 由系统定时器驱动(低功耗友好,仅在需要时唤醒),handler_high 由应用上报任务驱动。两者共享 OMSensor_hdl 与静态累加器,因此调用频率必须匹配传感器数据率;若 sensor_send_flag 握手机制保证累加器不会因上报延迟而溢出。
红外发送流程:初始化 → 发帧 → 重复码 → 停止
flowchart TD
Start([应用启动]) --> Init["ir_encoder_init(gpio, 38000, 5000)"]
Init --> KeyDown{"按键事件?"}
KeyDown -->|"单击"| Tx["ir_encoder_tx(addr, cmd, 0)<br/>单帧发送"]
KeyDown -->|"长按"| TxHx["ir_encoder_tx_hx(addr, cmd, 1)<br/>帧 + 重复码"]
Tx --> Done([发送完成])
TxHx --> Repeat["ir_encoder_repeat_stop()<br/>按键释放时停止重复码"]
Repeat --> Done
Done --> Deinit["ir_encoder_deinit()<br/>进入低功耗前释放资源"]
流程说明: 单击场景关闭重复码,避免遥控器持续占用电波;长按场景开启重复码并持续到按键释放。deinit 放在低功耗流程中,释放 GPIO 与定时资源以降低休眠电流。
使用示例
注册一个光学鼠标传感器驱动
以下代码展示了如何基于 REGISTER_OMSENSOR 注册一个新传感器驱动(模式骨架,字段来自接口定义):
#include "OMSensor_manage.h"
static u8 my_sensor_init(OMSENSOR_PLATFORM_DATA *plat)
{
/* 初始化 SPI 时钟脚/数据脚/中断脚,写入传感器寄存器序列 */
return 1;
}
static u8 my_sensor_read_motion(s16 *x, s16 *y)
{
/* 读取传感器位移寄存器,输出原始增量 */
*x = read_register(REG_X);
*y = read_register(REG_Y);
return 1;
}
static u8 my_sensor_data_ready(void)
{
/* 读取中断脚或状态寄存器判断数据是否就绪 */
return gpio_read(OMSENSOR_INT_IO);
}
REGISTER_OMSENSOR(my_sensor_dev) = {
.OMSensor_id = "MY_SENSOR",
.OMSensor_init = my_sensor_init,
.OMSensor_read_motion = my_sensor_read_motion,
.OMSensor_data_ready = my_sensor_data_ready,
.OMSensor_set_cpi = my_sensor_set_cpi,
};
Source: OMSensor_manage.h
REGISTER_OMSENSOR(my_sensor_dev) 会把该实例放入 .omsensor_dev 链接段,管理层通过 list_for_each_omsensor 自动发现它。示例中仅实现了必选回调,wakeup/led_switch/status_dump 等可选回调可省略(管理层调用前均判空)。
平台数据声明
板级配置使用宏声明传感器硬件资源:
OMSENSOR_PLATFORM_DATA_BEGIN(omsensor_platform_data)
.OMSensor_id = "MY_SENSOR",
.OMSensor_sclk_io = IO_PORTA_00,
.OMSensor_data_io = IO_PORTA_01,
.OMSensor_int_io = IO_PORTA_02,
OMSENSOR_PLATFORM_DATA_END()
Source: OMSensor_manage.h
OMSENSOR_PLATFORM_DATA_BEGIN/END 是标准的"初始化器括号"宏,保证所有板级配置书写格式一致;OMSensor_id 用于与驱动实例的型号 ID 匹配。
IR 编码器典型调用序列
#include "ir_encoder.h"
/* 系统初始化阶段:GPIO 发射脚、38kHz 载波、50% 占空比 */
ir_encoder_init(IO_PORTA_05, 38000, 5000);
/* 按键单击:发送地址 0x00、命令 0x45,不带重复码 */
ir_encoder_tx(0x00, 0x45, 0);
/* 按键长按:HX 协议,带重复码,松开时停止 */
ir_encoder_tx_hx(0x00, 0x45, 1);
...
ir_encoder_repeat_stop();
/* 进入低功耗前 */
ir_encoder_deinit();
Source: ir_encoder.h
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_OMSENSOR_ENABLE | 宏开关 | 未定义(按方案裁剪) | 编译开关:定义后编译 OMSensor_manage.c 全部逻辑,未定义时整个传感器管理层被排除在固件之外 |
OMSensor_sclk_io | u8 | 板级决定 | 传感器 SPI 时钟引脚 |
OMSensor_data_io | u8 | 板级决定 | 传感器 SPI 数据引脚 |
OMSensor_int_io | u8 | 板级决定 | 传感器数据就绪中断引脚 |
OMSensor_id | u8[20] | 板级决定 | 传感器型号 ID 字符串,用于平台与驱动匹配 |
ir_encoder_init 的 freq | u32 | 调用方指定 | 红外载波频率(Hz),典型值 38000 |
ir_encoder_init 的 duty | u32 | 调用方指定 | 载波占空比,满量程 10000,50% 即 5000 |
ir_encoder_tx 的 repeat_en | u8 | 调用方指定 | 是否在命令帧后持续发送重复码 |
传感器管理层的裁剪机制值得注意:整个 .c 文件被 #ifdef TCFG_OMSENSOR_ENABLE 包裹(见 OMSensor_manage.c),未启用传感器功能的方案不会产生任何代码与链接段占用。
API 参考
传感器管理(OMSensor_manage)
| 函数 | 签名 | 说明 |
|---|---|---|
optical_mouse_sensor_init | bool optical_mouse_sensor_init(OMSENSOR_PLATFORM_DATA *priv) | 遍历设备表选中匹配驱动并调用其 OMSensor_init;失败返回 false |
optical_mouse_sensor_read_motion_handler | void optical_mouse_sensor_read_motion_handler(void *priv) | 定时器回调:就绪时读取位移、坐标变换后发事件 mouse_optical_sensor_event |
optical_mouse_read_sensor_handler_high | void optical_mouse_read_sensor_handler_high(mouse_packet_data_t *mouse_packet, mouse_send_flags_t *mouse_flags) | 高频路径:累加增量、饱和裁剪并打包 HID 12 位位移字段 |
optical_mouse_sensor_set_cpi | u16 optical_mouse_sensor_set_cpi(u16 dst_cpi) | 透传设置 CPI,返回实际生效值 |
get_optical_mouse_sensor_status | u8 get_optical_mouse_sensor_status(void) | 查询传感器状态(透传 OMSensor_status_dump) |
optical_mouse_sensor_force_wakeup | void optical_mouse_sensor_force_wakeup(void) | 唤醒休眠中的传感器(透传 OMSensor_wakeup) |
optical_mouse_sensor_led_switch | void optical_mouse_sensor_led_switch(u8 led_status) | 开关传感器内置 LED(透传 OMSensor_led_switch) |
参数与返回约定:
optical_mouse_sensor_read_motion_handler(void *priv):priv参数与 sys_timer 回调约定一致,实现中未使用,仅占位满足定时器回调签名。optical_mouse_read_sensor_handler_high:mouse_packet指向待填充的 HID 报告,mouse_flags指向发送标志;函数内部会清零sensor_send_flag,调用方无需额外处理。optical_mouse_sensor_set_cpi:dst_cpi为目标值(如 800/1600);返回值为驱动实际写入的 CPI,可能与目标不同(硬件档位限制),应用层应以返回值为准显示。
IR 编码器(ir_encoder)
| 函数 | 签名 | 说明 |
|---|---|---|
ir_encoder_init | void ir_encoder_init(u32 gpio, u32 freq, u32 duty) | 配置发射脚、载波频率与占空比(满量程 10000) |
ir_encoder_deinit | void ir_encoder_deinit() | 释放红外发送资源 |
ir_encoder_tx | u32 ir_encoder_tx(u8 ir_addr, u8 ir_cmd, u8 repeat_en) | 发送 NEC 风格红外帧,repeat_en 控制重复码 |
ir_encoder_tx_hx | u32 ir_encoder_tx_hx(u8 ir_addr, u8 ir_cmd, u8 repeat_en) | 发送 HX 协议红外帧 |
ir_encoder_repeat_stop | void ir_encoder_repeat_stop() | 停止重复码发送 |
设计约束: ir_encoder_init 的 duty 参数满量程为 10000,因此 50% 占空比应传 5000,而非 50——这是 SDK 使用固定点表示的惯例,避免浮点运算。
故障模式、边界情况与并发性
传感器未就绪 / 驱动缺失
OMSensor_hdl是static全局句柄,初始为NULL。若设备表为空(未注册任何驱动)或optical_mouse_sensor_init未成功执行,optical_mouse_sensor_data_ready()内部会因OMSensor_hdl为空而解引用崩溃——因此调用链要求optical_mouse_sensor_init必须先于任何读取回调执行,且返回 false 时应用不应启动读取任务。- 所有可选回调(
wakeup、led_switch、status_dump、set_cpi)调用前都有判空保护(如if (OMSensor_hdl->OMSensor_set_cpi)),缺省驱动不会导致崩溃,只是能力降级(例如无法调 CPI、无法唤醒)。
位移饱和与数据丢失
- HID 12 位位移范围为 ±2047。当单帧内位移累加超出范围时,实现选择"整帧丢弃该轴"而非截断(OMSensor_manage.c)。原因:截断会产生方向错误的"回跳"光标,视觉上比丢帧更突兀;代价是高速甩动鼠标时部分位移被丢弃,属于有意的取舍。
sensor_send_flag握手:若 HID 上报链路(BLE 连接)拥塞,应用置位该标志,下一次采样时delta_x/delta_y清零重来。这避免了累加器无限增长导致的溢出,但会丢失拥塞期间的位移。
定时器回调与上报回调的并发
read_motion_handler(定时器上下文)与 handler_high(上报上下文)共享 OMSensor_hdl 和静态累加器,但 SDK 中两者通常在同一任务上下文或由互斥的调度保证串行执行。若在 RTOS 多任务环境中同时调用,需要调用方自行保证互斥——源码层面没有加锁(这也是设计约束:单上下文、低延迟优先)。
IR 发送的时序敏感
红外协议是严格时序协议(位周期、引导码宽度、重复码间隔均以微秒计)。ir_encoder_tx/ir_encoder_tx_hx 发送期间应避免被高优先级任务抢占过长时间,否则接收端会解码失败。ir_encoder_repeat_stop 必须在按键释放事件中及时调用,否则重复码会持续占用发射资源并造成接收端持续响应(如音量一直增大)。
性能与运维注意事项
- 读取频率与功耗:传感器管理层由系统定时器驱动,数据率可调。低功耗场景应降低轮询频率或依赖
OMSensor_int_io中断(OMSensor_data_ready)唤醒;optical_mouse_sensor_force_wakeup配合休眠策略可进一步降低平均电流。 - CPU 占用:
handler_high是纯整数运算(移位、与、加法),无除法/浮点,可在高频路径上安全执行;avg_filter含除法但仅用于低速滤波场景。 - 调试手段:
log_info(">>>>>>>>>set cpi: %d\n", dst_cpi)(OMSensor_manage.c)与OMSensor_status_dump提供了 CPI 写入与传感器状态的可观测性,量产调试时可通过串口日志确认驱动是否生效。 - 裁剪收益:
TCFG_OMSENSOR_ENABLE编译开关保证未用传感器功能的固件零代码占用;IR 编码器同样仅在调用方编译ir_encoder.c时链接。
扩展点
- 新增传感器型号:实现
OMSENSOR_INTERFACE函数指针表并REGISTER_OMSENSOR,放置到.omsensor_dev段即完成接入;若板级使用新引脚组合,仅需修改OMSENSOR_PLATFORM_DATA。 - 新增红外协议:参照
ir_encoder_tx/ir_encoder_tx_hx的成对模式,在ir_encoder.c中增加协议入口,公共头文件同步声明;协议差异封装在实现内部,不影响调用方。 - 坐标映射定制:轴交换与
VECTOR_REVERS集中在read_motion_handler/handler_high两处,若产品安装方向不同,只需调整这两处的变换顺序,无需改动驱动。 - 滤波策略替换:
avg_filter为static内部函数,可在不改接口的前提下替换为滑动平均/中值滤波等更强算法,作为管理层私有增强。
相关链接
- OMSensor_manage.h(接口与宏定义)
- OMSensor_manage.c(管理层实现)
- ir_encoder.h(IR 编码器公共接口)
- ir_encoder.c(IR 编码器实现)
- ir_encoder_decoder.c(红外收发演示工程)
说明:IR 编码器实现细节(位时序、重复码间隔)位于
ir_encoder.c与演示工程中,本文档依据公共头文件ir_encoder.h的 API 契约撰写;传感器驱动的具体寄存器操作属于各型号驱动私有实现,SDK 中以设备表形式链接。