news 2026/6/24 5:28:38

RFID 仓库管理系统 项目总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RFID 仓库管理系统 项目总结

RFID 仓库管理系统 —— 项目技术总结

第一部分:项目概述

1.1 项目背景

制造业的物料仓库有一个绕不开的问题:东西太多、流动太快、人工根本数不过来。一个中等规模的电子厂仓库,物料品类动辄上千种,每天的领料和入库操作超过百次。靠人工拿扫码枪逐件登记,光是盘点一个货架就得花掉近十分钟,更麻烦的是,实际库存和账面库存经常对不上账。一盘出错,轻则多买物料压库存,重则产线缺料停工。

超高频 RFID 在物理层面解决了"逐件扫描"的瓶颈。读写器一发信号,电磁波覆盖范围内的标签同时应答,不需要把每件货翻出来对条码。
功能:

  • 入库/出库操作:仓管员在操作台的 PC 上打开 Qt 客户端,连上板子后开始扫码。标签数据实时回传到 PC 屏幕,去重、入库确认都在图形界面完成。操作完关了 PC 就行,板子继续在货架端待命。
  • 库存盘点:PC 不开机的时候,板子处于空闲状态——它本身就是一块 Linux 单板计算机,随时可以通过网络被唤醒。哪天要盘点,打开 Qt 客户端连上板子,发一条命令就能启动 RFID 扫描,几十秒扫完一个货架,结果自动和数据库比对。不需要人钻进货架里翻箱倒柜。
  • 防伪追溯:RFID 标签的 TID 码是芯片出厂时固化、永远改不了的。入库时系统把 TID 和首次 EPC 绑定记录到数据库。后续任何查找或盘点操作都会自动比对当前 EPC 和首次 EPC——不一致就说明标签被人改写过了,界面直接标红告警。等于给每个标签建了一条从入库到出库防伪可追溯的完整档案。

本项目用 MX6ULL 开发板做嵌入式 C 服务端,负责 RFID 控制和 SQLite 数据库;用 Qt5 C++ 做桌面客户端,跑在 PC 上负责全部图形交互。两端通过 TCP JSON 协议通信。

1.2 核心模块与目标

模块做什么目标
入库管理RFID 批量扫描入库,自动去重,批次记录,历史查询单批次 300+ 标签,扫描+确认 < 30 秒,去重准确率 100%
出库管理扫描出库,自动校验在库状态,防重复出库正常出库零差错,异常场景有强制出库兜底
库存盘点RFID 非接触批量盘点,精准定位仓库货架与现有货物准确校验单货架盘点 < 30 秒(人工需 10 分钟),不符项自动标红,罗列
查找与防伪EPC/TID 关键词检索货物,TID-EPC 绑定追溯,EPC 变更告警模糊搜索秒级响应,标签级别全生命周期可追溯
系统设置网络连接配置,天线功率查询与调节连接即用,功率 0-33dBm 可调

1.3 系统架构

系统由两个物理节点组成,通过网络直连:

i.MX6ULL 嵌入式端— 运行 C 语言服务程序,直连 RFID 读写器模块,负责射频信号收发、标签数据解析、SQLite 数据库读写、TCP 网络服务。选用 SQLite :嵌入式板子跑数据库守护进程太重,SQLite 编译进去就是一个 .c 文件,数据库单文件,掉电不丢。建了 7 张表覆盖入库/出库/盘点/防伪的全部数据需求。

Qt 桌面端— 5 个功能页面(入库、出库、盘点、查找防伪、系统设置),所有页面共用一个 Client 网络实例,页面之间只通过信号槽通信,互相不引用——加新页面不需要改动已有代码。

两端通信— TCP 长连接 + JSON 文本行协议,共 13 条命令覆盖全部业务操作,支持请求-响应匹配 和 实时事件推送。

1.4 关键设计选择

TCP JSON 协议— RFID 扫描是实时事件流,标签一个接一个被读到,服务端必须主动推送给 Qt 端。TCP 长连接天然支持双向推送:Qt 端发命令,C 端回结果,同时 C 端可以随时把 事件主动推给 Qt。协议选 JSON 文本行格式(每行一个 JSON 对象,\n结尾),解析简单、抓包可读,粘包问题在里循环按行拆分完美解决。

