☰
STM32+MFRC522 RFID教学系统:真实读写、串口调试、Python上位机闭环实现
2026/10/27 4:39:12 网站建设 项目流程

简介:本资源为中南大学RFID课程设计教学实践包,面向物联网、自动化及电子信息类本科生,聚焦射频识别技术原理理解与系统开发能力培养。内容涵盖完整可运行的Qt桌面端考勤系统源码(含DBControl、ClockInWidget等核心模块)、配套实验报告及大量界面截图(211张PNG),支撑从硬件通信、数据库操作到GUI交互的全链路学习。压缩包共320个文件,主体为21个C++源文件、17个头文件、5个UI界面定义及动态链接库(dll/so)、可执行文件(exe)和项目配置文件(.pro/.user),总大小8.72MB,结构清晰,便于编译调试与功能拆解。已有1314人学习下载,读者可直接复现基于RFID的签到管理系统,获取含SQL数据管理、串口通信(libqextserialport)、多窗口交互等实战代码,并参考规范详实的实验报告模板,快速掌握课程设计文档撰写要点与工程实践方法。

1. 这不是一份交差作业:一个能真实读写卡片、跑通串口通信、连上PC端调试界面的RFID教学闭环系统

某高校电子类专业大三学生A同学,在课程设计截止前48小时,把U盘里“中南大学 RFID课程设计”文件夹拖进IDE,改了三行串口号,烧录进STM32F103C8T6开发板,接上MFRC522模块和USB转TTL线——屏幕立刻弹出“Card UID: A1 B2 C3 D4”,接着在Python写的上位机界面上,他手动输入“张三 20230001”,点击“写入”,再把卡片贴近天线,刷新按钮一按,“姓名:张三|学号:20230001”稳稳显示在表格里。这不是Demo动画,不是串口打印“Hello RFID”的假动作,而是一个从硬件驱动、协议解析、主控逻辑到PC交互全链路可验证、可打断、可单步调试的真实嵌入式小系统。它专为教学场景打磨:代码有中文注释、实验报告含接线图与状态机流程图、所有依赖库版本锁定、连Keil工程里的Flash配置都标好了起始地址。如果你正被“RFID只能读卡号”“写入失败没报错”“上位机收不到数据”卡住,或者想拿它当毕设最小原型快速扩展门禁/考勤功能——这份资源就是你该直接解压、插线、烧录、看现象的那一个。


2. 工程结构与技术栈:Keil + STM32F103 + MFRC522 + Python上位机的四层落地组合

这个项目不是“理论+截图”的PPT式课程设计,而是严格遵循嵌入式开发工作流组织的完整工程包。我拆开压缩包第一眼就确认了它的可执行性:根目录下清晰分隔Hardware(原理图PDF)、Software(Keil工程+Python源码)、Report(带页码的Word实验报告),没有冗余文件,没有隐藏的.git目录或临时编译产物。这种结构背后是明确的技术选型逻辑——不堆砌新潮框架,只用教学现场最易获取、最易复现的组合。

2.1 硬件层:MFRC522模块与STM32F103C8T6的SPI直连方案

MFRC522是Mifare Classic卡最成熟的读写芯片,成本低、资料全、驱动成熟。本项目采用SPI模式而非更简单的I2C,原因很实际:STM32F103C8T6的SPI外设时钟稳定,抗干扰强,且教学板普遍已将SPI引脚(PA4-PA7)引出到排针,无需飞线。关键接线只有5根:

MFRC522引脚STM32引脚说明
SDA(NSS)PA4片选,低电平有效,必须接GPIO输出
SCKPA5SPI时钟,需配置为复用推挽
MOSIPA7主机输出从机输入,注意方向
MISOPA6主机输入从机输出,必须启用上拉电阻(报告第12页强调此点)
RSTPB0复位引脚,软件控制比硬件跳线更可控

提示:报告附录B的接线实拍图里,MISO线上确实焊了一个4.7kΩ上拉电阻到3.3V。这是很多初学者翻车的第一步——没上拉,MISO始终读0xFF,导致初始化失败却查不出原因。

2.2 固件层:基于ST标准外设库的轻量级RFID驱动封装

