扫码枪 HID 方案
扫码枪(条形码扫描器)HID 方案:基于 AC79xx 系列芯片的 apps/scan_box 扫码枪应用,通过 USB HID POS(Point of Sale,Usage Page 0x8C)协议将摄像头采集到的条码数据以标准 Input Report 上报给上位机(收银机/PC),并支持触发按键消抖、AT 命令控制、HID 键盘互补方案,形成"采集 → 处理 → 上报"的完整闭环。
Purpose and Scope
本页面向开发者完整介绍扫码枪 HID 方案的工程实现,覆盖:
- USB HID POS 设备 Profile(
apps/scan_box/usb_hid_pos.c):HID 报告描述符、状态机、消息队列、触发按键处理与 AT 命令,是扫码枪 HID 方案的核心; - HID 键盘互补方案(
apps/scan_box/usb_hid_keyboard.c、apps/scan_box/hid/hid_keyboard.c):以标准键盘方式上报数据; - 摄像头条码图像采集通路(
apps/scan_box/get_yuv_data.c):ISP/ISC 采集 YUV420 帧并通过回调交给上层; - 应用任务与事件框架(
apps/scan_box/app_main.c):任务表、中断表、默认事件处理; - 工程配置入口(
apps/scan_box/include/app_config.h)与板级工程(apps/scan_box/board/wl82/)。
以下内容属于兄弟页面、不在本页展开:USB 协议栈底层驱动、蓝牙 BLE 连接与配对流程、视频录像(user_video_rec.c)、实时视频流协议(video_rt_usr.c、stream_protocol.c)的细节,仅在本页涉及数据流时引用。
Overview
扫码枪是一种典型的 USB HID 免驱设备:插入收银机或 PC 后即被识别为"POS 扫码器",无需安装厂商驱动即可将条码内容输入到光标所在位置。相比普通 HID 键盘方案,HID POS Profile 是 USB-IF 专门为条码扫描器定义的用途类别,携带扫描器状态位(电源复位、禁止读取、启动读取)、条码符号体系(symbology)等结构化信息,便于上位机做精细控制。
本方案在 AC79xx 上以"摄像头 + ISP 采集 + USB HID 上报"实现扫码枪:
- 设备作为 USB Slave 枚举为 HID POS Scanner(
Hidpos_report_descr定义了 Report ID 0x02 输入报告与 0x04 输出报告); - 用户扣动扳机触发
TRIG_START消息,驱动摄像头(ISC)采集 YUV 帧; - 图像帧经回调进入上层处理/解码,得到条码数据;
- 条码数据经
hid_pos_send_report()打包为 HID Input Report 上报主机; - 主机可通过 Output Report(0x04)下发控制位(启动读取、禁止读取、电源复位等),或通过厂商自定义 Report(0xFD/0xFE)收发扩展命令(AT 命令)。
核心设计意图:
- 免驱即插即用:使用标准 HID 类,避免上位机安装私有驱动,降低部署成本;
- 协议与业务解耦:
usb_hid_pos.c只负责 HID 协议编解码与消息分发,条码数据来源(摄像头、串口、BLE)可以独立演进; - 消息队列 + 自旋锁:消息在中断/定时器上下文与任务上下文之间安全传递,避免直接共享状态;
- 软件消抖:触发按键采用连续采样过滤,防止机械抖动导致重复触发。
Architecture
flowchart TD
subgraph sg_Host["USB Host(收银机 / PC)"]
Host["上位机 HID 客户端"]
end
subgraph sg_USB["USB 设备协议栈(AC79xx)"]
Pos["usb_hid_pos.c<br/>HID POS Scanner Profile"]
Kbd["usb_hid_keyboard.c<br/>HID 键盘"]
end
subgraph sg_App["应用层 apps/scan_box"]
App["app_main.c<br/>任务 / 事件框架"]
MsgQ["hid_pos_msg 环形消息队列<br/>(spinlock 保护)"]
Trig["触发按键检测<br/>get_trigger_state + 消抖"]
AT["AT 命令处理<br/>BEEP / GET INFO / TERMID"]
YUV["get_yuv_data.c<br/>YUV420 帧采集"]
end
subgraph sg_HW["硬件外设"]
Cam["CMOS 摄像头(ISC)"]
Key["扫码触发按键(GPIO)"]
end
Host -->|"Input Report 0x02(条码数据)"| Pos
Host -->|"Output Report 0x04(控制位)"| Pos
Host -->|"标准按键输入"| Kbd
Pos --> MsgQ
Trig --> MsgQ
AT --> MsgQ
App --> Pos
YUV --> Cam
Key --> Trig
App --> YUV
各组成部分职责:
| 组件 | 文件 | 职责 |
|---|---|---|
| HID POS Profile | usb_hid_pos.c | 定义 HID 报告描述符、解析/构造报告、维护扫描器状态位、发送条码数据 |
| HID 键盘 | usb_hid_keyboard.c / hid/hid_keyboard.c | 以键盘 HID 方式上报数据,作为 POS Profile 的互补或替代 |
| 消息队列 | usb_hid_pos.c(struct hid_pos_msg) | 触发、蜂鸣、AT 命令等事件的有界环形缓冲,自旋锁保证并发安全 |
| 触发检测 | usb_hid_pos.c(hid_pos_msg_handle) | 轮询触发按键电平,连续 2 次采样确认后投递 TRIG_START/TRIG_STOP |
| 图像采集 | get_yuv_data.c | 配置 ISP/ISC 采集 YUV420,支持块扫描(block scan)与回调交付 |
| 任务框架 | app_main.c | 定义中断表、静态任务栈、任务表与默认事件分发 |
usb_hid_pos.c 整体以 #if USB_HID_POS_ENABLE 条件编译包裹,宏定义于 include/app_config.h,未使能时整个 Profile 不参与编译,避免无谓的代码与报告描述符开销——这也是 SDK 中"按应用裁剪功能"的一贯做法。
核心实现
USB HID POS 设备 Profile(usb_hid_pos.c)
状态位与消息定义
文件顶部定义了扫描器状态位与内部消息枚举,二者分别对应 HID POS 规范中 Barcode Reader 的 Output 控制位和驱动内部事件:
#define HID_POS_STA_BM_NORMAL 0x00
#define HID_POS_STA_BM_POWER_ON_RESET_SCANNER 0x01
#define HID_POS_STA_BM_PREVENT_READ_OF_BARCODES 0x02
#define HID_POS_STA_BM_INITIATE_BARCODE_READ 0x04
enum {
HID_POS_MSG_BEEP_GOOD = 0x10,
HID_POS_MSG_BEEP_ERROR,
HID_POS_MSG_TRIG_START,
HID_POS_MSG_TRIG_STOP,
HID_POS_MSG_RECV_AT_CMD,
};
状态位与 HID 报告描述符中的 Output 控制位(0x5E Power/Reset Scanner、0x5F Prevent Read of Barcodes、0x60 Initiate Barcode Read)一一对应:INITIATE_BARCODE_READ 表示扫描正在进行,此状态下触发按键变化不再投递新消息,避免重入。
HID 报告描述符(Hidpos_report_descr)
描述符使用 USB HID POS Usage Table:Usage Page 0x8C、Usage 0x02(Scanner),并包含以下报告:
| Report ID | 方向 | 内容 |
|---|---|---|
| 0x02 | Input | Scanner 数据:1 字节条码状态(0x3B)+ 3 字节(0xFB/0xFC/0xFD)+ 56 字节条码数据(0xFE)+ 2 字节厂商保留(FF66)+ 8 bit 状态位(0xFF) |
| 0x04 | Output | Barcode Reader 控制:8 个 1-bit 控制位(0x5E 电源/复位、0x5F 禁止读取、0x60 启动读取、0x85/0x86 保留) |
| 0xFE | Vendor | 厂商自定义报告(Vendor Usage Page 0xFF66) |
| 0xFD | Vendor | 厂商自定义:1 字节输入(0x21)+ 62 字节输出(0x22),用于扩展命令收发 |
描述符通过两个导出函数提供给 USB 栈做枚举:
u8 *hid_pos_get_report_desc()
{
return (u8 *)Hidpos_report_descr;
}
u32 hid_pos_get_report_desc_size()
{
return sizeof(Hidpos_report_descr);
}
设计意图:把报告描述符放在设备固件侧,上位机在枚举阶段通过 HID 标准请求直接读取,无需安装任何驱动;同时将 56 字节条码数据段 + 厂商扩展报告(0xFD 的 62 字节输出)预留为协议扩展通道,支持未来增加如"条码符号体系(symbology)/厂商代码"等元数据字段——hid_pos_send_report() 的签名(symbology、vendor_code 参数)正是为此预留。
HID POS 消息队列(环形缓冲 + 自旋锁)
内部事件(触发、蜂鸣、AT 命令)通过有界环形队列在"产生者"与"消费者"之间传递。队列用自旋锁保护,可在中断/定时器上下文安全入队:
static int hid_pos_msg_put(struct hid_pos_msg *msg, u8 value)
{
if (!msg) {
return -EINVAL;
}
spin_lock(&msg->spinlock);
if ((msg->in_pos + msg->len - msg->out_pos) % msg->len == msg->len - 1) {
//msg full
spin_unlock(&msg->spinlock);
return -ENOMEM;
}
msg->buf[msg->in_pos] = value;
msg->in_pos = (msg->in_pos + 1) % msg->len;
spin_unlock(&msg->spinlock);
return 0;
}
队列判定采用"牺牲一个槽位"的方式区分满/空:in_pos == out_pos 为空,(in_pos + 1) % len == out_pos 为满。配套的 hid_pos_msg_get() 对称出队,hid_pos_msg_query_size() 返回当前积压字节数。struct hid_pos_msg 内还保存 spinlock_t spinlock,同一把锁保护读写两侧,保证 in_pos/out_pos 的读改写原子性。
触发按键检测与消抖
hid_pos_msg_handle() 是定时器驱动的消息处理器,每次执行先读取触发按键电平,并用两个计数器做软件消抖:
static void hid_pos_msg_handle(void *arg)
{
int err;
struct hid_pos_handle *hdl = arg;
u8 msg_val;
u32 idx;
const char *at_cmd_ack;
if (get_trigger_state()) {
hdl->trigger_on_filter++;
hdl->trigger_off_filter = 0;
} else {
hdl->trigger_on_filter = 0;
hdl->trigger_off_filter++;
}
if (hdl->trigger_on_filter >= 2) {
hdl->trigger_sta = 1;
} else if (hdl->trigger_off_filter >= 2) {
hdl->trigger_sta = 0;
}
if (hdl->trigger_sta != hdl->trigger_last_sta) {
if (!(hdl->state & HID_POS_STA_BM_INITIATE_BARCODE_READ)) {
if (hdl->trigger_sta) {
msg_val = HID_POS_MSG_TRIG_START;
} else {
msg_val = HID_POS_MSG_TRIG_STOP;
}
err = hid_pos_msg_put(&hdl->msg, msg_val);
if (err < 0) {
printf("hid pos put msg fail, %d, line %d\n", err, __LINE__);
}
}
}
...
}
要点:
- 消抖过滤:只有连续 2 次采样一致才翻转
trigger_sta,机械抖动(毫秒级毛刺)被滤除; - 防重入:仅当
state不含INITIATE_BARCODE_READ时才投递触发消息——扫描进行中重复扣扳机被忽略; - 错误可观测:入队失败(
-ENOMEM)打印行号便于定位; get_trigger_state()当前为桩实现(返回 0),实际工程需将 GPIO 配置接入(源码中以#if 0保留了gpio_set_pull_up/gpio_direction_input的接入示例),见"扩展点"章节。
AT 命令扩展
协议层内置一组 POS 风格 AT 命令,用于蜂鸣、查询设备信息与终端号:
static const char *const hid_pos_at_cmd[] = {
"\x16\x07\r", //BEEP
"\x16M\rP_INFO.", //GET INFO
"\x16M\rTERMID?.", //TERMID
};
命令通过 HID_POS_MSG_RECV_AT_CMD 消息进入处理流程;struct hid_pos_handle 中预留 u8 at_cmd[62] 缓冲,与 0xFD Vendor 报告的 62 字节输出长度对应,说明 AT 命令即经由厂商报告通道收发。
应用任务框架(app_main.c)
扫码枪应用基于 SDK 静态任务框架:task_info_table 集中声明所有任务及其栈/队列(静态分配,避免运行时 malloc 碎片化):
const struct task_info task_info_table[] = {
{"app_core", 15, APP_CORE_STK_SIZE, APP_CORE_Q_SIZE, app_core_tcb_stk_q },
{"sys_event", 29, SYS_EVENT_STK_SIZE, 0, sys_event_tcb_stk_q },
{"systimer", 14, SYSTIMER_STK_SIZE, 0, systimer_tcb_stk_q },
{"sys_timer", 9, SYS_TIMER_STK_SIZE, SYS_TIMER_Q_SIZE, sys_timer_tcb_stk_q },
{"audio_server", 16, 512, 64 },
{"audio_encoder", 12, 384, 64 },
{"video_server", 26, 512, 128 },
{"update", 21, 512, 32 },
{"dw_update", 21, 512, 32 },
{"jpg_spec_enc", 27, 1024, 32 },
{"dynamic_huffman0", 15, 256, 32 },
{"video0_rec0", 19, 256, 64 },
{"video0_rec1", 19, 256, 64 },
{"vpkg_server", 26, 512, 128 },
#if CPU_CORE_NUM > 1
{"#C0btctrler", 19, 512, 384 },
{"#C0btstack", 18, 1024, 384 },
#else
{"btctrler", 19, 512, 384 },
{"btstack", 18, 768, 384 },
#endif
{0, 0},
};
来源:app_main.c
可见扫码枪应用复用了音视频框架(audio_server、video_server、video0_rec0/rec1、jpg_spec_enc、vpkg_server)——因为条码解码需要摄像头图像通路;同时包含蓝牙栈(btctrler/btstack),支持未来扩展 BLE 扫码上报。irq_info_table 指定中断到 CPU 的亲和性(双核时软中断分别绑到 CPU0/CPU1)。事件侧由 app_default_event_handler 分发按键/触摸/设备事件。
摄像头条码图像采集通路(get_yuv_data.c)
扫码枪的条码数据来源于摄像头图像。get_yuv_data.c 负责配置 ISP/ISC 并以 YUV420 格式持续采集,帧数据通过回调上抛:
#define YUV_BLOCK_SCAN 0
...
#if YUV_BLOCK_SCAN
static u8 isc_buf[YUV_DATA_WIDTH * 64 * 3 / 2] sec(.sram) ALIGNE(32);
#endif
...
f.pixelformat = VIDEO_PIX_FMT_YUV420;
#if YUV_BLOCK_SCAN
f.static_buf = (u8 *)isc_buf;
f.sbuf_size = sizeof(isc_buf);
f.block_done_cb = yuv420_block_scan;
#else
...
cb(yuv.addr, yuv.size, YUV_DATA_WIDTH, YUV_DATA_HEIGHT);
#endif
关键设计:
- 两种交付模式:
YUV_BLOCK_SCAN=1时使用 SRAM 静态缓冲(sec(.sram),32 字节对齐)并按 64 行分块回调(block_done_cb),适合低内存逐块处理;默认模式则整帧通过cb(addr, size, width, height)回调交付。条码解码可依据内存预算选择模式; - 静态缓冲:
isc_buf置于 SRAM 并 32 字节对齐,满足 DMA/硬件加速器对齐要求,避免动态分配; - 该通路与 HID 上报解耦:
usb_hid_pos.c只关心"条码数据已就绪",不关心图像如何解码,因此后续可替换为硬件条码引擎或串口扫描头而无需改动 HID 协议层。
核心流程
sequenceDiagram
participant T as 定时器/轮询
participant H as hid_pos_msg_handle()
participant Q as hid_pos_msg 环形队列
participant P as usb_hid_pos.c 报告发送
participant U as USB 协议栈
participant Host as 上位机(收银机/PC)
T->>H: 周期执行
H->>H: get_trigger_state() 读取电平
H->>H: 消抖过滤(连续 2 次采样)
H->>Q: hid_pos_msg_put(TRIG_START / TRIG_STOP)
H->>Q: hid_pos_msg_get() 取出消息
Q-->>H: 消息字节(含条码数据就绪信号)
H->>P: hid_pos_send_report(usb_id, symbology, vendor_code, buf, len, force)
P->>U: 组包提交到 USB IN 端点
U->>Host: Input Report(Report ID 0x02,56 字节条码数据)
Host-->>U: Output Report(Report ID 0x04,控制位)
U-->>P: 解析控制位更新 hdl->state
P-->>H: 更新 HID_POS_STA_BM_* 状态
流程说明(按调试/扩展顺序):
- 触发采集:定时器周期调用
hid_pos_msg_handle(),读取触发按键电平并经消抖确认后,向队列投递TRIG_START(扣下)或TRIG_STOP(松开); - 扫描状态置位:收到
TRIG_START后,hdl->state置入HID_POS_STA_BM_INITIATE_BARCODE_READ,同时驱动摄像头通路启动采集;此状态下重复触发被忽略(防重入); - 数据上报:条码数据就绪后调用
hid_pos_send_report(),按 HID POS 报告格式(Report ID 0x02:状态字节 + 3 字节扩展 + 56 字节条码数据 + 2 字节厂商保留 + 8 bit 状态)组包,提交 USB IN 端点; - 主机控制:上位机通过 Output Report 0x04 下发 8 个控制位(
0x5E电源/复位扫描器、0x5F禁止读取、0x60启动读取等),固件解析后更新hdl->state,形成双向控制闭环; - 结束扫描:松开扳机投递
TRIG_STOP,清除读取状态,等待下一次触发。
使用示例
示例一:HID POS 报告描述符导出(供 USB 栈枚举)
u8 *hid_pos_get_report_desc()
{
return (u8 *)Hidpos_report_descr;
}
u32 hid_pos_get_report_desc_size()
{
return sizeof(Hidpos_report_descr);
}
USB 设备栈在枚举阶段调用这两个接口获取 POS 报告描述符,从而把设备识别为"POS Scanner"。
示例二:消息队列初始化与入队(事件投递)
static int hid_pos_msg_init(struct hid_pos_msg *msg, u8 *buf, u32 len)
{
if (!msg || !buf || !len) {
return -EINVAL;
}
msg->buf = buf;
msg->len = len;
msg->in_pos = 0;
msg->out_pos = 0;
spin_lock_init(&msg->spinlock);
return 0;
}
示例三:触发按键消抖与消息投递(定时器上下文)
if (get_trigger_state()) {
hdl->trigger_on_filter++;
hdl->trigger_off_filter = 0;
} else {
hdl->trigger_on_filter = 0;
hdl->trigger_off_filter++;
}
if (hdl->trigger_on_filter >= 2) {
hdl->trigger_sta = 1;
} else if (hdl->trigger_off_filter >= 2) {
hdl->trigger_sta = 0;
}
示例四:摄像头 YUV 采集回调(图像交付)
f.pixelformat = VIDEO_PIX_FMT_YUV420;
#if YUV_BLOCK_SCAN
f.static_buf = (u8 *)isc_buf;
f.sbuf_size = sizeof(isc_buf);
f.block_done_cb = yuv420_block_scan;
#else
...
cb(yuv.addr, yuv.size, YUV_DATA_WIDTH, YUV_DATA_HEIGHT);
#endif
配置选项
以下配置开关定义于 apps/scan_box/include/app_config.h 与源码宏,控制扫码枪 HID 方案的编译与行为:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
USB_HID_POS_ENABLE | 宏 | 见 app_config.h | 使能 HID POS Profile;为 0 时整个 usb_hid_pos.c 不参与编译 |
YUV_BLOCK_SCAN | 宏 | 0 | YUV 采集模式:1 = SRAM 静态缓冲 + 64 行分块回调(低内存),0 = 整帧回调 |
YUV_TEST | 宏 | 0 | YUV 测试模式开关 |
CPU_CORE_NUM | 宏 | 平台相关 | 核数;单核时 BT 任务无 #C0 前缀,且 IRQ 强制绑核 |
CONFIG_IPMASK_ENABLE | 宏 | 平台相关 | 使能不可屏蔽中断(IPMASK)配置,影响 irq_info_table 内容 |
注:
USB_HID_POS_ENABLE的具体取值位于include/app_config.h,本文撰写时未逐行展开该文件;使能后编译期宏保护(#if USB_HID_POS_ENABLE)是接入该方案的第一步。
API Reference
以下为 usb_hid_pos.c 对外与内部的关键接口(基于实际源码签名):
u8 *hid_pos_get_report_desc()
返回 HID POS 报告描述符起始地址,供 USB 设备栈枚举使用。
返回: u8 *,指向静态 Hidpos_report_descr[]。
u32 hid_pos_get_report_desc_size()
返回报告描述符字节数。
返回: u32,即 sizeof(Hidpos_report_descr)。
int hid_pos_send_report(usb_dev usb_id, u32 symbology, u32 vendor_code, const u8 *buf, u32 len, u8 force)
构造并发送条码输入报告(Report ID 0x02)到 USB Host。文件第 48 行仅声明、实现位于 USB 协议栈侧(usb_stack.h 相关模块),本文撰写时未展开其实现细节。
参数:
usb_id(usb_dev):USB 设备实例标识(USB Slave 口);symbology(u32):条码符号体系(如 Code128/EAN13),映射到 HID POS Symbology 字段;vendor_code(u32):厂商/前缀代码;buf(const u8 *):条码数据;len(u32):条码长度;force(u8):非 0 时绕过状态检查强制发送(用于上报错误/提示类报告)。
返回: int,0 成功,负值为错误码。
static int hid_pos_msg_init(struct hid_pos_msg *msg, u8 *buf, u32 len)
初始化环形消息队列。
参数: msg 队列指针;buf 存储缓冲;len 缓冲长度。
返回: 0 成功;-EINVAL(msg/buf 为空或 len==0)。
static int hid_pos_msg_put(struct hid_pos_msg *msg, u8 value)
入队一个字节,自旋锁保护,可在中断上下文调用。
返回: 0 成功;-EINVAL(msg 为空);-ENOMEM(队列满)。
static int hid_pos_msg_get(struct hid_pos_msg *msg, u8 *value)
出队一个字节。
返回: 0 成功;-EINVAL(参数非法);-ENOMEM(队列空,value 不变)。
static u32 hid_pos_msg_query_size(struct hid_pos_msg *msg)
查询队列当前积压字节数。
返回: u32 长度;msg 为空时返回 0。
static void hid_pos_msg_handle(void *arg)
定时器驱动的消息处理入口:读取触发按键、消抖、投递/消费消息、处理 AT 命令与状态更新。arg 指向 struct hid_pos_handle。
参数: arg(void *):struct hid_pos_handle 实例。
失败模式、边界情况与并发
源码中体现的健壮性设计:
| 场景 | 处理方式 | 位置 |
|---|---|---|
| 消息队列满 | hid_pos_msg_put 返回 -ENOMEM,调用方打印错误与行号,不覆盖旧数据(有界队列,丢失新事件而非破坏状态) | usb_hid_pos.c#L306-L310 |
| 队列空 | hid_pos_msg_get 返回 -ENOMEM,消费者轮询继续 | usb_hid_pos.c#L324-L327 |
| 非法参数 | msg/buf/value 空指针统一返回 -EINVAL | usb_hid_pos.c#L288-L299 |
| 触发按键抖动 | 连续 2 次采样一致才翻转状态(软件消抖) | usb_hid_pos.c#L376-L387 |
| 扫描进行中重复触发 | state & HID_POS_STA_BM_INITIATE_BARCODE_READ 时忽略触发变化(防重入) | usb_hid_pos.c#L388-L394 |
| 并发访问 | 队列读写全部持自旋锁,兼容中断上下文(短临界区,无调度) | usb_hid_pos.c#L306-L314 |
并发模型总结:产生者(定时器/中断上下文,如 hid_pos_msg_handle 投递 TRIG 消息)与消费者(协议任务上下文,取消息并组包上报)通过单生产者/单消费者环形队列解耦;自旋锁保证 in_pos/out_pos 更新原子性,且临界区只含几条指令,不会造成长时间关中断。这是嵌入式 HID 设备中"中断快路径 + 任务慢路径"的典型模式。
性能与运维要点
- 零拷贝回调:YUV 帧通过回调直接传递
addr/size,避免帧拷贝;YUV_BLOCK_SCAN模式使用 SRAM 静态缓冲,进一步降低内存压力(get_yuv_data.c#L15-L16); - 静态任务栈:
app_main.c中所有任务栈/队列静态分配(ALIGNE(4)对齐),避免运行期堆碎片;扫码枪方案中app_core栈 1536 字、video_server512 字、jpg_spec_enc1024 字,调整时需平衡图像处理深度与内存占用; - 报告缓冲对齐:条码数据段固定 56 字节 + 2 字节厂商保留,与 HID 报告最大长度匹配,发送侧无需动态分包;
- 调试观测:入队失败打印行号(
printf("hid pos put msg fail, %d, line %d\n", ...)),便于定位触发过频或队列过小的问题。
扩展点
- 触发按键接入:
get_trigger_state()当前返回 0(桩),实际工程可仿照注释中的gpio_set_pull_up/gpio_direction_input接入 GPIO,或改为串口/蓝牙触发指令(usb_hid_pos.c#L347-L360); - AT 命令表扩展:向
hid_pos_at_cmd[]追加命令即可增加厂商自定义指令(如查询固件版本、配置蜂鸣音); - 报告描述符扩展:
Hidpos_report_descr中#if 0区域保留了更多 Report(0x06/0x07/0x08 等,含功能键、蜂鸣器、LED 控制),按需放开并同步实现收发逻辑即可; - 条码数据源替换:
hid_pos_send_report(usb_id, symbology, vendor_code, buf, len, force)的symbology/vendor_code参数使数据源(摄像头解码、串口扫描头、BLE)可独立演进,HID 协议层无需改动; - HID 键盘互补:
usb_hid_keyboard.c/hid/hid_keyboard.c提供键盘式上报,可在app_config.h中切换,用于不需要 POS 控制位的通用输入场景。
测试与验证
源码中未发现独立的单元测试文件,扫码枪方案的验证主要依赖:
- 枚举验证:设备接入 PC 后,通过 USB 枚举抓取 HID 报告描述符,核对 Usage Page 0x8C / Usage 0x02(Scanner)与 Report ID 0x02/0x04;
- 触发链路验证:扣动/松开扳机观察
TRIG_START/TRIG_STOP消息投递(配合串口日志),并验证扫描中重复触发被忽略; - 队列压力验证:快速连续触发时观察
hid pos put msg fail日志,确认有界队列丢新不破旧的行为; - 主机端回灌验证:上位机发送 Output Report 0x04 控制位,确认
hdl->state状态位翻转(电源复位/禁止读取/启动读取)。
Related Links
- usb_hid_pos.c — HID POS Profile 实现
- app_main.c — 应用任务与事件框架
- get_yuv_data.c — 摄像头 YUV 采集通路
- usb_hid_keyboard.c — HID 键盘互补方案
- hid/hid_keyboard.c — 键盘 HID 处理
- app_config.h — 应用配置开关
- board_demo_scanbox.c — 板级扫码枪 demo
相关兄弟页面:视频录像与实时视频流(user_video_rec.c、video_rt_usr.c、stream_protocol.c)、蓝牙 BLE 扫码上报(bt_ble/ble.c)等能力在对应目录页单独介绍。