TID 而不是 EPC 做唯一标识— EPC 是用户可编程区,可以被改写;TID 是芯片出厂激光烧录,永远改不了。所有数据库关联都用 TID,即使 EPC 被改,系统也能认出"唯一的身份证"——以此实现防伪追溯功能。

sqlite3_prepare参数化查询 — 同时大量插入 标签明细,每条 SQL 骨架一样,只是 EPC、TID 的值不同。用 sqlite3_prepare_v2 编译一次得到语句句柄,后续 只做三件事:sqlite3_bind_xxx 绑新值 → sqlite3_step 执行 → sqlite3_reset 重置复用。值通过 ? 占位符传入而非拼进 SQL 字符串,避免重复解析和编译,实测性能与exec差异在十倍以上。

信号槽解耦而不是页面直接引用— 5 个页面的 .h 文件互相之间没有任何#include,MainWindow 统一管理信号路由。加第六个页面不需要改前五个。

第二部分:整体架构

2.1硬件架构:

上图为三个物理节点的连接关系
部署方式:板子+读写器固定在货架端,5V 供电全时待命。PC 在操作台,网线或 WiFi 连接。仓管员打开 Qt 客户端连上板子执行入库/出库/盘点操作,操作完关闭 Qt 即可,板子不关机。

2.2软件架构:

嵌入式端(i.MX6ULL)四层:

  • 硬件层:900 UHF RFID 读写器 + i.MX6ULL 开发板
  • 系统层:Buildroot Linux、交叉编译工具链、UART 串口驱动、TCP/IP 协议栈
  • 核心服务层:TCP Server、JSON 协议解析、SQLite3 数据库、LTM200 SDK、标签去重算法、TID 防伪绑定
  • 业务逻辑层:入库/出库/盘点/防伪四模块,掉电不丢、单文件部署、7×24小时·待命

Qt 端两层+特点:

  • UI 交互层:入库/出库/盘点/查找·防伪/系统设置 5 页面,QStackedWidget 切换,信号槽解耦
  • 通信架构层:QTcpSocket 异步连接、Client 实例注入、parseBuffer 换行拆包、信号槽路由
  • 核心特点:跨平台(Windows 开发 → ARM 运行)、页面解耦(5 页面 .h 互不 include)、易扩展(新增页面不改旧代码)

两端通过 TCP JSON 协议双向实时通信,共 13 条命令,详见 2.3。

2.3 通信协议

13 条 TCP JSON 命令一览

模块分类命令名通信方向功能说明
扫描控制scan_startQt → C 端请求开始 RFID 扫描,可携带 TID 开关
scan_stopQt → C 端请求停止 RFID 扫描
tag_foundC 端 → Qt实时推送扫描到的标签信息(EPC、TID、RSSI)
scan_doneC 端 → Qt扫描完成通知,返回本次扫描的标签总数
入库管理inbound_confirmQt → C 端提交入库确认,携带批次号、货物信息、扫描到的标签列表
inbound_resultC 端 → Qt返回入库结果,包含状态校验 、提示消息、跳过的重复标签列表
get_batch_listQt → C 端请求查询所有入库批次列表
batch_listC 端 → Qt入库记录获取入库批次列表数据(批次号、仓库、数量等)
出库管理outbound_confirmQt → C 端提交出库确认,携带批次号、待出库标签列表
outbound_resultC 端 → Qt返回出库结果,包含状态校验、提示消息、出库批次号
get_outbound_listQt → C 端请求查询出库批次列表,可携带日期筛选参数
outbound_listC 端 → Qt返回出库记录批次列表数据(批次号、标签总数等)
盘点管理inventory_checkQt → C 端提交盘点校验请求,携带仓库、货架、扫描标签列表
inventory_check_resultC 端 → Qt返回盘点结果,包含匹配数、缺失数、多余标签明细
查找防伪query_searchQt → C 端提交查询请求,支持按名称 / 仓库 / 货架 / EPC/TID 多维度查询+id模糊搜索
query_resultC 端 → Qt返回查询结果,包含总数、货物明细列表
check_tid_bindingQt → C 端提交 TID-EPC 绑定校验请求,防伪核验
tid_verify_resultC 端 → Qt返回防伪校验结果,包含首次/更改后 EPC、是否变更、历史批次
系统设置set_antenna_powerQt → C 端设置 RFID 天线发射功率
get_antenna_powerQt → C 端查询当前天线功率配置
antenna_power_resultC 端 → Qt返回功率设置 / 查询结果,包含当前功率、功率范围