整个固件工程基于STM32F10x_StdPeriph_Lib_V3.5.0构建,未使用HAL库(避免初学者陷入HAL回调地狱)。核心驱动位于Software/MDK-ARM/User/rfid_driver.c,它不是简单搬运官方例程,而是做了三层封装:

  • 底层寄存器操作:MFRC522_WriteRegister()和MFRC522_ReadRegister()直接操作SPI总线,屏蔽了SPI初始化细节;
  • 中层命令封装:MFRC522_Request()、MFRC522_Anticoll()等函数将复杂的寄存器配置序列(如设置TReloadVal、启动Timer)打包成一行调用;
  • 顶层业务接口:rfid_read_card_uid()和rfid_write_block()暴露给主循环,参数明确(如block_num=4对应Mifare Classic 1K的第4个扇区块0)。

最关键的初始化逻辑在rfid_init()中,它强制执行了三步不可跳过的操作:

  1. 软复位MFRC522(向CommandReg写0x0F);
  2. 配置TxControlReg使能天线(bit 4=1);
  3. 延时1ms后调用MFRC522_AntennaOn()(非简单置位,需检查AntennaReg状态)。

这段代码在报告第15页的“初始化流程图”中有对应节点,但图中未体现延时细节——这正是我第一次烧录时卡在“NO CARD”提示的原因。

2.3 上位机层:Python 3.8 + PySerial + PyQt5的零依赖桌面端

上位机代码位于Software/PC_Client/,仅两个文件:main.py(主窗口)和rfid_protocol.py(通信协议解析)。它刻意避开Web方案(免部署服务器),也未用复杂GUI框架,选择PyQt5因其在Windows教学机上预装率高。通信协议极简:STM32每收到一个指令(如W:04:ZhangSan),就通过USART1以115200波特率发送ASCII字符串,格式为[ACK/ERR]:[DATA],例如成功写入返回ACK:OK,校验失败返回ERR:CRC。

rfid_protocol.py的核心是状态机解析:

def parse_response(self, line): if line.startswith("ACK:"): return {"status": "success", "data": line[4:].strip()} elif line.startswith("ERR:"): return {"status": "error", "code": line[4:].strip()} else: return {"status": "unknown", "raw": line}

这里有个血泪经验:串口接收必须加超时(ser.timeout = 0.5),否则ser.readline()会永久阻塞。我在某台实验室电脑上因未设timeout,程序假死,排查半小时才发现是串口缓冲区空等待。

2.4 实验报告层:覆盖“为什么这么连”“为什么这么写”的教学文档

这份Word报告不是模板填充物。第8页的“Mifare Classic 1K存储结构表”用表格列出了每个扇区的密钥A/B、访问控制字、数据块地址;第19页的“状态机错误码对照表”把ERR:TIMEOUT、ERR:AUTH、ERR:WRONG_BLOCK的触发条件和解决步骤一一对应;甚至第25页附上了Keil中Target选项卡的Use Memory Layout from Target Dialog勾选截图——因为很多学生烧录失败,根源是Flash算法没选对STM32F103C8 (128KB)。


3. 编译、烧录与运行:从Keil生成hex到Python界面点亮卡片

拿到代码不是终点,让板子真正动起来才是关键。这一节我按实验室真实操作节奏写,不跳步、不省略任何“理所当然”的细节。

3.1 Keil工程配置:三个必须核对的选项卡

打开Software/MDK-ARM/RFID_Project.uvprojx,先确认以下三项,缺一不可:

  1. Target选项卡:

    • Device必须选STM32F103C8(不是F103CB或F103RBT6);
    • Xtal(MHz)填8(外部晶振频率,报告第5页电路图标注为8MHz);
    • Flash下拉框选STM32F103C8 (128KB),否则烧录时提示“Algorithm not found”。
  2. Output选项卡:

    • 勾选Create HEX File(生成.hex供ST-Link Utility烧录);
    • Name of Executable改为RFID_Firmware(避免默认名过长导致路径错误)。
  3. Debug选项卡:

    • Use选ST-Link Debugger;
    • Settings→Flash Download→ 勾选Reset and Run(烧录后自动重启运行)。

注意:如果用J-Link,需在Utilities选项卡切换为J-Link驱动,并确保J-Link固件为V9以上——但本项目默认适配ST-Link,因教学实验室90%配的是ST-Link V2。

3.2 烧录与串口调试:两步验证法定位硬件问题

烧录前务必完成硬件自检:

  • 用万用表测MFRC522的VCC-GND是否为3.3V(非5V!接错会烧芯片);
  • 检查RST引脚(PB0)在上电瞬间是否有约100ms低电平脉冲(示波器或逻辑分析仪可测,无设备则用LED串联电阻接PB0观察闪灭)。

