STM32+RFID实战:从零搭建一个带Python上位机的智能仓库管理系统

在嵌入式系统开发领域,能够将硬件感知、数据处理和软件交互完整串联起来的项目往往最能体现工程师的系统思维。对于电子工程、计算机科学等相关专业的学生而言,一个融合了单片机编程、无线通信和上位机开发的综合性项目不仅能巩固多门课程知识,更能为毕业设计或求职作品集增添亮点。本文将详细介绍如何基于STM32微控制器和RFID技术,配合Python开发的图形界面,构建一个完整的智能仓库管理系统原型。

1. 系统架构设计与硬件选型

任何物联网系统的开发都需要从整体架构入手。我们的智能仓库管理系统采用三层设计:感知层、传输层和应用层。感知层由STM32F103C8T6(俗称"蓝莓板")作为主控制器,搭配RC522 RFID读卡模块实现货物识别;传输层使用ESP-01S WiFi模块建立硬件与上位机之间的桥梁;应用层则是运行在电脑上的Python GUI程序,负责数据可视化和用户交互。

硬件选型考量因素

  • 主控芯片 :STM32F103C8T6拥有72MHz主频、64KB Flash和20KB RAM,完全满足RFID数据处理和通信需求,且性价比极高
  • RFID模块 :RC522支持ISO14443A协议,最大识别距离约5cm,适合仓库货架场景
  • 无线模块 :ESP-01S体积小巧,支持802.11 b/g/n协议,可通过AT指令配置

提示:初学者建议购买已经集成USB转串口的STM32开发板,这将大大简化调试过程。常见的"蓝莓板"市场价格约25-35元。

硬件连接示意图如下:

+-------------------+     +-------------------+     +-------------------+
|    STM32F103C8T6  |     |      RC522        |     |     ESP-01S       |
|                   |<--->|                   |<--->|                   |
| PA4   - CS        |     | SDA  - PA7        |     | TX   - PA10       |
| PA5   - SCK       |     | SCK  - PA5        |     | RX   - PA9        |
| PA6   - MISO      |     | MOSI - PA6        |     | RST  - PA0        |
| PA7   - MOSI      |     | IRQ  - NC         |     | CH_PD- 3.3V       |
| PA9   - USART1_TX |     | GND  - GND        |     | VCC  - 3.3V       |
| PA10  - USART1_RX |     | 3.3V - 3.3V       |     | GND  - GND        |
+-------------------+     +-------------------+     +-------------------+

2. STM32下位机开发实战

下位机程序需要完成三项核心任务:RFID标签读取、数据处理封装和无线数据传输。我们使用STM32CubeIDE进行开发,充分利用HAL库提高开发效率。

2.1 RFID驱动实现

RC522模块通过SPI接口与STM32通信。首先在CubeMX中配置SPI1:

  1. 启用SPI1全双工主模式
  2. 配置PA5为SCK,PA6为MISO,PA7为MOSI
  3. 设置预分频器使SPI时钟不超过10MHz(RC522限制)
  4. 将PA4设置为GPIO输出作为片选信号

RFID读取的核心代码如下:

// 读取RC522寄存器
uint8_t RFID_ReadReg(uint8_t reg) {
    uint8_t value;
    HAL_GPIO_WritePin(RFID_CS_GPIO_Port, RFID_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(&hspi1, &reg, &value, 1, 100);
    HAL_GPIO_WritePin(RFID_CS_GPIO_Port, RFID_CS_Pin, GPIO_PIN_SET);
    return value;
}

// 检测新卡片
uint8_t RFID_Check(uint8_t* id) {
    if(RFID_ReadReg(0x02) & 0x08) {
        RFID_Request(0x26, id);
        RFID_Anticoll(id);
        return 1;
    }
    return 0;
}

2.2 WiFi通信协议设计

ESP-01S模块通过AT指令集进行控制。我们需要设计一个简单的应用层协议来传输RFID数据:

[HEAD][LEN][CMD][DATA][CRC]
  • HEAD:固定为0xAA 0x55
  • LEN:数据长度(不包括头和长度本身)
  • CMD:命令字(0x01表示RFID数据)
  • DATA:实际数据(RFID UID等)
  • CRC:CRC8校验

示例传输函数:

void WiFi_SendData(uint8_t cmd, uint8_t* data, uint8_t len) {
    uint8_t buf[64];
    uint8_t crc = 0;
    
    buf[0] = 0xAA;  // HEAD
    buf[1] = 0x55;
    buf[2] = len + 1;  // LEN (CMD included)
    buf[3] = cmd;      // CMD
    
    memcpy(&buf[4], data, len);
    
    for(int i=2; i<4+len; i++) {
        crc ^= buf[i];
    }
    buf[4+len] = crc;
    
    HAL_UART_Transmit(&huart1, buf, 5+len, 100);
}

3. Python上位机开发

上位机程序采用Python 3.x开发,主要依赖以下库:

  • tkinter :GUI界面
  • pyserial :串口通信
  • sqlite3 :本地数据库
  • matplotlib :数据可视化(可选)

3.1 数据库设计

创建SQLite数据库存储货物信息:

import sqlite3

def init_database():
    conn = sqlite3.connect('warehouse.db')
    c = conn.cursor()
    
    # 货物表
    c.execute('''CREATE TABLE IF NOT EXISTS items
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  rfid_uid TEXT UNIQUE,
                  name TEXT,
                  category TEXT,
                  quantity INTEGER,
                  location TEXT,
                  last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
    
    # 出入库记录表
    c.execute('''CREATE TABLE IF NOT EXISTS records
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  item_id INTEGER,
                  operation TEXT,  # 'IN' or 'OUT'
                  quantity INTEGER,
                  timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                  FOREIGN KEY(item_id) REFERENCES items(id))''')
    
    conn.commit()
    conn.close()

3.2 串口通信实现

创建一个独立的线程处理串口数据:

import serial
import threading
from queue import Queue

class SerialHandler:
    def __init__(self, port, baudrate=115200):
        self.ser = serial.Serial(port, baudrate, timeout=1)
        self.queue = Queue()
        self.running = True
        self.thread = threading.Thread(target=self._read_serial)
        self.thread.start()
    
    def _read_serial(self):
        buffer = bytearray()
        while self.running:
            if self.ser.in_waiting > 0:
                data = self.ser.read(self.ser.in_waiting)
                buffer.extend(data)
                
                while len(buffer) >= 5:  # 最小包长度
                    # 查找包头
                    try:
                        idx = buffer.index(0xAA)
                        if idx+1 < len(buffer) and buffer[idx+1] == 0x55:
                            # 找到有效包头
                            if len(buffer) >= idx+2+buffer[idx+2]:
                                # 完整包已接收
                                pkt_len = buffer[idx+2]
                                pkt = buffer[idx:idx+3+pkt_len]
                                if self._check_crc(pkt):
                                    self.queue.put(pkt)
                                buffer = buffer[idx+3+pkt_len:]
                            else:
                                break  # 等待更多数据
                        else:
                            buffer = buffer[idx+1:]  # 跳过无效数据
                    except ValueError:
                        buffer.clear()  # 没有找到包头
    
    def _check_crc(self, pkt):
        crc = 0
        for b in pkt[2:-1]:
            crc ^= b
        return crc == pkt[-1]
    
    def get_packet(self):
        return self.queue.get() if not self.queue.empty() else None
    
    def close(self):
        self.running = False
        self.thread.join()
        self.ser.close()

4. 系统集成与调试技巧

将各个模块整合时,调试是关键环节。以下是几个实用技巧:

硬件调试步骤

  1. 先单独测试RFID模块:使用STM32读取标签UID并打印到串口
  2. 单独测试WiFi模块:通过AT指令确保能连接路由器
  3. 组合测试:确保STM32能正确通过WiFi发送数据

常见问题排查表

现象 可能原因 解决方案
RFID不读卡 电源不稳定 确保使用3.3V稳压电源,必要时加100μF电容
WiFi连接失败 AT指令超时 检查波特率(通常115200),确保发送回车换行
数据包错误 缓冲区溢出 增加数据包头尾校验,上位机添加超时重发机制
GUI界面卡顿 数据库操作阻塞 将数据库操作放入单独线程,使用队列通信

性能优化建议

  • RFID轮询间隔设置为200-300ms,避免过频读取
  • WiFi数据包尽量压缩,例如将RFID UID转为16进制字符串传输
  • 上位机采用增量更新策略,只刷新变化的数据显示区域

在完成基础功能后,可以考虑添加以下增强功能:

  1. 多语言支持 :使用gettext模块实现国际化
  2. 数据备份 :定期将SQLite数据库导出为CSV或Excel
  3. 用户权限 :不同操作员设置不同权限级别
  4. 报表生成 :使用reportlab库生成PDF格式的库存报表

实际开发中,我发现在STM32和Python之间采用JSON格式传输数据虽然会增加一些开销,但大大简化了协议解析的复杂度,特别是在需要传输多种类型数据时。例如:

// STM32端简化的JSON生成
void send_as_json(uint8_t* uid, int16_t temperature) {
    char buffer[128];
    sprintf(buffer, "{\"uid\":\"%02X%02X%02X%02X\",\"temp\":%d}", 
            uid[0], uid[1], uid[2], uid[3], temperature);
    WiFi_SendData(0x05, (uint8_t*)buffer, strlen(buffer));
}

对应的Python端可以直接使用json模块解析:

import json

data = ser_handler.get_packet()
if data:
    try:
        item = json.loads(data.decode('ascii'))
        update_gui(item)
    except json.JSONDecodeError:
        print("Invalid JSON data")

更多推荐