第三部分:功能模块详解

1.入库管理流程

一、图1:入库扫描主界面

功能说明:这是入库第一步,填写基本信息后点击"开始扫描"扫标签。

界面元素

  • 批次号:点击开始扫描,自动生成 批次号如RK20260622-050411(RK+年月日-时分秒)
  • 仓库/货物/产品:下拉选择,扫到的标签会关联这些信息
  • 表格:实时显示扫到的标签,EPC列是标签唯一码,信号列显示RSSI(负值越小信号越强)
  • 信号强度与信号丢失机制:检测到最后传回信号大于60标记货物并显示其EPC/TID

图2:标签详情弹窗

功能说明:点击表格右侧"详情"按钮,查看单个标签的完整信息。

显示内容

  • 批次号、EPC(可改写)、TID(芯片固化)、信号强度
图3:入库完成提示(校验是/否为重复标签)


功能说明:点击"确认入库"后,C端会根据数据库当前入库情况,返回处理结果。改图显示"成功0件,12件被跳过",说明这12个标签已经在库中标红显示(提示用户)。
为什么跳过:防止同一标签重复入库,保证库存数据准确。

二、图1:入库记录·主界面


功能说明:点击"入库记录"按钮,可查看所有历史入库批次(进行入库信息溯源)。
点击"详情"弹出该入库批次内的所有标签的EPC和TID列表。
底部附有日期选择器:可根据日期筛选,精准定位到入库信息

图2:日期筛选

功能说明:点击起始/结束日期按钮,弹出日历,选择日期范围后点"筛选",只显示该日期范围内的入库记录。


2.出库管理流程

一、图1:出库扫描主界面

功能说明:出库第一步,系统自动生成出库批次号(CK+年月日-时分秒格式),点击"开始扫描"扫标签。

界面元素

  • 出库批次号:自动生成CK20260622-050342(CK+年月日-时分秒)
  • 表格:实时显示扫到的标签,包含EPC、TID、信号强度三列
  • 底部统计:显示"已扫描:12件"

与入库的区别:出库不需要预先填写仓库、货架、货物信息,系统自动从数据库关联查询。

图2:出库成功

功能说明:点击"确认出库"后,C端校验通过,所有标签状态正常(在库未出库),出库成功。

业务流程

  1. Qt端发送出库请求,附带所有扫描到的TID列表
  2. C端逐个校验:查询inbound_items表,检查该TID的status字段是否为'in'(在库)
  3. 所有标签都校验通过 → 更新状态为'out'→ 插入出库记录 → 返回成功
  4. Qt端弹窗提示"出库成功"
图3:出库失败警告

功能说明:点击"确认出库"后,C端校验发现部分或全部标签已出库未入库,无法完成正常出库。

触发条件

  • 标签状态为'out'(已出库),标红
  • 标签在数据库中不存在(从未入库),标红
  • 标签TID为空无法校验,标红

系统响应:弹窗显示"出库警告 - 出库失败",阻止出库操作,防止重复出库或非法出库。

图4:强制出库选择


功能说明:出库失败后,系统提供"强制出库"和"取消"两个选项,表格中异常标签·标红显示。

按钮变化

  • "确认出库"按钮变成"强制出库"(红色警示)
  • 点击取消按钮(返回正常状态)

标红显示:表格中所有行背景变红,提示这些标签都存在异常(已出库或未入库)。

强制出库逻辑:用于处理特殊情况,如工人未按程序入库、紧急领料等场景,直接执行出库操作。
避免风险:强制出库同样进入出库记录(完整可溯源),系统记录该操作为强制出库类型,便于后续审计追踪。

二、图1:出库记录主界面

功能说明:点击"出库记录"按钮,切换到记录查询模式,显示历史出库批次列表。

列表字段

  • 序号
  • 出库批次号(如CK20260622-062128)
  • 出库数量(该批次出库的标签总数)
  • 操作按钮("详情"查看该批次明细)

与入库记录的区别:出库记录不显示仓库、货架、货物信息,因为出库是从库中移除,不需要指定存放位置。

图2:出库详情弹窗

功能说明:点击记录列表中的"详情"按钮,弹出该出库批次的完整标签列表。