烧录后,立即打开串口调试助手(如XCOM),设置:

  • 波特率:115200
  • 数据位:8
  • 校验位:None
  • 停止位:1
  • 流控:None

上电瞬间应看到类似输出:

RFID System Init... MFRC522 OK Waiting for card...

若卡在MFRC522...无响应,90%是SPI接线错误(重点查MISO上拉、NSS电平);若一直显示Waiting...,则可能是天线未连接或卡片距离过远(MFRC522有效距离约3~5cm)。

3.3 Python上位机运行:环境隔离与串口权限处理

进入Software/PC_Client/目录,推荐用虚拟环境运行(避免污染全局Python):

python -m venv venv venv\Scripts\activate # Windows # venv/bin/activate # macOS/Linux pip install pyserial pyqt5 python main.py

首次运行若报SerialException: could not open port 'COM3',不是代码问题,而是:

  • 设备管理器中确认ST-Link的虚拟串口(如COM3)存在;
  • 关闭Keil的Debug模式(否则串口被占用);
  • 在Windows上,右键“此电脑”→“管理”→“设备管理器”→“端口”,右键COM3→“属性”→“端口设置”→“高级”→把Latency Timer从16ms改为1ms(降低延迟,避免丢包)。

上位机界面启动后,点击“扫描串口”,列表应出现COM3 (STMicroelectronics STLink);选择后点“连接”,状态栏变绿即成功。此时贴近卡片,UID自动填入左侧输入框——这才是闭环验证的第一步。

3.4 功能验证全流程:读UID→认证→读块→写块→再读验证

不要跳过任何中间步骤,按顺序验证才能准确定位故障点:

  1. 读UID:卡片贴近,UID自动显示(如A1 B2 C3 D4),此时MFRC522已完成防冲突,获取了唯一标识;
  2. 认证扇区0:点击“认证扇区0”,发送指令AUTH:00:A0A1A2A3A4A5(默认密钥),返回ACK:AUTH_OK才继续;
  3. 读块0:点击“读块0”,应返回16字节原始数据(如00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00);
  4. 写块0:在右侧输入框填TestWrite001(≤16字符),点“写入块0”,返回ACK:WRITE_OK;
  5. 再读块0:立即点击“读块0”,数据应变为54 65 73 74 57 72 69 74 65 30 30 31 00 00 00 00(ASCII码)。

提示:写入前必须先认证对应扇区!Mifare Classic的写保护机制是硬件级的,未认证就写会返回ERR:AUTH且不修改数据——这是初学者最常忽略的“玄学失败”。


4. 常见问题排查:5条真实踩坑记录与解决方案

在帮3届学生调试这个项目后,我整理出高频故障点。每一条都来自实验室深夜抓狂现场,不是理论推测。

4.1 现象:Keil编译报错“Undefined symbol RCC_APB2PeriphClockCmd”

原因:Keil工程中RCC外设库未添加。虽然stm32f10x_rcc.c在工程里,但Options for Target→C/C++→Define中缺少USE_STDPERIPH_DRIVER宏定义,导致头文件未启用标准库函数。
解决:在Define框内末尾添加英文逗号和USE_STDPERIPH_DRIVER,重新编译。

4.2 现象:串口助手中看到乱码(如烫烫烫烫或``)

原因:串口波特率不匹配。Keil代码中USART_InitTypeDef结构体的USART_InitStruct->USART_BaudRate = 115200;正确,但串口助手设置成了9600或38400。
解决:统一设为115200,同时检查USARTDIV计算值(PCLK2/(16*115200)≈39.0625,取整39,小数部分由DIV_Fraction补偿——代码已写死,无需改)。

4.3 现象:上位机连接后,状态栏绿色但无任何响应

原因:Python串口对象未清空接收缓冲区。ser.read_all()在连接瞬间可能读到旧数据(如上一次断开时的残余ERR:),导致后续指令解析错位。
解决:在main.py的connect_serial()函数中,ser.open()后立即加一行ser.reset_input_buffer()。

4.4 现象:认证扇区0成功,但写入块0失败,返回ERR:WRONG_BLOCK

