STM32与QT无人超市系统开发:软硬件结合实战指南
发布时间:2026/9/3 3:23:46 作者:尧图编辑部 阅读量:1,286

简介本资源是一套完整的嵌入式无人超市消费系统实战项目面向STM32与Qt跨平台开发学习者、毕业设计学生及智能硬件爱好者解决多角色权限管理、RFID会员卡全流程业务注册/充值/消费/挂失、闸机联动控制等典型物联网应用问题。压缩包含164个文件总计77.95MB涵盖Qt上位机源码.cpp/.ui/.pro、STM32底层驱动代码main.c/timer.c/motor.c/RFID_RC522.c等、可执行程序.exe、编译输出文件.axf/.hex/.map、本地化翻译.qm、设计文档PDF及图标界面资源.ico/.jpg结构清晰模块解耦明确。已有1087人学习下载配套B站实操视频与CSDN系列设计文档提供从硬件驱动到GUI交互的全链路实现细节开箱即用无需额外配置即可快速部署验证功能逻辑。1. 项目概述与核心价值最近几年无人零售的概念从风口逐渐沉淀为一种可行的商业补充模式。作为一名长期混迹在嵌入式与上位机开发领域的工程师我经手过不少相关的项目原型。今天想和大家深入聊聊一个非常典型的综合性练手项目——基于STM32和QT的无人超市消费系统。这个项目听起来高大上实际上它完美地串联了嵌入式硬件控制、本地数据处理、网络通信以及图形用户界面设计等多个核心技能点无论是用于课程设计、毕业答辩还是作为个人技术能力的展示都极具分量。简单来说这个系统模拟了无人超市的购物结算流程顾客在货架由STM32模拟选取商品STM32负责采集商品信息比如通过RFID或条码扫描并计算金额QT开发的上位机软件则扮演着“收银台”和“后台管理”的角色它需要与下位机STM32通信实时显示购物清单和总价处理支付请求模拟扫码支付并管理商品库存和交易记录。整个系统涉及STM32的串口/USB通信、传感器数据采集、QT的串口编程、GUI设计、SQLite数据库操作以及简单的网络请求模拟。通过复现这个项目你不仅能巩固单片机编程更能踏入“软硬结合”的系统级开发大门理解一个完整产品从感知层到应用层的运作逻辑。对于初学者这是从点亮LED到实现一个实用系统的跨越对于有经验的开发者这是一个优化架构、提升代码健壮性的好案例。2. 系统整体架构与设计思路拆解拿到“无人超市消费系统”这个标题我们首先要摒弃“简单堆砌功能”的想法。一个健壮的系统其架构设计决定了后续开发的效率和系统的稳定性。我的设计思路遵循“分层解耦模块化通信”的原则。2.1 硬件与软件角色划分整个系统可以清晰地划分为两个物理部分和三个逻辑层次下位机硬件层 - STM32扮演“感知与执行终端”的角色。它需要连接具体的输入输出设备例如输入设备RFID读卡器模拟商品标签识别、矩阵键盘模拟手动输入商品编号、条形码扫描模块预留接口。输出设备OLED显示屏显示当前商品、单价、小计、蜂鸣器操作提示音。核心职责轮询或中断方式读取输入设备获取“商品ID”根据ID查询本地存储的简单商品数据库或等待上位机查询结果进行单价和数量的累加计算通过串口UART将结构化的交易数据包发送给上位机。上位机应用层 - QT扮演“数据处理与交互中心”的角色。它运行在PC或工控机上负责所有复杂的业务逻辑和用户交互。通信模块通过QT的QSerialPort类与STM32进行稳定可靠的串口数据收发解析下位机发来的数据包并向下位机发送控制指令如清空购物车。业务逻辑模块维护一个完整的商品数据库使用SQLite包含商品ID、名称、单价、库存等处理购物车逻辑添加、删除、修改数量计算总价、折扣如果有。支付模拟模块生成支付二维码模拟接入支付宝/微信支付API并处理支付成功/失败的逻辑回调。数据管理模块记录每一笔交易的详细日志时间、商品、金额、支付状态并提供简单的查询、统计和库存管理界面。用户界面GUI使用QT Designer设计直观的界面实时显示购物清单、总价、支付二维码并提供管理入口。数据交换层通信协议这是连接上下位机的“桥梁”设计的好坏直接关系到系统的稳定性。我强烈建议自定义一个简单、高效的应用层协议而不是直接发送原始字符串。例如可以设计如下格式的数据帧[帧头][命令字][数据长度][数据内容][校验和][帧尾]帧头/帧尾用于标识一个完整数据包的开始和结束如0xAA0x55。命令字区分不同类型的消息如0x01商品信息上报0x02上位机确认0x03清空指令。数据长度指明后续数据内容的字节数便于接收方正确解析。校验和对数据内容进行累加和或CRC校验确保数据传输的准确性。STM32在发送前计算QT在接收后验证校验失败则请求重发。设计心得很多初学者喜欢用字符串拼接如“Apple,3.5,2\n”这种方式在简单演示中可行但在复杂、高速或需要传输二进制数据的场景下极易出错如数据本身包含逗号或换行符。采用二进制帧结构虽然前期解析稍麻烦但能从根本上提升通信的鲁棒性。2.2 开发环境与工具链选型STM32开发MCU型号STM32F103C8T6蓝色小板是性价比极高的选择资源足够64KB Flash 20KB RAM 多个串口。开发环境Keil MDK或STM32CubeIDE。对于此项目我推荐STM32CubeMX Keil的组合。CubeMX可以图形化配置引脚、时钟、串口、中断生成初始化代码极大提升效率。固件库使用HAL库。虽然标准库更底层、代码量小但HAL库的抽象程度更高跨STM32系列移植更方便且CubeMX直接支持适合快速开发和初学者。QT开发QT版本推荐QT 5.15 LTS或QT 6.2。LTS版本长期支持更稳定。注意选择带有MSVC编译器套件的版本如果使用Windows。开发环境QT Creator是官方IDE与QT框架集成度最高设计界面Qt Designer、编写代码、调试都非常方便。不建议初学者在项目初期使用VSCode配置容易在环境问题上耗费过多时间。数据库使用SQLite。它是一个轻量级的、无需服务器的文件数据库非常适合本地存储商品信息和交易记录。QT通过QSqlDatabase模块提供了对SQLite的完美支持。串口通信使用QT自带的QSerialPort类功能强大无需额外库。3. 下位机STM32核心实现细节下位机是系统的“手和眼”其稳定性和实时性是第一要务。3.1 硬件接口设计与驱动假设我们使用RFID读卡器通过UART通信和OLED显示屏I2C接口。STM32CubeMX配置打开CubeMX选择你的MCU型号。时钟树将系统时钟SYSCLK配置到最大允许频率对于F103C8T6通常是72MHz以获得最佳性能。串口配置一个UART如USART1用于与上位机QT通信参数通常为波特率115200 8位数据位 无校验 1位停止位。再配置一个UART如USART2连接RFID读卡器波特率根据读卡器手册设定常见为9600或115200。I2C配置一个I2C如I2C1用于驱动OLED注意上拉电阻如果模块没有需要在CubeMX中启用内部上拉或外接物理电阻。GPIO配置一个GPIO引脚驱动蜂鸣器配置几个按键引脚如有为输入模式并启用上拉。生成代码在Project Manager选项卡设置好项目名称、路径、IDEMDK-ARM然后生成代码。RFID数据读取 RFID模块通常会自动读取卡片并输出一串卡号数据。我们需要在STM32中为USART2启用串口空闲中断。// 在main.c的初始化部分开启串口和空闲中断 HAL_UART_Receive_DMA(huart2, rx_buffer, BUFFER_SIZE); // 使用DMA接收 __HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE); // 使能空闲中断在stm32f1xx_it.c的中断服务函数中处理空闲中断void USART2_IRQHandler(void) { if(__HAL_UART_GET_FLAG(huart2, UART_FLAG_IDLE) ! RESET) { __HAL_UART_CLEAR_IDLEFLAG(huart2); HAL_UART_DMAStop(huart2); // 计算本次接收到的数据长度 uint16_t len BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart2.hdmarx); // 处理rx_buffer中的数据解析出卡号 process_rfid_data(rx_buffer, len); // 重新启动DMA接收 HAL_UART_Receive_DMA(huart2, rx_buffer, BUFFER_SIZE); } HAL_UART_IRQHandler(huart2); }process_rfid_data函数负责将接收到的字节数组可能是ASCII码转换成可用于查询的卡号字符串或整数。这里有个坑RFID数据可能包含非打印字符如帧头帧尾需要根据其通信协议手册仔细解析。OLED显示驱动 使用成熟的OLED驱动库如SSD1306的驱动。将.c/.h文件加入工程。在代码中初始化后可以调用封装好的函数显示文本或图形。OLED_Init(); // 初始化 OLED_ShowString(0, 0, Total:, 16); // 在(0,0)位置显示16号字体的“Total:” OLED_ShowNum(48, 0, total_price, 5, 16); // 显示总价注意事项I2C通信对时序敏感如果OLED无法初始化首先检查硬件连接SDA SCL GND VCC然后检查CubeMX中I2C的配置速度模式 Fast Mode通常没问题最后检查驱动代码中的设备地址0x78或0x7A。3.2 业务逻辑与通信协议实现在process_rfid_data函数解析出商品ID后我们需要查询“价格”。在简单的演示中可以在STM32内维护一个商品数组。但在更真实的场景下商品信息应由上位机管理。这里采用折中方案STM32收到ID后立即打包向上位机查询。定义通信协议帧// 定义帧结构 typedef struct { uint8_t header; // 帧头 0xAA uint8_t cmd; // 命令 0x01代表上报商品ID uint8_t len; // 数据长度 uint8_t data[10]; // 数据 这里存放商品ID字符串 uint8_t checksum; // 校验和 uint8_t footer; // 帧尾 0x55 } UartFrame_t;数据打包与发送函数void send_goods_id_to_pc(const char* goods_id) { UartFrame_t frame; frame.header 0xAA; frame.cmd 0x01; frame.len strlen(goods_id); strcpy((char*)frame.data, goods_id); // 计算校验和简单累加和示例 frame.checksum 0; frame.checksum frame.cmd; frame.checksum frame.len; for(int i0; iframe.len; i) { frame.checksum frame.data[i]; } frame.footer 0x55; // 通过USART1发送整个结构体 HAL_UART_Transmit(huart1, (uint8_t*)frame, sizeof(frame.header)sizeof(frame.cmd)sizeof(frame.len)frame.lensizeof(frame.checksum)sizeof(frame.footer), 1000); }接收上位机指令 同样STM32需要循环或中断接收来自上位机的数据。上位机可能发回商品价格、名称或者发送清空购物车指令cmd0x03。接收解析的逻辑与发送类似需要一个状态机来解析帧。例如在串口接收中断中根据预先定义的协议寻找帧头、计算长度、验证校验和与帧尾最终解析出有效的命令和数据。4. 上位机QT核心实现细节QT部分是本系统的“大脑”其代码组织结构和健壮性至关重要。4.1 项目架构与界面设计创建QT项目在QT Creator中创建Qt Widgets Application。建议采用分层或模块化的文件夹结构SupermarketSystem/ ├── main.cpp ├── SupermarketSystem.pro ├── include/ // 头文件 ├── src/ // 源文件 │ ├── serial/ // 串口通信类 │ ├── database/ // 数据库操作类 │ ├── business/ // 业务逻辑类 │ └── ... ├── forms/ // .ui界面文件 └── resources/ // 图片、图标等资源设计主界面使用QT Designer拖拽控件。主界面至少包含一个QTableWidget或QListView用于实时显示购物清单商品名、单价、数量、小计。多个QLabel用于显示总价、欢迎语。一个QLabel用于显示支付二维码图片。按钮结算、清空购物车、进入管理等。一个QTextBrowser或只读的QTextEdit用于显示系统日志如“已扫码XXX商品”。串口连接状态指示。设计管理界面可以是一个新的对话框QDialog包含对商品信息的增删改查CRUD功能使用QTableWidget展示数据库内容并提供输入框和按钮进行操作。4.2 串口通信模块封装创建一个独立的SerialPortManager类来管理所有串口操作遵循“高内聚低耦合”的原则。// serialportmanager.h #ifndef SERIALPORTMANAGER_H #define SERIALPORTMANAGER_H #include QObject #include QSerialPort #include QSerialPortInfo class SerialPortManager : public QObject { Q_OBJECT public: explicit SerialPortManager(QObject *parent nullptr); ~SerialPortManager(); bool openPort(const QString portName, qint32 baudRate); void closePort(); void sendData(const QByteArray data); QListQSerialPortInfo getAvailablePorts(); signals: void dataReceived(const QByteArray data); // 收到完整一帧数据后发出信号 void portOpened(bool success); void errorOccurred(const QString error); private slots: void onReadyRead(); // 处理串口数据到达 private: QSerialPort *m_serialPort; QByteArray m_buffer; // 用于累积数据 // 协议解析相关变量 bool m_inFrame; int m_expectedLength; // ... 其他状态变量 bool parseFrame(const QByteArray rawData); // 协议解析函数 }; #endif // SERIALPORTMANAGER_H在onReadyRead槽函数中不能假设一次readAll()就能读到完整的一帧。需要将数据追加到m_buffer然后调用parseFrame函数尝试解析。parseFrame函数就是实现我们之前定义的二进制协议的地方寻找0xAA 根据len字段截取数据验证校验和与0x55。解析成功则组装有效数据并通过dataReceived信号发出同时从缓冲区移除已处理的数据解析失败则可能需要清空缓冲区或等待更多数据。实操心得串口通信的稳定性是项目的“生命线”。务必做好超时处理和错误恢复。例如如果超过一定时间没有收到完整的帧应该清空m_buffer防止因某个字节丢失导致后续所有数据错乱。此外所有对界面的更新操作如更新购物车列表必须在主线程中通过信号槽机制触发切忌在串口读取线程中直接操作UI。4.3 数据库模块与业务逻辑数据库初始化// databasemanager.cpp bool DatabaseManager::initDatabase() { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(supermarket.db); // 数据库文件 if (!db.open()) { qDebug() Error: connection with database failed; return false; } QSqlQuery query; // 创建商品表 QString createGoodsTable CREATE TABLE IF NOT EXISTS goods ( id TEXT PRIMARY KEY, // 商品ID 如RFID卡号 name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER NOT NULL); if (!query.exec(createGoodsTable)) { qDebug() Create goods table error: query.lastError(); return false; } // 创建交易记录表 QString createOrderTable CREATE TABLE IF NOT EXISTS orders ( order_id INTEGER PRIMARY KEY AUTOINCREMENT, goods_id TEXT, quantity INTEGER, total_price REAL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP); // ... 执行创建 return true; }业务逻辑处理 在主窗口类中连接SerialPortManager::dataReceived信号到一个槽函数如MainWindow::onSerialDataReceived。void MainWindow::onSerialDataReceived(const QByteArray data) { // 1. 解析数据 得到商品ID QString goodsId parseGoodsIdFromData(data); // 2. 查询数据库 QSqlQuery query; query.prepare(SELECT name, price, stock FROM goods WHERE id ?); query.addBindValue(goodsId); if (query.exec() query.next()) { QString name query.value(0).toString(); double price query.value(1).toDouble(); int stock query.value(2).toInt(); // 3. 检查库存 if (stock 0) { appendLog(商品 [ name ] 库存不足); return; } // 4. 更新购物车模型例如一个QListCartItem addItemToCart(goodsId, name, price, 1); // 数量默认为1 // 5. 更新UI通过信号槽或直接调用 注意线程安全 updateCartTableView(); updateTotalPriceLabel(); // 6. 可选发送确认信息回STM32 包含商品名称和价格用于OLED显示 QByteArray ackData packAckData(goodsId, name, price); m_serialManager-sendData(ackData); // 7. 可选播放提示音通过发送特定指令给STM32控制蜂鸣器 } else { appendLog(未找到商品ID: goodsId); } }4.4 支付模拟与二维码生成支付功能可以高度简化。在用户点击“结算”按钮后计算最终金额。使用第三方库如QR-Code-generator库或qrencode生成一个包含订单金额和随机订单号的二维码图片。// 生成支付二维码 QString orderInfo QString(模拟支付|订单号:%1|金额:%2).arg(generateOrderId()).arg(totalPrice); QImage qrImage generateQRCode(orderInfo); // 调用库函数生成QImage ui-label_qrcode-setPixmap(QPixmap::fromImage(qrImage).scaled(200, 200));启动一个定时器模拟轮询支付结果。在真实的系统中这会是一个网络请求查询支付平台。这里我们可以简单地在定时器超时后随机或根据一个“模拟支付”按钮来决定支付成功与否。支付成功后更新库存、插入交易记录、清空购物车并通过串口通知STM32“交易完成请清屏”。5. 系统联调与核心问题排查软硬件结合的项目联调是最容易出问题也最考验耐心的环节。5.1 联调步骤与技巧分模块独立测试STM32独立测试不接上位机让STM32通过串口调试助手如XCOM发送数据。确保其协议帧格式正确数据内容准确。同时测试RFID刷卡后OLED显示是否正常。QT独立测试不接STM32使用虚拟串口工具如VSPD创建一对虚拟COM口。让QT连接其中一个用串口调试助手连接另一个模拟STM32向QT发送数据测试QT的协议解析、数据库查询、界面更新是否正常。物理连接与基础通信使用USB转TTL模块连接STM32和PC。务必确认TX/RX交叉连接STM32的TX接模块的RX STM32的RX接模块的TX。在QT中扫描可用串口选择正确的端口号。波特率、数据位、停止位、校验位必须与STM32配置完全一致。先测试最简单的“回声”功能STM32发送一个字符串QT收到后原样发回STM32再在OLED上显示。这能最快验证物理链路和基础通信是否通畅。协议联调在QT的接收数据槽函数中将原始字节数组以十六进制形式打印到日志中。对比STM32发送的原始帧检查帧头、长度、数据、校验和、帧尾是否完全匹配。常见问题字节序问题多字节数据、校验和计算方式不一致、长度字段包含自身与否、字符串末尾的\0是否被当作数据发送。5.2 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案QT无法扫描到串口驱动未安装或USB线仅供电1. 检查设备管理器端口列表。2. 安装CH340/CP2102等USB转串口芯片驱动。3. 换一条数据线。串口能打开但收不到数据接线错误波特率不匹配STM32未发送1. 确认TX/RX交叉连接。2. 用示波器或逻辑分析仪检查STM32的TX引脚是否有波形或用另一个串口助手监听。3. 核对双方波特率、数据格式。收到数据但乱码波特率不匹配编码问题1. 确认波特率精确匹配常用115200。2. 检查QT端QSerialPort的编码设置对于二进制协议应使用QByteArray而非QString。数据帧解析不全或错位未处理粘包/拆包协议解析逻辑有误1. 在QT端确保onReadyRead中采用缓冲区累积和状态机解析而非假设一包完整。2. 在STM32端发送一帧后适当延时如几毫秒避免连续发送导致粘包。3. 仔细调试parseFrame函数添加详细日志。数据库操作失败数据库文件路径权限问题SQL语法错误1. 使用绝对路径或确保程序运行目录可写。2. 打开SQLite可视化工具如DB Browser直接执行SQL验证语法。3. 检查QT的SQL驱动是否已包含QT sql。界面卡顿或无响应在非主线程中操作UI耗时操作阻塞事件循环1. 确保所有界面更新操作都在主线程通过信号槽触发。2. 将数据库查询等可能耗时的操作移到子线程QThread或使用异步查询。STM32程序跑飞堆栈溢出中断冲突硬件故障1. 在CubeMX中适当调大堆栈大小。2. 检查中断优先级配置避免在中断中执行耗时操作。3. 简化程序逐步添加功能定位问题。5.3 性能优化与扩展思考当基础功能跑通后可以考虑以下优化和扩展让项目更出彩多线程优化将串口数据读取、数据库查询特别是复杂查询、网络请求如果接入真实支付放入独立的工作线程防止阻塞GUI主线程保持界面流畅。数据持久化与同步商品信息变更后如何同步到STM32可以设计一个“同步”按钮点击后将最新的商品简表ID和价格下发到STM32STM32将其存入自身的Flash或EEPROM中。这样即使短暂与上位机断开也能进行离线计价。网络化扩展将QT程序升级为C/S架构。QT作为客户端连接到一个中心服务器可以用Python Flask/ Django或C编写。服务器统一管理所有无人售货机的商品、库存和交易数据。QT客户端通过HTTP或WebSocket与服务器通信。引入更多硬件增加重量传感器模拟自动称重计价增加人脸识别模块模拟会员登录增加温湿度传感器监控环境等。改善用户体验在QT界面添加动画效果如商品加入购物车的飞入动画添加语音提示利用QT的文本转语音或播放音频文件。这个项目就像一棵技能树的主干掌握了它那些枝叶——更复杂的通信协议如MQTT、更美观的界面QML、更强大的后端云服务器——就有了生长的根基。调试过程虽然可能充满挑战但每一次问题的解决都是对“系统思维”和“调试能力”的扎实锤炼。希望这份超详细的拆解能帮你少走弯路更顺畅地完成这个精彩的项目。本文还有配套的精品资源点击获取