十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32与RC522的智能门禁卡系统设计与实现

基于STM32与RC522的智能门禁卡系统设计与实现 简介一套面向电子/嵌入式方向学生的智能家居门禁卡综合管理系统毕业设计与课程设计资料包覆盖原理图、源码、部署到演示的完整流程。系统基于STM32单片机与RC522射频模块实现密码开锁、指纹开锁、刷卡开锁管理员可通过密码进入后台添加或删除指纹、注册或注销门禁卡、修改密码密码错误时有语音播报提示兼具低成本与稳定性。资源共87个文件以38个h头文件、36个c源码为主另有2个mp4演示视频、原理图、门禁卡管理文档和工程配置文件zip包约85.89MB当前已有79人学习。内容预览显示工程采用CORE、SYSTEM、FWLIB、USER、HARDWARE等标准目录并附演示视频与原理图便于按模块理解继电器、显示、语音播报、矩阵按键等设计细节适合需要快速完成课设/毕设并开箱即用的学生参考。1. 智能家居门禁卡系统STM32这套方案先拆开看答辩现场最出效果的一幕通常是这样一张IC卡靠近读卡模块OLED屏上立刻弹出卡号继电器“咔”一声吸合电磁锁打开三秒后自动落锁。整套系统的物理载体就是一块STM32最小系统板、一个RC522读卡模块、一块小显示屏和一个继电器模块。标题里的“综合管理系统”听起来复杂拆开无非是三条链路读卡、判权、执行外加一个把三者串起来的主循环。这类项目对上手者最大的价值在于它把单片机外设调度串成了一条完整链路SPI读卡、I2C显示、GPIO控锁、状态机收拢全局。真正花时间的不是RC522驱动移植而是外设之间怎么配合、状态怎么迁移。适合正在做毕设开题、想直接跑通整套代码的本科生也适合想弄明白一个带外设协作的嵌入式项目如何组织的初学者。先建立整体框架再逐层往下拆是打开这套代码的正确姿势。2. 硬件选型和引脚分工STM32F103与RC522的外设清单门禁卡系统最常见的硬件组合是STM32F103C8T6加RC522射频模块这个搭配在智能家居类毕业设计里基本属于默认方案。F103C8T6有64KB Flash、20KB RAM主频72MHz跑门禁管理逻辑绰绰有余RC522支持ISO14443A协议可以直接读M1卡S50的UID模块便宜、例程多资料到处都能找到。为什么不选51单片机51的SPI大多要靠IO口模拟RC522的读写时序虽然不算苛刻但模拟SPI会占用大量CPU时间。主循环里如果还要刷OLED、扫按键响应会明显变慢。STM32把SPI和I2C通信全部卸给硬件外设主循环只做状态判断这才是“综合管理系统”和纯读卡Demo的根本区别。2.1 外设引脚映射与JTAG复用引脚的处理拿到源码第一步是打开引脚定义文件把下面的映射表和自己的板子对照一遍。不同最小系统板的丝印略有差异按表接线能少走一半弯路。外设信号STM32引脚说明RC522SCKPA5SPI1_SCK速率2MHz以下RC522MISOPA6主入从出RC522MOSIPA7主出从入RC522CSPA4软件片选低有效RC522RSTPB0复位引脚高电平复位OLEDSCLPB6I2C1时钟线OLEDSDAPB7I2C1数据线继电器INPB1高电平触发蜂鸣器BEEPPB10有源蜂鸣器按键注册KEY_ADDPB3按下进入发卡模式按键删除KEY_DELPB4按下进入删卡模式PB3和PB4默认被JTAG占用直接当普通IO用寄存器配置不生效按键读回来永远是高电平。需要在初始化最前面关闭JTAG只保留SWD调试口。这也是很多同学程序下载正常、按键却没有反应的原因。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); GPIO_InitTypeDef gpio_init; gpio_init.GPIO_Pin GPIO_Pin_3 | GPIO_Pin_4; gpio_init.GPIO_Mode GPIO_Mode_IPU; gpio_init.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, gpio_init);代码先开启GPIOB和AFIO时钟再用GPIO_Remap_SWJ_JTAGDisable释放PB3、PB4、PA15同时保留SWDIO和SWCLK所以ST-Link下载调试不受影响。最后把PB3、PB4配成上拉输入悬空保持高电平按键按下接地读到低电平。注意如果调试器用的还是古老的JTAG四线模式关闭JTAG后就会失效。现在市面上的ST-Link、DAP-Link默认都是SWD模式不受影响。2.2 电源与驱动电路3.3V逻辑和5V继电器的衔接RC522模块和OLED都是3.3V供电直接接单片机3.3V即可。继电器模块常见两种光耦隔离型和三极管驱动型。光耦隔离模块常见5V或12V版本有跳线选择高电平触发或低电平触发代码里的GPIO电平必须和跳线一致。如果不用现成模块而是自己在面包板上搭电路会碰到一个经典问题3.3V的单片机能不能驱动达林顿模块答案是能但不能拿GPIO直接推继电器。标准做法是GPIO串一个1k电阻接到NPN三极管如S8050基极继电器线圈跨接在5V和集电极之间线圈两端反向并联1N4007续流二极管。电磁锁是感性负载断电瞬间反电动势可以到几十伏没有续流二极管会直接把单片机电平拉乱严重时整个系统复位现象就是“一开锁就重启”。2.3 上电检查清单上电后按下面顺序检查能筛掉大部分硬件问题3.3V稳压输出正常电源指示灯常亮。用万用表量RC522模块供电引脚电压在3.2V到3.4V之间。继电器模块电源指示灯亮控制端悬空时不动作。ST-Link连接后能读到芯片ID。如果RC522模块供电正常但指示灯不亮优先检查模块背面的磁珠、电感焊点。这类模块出货量大偶尔会有虚焊拿放大镜看一眼比改半天代码有效得多。3. 读卡与校验RC522的SPI驱动和卡号比对流程RC522驱动是所有门禁功能的起点。物理层是SPI通信对象是一堆寄存器。初次接触的人容易被PCD_TRANSCEIVE、RC522_Command这些名字吓住其实只要分三层看底层SPI读写寄存器中间层封装寻卡、防碰撞、选卡上层拿卡号做认证。下面按这个层次展开。3.1 SPI初始化的参数选择与上电时序RC522的SPI接口最高能跑10Mbps但实战中不需要往极限上靠。速率太高时杜邦线稍长就会出现偶发读卡失败——这种“偶尔不灵”的问题在毕设验收现场非常致命。分频取3272MHz主频下的实际SCK约2.25MHz既保证速度又留足余量如果接线超过20cm直接降到64分频1.125Mbps依然够用。void SPI1_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); gpio.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_10MHz; GPIO_Init(GPIOA, gpio); spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; spi.SPI_CPOL SPI_CPOL_Low; spi.SPI_CPHA SPI_CPHA_1Edge; spi.SPI_NSS SPI_NSS_Soft; spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_32; spi.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spi); SPI_Cmd(SPI1, ENABLE); }几个关键参数逐个说SPI固定为主模式8位数据高位先出CPOL和CPHA都为0即空闲时钟低电平、第一个边沿采样对应MFRC522芯片手册要求的SPI Mode 0。片选用普通GPIO控制读写前拉低结束后拉高比硬件NSS更可控。RC522初始化还有一个容易忽略的步骤配置完成后要把天线发射位打开也就是TxControlReg的Bit0和Bit1置1否则卡片根本感应不到寻卡永远超时。不少移植例程在这里漏了一行现象就是模块灯亮、程序在跑、但死活不读卡。3.2 寻卡、防碰撞与选卡拿到卡号的三步M1卡的读卡流程是固定的寻卡、防碰撞、选卡。寻卡让感应区内的卡片响应防碰撞在多张卡同时靠近时选出一张返回4字节序列号选卡对序列号做校验并确认之后才能对该卡继续操作。对应命令如下。命令宏值作用PICC_REQALL0x52寻卡感应区内所有卡都响应PICC_REQIDL0x26寻卡只响应空闲状态卡PCD_TRANSCEIVE0x0E发送数据并等待接收应答uint8_t RC522_GetCardUid(uint32_t *uid) { uint8_t status, len 0, size 0; uint8_t tag_type[2], snr[4]; uint8_t cmd[1]; // 第一步寻卡0x52请求感应区内所有卡响应 cmd[0] PICC_REQALL; status RC522_Command(PCD_TRANSCEIVE, cmd, 1, tag_type, len); if (status ! MI_OK) return 0; // 第二步防碰撞取回四字节卡序列号 status RC522_AntiColl(snr); if (status ! MI_OK) return 0; // 第三步选卡确认选中并返回卡容量 status RC522_Select(snr, size); if (status ! MI_OK) return 0; *uid ((uint32_t)snr[0] 24) | ((uint32_t)snr[1] 16) | ((uint32_t)snr[2] 8) | snr[3]; return 1; }三层的返回状态各有含义寻卡失败说明卡不在天线感应区或者天线驱动没起来防碰撞失败说明两张卡叠在一起选卡失败多半是通信中断回头查SPI接线和片选有没有被其它外设占用。卡号拼成uint32_t后后面白名单比对就变成整数相等判断。需要注意的是演示用的门禁卡大多是4字节UID的M1卡如果换成CPU卡或身份证UID长度不一定是4字节这段代码就需要改成自适应长度解析。3.3 白名单认证把卡号变成权限作为门禁系统卡号不能只显示还得校验。白名单的存储结构在第5章展开这里先把认证逻辑独立出来看uint8_t Card_CheckAccess(uint32_t uid) { for (uint8_t i 0; i CARD_TABLE_MAX; i) { if (card_table[i].valid 0xFF card_table[i].uid uid) { return i 1; // 返回卡号表中的位置0表示未授权 } } return 0; }代码里返回的是“索引加1”调用方拿到0就显示验证失败拿到正数则取出该卡对应的用户名。这套校验依赖的是卡号本身没有做M1扇区密钥验证毕设场景完全够用。如果想让系统往产品级靠至少还要补两步用密钥读M1卡扇区数据确认卡的真伪再对卡号做时间窗口防重放避免同一张卡被异常频繁使用。4. 主循环与联动执行状态机、OLED显示与继电器控锁读卡代码写完后真正的难点是整合。很多人的第一个版本是顺序执行读卡、开锁、延时3秒、锁闭。问题在于延时期间单片机被完全占用此时再刷一张卡不会有任何响应演示中很容易被判定为“卡死”。状态机是这类外设协作项目的标准解法把当前所处的工作状态存下来主循环只负责按事件切换状态。4.1 状态定义与主循环框架门禁行为可以拆成待机、验证、开锁、发卡、删卡五个状态。全局状态变量加一个switch主循环逻辑一目了然。typedef enum { STATE_IDLE, // 待机 STATE_VERIFY, // 验证卡号 STATE_UNLOCK, // 开锁状态等待超时 STATE_ADD_CARD, // 注册新卡 STATE_DEL_CARD // 删除卡片 } sys_state_t; sys_state_t g_state STATE_IDLE; uint32_t g_uid 0; uint16_t g_unlock_ticks 0; while (1) { switch (g_state) { case STATE_IDLE: if (KEY_ADD_IsPressed()) { g_state STATE_ADD_CARD; } else if (RC522_GetCardUid(g_uid)) { g_state STATE_VERIFY; } break; case STATE_VERIFY: if (Card_CheckAccess(g_uid)) { Relay_On(); g_unlock_ticks 0; g_state STATE_UNLOCK; } else { Buzzer_Beep(150); OLED_ShowDenied(g_uid); g_state STATE_IDLE; } break; case STATE_UNLOCK: if (g_unlock_ticks UNLOCK_TIMEOUT_MS / LOOP_MS) { Relay_Off(); g_state STATE_IDLE; } break; case STATE_ADD_CARD: OLED_ShowString(Add Card...); if (RC522_GetCardUid(g_uid)) { Card_Add(g_uid); g_state STATE_IDLE; } break; case STATE_DEL_CARD: OLED_ShowString(Del Card...); if (RC522_GetCardUid(g_uid)) { Card_Delete(g_uid); g_state STATE_IDLE; } break; } DelayMs(LOOP_MS); }主循环每一轮做的事很薄读状态、处理事件、延时。UNLOCK_TIMEOUT_MS和LOOP_MS是宏开锁时间3秒实测比较合理太短人还没走到门就锁了。开锁期间状态停留在STATE_UNLOCK不再重复寻卡避免继电器抖个不停。用计数代替DelayMs(3000)关键区别是STATE_UNLOCK期间主循环依然活着按键可以中断开锁OLED保持刷新将来接蓝牙模块或WiFi模块也能随时插入新状态而不改动主干。状态迁移可以概括成下面这张表写论文的时候这张表可以直接用。当前状态触发事件输出动作下一状态STATE_IDLE检测到卡读取UIDSTATE_VERIFYSTATE_VERIFY白名单命中继电器吸合STATE_UNLOCKSTATE_VERIFY白名单未命中蜂鸣器提醒显示拒绝STATE_IDLESTATE_UNLOCK3秒计时到继电器释放STATE_IDLESTATE_IDLE按下注册键显示发卡界面STATE_ADD_CARD4.2 OLED显示调程序时的“眼睛”OLED用I2C接SSD1306128x32或128x64都可以。初始化之后调试期最常用的显示内容是卡号和当前状态。OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, UID:); char buf[16]; sprintf(buf, %08X, g_uid); // 卡号按8位十六进制显示 OLED_ShowString(0, 2, buf);坐标单位是页。128x32的屏幕一共4页每页8像素高纵坐标只能取0、1、2、3取4就超出屏幕了如果是128x64纵坐标可以到0到7。sprintf格式化卡号要记得包含stdio.h在Keil里勾选Use MicroLIB否则这类库函数体积偏大Flash剩得就不宽裕。4.3 继电器与电磁锁动作执行与保护电路继电器的控制逻辑是最简单的GPIO输出高电平三极管导通线圈吸合常开触点闭合电磁锁通电开锁。但简单电路对细节要求高。买模块时注意跳线选高电平触发对应代码里的Relay_On()自己搭电路时续流二极管和基极限流电阻都不能省。电磁锁通常是12V供电与单片机系统是两套电源唯一公共点是GND。常见的坑是直接从单片机稳压器前端取电给电磁锁开锁瞬间电流拉低电压单片机随即复位。正确的接法是电磁锁电源从总电源单独拉一路走线尽量粗继电器触点按锁的规格选12V、1A以下的电插锁用常见5V继电器模块也能顶住但超过这个规格最好换成12V继电器模块或中间继电器。5. 授权与记录EEPROM上的卡号表和访客日志毕业设计的评分表里“综合管理”通常对应两个功能点管理员能发卡、删卡系统能记录最近的开锁记录。数据只放在内存里断电就全丢答辩老师一重启板子就露馅。所以必须把卡号表持久化存储最省事的方案是I2C接口的EEPROM。5.1 存储选型为什么不用STM32片内FlashSTM32F103C8T6片内Flash有64KB放卡号表和日志绝对够但擦写寿命只有约1万次。卡号表写入不频繁日志却是每次开锁都写一天几十次两年就到寿命边缘。外挂EEPROM的单字节擦写寿命在100万次量级成本几毛钱选型优先级明显更高。常用芯片是AT24C16容量2KB支持16字节页面写。可以规划出30张授权卡和约200条访问记录区域起始地址长度说明卡号表0x000030 × 8B 240B最多30张授权卡访问日志0x00F0200 × 8B 1600B环形覆盖保留最近记录卡号表用固定结构体数组删除就是标记失效追加就是找空位覆盖逻辑最简单也最容易在论文里讲清楚。5.2 卡号表结构设计#define CARD_TABLE_MAX 30 #define CARD_TABLE_ADDR 0x0000 typedef struct { uint32_t uid; // 卡号从RC522读出 uint8_t valid; // 0xFF表示有效0x00表示空位 uint8_t user; // 用户编号用于显示 } card_entry_t;valid单独占一个字节而不是用uid等于0来判断空位是因为M1卡虽然出厂UID通常不为0但理论上存在全0的卡拿0当空位标记这类卡永远无法注册。用独立的标记字节删除时只需把valid写成0uid留在原地无妨新卡注册时直接覆盖。这里要注意结构体对齐uint32_t占4字节两个uint8_t在它之后sizeof(card_entry_t)实际取到8字节而不是6字节。所有读写都用sizeof计算不要手写长度。5.3 EEPROM分页写入的边界处理AT24C16每个页面16字节页写不能跨页。8字节一条的卡号记录如果起始地址在页内偏移大于8这一条就会跨页。所以封装EEPROM驱动时必须自己处理分页对齐void EEPROM_WriteBuffer(uint16_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint8_t page_left 16 - (addr % 16); // 当前页剩余空间 uint8_t chunk (len page_left) ? len : page_left; // AT24C16的页选择位在器件地址里不需要左移 EEPROM_PageWrite(0xA0 | ((addr 8) 0x07), addr 0xFF, buf, chunk); DelayMs(5); // 等待内部写周期完成 addr chunk; buf chunk; len - chunk; } }逐行解释page_left算的是当前页还能写多少字节chunk取“剩余长度”和“页剩余空间”的较小值保证每次PageWrite不跨页写。AT24C16和普通24C02的寻址不同前者的A2、A1、A0引脚不存在对应的三位被用作页选择所以器件地址不是固定0xA0而是从addr的高字节里取页号拼进去。(addr 8) 0x07就是这三位的值直接用0xA0按位或拼接即可不要左移。注意每次写周期结束必须延时等待AT24C16内部写周期典型值3到5ms。写完后立刻读回读出来的数据和写入不一致多半就是延时不够。5.4 发卡、删卡与日志追加的实现要点卡号注册的核心是先查重后追加uint8_t Card_Add(uint32_t uid) { for (uint8_t i 0; i CARD_TABLE_MAX; i) { if (card_table[i].valid 0xFF card_table[i].uid uid) { return 2; // 已存在不重复注册 } } for (uint8_t i 0; i CARD_TABLE_MAX; i) { if (card_table[i].valid 0x00) { card_table[i].uid uid; card_table[i].valid 0xFF; EEPROM_WriteBuffer(CARD_TABLE_ADDR i * sizeof(card_entry_t), (uint8_t *)card_table[i], sizeof(card_entry_t)); return 1; } } return 0; // 卡表满了 }第一遍循环做查重第二遍找空位。如果只用一次循环边查边找空位卡号表前段全是有效卡时直到遍历结束才发现卡已存在空位信息早就丢了追加逻辑会变复杂两遍更清晰。删除逻辑相反找到uid后把valid写成0再写回。日志区用环形覆盖策略每条约8字节按时间戳顺序写写满后回到起始地址覆盖最旧记录代码里就是维护一个log_write_index每次写完后自增并取模。6. 部署验证与排错串口日志和调试器连接的三个坑6.1 把串口日志当成最小示波器代码收尾后不要急着接电磁锁先把USART1用115200波特率输出日志把状态机的每次转换打出来。完整的日志链长这样[INFO] RC522 init OK [EVENT] card detected, UID0x1A2B3C4D [CHECK] access granted, user3 [ACTION] relay ON [ACTION] relay OFF after 3000ms [EVENT] card detected, UID0x99887766 [CHECK] access denied对照日志能快速定位问题层没有card detected是RFID驱动或接线问题有检测但access denied是存储或比对问题有granted但电磁锁不动是执行层问题。这一条日志链能把故障范围从整套系统缩小到几个引脚。串口初始化时要重定向fputc到USART1并在Keil里开启Use MicroLIB否则printf输出不完整。6.2 三个最容易踩的坑现象根因解决方案下载时报Error: No STM32 target found! If your product embeds debug authentication程序无法烧录目标板没供电或SWDIO/SWCLK接反少数情况是芯片进入低功耗模式或调试口被复用确认板子有独立3.3V供电只接SWDIO、SWCLK、GND三根线再试按住复位键的同时点击下载仍不通就用ST-Link Utility执行整片擦除Keil5里找不到STM32F103C8Tx器件只装了MDK没装芯片支持包打开Pack Installer安装Keil.STM32F1xx_DFP完成后Device列表出现F1系列设备管理器里STM32 Virtual Com Port显示黄色叹号串口驱动缺失或版本不匹配安装STSW-STM32050驱动包重新插拔USB后确认枚举出COM口“No STM32 target found”是毕设季出现频率最高的一类报错。如果目标板是单独供电的先量一下3.3V是不是真的存在再用最短的杜邦线重新接一次SWDIO和SWCLK——很多人就是这两根线在面包板上插反了。芯片锁死的情况用ST-Link Utility连不上时可以试试把NRST对地短接一下边短接边连接成功率会高不少。6.3 收尾前的小技巧把所有延时和超时参数集中放到config.h里LOOP_MS、UNLOCK_TIMEOUT_MS、I2C_SPEED、SPI_PRESCALER。答辩演示时想缩短开锁时间改一行宏定义重新编译即可不用满工程翻硬编码。刷完新固件后用串口回读配置参数确认实际生效防止Keil用了旧工程缓存烧进去的还是上一版代码。给别人部署时把这段串口日志格式说明写进交付文档第二个人接手排查会轻松很多。本文还有配套的精品资源点击获取
返回列表