显示内容

  • EPC(标签可改写编码)
  • TID(标签唯一固化编码)
  • 仓库(该标签入库时的仓库)
  • 货架(该标签入库时的货架)
  • 货物(该标签关联的货物名称)
  • 产品(该标签关联的产品名称)

3.库存盘点流程

图1:盘点前置校验

功能说明:进入库存盘点页面后,可精准选中仓库和货架,扫描盘点货物。

界面元素

  • 仓库下拉框:默认显示"请选择仓库"
  • 货架下拉框:默认显示"请先选择仓库"(与仓库联动,选择仓库后才可选货架)
  • 应盘数量:显示"--"(未选择仓库货架时)

触发条件:未选择仓库时点击"开始扫描"按钮,系统弹窗提示"请先选择仓库!"。

设计目的:盘点针对特定库位进行,防止误操作导致盘点数据混乱。

图2:扫描实盘标签

功能说明:选择"2号仓库"和"A区02排"后,点击"开始扫描",实时扫描货架上的实际标签。

界面变化

  • 应盘数量:显示"2"(系统根据仓库+货架查询数据库,该位置应有2件货物)
  • 表格数据:显示12个扫描到的标签,包含EPC、货物名称、信号强度
  • 底部统计:显示"已扫描:12
  • 详情按钮:"查看不符标签"(黄色按钮)

关键逻辑

  • 实扫数量(12件)远大于应盘数量(2件),说明该货架上有多余标签或相邻货架标签被误扫
  • 货物名称列为空,因为扫描时只获取EPC/TID,货物名称需要后续关联查询

图3:校对结果展示

功能说明:扫描完成后点击"开始校对"按钮,系统将"实盘"(扫描到的12件)与"应盘"(数据库记录的2件)进行比对。

结果显示

  • 表格标红:所有12个扫描到的标签全部标红,说明都不在应盘列表中
  • 底部统计:"校对完成:匹配0件,不符14件"
    • 匹配0件:扫描到的标签中没有一个在数据库应盘列表里
    • 不符14件:12个扫描标签 + 2个应盘但未扫描到的标签 = 14个差异

业务含义

  • 盘盈:扫描到但数据库没有记录的标签(可能错放货架、未入库、或已出库但标签还在)
  • 盘亏:数据库记录应在但扫描未到的标签(可能丢失、被盗、或已出库但系统未更新)

后续处理:根据不符标签列表,仓管员需要现场核查,确认是标签放错位置、系统数据滞后,还是实际丢失。

4.查询管理流程

图1:关键词搜索

功能说明:通过关键词模糊搜索EPC或TID,快速定位货物。

操作方式
支持模糊搜索

  • 搜索框:输入"E2"(EPC/TID的部分或全部字符)
  • 搜索按钮:点击右侧"搜索"按钮执行查询
  • 结果展示:显示11条匹配结果,包含:
    • 序号
    • EPC(标签可改写编码)
    • TID(标签唯一固化编码)
    • 仓库(标签当前所在仓库)
    • 货架(标签当前所在货架)
    • 货物(关联的货物名称)
    • 详情(点击查看完整信息),详情页见下文

搜索逻辑:系统对EPC和TID进行模糊匹配,输入"E2"会返回所有包含"E2"的标签,无论它在EPC还是TID中。

图2:标签详情与EPC防伪校验

功能说明:点击表格中的"详情"按钮,查看单个标签的完整生命周期信息,并进行EPC防伪校验。

详情内容

  • 标题:标签详情 - TID 【E280F302000000010B04C6E3】
  • 入库时间:2026-06-22 11:21:24
  • 出库时间:未出库(说明标签当前仍在库中)

EPC防伪校验区域

  • 首次EPC(入库时):E280F302000000010B04C6E3
    • 这是标签首次入库时记录的EPC值,作为防伪基线
  • 当前EPC(本次扫描):E280F302000000010B04C6E3
    • 这是当前查询时标签的实际EPC值
  • 状态:正常(EPC未变更)
    • 系统比对首次EPC和当前EPC,两者一致说明标签未被篡改

防伪原理:RFID标签的TID是芯片出厂时固化、永远改不了的,但EPC是用户可以改写的。如果有人恶意改写标签EPC,系统通过比对"入库时的EPC"和"当前的EPC"就能发现异常。这种设计实现了标签级别的防伪追溯。

图3:仓库筛选查询

