
简介一套基于STM32微控制器和射频识别技术的停车场管理系统完整工程包主要面向电子、计算机类毕业设计或课程作业人群也适合希望入门嵌入式开发的读者参考。压缩包内共240个文件整体大小约8.33兆字节文件构成以C语言源文件、头文件为主同时包含编译生成的目标文件与中间文件、Keil工程配置和烧录文件既有完整项目源码也能直接打开工程进行编译调试。目前已有239人学习下载。项目内容覆盖微控制器常用外设的配置与应用、射频识别模块的通信协议处理以及停车场车辆进出控制逻辑和电路连接方案通过研读源码与工程结构可以掌握从硬件初始化到功能联调的实际流程也能学到嵌入式软件的分层组织与常见排错思路对于完成类似智能系统设计有直接借鉴价值。尤其适合需要快速搭建原型、理解整套软硬件协作流程的学生参考。1. 为什么基于 STM32 的 RFID 停车场是毕设里最划算的题目很多毕设题目的问题不是技术难而是“演示效果”难。你写了三个月算法机器只在屏幕上打一行字而基于 STM32 的 RFID 停车场题目天然包含“读卡-判断-开闸-显示”四个可见动作评委一眼就能看懂。RFID 负责拿卡号STM32 负责把卡号变成进出场的业务决策模型道闸负责把结果做成物理动作。它处在“看起来复杂”和“实际可完成”的黄金位置用到 SPI、定时器、串口、I2C、GPIO几乎覆盖 STM32 入门到中级的所有知识点但没有一样是学不会的。对 5 年以上工程师来说这套题目也值得重新做一次你会认真处理状态机、掉电存储和通讯可靠性而不是只抄一套读卡代码就跑。2. 从硬件选型到数据流搭建 STM32 RFID 停车场的最小系统2.1 读卡器选型为什么默认用 13.56MHz 的 MFRC522读卡方案决定了整个项目能讲多深。125kHz 的 ID 卡模块更便宜但它只返回一个固定序列号没有分区、没有加密做停车场道闸“能用”但在论文里很难延伸出“月卡/临时卡/黑名单”这些内容。MFRC522 工作在 13.56MHz支持 ISO14443A 协议可以读 Mifare Classic S50 这类常见卡除了 UID 还支持扇区读写。毕设演示时你可以现场往扇区里写卡号、余额、进出次数这套东西比单纯“读到卡号开闸”有信息量得多。读卡方案工作频率接口读卡距离成本适合场景MFRC52213.56MHzSPI3~6cm低门禁、停车场、图书借还PN53213.56MHzI2C/SPI/HSU5~7cm中高NFC 卡读写、手机模拟RDM6300125kHzUART5~8cm低低频 ID 卡仅读序号我一般直接选 MFRC522。如果预算敏感国产兼容芯片 FM17550 可以 pin-to-pin 替换寄存器基本一致现有驱动不用重写。卡片尽量用标准的 Mifare Classic S50也就是市面上最常见的门禁卡替换成本低不要买那种带塑料壳的异形卡读卡距离会明显变短。2.2 引脚分配与供电RC522 和 STM32F103C8T6 怎么接主控选 STM32F103C8T6 是最省心的方案72MHz 主频、20KB RAM、64KB Flash跑一个 10 车位的白名单查表绰绰有余。RC522 模块通常引出 8 个端子我习惯使用硬件 SPI1片选 CS 用普通 GPIO 控制这样 GPIO 最省速度也稳定。RC522 端子信号STM32F103 引脚说明SDA/CS片选PA4软件控制低电平有效SCKSPI 时钟PA5SPI1_SCKMOSI主出从入PA7SPI1_MOSIMISO主入从出PA6SPI1_MISORST复位PB1上电拉高复位芯片IRQ中断请求不接默认轮询第 5 章再讲中断接法VCC电源3.3V严禁接 5VGND地GND与 STM32 共地最容易烧板子的错误是 RC522 的 VCC 接 5V。很多模块板载稳压但 SDA/CS 一端的耐压并不高长期 5V 供电会出现“偶尔能读卡、换张卡就死机”的随机问题严重时芯片直接发烫。舵机或继电器必须单独供 5V/12V电源并且把电源的 GND 接到 STM32 的 GND也就是“共地”。否则 STM32 发过来的控制信号和舵机电源之间没有公共参考点开闸动作会变得非常不可靠。2.3 数据流一张卡从进场到出场经过哪些环节停车场逻辑不是简单的“读到卡就开闸”。设计上我先画数据流卡片贴近读卡器MFRC522 天线为卡片供电。卡片返回 ATQARC522 完成防冲突取回 4 字节 UID。STM32 通过 SPI 读到 UID换算成uint32_t卡号。在内存维护的在场表里查这张卡。表里没有且卡在授权白名单内则记录进场时间置状态为“在场”开闸。表里已有且状态为“在场”则算出出场时间与费用清空表项开闸放行。这张状态表就是整个项目的心脏。难点在于“同一条读卡事件”如何判定它是进场还是出场。我的做法是每个卡号只在表里存在一条记录记录它的进场时间而不是单独记录“进/出”两次事件。这样同一张卡不会同时出现在进场表和出场表里计费时直接拿当前时间减进场时间逻辑最简单答辩时也最好讲。2.4 为什么系统里必须放进一块非易失存储车辆进场登记后如果断电再上电绝不能把在场车辆“忘掉”否则出场时费用就乱了。常见方案有三个STM32 内部 Flash、I2C 接口的 AT24C02、RTC 后备存储。毕设强度下我推荐 AT24C02按字节读写没有块擦除概念代码量小。2Kbit 容量存 10 条在场记录加白名单足够。I2C 时序通过逻辑分析仪看得非常直观排错成本低。内部 Flash 可以留给配置参数比如收费标准、黑名单等。第 4 章我会给一套直接能用的 EEPROM 读写封装和校验方法。3. 用 STM32 SPI 驱动 MFRC522读卡号的最小代码3.1 先在 CubeMX 里把 SPI 引脚配对打开 STM32CubeMX选 STM32F103C8T6把 PA5、PA6、PA7 分别配置为 SPI1_SCK、SPI1_MISO、SPI1_MOSIPA4 和 PB1 配置为 GPIO_Output生成工程时选择 HAL 库或标准库都可以。SPI 参数照着下面的表填这里最影响后面稳定性的是时钟极性和时钟频率。参数值原因ModeFull-Duplex MasterSTM32 主动发起通讯Data Size8 BitsRC522 寄存器是 8 位Clock PolarityLowSPI Mode 0Clock Phase1 EdgeRC522 默认 Mode 0Prescaler32 分频72MHz/322.25MHz稳定优先NSSSoftware片选用 GPIO 手动拉低如果你还在用 Keil MDK 写 STM32注意安装 F1 系列芯片支持包新建工程时不要和 C51 的工程模板混在一起否则会出现“编译通过但下载后不运行”的老问题。工程模板这件事解决掉后面的代码就可以专心放在 RC522 上。3.2 RC522 寄存器读写一切操作的入口MFRC522 的 SPI 地址不是直接写寄存器号。地址字节的最高位是读写标志位其余位是寄存器号左移后的结果。我封装了两个底层函数后续所有寻卡、防冲突、选卡操作都建立在它们上面static uint8_t rc522_spi_io(uint8_t tx) { uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, 10); return rx; } void RC522_WriteReg(uint8_t reg, uint8_t value) { RC522_CS_LOW(); rc522_spi_io((reg 1) 0x7E); /* 写地址读写标志置0 */ rc522_spi_io(value); /* 写入一个字节 */ RC522_CS_HIGH(); } uint8_t RC522_ReadReg(uint8_t reg) { uint8_t val; RC522_CS_LOW(); rc522_spi_io(((reg 1) 0x7E) | 0x80); /* 读地址读写标志置1 */ val rc522_spi_io(0x00); /* 发0x00只是给时钟返回值为寄存器内容 */ RC522_CS_HIGH(); return val; }逻辑说明reg 1是因为 RC522 的 SPI 帧要求寄存器号占 7 位最低位留给读写标志。0x7E把最低位清掉表示写操作再|0x80把最高位置成 1表示读操作。rc522_spi_io是硬件 SPI 的字节收发发送和接收同时完成所以读寄存器时发送的 0x00 并不重要真正的数据在返回值里。这套函数是整个 RC522 驱动的基石。不要在业务代码里直接操作 FIFO 和命令寄存器业务层只调封装好的 API后面维护起来会舒服很多。3.3 寻卡、防冲突、选卡一路跑通才能拿到 UIDISO14443A 的读卡流程分三步REQA发送寻卡命令 0x26卡片返回 ATQA。Anticollision发送防冲突命令 0x93 和 NVB 0x20卡片返回 4 字节 UID 和 1 字节 BCC 校验。Select再次发送 0x93 并按格式拼接 UID卡片返回 SAK选卡才算完成。我把这三步封成三个函数uint8_t RC522_Request(uint8_t req_mode, uint8_t *atqa); uint8_t RC522_Anticoll(uint8_t *uid); uint8_t RC522_Select(uint8_t *uid);在业务里的调用方式如下uint8_t atqa[2]; uint8_t uid[4]; if (RC522_Request(PICC_REQIDL, atqa) MI_OK) { if (RC522_Anticoll(uid) MI_OK) { RC522_Select(uid); /* 选卡通过后才能访问扇区 */ uint32_t card_id ((uint32_t)uid[0] 24) | ((uint32_t)uid[1] 16) | ((uint32_t)uid[2] 8) | uid[3]; printf(UID%08lX\r\n, card_id); } }逻辑说明PICC_REQIDL就是 0x26表示“在空闲状态发出请求”。MI_OK约定为 0函数返回非 0 说明这步没有成功。拿到 4 字节 UID 后转成 32 位整数作为卡号查表、存储都直接比对这个整数比逐字节比较数组快代码也更短。注意一点RC522_Anticoll内部要做清 FIFO、往 FIFO 写命令、启动 Transceive、读回 5 字节这一整套动作。如果只看教程理解不深翻 MFRC522 数据手册里的Anticollision Loop时序图比反复试代码靠谱得多。3.4 读不到卡时先量版本号再找协议调试读卡不要靠玄学。第一步就读取版本寄存器uint8_t RC522_CheckVersion(void) { return RC522_ReadReg(0x37); }上电初始化后打印Version0x%02X正常值一般是 0x92 或 0x91部分兼容芯片返回 0x85。如果读出来是 0x00 或 0xFF说明硬件链路没通后面不要看协议了。下面是我在现场常遇到的现象和对应检查点现象检查点处理方法版本寄存器返回 0x00MISO 没接对确认 PA6 到模块 MISO 通断版本寄存器返回 0xFFMOSI 或 SCK 接反按引脚表重排不要只调 CS能读到版本但一直寻卡失败天线区域有金属把卡片贴到天线线圈中央悬空测试卡号随机跳变SPI 时钟太快Prescaler 调到 64降到 1.125MHz上电后第一次读卡必失败复位后没有延时RST 拉高后加 10ms 再初始化把这些代码整理成独立的rc522.c文件业务层只关心RC522_Request/Anticoll/Select的返回值后续停车场逻辑会干净很多。4. 把读卡变成停车逻辑进出场状态表、计费公式与掉电保存4.1 用一张结构体表管理车辆状态在park.h里定义车辆槽位#define MAX_PARKING 10 typedef struct { uint32_t uid; /* 卡号主键 */ uint8_t status; /* 0在场 1已离场 */ uint32_t in_time; /* 进场时刻 Unix 秒 */ uint32_t out_time; /* 出场时刻 Unix 秒 */ uint32_t fee; /* 费用单位角 */ } ParkSlot; ParkSlot g_slots[MAX_PARKING];逻辑说明uid是从RC522_Anticoll拿到的 4 字节整数一个卡号只在表里存在一条记录。status决定“读卡后是登记进场还是结算出场”。fee用“角”表示避免浮点比较显示时fee/10是元、fee%10是角。out_time出场时写入保留它至少能查最近一次离场时间。维护这个表的核心是查询和释放两个函数ParkSlot *Park_FindByUID(uint32_t uid); void Park_Remove(uint32_t uid);Park_FindByUID遍历数组匹配 uid 且 status 为“在场”找不到就返回 NULL。新车进场则找一个 status 为“已离场”的空槽写入。查询复杂度 O(n)10 个车位不构成任何压力。4.2 进出场判断去抖、防重读和开闸RFID 读卡有一个天然问题卡片贴近天线时会被持续读到RC522 可能在几十毫秒内重复上报同一次刷卡。停车场不能因为一次刷卡就反复开闸。我的做法是加一个“上次事件”缓存static uint32_t last_uid 0; static uint32_t last_time 0; if (uid last_uid (now_sec - last_time) 5) { return; /* 5秒内同一张卡视为重复刷卡 */ } last_uid uid; last_time now_sec;这段代码放在读卡成功之后、进入业务判断之前。now_sec取自 DS3231 的 Unix 秒也可以用 STM32 定时器累加。参数说明5 秒对停车场场景比较合适如果做门禁这个窗口可以缩到 2 秒。开闸动作使用继电器或舵机。舵机需要单独接 5V 电源信号线接 STM32 的 PA10。开闸之后必须有一段时间落杆比如 2 秒后自动关闭演示时才不会出现“杆一直抬着”的尴尬。把开闸时间做成宏#define GATE_OPEN_MS 20004.3 计时与计费DS3231 时间戳套一个公式停车场项目必须记录真实时间掉电重启后不能回到 1970 年。STM32F103 内部 RTC 需要 VBAT 引脚接 3V 电池才能维持但精度有限。毕设里我更喜欢外接 DS3231I2C 通信自带温补晶振备电用 CR2032年误差几十秒答辩时可以强调“高精度实时时钟”。时间方案保持方式精度代码量STM32 内部 RTCVBAT 接 3V 电池一般小DS3231 外部 RTCCR2032高中DS3231 读取时间后转成 Unix 时间戳直接给业务层用uint32_t now_sec DS3231_UnixTime();计费规则定为“进场前 15 分钟免费之后每 30 分钟计费 5 角不足 30 分钟按 30 分钟计算”。用整数运算写成公式static uint32_t Park_CalcFee(uint32_t in, uint32_t out) { uint32_t mins (out - in) / 60; if (mins 15) return 0; uint32_t bill_mins mins - 15; uint32_t units (bill_mins 29) / 30; return units * 50; /* 单位角 */ }逻辑说明(out - in) / 60先算出相差分钟数整数除法直接截掉秒数符合“按整分钟计费”的常识。(bill_mins 29) / 30是向上取整的经典写法30 分钟一档它把第 31 分钟算成 2 个单位而不是 1 个。units * 50换成分即“角”。调试计费时把进场、出场时间都通过串口打出来printf(IN%lu OUT%lu MINS%lu FEE%lu\r\n, in, out, mins, fee);用串口助手一眼就能看出时间戳有没有跳变这是排查 DS3231 读数错误的第一步。4.4 掉电保存AT24C02 存在场记录把g_slots数组整体写进 AT24C02上电再读回内存。AT24C02 按字节读写没有必要动内部 Flash代码简单很多void Park_EEPROM_Save(ParkSlot *slots, uint8_t n) { AT24C02_WriteBytes(0x00, (uint8_t *)slots, n * sizeof(ParkSlot)); } void Park_EEPROM_Load(ParkSlot *slots, uint8_t n) { AT24C02_ReadBytes(0x00, (uint8_t *)slots, n * sizeof(ParkSlot)); }这里有一个隐蔽的坑结构体成员uid是 4 字节编译器会做字节对齐ParkSlot中间可能出现空洞直接按sizeof(ParkSlot)连续读写 EEPROM写入内存后面可能带上不对齐的填充字节。解决方法是把结构体改成紧凑模式typedef struct __packed { uint32_t uid; uint8_t status; uint32_t in_time; uint32_t out_time; uint32_t fee; } ParkSlot;在 EEPROM 末尾再存一个魔术字节 0xA5上电加载时检查不匹配就说明记录无效直接清空出场状态。AT24C02 的寿命按 10 万次写算一天写 10 次也能用二十多年毕设完全不用担心。5. 让 RC522 性能再进一步IRQ 唤醒、双读卡器与三步故障定位5.1 用 IRQ 中断代替轮询省电而且响应更快主循环一直调RC522_Request轮询功能没问题但系统进不了低功耗。改进方法是把 RC522 的 IRQ 引脚接到 PA0在ComIEnReg里使能“接收完成中断”。卡片靠近后RC522 的 IRQ 输出变低触发 STM32 的外部中断void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin IRQ_GPIO_PIN) { Park_Task(); } }注意中断回调里不要做耗时超过 1ms 的操作尤其不要直接写 EEPROM。常见做法是在回调里只置一个标志主循环检测到标志后再去读 FIFO、做业务安全得多。5.2 出入口各一个读卡器只需复用一条 SPI停车场要区分入口和出口可以接两个 RC522。两个模块共用 SCK、MOSI、MISOCS 分别接 PA4 和 PB12。MISO 是两个模块的输出端但哪个 CS 被拉低哪个模块的 MISO 才真正输出所以总线上不会数据打架。操作第二个模块时先拉高 PA4再操作 PB12。如果两个模块安装距离很近会出现天线互相干扰导致寻卡变慢。安装距离至少拉开 10cm或者给其中一个模块改用软件 SPI 错开时钟。这个方案比网上常见的“两套独立 SPI”更省引脚代码改动也只是多封装一层 CS 选择。5.3 3 分钟定位读卡故障我现场排错只按三步走顺序不能变先量 VCC 对地电压3.30V 左右电压不对后面都不查。初始化后读版本寄存器 0x37正常是 0x92。用逻辑分析仪抓 CS 拉低时 SCK、MOSI、MISO 的时序确认极性是 Mode 0时钟频率不高于 2MHz。如果版本正常、ATQA 也返回但寻不到卡就用万用表量天线线圈两端正常在 1Ω 左右断路就换模块。这套顺序能解决我遇到过的九成 RC522 问题剩下的才是芯片本身损坏。用逻辑分析仪看这三个信号问题往往一眼就能找到。本文还有配套的精品资源点击获取