STM32与QT开发无人超市系统:从硬件驱动到上位机设计的全流程解析

发布时间:2026/9/2 9:46:08
STM32与QT开发无人超市系统:从硬件驱动到上位机设计的全流程解析 简介本资源是一套完整的嵌入式智能零售系统实战项目面向STM32与Qt跨平台开发学习者、毕业设计学生及嵌入式初学者解决无人超市核心功能RFID会员管理、商品电子标签识别、闸机联动控制的软硬件协同实现问题。压缩包共164个文件含28个运行依赖DLL、22个Qt多语言翻译QM、16个头文件.h、14个目标文件.o与源码.c/.cpp以及可执行程序.exe、设计文档.pdf/.doc、原理图素材.jpg/.png和Keil工程文件.uvproj/.axf整体77.95MB结构清晰模块划分明确。已有1087人学习下载。用户可直接部署运行包含完整Qt上位机支持管理员/顾客双角色、STM32F103底层固件含RC522读卡、步进电机驱动、串口通信等关键驱动、全流程设计文档及B站配套讲解视频覆盖从IC卡注册充值、商品扫码结算到步进电机模拟闸机开门的全链路逻辑代码注释充分便于理解与二次开发。1. 项目概述与核心价值最近几年无人零售的概念从风口逐渐沉淀为实际落地的应用场景。我手头这个“基于STM32QT设计的无人超市消费系统”项目就是一个非常典型的、软硬件结合的嵌入式综合实践案例。它不仅仅是一个简单的课程设计或毕业设计更是一个能让你完整走通从底层硬件感知、数据处理到上层用户界面交互、网络通信全流程的绝佳练手项目。对于正在学习STM32、QT或者对物联网、嵌入式系统开发感兴趣的朋友来说这个项目就像一份详尽的“地图”告诉你各个技术点如何串联解决一个真实场景下的问题。这个系统的核心逻辑很清晰它模拟了一个无人值守的超市购物环境。顾客自助选取商品系统通过硬件如RFID、摄像头、重量传感器等具体取决于设计自动识别商品并生成账单顾客通过扫码或刷卡等方式完成支付最后闸机放行。整个过程无需店员介入。其技术栈的核心是STM32作为下位机负责硬件控制、数据采集和初步处理QT作为上位机运行在PC或触摸屏上提供友好的人机交互界面并负责更复杂的业务逻辑如商品管理、交易处理、数据存储等。两者之间通常通过串口、USB或网络如TCP/IP进行通信。为什么这个组合值得深入STM32是嵌入式领域的“瑞士军刀”资源丰富、生态成熟能很好地处理实时性要求高的硬件交互。而QT是跨平台的C图形界面框架其信号槽机制、丰富的UI控件和网络库非常适合开发需要稳定交互和复杂逻辑的上位机软件。将两者结合你既能深入理解微控制器如何与真实世界“对话”也能掌握如何构建一个稳定、美观的桌面级应用。这个项目涵盖的知识点非常密集STM32的GPIO、定时器、中断、ADC、通信接口UART、SPI、I2CQT的界面布局、信号槽、多线程、数据库如SQLite、网络编程以及两者间的数据协议设计。搞懂它你对嵌入式系统开发的认知会上一个大台阶。2. 系统整体架构与设计思路拆解拿到一个项目最忌讳的就是一头扎进代码里。我们先从顶层视角看看这个无人超市消费系统是怎么被“组装”起来的。一个健壮的系统源于清晰的架构设计。2.1 硬件层STM32端核心职责STM32在这里扮演着“前线哨兵”和“数据搬运工”的角色。它的核心任务不是运行复杂的业务逻辑而是可靠、实时地感知物理世界并执行控制命令。根据常见的无人超市方案其硬件模块可能包括商品识别模块这是系统的“眼睛”。方案有多种RFID射频识别每件商品贴一个RFID标签货架或购物车/篮内置阅读器。当顾客拿起商品时阅读器通过射频信号识别商品ID。这是目前无人店较主流的技术成本适中识别速度快。STM32需要驱动RFID读卡器模块通常通过UART或SPI通信并解析其输出的卡号数据。重量传感器用于货架。通过称重传感器如HX711模块感知货品重量变化结合预设的单品重量推断被取走或放回的商品数量。STM32需要处理ADC数据进行滤波和校准。图像识别通过摄像头捕捉商品图像在STM32上运行轻量级AI模型或在云端识别。这对STM32算力要求较高可能需要F4/F7/H7系列通常作为辅助或特定场景方案。用户交互与支付模块这是系统的“手”和“收银台”。显示屏与输入可能是一个小OLED或LCD屏用于显示简单提示如“请扫码”。配合按键或触摸屏进行选择。支付终端集成二维码扫描头扫描顾客手机支付码或刷卡模块读取银行卡、会员卡。STM32驱动这些模块获取支付凭证信息。执行机构控制模块这是系统的“门卫”。闸机控制通过GPIO控制继电器或直接驱动电机来控制闸门的开关。支付成功后STM32收到上位机指令驱动闸机打开。通信模块这是系统的“传令兵”。与上位机通信最常用的是UART串口简单可靠。如果距离较远或有组网需求可能会用到Wi-Fi模块如ESP8266/ESP32或以太网模块使STM32具备TCP/IP通信能力能够向上位机发送商品ID、重量数据并接收控制指令。设计心得在STM32端编程实时性和稳定性是第一要务。中断要合理使用比如支付模块扫码成功、RFID读到卡号都应该触发中断及时处理。通信协议要设计得健壮包含帧头、帧尾、长度、校验和如CRC16防止数据错乱。我曾在一个项目里因为校验和没做好导致偶尔的错误数据被上位机当成正确指令引发了闸机误动作。2.2 软件层QT端核心职责QT上位机是整个系统的大脑和指挥中心。它运行在性能更强的计算机上负责协调所有硬件单元管理核心业务数据并提供管理界面。其核心模块包括通信服务模块负责与一个或多个STM32下位机建立稳定连接串口或Socket按照约定好的协议解析下位机上报的数据如商品ID、支付信号并向下位机发送查询或控制指令如查询库存、控制闸机。这里必须使用多线程将通信放在独立的线程中避免阻塞UI主线程导致界面卡死。业务逻辑处理模块这是最核心的部分。购物车管理实时维护当前顾客的购物车状态根据STM32上报的商品增减事件更新购物车商品列表和实时总价。支付处理模拟或对接真实的支付网关。收到支付凭证后进行验证可以是本地模拟成功然后生成订单。订单与库存管理支付成功后生成正式订单记录并更新系统库存。所有数据需要持久化存储。数据持久化模块使用轻量级数据库如SQLite来存储商品信息ID、名称、价格、库存、订单记录、用户日志等。SQLite无需单独安装数据库服务器单个文件即可非常适合嵌入式或桌面应用。用户界面模块顾客交互界面全屏显示的触摸界面展示商品列表、购物车、实时金额、支付二维码等。要求布局清晰、响应灵敏。管理后台界面供管理员使用进行商品上下架、价格调整、库存查询、订单统计、销售报表查看等操作。2.3 通信协议设计软硬件对话的“语言”STM32和QT之间要顺畅交流必须有一套严谨的“语言”这就是通信协议。设计一个自定义的简单应用层协议是关键。一个典型的帧结构可以设计如下[帧头 0xAA 0x55] [数据长度 LEN] [命令字 CMD] [数据域 DATA] [校验和 CHK] [帧尾 0x0D 0x0A]帧头/帧尾用于在数据流中识别一帧数据的开始和结束。数据长度指明数据域的长度便于接收方正确解析。命令字定义此帧数据的含义。例如0x01: STM32上报商品ID0x02: STM32上报支付成功0x81: QT查询STM32状态0x82: QT下发打开闸机指令数据域携带具体信息。如上报商品ID时数据域就是商品的唯一编号。校验和对前面所有字节进行累加和或CRC计算用于验证数据在传输过程中是否出错。在QT端你需要一个数据解包器。由于串口数据是流式的可能一次收到不完整帧或粘包多帧连在一起所以需要有一个缓冲区不断接收数据并搜索帧头帧尾根据长度字段截取出完整的一帧验证校验和后再根据命令字分发给不同的处理函数。避坑指南协议设计一定要考虑超时和重发机制。比如QT下发开闸指令后如果5秒内没收到STM32的应答应该重发一次。同样STM32上报重要数据后也应等待QT的确认帧否则在异常情况下可能导致状态不一致。我早期版本没做重发在网络抖动时偶尔丢指令顾客付了钱门却没开体验非常糟糕。3. STM32下位机核心功能实现详解现在我们深入到STM32的代码层面看看几个关键功能如何实现。这里以使用HAL库为例因为它比标准库更通用和便捷。3.1 商品识别RFID模块驱动与数据解析假设我们选用常见的MFRC522 RFID模块通过SPI接口通信。首先需要在CubeMX中配置SPI外设。关键步骤硬件与软件初始化// 在CubeMX中配置SPI1模式为全双工主模式数据大小8位时钟极性低相位第1边沿。 // 生成代码后初始化RFID模块MFRC522 void RFID_Init(void) { uint8_t byte; // 软复位MFRC522 MFRC522_WriteRegister(MFRC522_REG_COMMAND, PCD_RESETPHASE); HAL_Delay(50); // 开启天线 byte MFRC522_ReadRegister(MFRC522_REG_TX_CONTROL); MFRC522_WriteRegister(MFRC522_REG_TX_CONTROL, byte | 0x03); }轮询寻卡在主循环或定时器中断中定期调用寻卡函数。void Main_Loop(void) { uint8_t status; uint8_t snr[4]; // 用于存储卡片的序列号4字节 // 寻卡 status MFRC522_Request(PICC_REQIDL, snr); if (status MI_OK) { // 防冲突获取卡号 status MFRC522_Anticoll(snr); if (status MI_OK) { // 成功获取到一张卡的序列号 snr // 将snr通过串口发送给上位机格式遵循自定义协议 Send_GoodsID_To_PC(snr); // 使卡片进入休眠状态避免重复读取 MFRC522_Halt(); } } HAL_Delay(100); // 适当延时避免过于频繁寻卡 }数据上报将获取到的4字节卡号封装成协议帧通过串口发送。void Send_GoodsID_To_PC(uint8_t *id) { uint8_t tx_buffer[20]; uint8_t chk_sum 0; int index 0; // 帧头 tx_buffer[index] 0xAA; tx_buffer[index] 0x55; // 数据长度命令字1字节 卡号数据4字节 5字节 tx_buffer[index] 5; // 命令字0x01 代表上报商品ID tx_buffer[index] 0x01; chk_sum 0x01; // 数据域卡号 for(int i0; i4; i) { tx_buffer[index] id[i]; chk_sum id[i]; } // 校验和简单累加和 tx_buffer[index] chk_sum; // 帧尾 tx_buffer[index] 0x0D; tx_buffer[index] 0x0A; // 通过串口发送 tx_buffer 中的 index 个字节 HAL_UART_Transmit(huart1, tx_buffer, index, 1000); }注意事项RFID读取容易受到金属环境干扰。在实际部署时天线周围应尽量避免金属物体。另外多张卡同时进入感应区会发生冲突代码中的MFRC522_Anticoll函数就是处理这个的但最好在物理布局上避免顾客将多件贴标商品紧挨着放在阅读器前。3.2 支付模块集成二维码扫描头驱动支付模块通常是一个集成的二维码扫描头它通过串口TTL电平输出识别到的字符串。STM32只需要配置一个UART去接收它即可。硬件连接将扫描头的TX接STM32的RX如USART2_RXGND和VCC接好。串口配置在CubeMX中配置对应的UART为异步模式波特率通常为9600或115200根据扫描头手册设置开启接收中断。中断处理// 在USART2的中断服务函数中 void USART2_IRQHandler(void) { if(__HAL_UART_GET_FLAG(huart2, UART_FLAG_RXNE) ! RESET) { uint8_t rx_byte (uint8_t)(huart2.Instance-DR 0xFF); // 将字节存入缓冲区 if(rx_index RX_BUF_SIZE) { rx_buffer[rx_index] rx_byte; } // 如果收到换行符假设扫描头以\n结束一帧数据则认为一帧数据接收完成 if(rx_byte \n) { // 处理一帧完整的支付数据 Process_Payment_Data(rx_buffer, rx_index); rx_index 0; // 重置缓冲区索引 } } }数据处理Process_Payment_Data函数会收到一个字符串比如是一串支付订单号。STM32需要将其封装成协议帧例如命令字0x02上报给QT上位机。注意STM32不负责验证支付是否成功它只负责“感知”到支付动作收到了一个有效的字符串并上报。支付成功与否的验证由联网的QT上位机完成。3.3 闸机控制与通信指令解析闸机控制通常是一个简单的GPIO输出控制继电器。重点在于如何解析来自QT的指令。指令解析STM32的UART1与QT通信需要接收并解析完整的协议帧。// 在主循环或串口接收完成中断中处理数据包 void Parse_PC_Command(uint8_t *data, uint8_t len) { // 检查帧头、长度、校验和... if(data[0]0xAA data[1]0x55 CheckSum_OK(data, len)) { uint8_t cmd data[3]; // 命令字 switch(cmd) { case 0x82: // 开闸指令 HAL_GPIO_WritePin(GATE_GPIO_Port, GATE_Pin, GPIO_PIN_SET); HAL_Delay(2000); // 开门2秒 HAL_GPIO_WritePin(GATE_GPIO_Port, GATE_Pin, GPIO_PIN_RESET); // 可以向上位机发送一个应答帧 Send_Ack_To_PC(0x82); break; case 0x83: // 查询状态指令 Send_Status_To_PC(); // 上报自身状态如传感器状态 break; default: break; } } }控制逻辑收到开闸指令后拉高GPIO电平2秒然后恢复。同时发送应答让上位机知道指令已执行。4. QT上位机软件设计与核心代码剖析QT端的开发是整个系统的“面子”和“里子”既要界面美观易用又要逻辑严谨稳定。4.1 通信服务模块多线程串口/网络通信绝不能在主UI线程中进行阻塞式的串口读写或网络等待。必须使用QSerialPort配合QThread或者使用QTcpSocket的异步信号槽机制。使用QSerialPort与QThread的示例创建工作线程类// serialworker.h class SerialWorker : public QObject { Q_OBJECT public: explicit SerialWorker(QObject *parent nullptr); public slots: void openSerialPort(const QString portName, qint32 baudRate); void closeSerialPort(); void sendData(const QByteArray data); private: QSerialPort *m_serial; };在线程中操作串口// serialworker.cpp void SerialWorker::openSerialPort(const QString portName, qint32 baudRate) { m_serial new QSerialPort(this); m_serial-setPortName(portName); m_serial-setBaudRate(baudRate); m_serial-setDataBits(QSerialPort::Data8); m_serial-setParity(QSerialPort::NoParity); m_serial-setStopBits(QSerialPort::OneStop); if (m_serial-open(QIODevice::ReadWrite)) { connect(m_serial, QSerialPort::readyRead, this, SerialWorker::handleReadyRead); emit portOpened(true); } else { emit errorOccurred(m_serial-errorString()); } } void SerialWorker::handleReadyRead() { QByteArray data m_serial-readAll(); // 将原始数据放入缓冲区并进行协议解包 m_buffer.append(data); while(unpackFrameFromBuffer(m_buffer)) { // 解包成功一帧发射信号携带命令和数据 emit frameReceived(cmd, dataField); } }在主线程UI线程中使用// 在主窗口类中 m_serialThread new QThread; m_serialWorker new SerialWorker; m_serialWorker-moveToThread(m_serialThread); connect(m_serialWorker, SerialWorker::frameReceived, this, MainWindow::onFrameReceived); m_serialThread-start(); // 打开串口 QMetaObject::invokeMethod(m_serialWorker, openSerialPort, Qt::QueuedConnection, Q_ARG(QString, COM3), Q_ARG(qint32, 115200));实操心得串口数据接收一定要用缓冲区并实现一个状态机式的解包函数unpackFrameFromBuffer。这个函数要能处理帧头不完整、数据粘包、帧尾缺失等各种情况。我常用的方法是维护一个解包状态找帧头、读长度、读数据、校验逐字节处理缓冲区这样最稳健。4.2 业务逻辑与数据库操作使用SQLite存储商品和订单信息。QT提供了QSqlDatabase和QSqlQuery来方便地操作数据库。初始化数据库与表bool DatabaseManager::initDatabase() { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(unmanned_store.db); if (!db.open()) { qDebug() Failed to open database; return false; } QSqlQuery query; // 创建商品表 query.exec(CREATE TABLE IF NOT EXISTS goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, rfid TEXT UNIQUE NOT NULL, name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER NOT NULL DEFAULT 0)); // 创建订单表 query.exec(CREATE TABLE IF NOT EXISTS orders ( order_id TEXT PRIMARY KEY, total_amount REAL NOT NULL, payment_method TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP)); // 创建订单详情表 query.exec(CREATE TABLE IF NOT EXISTS order_details ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id TEXT, goods_id INTEGER, quantity INTEGER, FOREIGN KEY(order_id) REFERENCES orders(order_id), FOREIGN KEY(goods_id) REFERENCES goods(id))); return true; }购物车与订单处理// 当收到STM32上报的商品RFID时 void MainWindow::onGoodsScanned(const QString rfidTag) { // 1. 根据rfidTag查询数据库获取商品信息 QSqlQuery query; query.prepare(SELECT id, name, price FROM goods WHERE rfid ?); query.addBindValue(rfidTag); if (query.exec() query.next()) { int goodsId query.value(0).toInt(); QString name query.value(1).toString(); double price query.value(2).toDouble(); // 2. 更新内存中的购物车模型如QListCartItem m_shoppingCart.addItem(goodsId, name, price, 1); // 3. 更新UI显示 ui-cartTableView-updateDisplay(m_shoppingCart); ui-totalLabel-setText(QString::number(m_shoppingCart.getTotal(), f, 2)); } else { qDebug() Unknown RFID tag: rfidTag; // 可以播放提示音或显示提示信息 } } // 支付成功回调 void MainWindow::onPaymentSuccess(const QString transactionId) { // 1. 生成订单号 QString orderId QUuid::createUuid().toString(QUuid::WithoutBraces); // 2. 开启数据库事务 QSqlDatabase::database().transaction(); QSqlQuery query; // 3. 插入订单主记录 query.prepare(INSERT INTO orders (order_id, total_amount, payment_method) VALUES (?, ?, ?)); query.addBindValue(orderId); query.addBindValue(m_shoppingCart.getTotal()); query.addBindValue(WeChat Pay); // 示例 if (!query.exec()) { /* 回滚并处理错误 */ } // 4. 插入订单详情 for (const auto item : m_shoppingCart.items()) { query.prepare(INSERT INTO order_details (order_id, goods_id, quantity) VALUES (?, ?, ?)); query.addBindValue(orderId); query.addBindValue(item.goodsId); query.addBindValue(item.quantity); if (!query.exec()) { /* 回滚并处理错误 */ } // 5. 更新商品库存 query.prepare(UPDATE goods SET stock stock - ? WHERE id ?); query.addBindValue(item.quantity); query.addBindValue(item.goodsId); if (!query.exec()) { /* 回滚并处理错误 */ } } // 6. 提交事务 if (!QSqlDatabase::database().commit()) { QSqlDatabase::database().rollback(); // 处理失败逻辑 } else { // 7. 清空购物车发送开闸指令给STM32 m_shoppingCart.clear(); sendOpenGateCommand(); // 8. 显示支付成功界面 showPaymentSuccessPage(orderId); } }关键点一定要使用数据库事务从插入订单到扣减库存必须是一个原子操作。否则可能在插入订单详情后扣库存前发生异常导致数据不一致卖了货但库存没减。事务能保证要么全部成功要么全部回滚。4.3 用户界面设计与交互QT Designer可以快速拖拽出界面但良好的代码结构更重要。建议使用Model-View架构。商品管理界面使用QTableView和QSqlTableModel。// 在管理窗口初始化时 QSqlTableModel *model new QSqlTableModel(this); model-setTable(goods); model-setEditStrategy(QSqlTableModel::OnManualSubmit); // 手动提交修改 model-select(); ui-goodsTableView-setModel(model); ui-goodsTableView-setColumnHidden(0, true); // 隐藏id列 // 连接按钮信号实现增删改查 connect(ui-addButton, QPushButton::clicked, this, [model](){ int row model-rowCount(); model-insertRow(row); model-setData(model-index(row, 1), New Product); // 设置默认值 }); connect(ui-saveButton, QPushButton::clicked, this, [model](){ if (!model-submitAll()) { QMessageBox::warning(this, Error, Save failed: model-lastError().text()); } });顾客购物界面全屏触摸界面使用QGridLayout或QStackedWidget来组织不同页面首页、购物车、支付页、完成页。关键是要响应灵敏避免复杂计算阻塞UI线程。所有耗时操作如网络请求、大量数据库查询都应放到工作线程中。5. 系统联调、问题排查与优化实录软硬件分别调通后联调才是真正的挑战。这里记录几个我踩过的坑和解决方法。5.1 通信不稳定问题排查表现象可能原因排查步骤与解决方案QT收不到STM32数据1. 物理连接错误RX/TX接反2. 波特率等参数不匹配3. STM32未正确发送4. 串口被其他程序占用1. 用万用表测电压或交换RX/TX线试一下。2. 双方严格检查波特率、数据位、停止位、校验位。3. 用逻辑分析仪或USB-TTL工具接在STM32的TX脚上看是否有数据波形发出。这是最直接的硬件调试方法。4. 关闭可能的串口调试助手、虚拟机等。收到数据但乱码1. 波特率误差太大2. 电平不匹配如3.3V与5V3. 协议解析错误1. 确保双方主频和波特率设置准确STM32的时钟树配置要检查。2. 检查双方MCU/模块的电平标准必要时加电平转换芯片。3. 在QT端以十六进制格式显示接收到的原始数据与STM32发送的进行比对。数据丢帧或粘包1. 发送/接收缓冲区溢出2. 处理速度跟不上3. 协议无帧边界或处理不当1. 增大串口缓冲区。在QT端readyRead信号触发后应尽快readAll()。2. STM32发送一帧后适当延时。QT端解包算法要能处理粘包。3.务必在协议中设计帧头帧尾和长度字段并在接收端实现状态机解析。控制指令无响应1. 指令格式错误2. 校验和失败3. STM32程序卡死1. 用调试工具确认QT发送的指令字节流完全正确。2. 双方校验和算法必须一致。打印出计算和接收的校验和进行对比。3. 检查STM32是否有看门狗或程序陷入死循环。可添加一个“心跳包”机制STM32定时上报状态QT监控其是否存活。5.2 性能与稳定性优化STM32端优化中断使用将RFID寻卡、串口接收等操作放在定时器中断或外部中断中避免主循环轮询带来的延迟和CPU占用。看门狗务必开启独立看门狗IWDG防止程序跑飞。在主线任务和关键中断中定期“喂狗”。电源管理如果设备是电池供电在空闲时段让STM32进入低功耗模式Stop或Sleep模式由中断事件唤醒。QT端优化数据库优化为经常查询的字段如goods.rfid建立索引。对于复杂的统计查询可以考虑定期预生成报表避免在UI线程中进行大规模表连接查询。内存管理避免在频繁调用的槽函数中创建大对象。使用对象池或智能指针管理资源。日志系统集成一个简单的日志库如QFileQTextStream记录所有关键操作、通信数据和异常。出问题时日志是唯一的“现场录像”。系统级优化心跳与重连QT端定时如每10秒向STM32发送心跳包STM32回应。如果连续3次收不到回应QT判定连接断开尝试重新初始化串口或网络连接。本地缓存QT端在启动时将商品信息RFID与价格映射加载到内存中的QHash或QMap中。这样在扫描商品时无需频繁查询数据库极大提升响应速度。当后台修改商品价格后需要同步更新这个缓存。5.3 扩展思考与进阶方向这个基础版本跑通后你可以考虑很多增强功能让项目更贴近商用多节点组网一个QT上位机管理多个货架STM32节点。可以使用RS-485总线半双工需要处理总线竞争或者让每个STM32都连接Wi-Fi通过TCP/IP与QT服务器通信。这时需要为每个节点定义唯一地址。云端同步将QT作为本地网关将销售数据、库存信息同步到云端服务器。可以使用HTTP/HTTPS协议或MQTT协议。QT端需要集成网络请求库如QNetworkAccessManager。人脸识别会员接入USB摄像头使用OpenCV或Dlib库实现简单的人脸检测与识别用于会员登录或无感支付。离线运行能力考虑网络断开的情况。QT端应能独立完成销售并将订单暂存本地待网络恢复后批量同步到云端。更复杂的商品识别结合重量传感器和RFID。当重量变化与RFID识别结果在逻辑上冲突时比如重量减少但未识别到取货系统应能发出警报或进入异常处理流程。这个项目从硬件选型、电路焊接如果自己做板子、嵌入式编程、桌面应用开发到联调测试覆盖了一个完整产品原型的绝大部分环节。把它吃透不仅仅是学会STM32和QT更是掌握了如何让软硬件协同工作来解决一个实际问题的系统工程思维。遇到问题、排查问题、解决问题的过程才是这个项目带给你的最大财富。本文还有配套的精品资源点击获取

相关新闻