功能说明:通过仓库和货架筛选,查询特定库位下的所有标签。

操作方式

  • 仓库选择:选择"3号仓库"
  • 货架选择:选择"全部货架"(也可选择具体货架如"A区02排")
  • 查询按钮:点击"查询"执行筛选

筛选结果

  • 显示"共找到2条结果"
  • 只显示3号仓库下的标签
  • 结果包含EPC、TID、仓库、货架、货物信息

使用场景:仓管员需要查看某个特定仓库或货架的库存情况时,使用此功能快速获取该库位下的所有标签清单。

5.系统设置
图1:系统设置主界面

功能说明:系统设置的入口页面,提供三个配置功能模块。

三个功能入口

  • 网络设置(太阳图标):配置TCP连接参数,连接i.MX6ULL开发板
  • 读码器设置(靶心图标):配置RFID读写器天线功率
  • 词典(书本图标):货物编码与名称映射词典(预留功能)

操作方式:点击对应图标进入详细设置页面。

图2:网络设置

功能说明:配置Qt客户端与i.MX6ULL嵌入式端的TCP连接参数。

配置项

  • 服务器IP192.168.0.165(i.MX6ULL开发板的IP地址)
  • 端口号1998(C端TCP Server监听端口)

操作流程

  1. 修改IP地址或端口号(如更换开发板或修改端口)
  2. 点击"保存并连接"按钮
  3. 系统尝试建立TCP连接
  4. 弹窗提示"连接成功!"表示连接已建立

使用场景

  • 首次使用需要配置正确的IP和端口
  • 开发板IP变更后需要重新配置
  • 网络故障排查时测试连接
图3:读码器设置(天线功率)

功能说明:调节RFID读写器的天线发射功率,控制标签识别距离。

当前状态

  • 当前天线功率:23 dBm
  • 功率范围:0 ~ 33 dBm(滑动条可调节)

操作按钮

  • 查询当前功率:从RFID读写器获取当前功率值并显示
  • 应用功率设置:将滑动条设置的功率值下发到读写器

功率调节原理

  • 功率越高(如30 dBm):读写距离越远,但可能扫到相邻货架的标签(串读)
  • 功率越低(如15 dBm):读写距离越近,定位更精确,但容易漏扫

使用场景

  • 密集货架环境:调低功率,避免串读隔壁货架标签
  • 开阔盘点场景:调高功率,扩大扫描覆盖范围
  • 标签信号弱:适当提高功率,增强标签激活能力

操作流程

  1. 拖动滑动条选择目标功率(如23 dBm)
  2. 点击"应用功率设置"
  3. 弹窗提示"正在设置天线功率为 23 dBm..."
  4. 设置完成后即可进行入库/出库/盘点操作
系统设置操作总结
功能模块配置项用途
网络设置IP地址、端口号连接IMX6ULL开发板
读码器设置天线功率(0-33 dBm)控制RFID扫描距离和范围
词典(预留)货物编码与名称映射

第四部分:C端解析:行业问题分析与技术优势论证

问题一:解决 RFID 批量读取下,查重逻辑随标签量膨胀带来的性能雪崩

扫了几十上百个标签,数据量大的性能衰减

升级体验一
uthash 哈希去重——查找从 O(n) 降到 O(1)

传统做法扫到一个标签就遍历数组查重:

/* 传统线性查找:1000个标签=1000次strcmp */ int found = 0; for(int k = 0; k < tag_count; k++) { if(strcmp(tags[k].epc, new_epc) == 0) { found = 1; break; /* 平均也要比500次 */ } }

本项目的做法——哈希表一次搞定:

/* HASH_FIND_STR: 直接跳到哈希桶,1~2次比较命中 */ HASH_FIND_STR(*hash_head, g_tag_queue[g_queue_read_idx].tid_Hex, entry); if(entry == NULL) { /* 全新标签 → 插入 */ HASH_ADD_STR(*hash_head, tid_Hex, new_tag); tcp_send_tag(new_tag->epc_Hex, new_tag->tid_Hex, new_tag->rssi); }
标签数量线性查找uthash加速比
10个~5次比较1次5x
100个~50次1次50x
1000个~500次1~2次250x

用户体验:天线扫一圈同时返回几十个标签,UI瞬间全部出现——没有"一个一个蹦出来"的卡顿感。这是哈希表给的底气。

