杰理 SDK 文档中心
首页
首页
  • SDK 概述与快速开始

    • SDK 概览与 AC791N 芯片平台
    • 环境搭建与编译指南
    • 烧录与固件升级
    • 工程结构导览
  • 产品方案应用

    • WiFi 摄像头方案
    • WiFi IPC 可视对讲方案
    • WiFi 故事机方案
    • 扫码枪 HID 方案
    • 开发板示例工程
  • 公共应用组件

    • 语音识别 ASR 引擎
    • LLM 与 AI 语音助手接入
    • 摄像头传感器驱动
    • UI 显示框架与驱动
    • USB 主机与设备栈
    • 文件系统与存储管理
    • 系统服务与外设管理
    • 生产测试与射频工具
  • 蓝牙协议栈

    • 经典蓝牙 BR/EDR
    • BLE 低功耗蓝牙
    • 蓝牙 Mesh 网络
    • 蓝牙扩展协议(RCSP/广播/无线麦克风)
  • WiFi 与网络协议栈

    • WiFi 驱动与网络模式
    • lwIP TCP/IP 协议栈
    • 网络安全与加密库
    • 应用层网络协议
    • 流媒体与音视频传输
    • 云平台接入 SDK
    • P2P 远程访问与设备互联
  • 芯片平台与驱动

    • wl82 平台与硬件加速
    • 外设驱动框架
    • 平台配置与固件打包工具
  • 媒体与音频引擎

    • 音频编解码与音源
    • 音效处理引擎
    • 视频与图像处理
  • 操作系统与运行时

    • 实时操作系统与 POSIX 层
    • C/C++ 运行时库
  • 开发资源与文档

    • 文档与规格书
    • 公共示例工程
    • UI 资源工程与打包
    • SDK 辅助工具与脚本

扫码枪 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 上报"实现扫码枪:

  1. 设备作为 USB Slave 枚举为 HID POS Scanner(Hidpos_report_descr 定义了 Report ID 0x02 输入报告与 0x04 输出报告);
  2. 用户扣动扳机触发 TRIG_START 消息,驱动摄像头(ISC)采集 YUV 帧;
  3. 图像帧经回调进入上层处理/解码,得到条码数据;
  4. 条码数据经 hid_pos_send_report() 打包为 HID Input Report 上报主机;
  5. 主机可通过 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 Profileusb_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,
};

来源:usb_hid_pos.c

状态位与 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方向内容
0x02InputScanner 数据:1 字节条码状态(0x3B)+ 3 字节(0xFB/0xFC/0xFD)+ 56 字节条码数据(0xFE)+ 2 字节厂商保留(FF66)+ 8 bit 状态位(0xFF)
0x04OutputBarcode Reader 控制:8 个 1-bit 控制位(0x5E 电源/复位、0x5F 禁止读取、0x60 启动读取、0x85/0x86 保留)
0xFEVendor厂商自定义报告(Vendor Usage Page 0xFF66)
0xFDVendor厂商自定义: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);
}

来源:usb_hid_pos.c

设计意图:把报告描述符放在设备固件侧,上位机在枚举阶段通过 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;
}

来源:usb_hid_pos.c

队列判定采用"牺牲一个槽位"的方式区分满/空: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__);
            }
        }
    }
    ...
}

来源:usb_hid_pos.c

要点:

  • 消抖过滤:只有连续 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
};

来源:usb_hid_pos.c

命令通过 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

来源:get_yuv_data.c、get_yuv_data.c

关键设计:

  • 两种交付模式: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_* 状态

流程说明(按调试/扩展顺序):

  1. 触发采集:定时器周期调用 hid_pos_msg_handle(),读取触发按键电平并经消抖确认后,向队列投递 TRIG_START(扣下)或 TRIG_STOP(松开);
  2. 扫描状态置位:收到 TRIG_START 后,hdl->state 置入 HID_POS_STA_BM_INITIATE_BARCODE_READ,同时驱动摄像头通路启动采集;此状态下重复触发被忽略(防重入);
  3. 数据上报:条码数据就绪后调用 hid_pos_send_report(),按 HID POS 报告格式(Report ID 0x02:状态字节 + 3 字节扩展 + 56 字节条码数据 + 2 字节厂商保留 + 8 bit 状态)组包,提交 USB IN 端点;
  4. 主机控制:上位机通过 Output Report 0x04 下发 8 个控制位(0x5E 电源/复位扫描器、0x5F 禁止读取、0x60 启动读取等),固件解析后更新 hdl->state,形成双向控制闭环;
  5. 结束扫描:松开扳机投递 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_hid_pos.c

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;
}

来源:usb_hid_pos.c

示例三:触发按键消抖与消息投递(定时器上下文)

    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;
    }

来源:usb_hid_pos.c

示例四:摄像头 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

来源:get_yuv_data.c

配置选项

以下配置开关定义于 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宏0YUV 采集模式:1 = SRAM 静态缓冲 + 64 行分块回调(低内存),0 = 整帧回调
YUV_TEST宏0YUV 测试模式开关
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 空指针统一返回 -EINVALusb_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_server 512 字、jpg_spec_enc 1024 字,调整时需平衡图像处理深度与内存占用;
  • 报告缓冲对齐:条码数据段固定 56 字节 + 2 字节厂商保留,与 HID 报告最大长度匹配,发送侧无需动态分包;
  • 调试观测:入队失败打印行号(printf("hid pos put msg fail, %d, line %d\n", ...)),便于定位触发过频或队列过小的问题。

扩展点

  1. 触发按键接入:get_trigger_state() 当前返回 0(桩),实际工程可仿照注释中的 gpio_set_pull_up / gpio_direction_input 接入 GPIO,或改为串口/蓝牙触发指令(usb_hid_pos.c#L347-L360);
  2. AT 命令表扩展:向 hid_pos_at_cmd[] 追加命令即可增加厂商自定义指令(如查询固件版本、配置蜂鸣音);
  3. 报告描述符扩展:Hidpos_report_descr 中 #if 0 区域保留了更多 Report(0x06/0x07/0x08 等,含功能键、蜂鸣器、LED 控制),按需放开并同步实现收发逻辑即可;
  4. 条码数据源替换:hid_pos_send_report(usb_id, symbology, vendor_code, buf, len, force) 的 symbology/vendor_code 参数使数据源(摄像头解码、串口扫描头、BLE)可独立演进,HID 协议层无需改动;
  5. 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)等能力在对应目录页单独介绍。

Prev
WiFi 故事机方案
Next
开发板示例工程