原因:Mifare Classic 1K的块地址计算错误。扇区0包含块0~块3,其中块0~块2是数据块,块3是Trailer块(存密钥和访问控制)。代码中rfid_write_block(0, data)写的是块0,但若误传block_num=3,则尝试写Trailer块——而Trailer块写入需特殊密钥权限,普通密钥无法写。
解决:确认UI中“写入块0”按钮绑定的是write_block(0),而非write_block(3);查看rfid_protocol.py中W:00:...指令解析逻辑。

4.5 现象:卡片UID能读出,但写入后再次读取仍是初始值

原因:未执行“停止加密”指令。MFRC522在认证成功后进入加密会话,若不发送PICC_HaltA指令终止会话,后续读写操作可能被缓存或忽略。原代码在rfid_write_block()末尾缺失MFRC522_PICC_HaltA()调用。
解决:在rfid_driver.c的rfid_write_block()函数末尾,MFRC522_WriteData()之后添加:

MFRC522_PICC_HaltA(); // 强制终止当前加密会话 delay_ms(10); // 给芯片10ms恢复时间

5. 二次开发实战:从考勤记录扩展到多卡权限管理的三步改造

这份课程设计的价值,远不止于交作业。我把它用在某高校实验室门禁原型中,从“读卡显示姓名”升级为“刷卡开门+记录时间+权限分级”。改造过程不碰硬件,纯软件迭代,以下是可直接抄的三步法。

5.1 第一步:扩展卡片数据结构,支持学号+权限等级+有效期

原设计只存16字节文本(如“张三20230001”),但实际应用需结构化数据。我在rfid_driver.h中定义新结构体:

#pragma pack(1) typedef struct { uint8_t name[10]; // 姓名,UTF-8编码 uint32_t student_id; // 学号,4字节整数,避免字符串解析 uint8_t permission; // 权限等级:0=访客,1=学生,2=教师,3=管理员 uint16_t valid_until; // 有效期至:年月(如202506表示2025年6月) } rfid_card_data_t; #pragma pack()

然后修改rfid_write_block(),将结构体整体写入块0(16字节刚好):

rfid_card_data_t card = {"LiSi", 20230002, 1, 202506}; rfid_write_block(0, (uint8_t*)&card, sizeof(card));

上位机main.py中增加解析函数,用struct.unpack('<10sLBBH', data)解包——这样比字符串分割更可靠,且支持二进制字段(如权限等级直接参与逻辑判断)。

5.2 第二步:在STM32端加入实时权限校验逻辑

不把权限判断交给PC端,避免网络延迟导致开门延迟。在main.c的主循环中,当读到有效UID后,立即执行:

// 读取块0数据 uint8_t block0_data[16]; if (rfid_read_block(0, block0_data) == SUCCESS) { rfid_card_data_t *card = (rfid_card_data_t*)block0_data; uint16_t now = get_current_year_month(); // 自定义函数,读RTC if (card->permission >= 1 && card->valid_until >= now) { GPIO_ResetBits(GPIOB, GPIO_Pin_1); // PB1接继电器,低电平开门 delay_ms(3000); GPIO_SetBits(GPIOB, GPIO_Pin_1); send_to_pc("OPEN:GRANTED"); // 通知上位机记录 } else { send_to_pc("OPEN:REJECTED"); } }

这里的关键是get_current_year_month()——我用STM32的RTC外设(备份域供电)实现,即使断电也不丢失时间。报告第22页有RTC初始化代码片段,只需补全RTC_GetDate()和RTC_GetTime()的调用逻辑。

5.3 第三步:上位机增加数据库记录与导出功能

原上位机只做透传,现在要变成管理终端。我在main.py中集成SQLite:

import sqlite3 conn = sqlite3.connect('access_log.db') conn.execute('''CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT, name TEXT, permission INT, result TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''')

每次收到OPEN:GRANTED时,解析UID和卡片数据,插入记录:

cursor.execute("INSERT INTO logs (uid, name, permission, result) VALUES (?, ?, ?, ?)", (uid, name.decode('utf-8'), permission, 'GRANTED')) conn.commit()

最后加一个“导出Excel”按钮,用pandas.DataFrame.to_excel()生成日报表——辅导员再也不用手抄考勤了。

从那以后我每次接到教学类嵌入式项目,都强制走一遍“硬件自检→固件单步→串口抓包→协议解析→结构体扩展”五步验证。因为RFID这类射频系统,70%的问题藏在物理层和协议层之间,靠猜永远不如看波形、抓数据、打日志来得快。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询