问题二:RFID 天线瞬时批量上报:单轮一次性读到 200 条 EPC/TID,JSON / 自定义二进制报文总长度轻易突破固定 buf 上限。

升级体验二
动态内存+分包重传——不预设上限,不丢一个字节

问题本质:固定的4096,沉默的截断

/* 修复前:50个标签batch_items JSON≈4600字节 */ char buf[4096]; snprintf(buf, sizeof(buf), "%s\n", json_str); /* snprintf到了4095字节自动停→末尾加了\0→最后200字节凭空消失 */ send(g_client_fd, buf, strlen(buf), 0); /* 发出去的JSON少了一个},接收端解析失败→静默丢数据 */

这不是网络问题——是数据完整性事故snprintf不会报错、不会崩溃,它只是默默地把第4096个字节以后的内容扔掉了。C端服务端认为自己发送成功,接收端却收到一句残缺JSON——整个过程没有任何错误提示,直到操作员发现按钮点不动。这种静默失败比崩溃更恶劣:崩溃能复现,截断随数据量浮动,难以排查。


1:按需分配,精确到字节

int len = strlen(json_str); /* 实际多长?4600就4600 */ char *buf = (char *)malloc(len + 2); /* 只分配4602字节,不多占1KB */ memcpy(buf, json_str, len); buf[len] = '\n';

性能对比

修复前(fixed 4K)修复后(dynamic)
20字节扫描停指令占用4096字节栈空间占用22字节堆空间
4600字节批次详情发送4095字节(截断)发送4600字节(完整)
每次调用栈开销固定4096字节0字节(堆上分配)
内存利用率最差0.4%(20/4096)接近100%

2:TCP分包重传——send成功≠数据全部发出

这是修复前代码更隐蔽的隐患:

/* 修复前:一次send,赌TCP内核缓冲区够大 */ send(g_client_fd, buf, strlen(buf), 0); /* 内核缓冲区80KB,你发4KB→OK。但万一内核缓冲区只剩2KB呢?→只发出2KB */

TCP的send()返回实际发出的字节数,可以小于请求发送的长度。触发条件:

  • 对端接收窗口已满(Qt端处理慢)
  • 内核发送缓冲区被其他连接挤占
  • 网络拥塞时TCP拥塞控制主动限流

优化后的分包循环

int total_sent = 0; while(total_sent < len + 1) { int n = send(g_client_fd, buf + total_sent, len + 1 - total_sent, 0); if(n <= 0) break; /* 连接断开→退出 */ total_sent += n; /* 追着发,直到发完 */ }

关键:不是等TCP缓冲区空出来再试一个巨大的send——而是每次send剩下的部分,缓冲区能收多少就发多少,发完为止。这种策略在嵌入式设备的低带宽/高延迟网络上尤其重要——一次发4KB可能被切分成3个TCP段,如果只发出1段就返回,剩余2段就被静默丢弃了。


接收端同样弹性:realloc自动扩容

服务端收数据做同样的事——recv一次收4096字节,TCP粘包攒多了自动扩:

char *pbuf = (char *)malloc(8192); /* 起手8K */ while(pbuf_len + recv_len >= pbuf_size) { pbuf_size *= 2; /* 8K→16K→32K→…容器追着数据长 */ pbuf = (char *)realloc(pbuf, pbuf_size); } memcpy(pbuf + pbuf_len, recv_buf, recv_len);

realloc在地址空间有空闲时会原地扩展(零拷贝),不够时才搬家。从8K翻到32K的过程通常2次realloc搞定,比一开始就malloc(65536)少浪费60KB内存。

问题三:RFID批量扫描线程竞争

技术价值:后台SDK线程永不阻塞,100ms内完成一轮"队列→哈希→TCP"全链路,50个标签瞬间入库

一、SPSC环形队列——RFID批量扫描零锁并发

痛点:SDK回调线程每秒推送几十个标签,主线程同时操作哈希表——不加锁数据竞争,加锁回调线程可能阻塞导致标签丢失。

解法:回调只写队列尾部,主线程只读队列头部——两个线程操作不同变量,天然无锁。

/* ===== 第1步:回调线程——只写队列,不碰哈希表 ===== */ void onData(HANDLE hDev, OutputInfoStruct *outputInfo) { /* 计算下一个写入位置 */ int queue_next = (g_queue_write_idx + 1) % QUEUE_MAX; if(queue_next == g_queue_read_idx) return; /* 队列满——丢一个新标签比阻塞线程更优 */ int idx = g_queue_write_idx; /* 只做两件事:copy原始数据 + 转十六进制 */ memcpy(g_tag_queue[idx].epc_Raw, epc_raw, epc_Len); memcpy(g_tag_queue[idx].tid_Raw, tid_raw, tid_Len); strcpy(g_tag_queue[idx].epc_Hex, epc_Hex); strcpy(g_tag_queue[idx].tid_Hex, tid_Hex); g_tag_queue[idx].rssi = atoi((char *)outputInfo->standard_data.data_format.RSSI); g_queue_write_idx = queue_next; /* ★ 只有回调线程写write_idx */ }
/* ===== 第2步:主线程——100ms轮询一次,批量消费 ===== */ static int drain_tag_queue(tag_entry **hash_head) { int tag_count = 0; tag_entry *entry = NULL; while(g_queue_read_idx != g_queue_write_idx) { /* 队列有数据就取 */ /* 用TID查哈希——命中就跳过,未命中才HASH_ADD */ HASH_FIND_STR(*hash_head, g_tag_queue[g_queue_read_idx].tid_Hex, entry); if(entry == NULL) { /* 全新标签 */ tag_entry *new_tag = (tag_entry *)calloc(1, sizeof(tag_entry)); memcpy(new_tag->epc_Raw, g_tag_queue[g_queue_read_idx].epc_Raw, ...); strcpy(new_tag->epc_Hex, g_tag_queue[g_queue_read_idx].epc_Hex); strcpy(new_tag->tid_Hex, g_tag_queue[g_queue_read_idx].tid_Hex); HASH_ADD_STR(*hash_head, tid_Hex, new_tag); /* ★ 只有主线程写哈希表 */ tcp_send_tag(new_tag->epc_Hex, new_tag->tid_Hex, new_tag->rssi); tag_count++; } g_queue_read_idx = (g_queue_read_idx + 1) % QUEUE_MAX; /* ★ 只有主线程写read_idx */ } return tag_count; }

为什么不用锁:两个索引分别由不同线程独占写入,满足SPSC(单生产者单消费者)无锁条件。ARM上省掉pthread_mutex_lock就是省掉几十微秒的上下文切换,在100ms轮询周期里能多处理2~3倍标签。

问题四:RFID批量扫描无货物丢失校验,传统方案只返回"扫到了"——是/否,信号变弱无提示。

升级体验三:RSSI追踪——货物掉落,丢失实时追踪

/* ===== 哈希表存RSSI,信号变了才更新(不刷屏) ===== */ static int drain_tag_queue(tag_entry **hash_head) { while(g_queue_read_idx != g_queue_write_idx) { HASH_FIND_STR(*hash_head, g_tag_queue[g_queue_read_idx].tid_Hex, entry); if(entry == NULL) { /* 新标签:直接发送 */ new_tag->rssi = g_tag_queue[g_queue_read_idx].rssi; tcp_send_tag(new_tag->epc_Hex, new_tag->tid_Hex, new_tag->rssi); } else { /* ★ 旧标签:信号变了才发——不刷屏,但每个dBm变化都感知到 */ if(entry->rssi != g_tag_queue[g_queue_read_idx].rssi) { entry->rssi = g_tag_queue[g_queue_read_idx].rssi; tcp_send_tag(entry->epc_Hex, entry->tid_Hex, entry->rssi); } /* 信号没变 → 跳过 → 不发 → 安静 */ } g_queue_read_idx = (g_queue_read_idx + 1) % QUEUE_MAX; } }
void InboundPage::checkRssiAndWarn() { if(m_tagRssiMap.isEmpty()) return; QStringList weakTags; QMapIterator<QString, int> it(m_tagRssiMap); while(it.hasNext()) { it.next(); if(it.value() != 0 && it.value() < RSSI_LOSS_THRESHOLD) { /* 只查静态阈值 */ weakTags.append(it.key()); } } if(!weakTags.isEmpty()) { QMessageBox warnBox(this); warnBox.setWindowTitle("信号检测警告"); warnBox.setText("!检测到 N 件货物信号强度低于阈值"); warnBox.setInformativeText("可能已移出读写范围或货物丢失"); warnBox.exec(); } }
/* ===== 信号强度颜色编码(Qt端) ===== */ void InboundPage::addScannedTag(const QString &epc, const QString &tid, int rssi) { QTableWidgetItem *rssiItem = m_scanTable->item(row, 4); rssiItem->setText(QString::number(rssi) + " dBm"); if (rssi >= -50) rssiItem->setForeground(QColor("#27ae60")); /* 绿:很近 */ else if (rssi >= -70) rssiItem->setForeground(Qt::black); /* 黑:正常 */ else rssiItem->setForeground(QColor("#e67e22")); /* 橙:弱,可能快丢了 */ }

核心价值:RSSI变弱入库前报警;天线靠近标签时RSSI从-70跳到-40,屏幕颜色从橙变绿,操作员就知道"找到了"。可凭颜色梯度找到货物。

第五部分:技术优化总结

指标传统本系统提升
1000件批量查重线性遍历→500次strcmputhash哈希O(1)→1~2次比较300倍
查重误判率依赖记忆(错误率~5%)TID自动去重(100%准确)零差错
信号丢失发现无感知(事后才发现)实时颜色+结束时弹窗从无到有
防伪溯源无法追溯(条码可复印)TID绑定+EPC变更告警从无到有
内存稳定性256KB栈数组→崩calloc堆分配+stmt=NULL全天候不崩
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/24 5:27:14

软件项目管理期末速记

第三章生存期模型知识点速记核心模型对比表表格模型适用场景关键优势风险点瀑布模型需求明确、变更少、小型项目流程简单&#xff0c;文档清晰需求变更成本高V模型需求明确、解决方案明确、高可靠性要求&#xff08;安全/性能&#xff09;测试前移&#xff0c;质量保障强需求不…

作者头像 李华
网站建设 2026/6/24 5:21:10

【日耕一题】7. 循环右移(2026第17届蓝桥杯C++B组省赛 C 题)

你好&#xff0c;我是林森lsjs 我的Github 地址&#xff1a;sqyCoder (Qiyang) GitHub 以博文记录成长&#xff0c;用心打磨代码与思维 欢迎来到日耕一题&#xff0c;今天这道是 2026 蓝桥杯省赛的 C 题&#xff0c; 纯思维题。抓住任意连续子数组这个强约束 一、题目完整解…

作者头像 李华
网站建设 2026/6/24 5:19:56

我的AI辅助开发工具链2026版:从编码助手到工业视觉检测的全栈实践

前言 2024年我们还在争论“AI写的代码能不能用”&#xff0c;2026年的现实是&#xff1a;不会用AI的开发者&#xff0c;正在被会用AI的开发者按在地上摩擦-21。区别已经不在“用不用”&#xff0c;而在“你的AI工具链组织得好不好”-21。工具单点强不算强&#xff0c;真正决定效…

作者头像 李华
网站建设 2026/6/24 5:17:46

立体视觉与深度学习融合:无人机如何智能识别与定位待修剪树枝

1. 项目概述&#xff1a;当无人机“看懂”了树在林业管理&#xff0c;尤其是像辐射松这类经济价值高、需要精细化养护的树种作业中&#xff0c;人工巡检和修剪一直是个老大难问题。林区地形复杂、树木高大&#xff0c;工人爬上爬下不仅效率低下&#xff0c;安全风险也高。更头疼…

作者头像 李华
网站建设 2026/6/24 5:16:56

DARLING框架:用变化检测增强强化学习应对非平稳环境

1. 项目概述&#xff1a;当强化学习遇上“善变”的世界在传统的强化学习&#xff08;RL&#xff09;叙事里&#xff0c;我们常常假设环境是“静止”的&#xff0c;或者说&#xff0c;是平稳的。智能体在一个固定的马尔可夫决策过程&#xff08;MDP&#xff09;中学习&#xff0…

作者头像 李华
网站建设 2026/6/24 5:14:15

跨平台眼底影像分析:基于任务熵与后验集中性的模型不确定性验证

1. 项目概述&#xff1a;当眼底影像分析遇上跨平台挑战作为一名在医学影像分析领域摸爬滚打了十多年的从业者&#xff0c;我见过太多“实验室里天花乱坠&#xff0c;临床落地一地鸡毛”的算法模型。今天想和大家深入聊聊一个既前沿又极其务实的话题&#xff1a;跨平台眼底影像分…

作者头像 李华