ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

51单片机RC522读卡器:SPI驱动、寻卡认证与扇区读写

51单片机RC522读卡器:SPI驱动、寻卡认证与扇区读写 简介这份资料围绕基于89C2051单片机的SLE4442接触式IC卡读卡器设计展开面向电子信息、自动化等专业的课程设计与毕业设计学习者也可供需要了解IC卡读写底层原理的嵌入式初学者参考。内容从硬件方案论证入手讲解89C2051核心控制、SLE4442芯片内部结构与传输协议、复位响应、命令模式、逻辑加密机制以及RS-232串行接口和卡座接口电路再延伸到汇编语言软件设计、数据校验与软硬件联合调试并附电路原理图、程序流程图与程序清单帮助读者理清读卡器从接口时序到上位机通信的完整链路。资源包内为1份pdf文档压缩包约1.86MB篇幅完整、结构清晰可作为课设报告撰写与方案复现的参考模板。目前已有120人学习浏览适合希望从硬件接口到软件协议系统掌握IC卡读写的读者入手研读。1. 从一张门禁卡讲起单片机读卡器到底在解决什么问题手边放一张 13.56MHz 的 Mifare 卡、一块 RC522 模块、一片 STC89C52 和一根 USB 转串口线卡片贴上去串口助手里跳出一串 4 字节 UID这就是一个最小可用的 IC 卡读卡器。门禁机、考勤机、校园卡、充电桩的刷卡模块剥到最里层基本都是这套结构单片机管时序和协议状态机射频前端管跟卡对话。选 51 还是 STM32、选 RC522 还是 PN532决定的不是能不能读而是你在软件模拟 SPI、天线调谐、扇区密钥管理上要花多少时间。这里把硬件选型、驱动代码、扇区读写参数和调不稳时的排查路径按顺序拆开讲一遍每条命令和每个寄存器值都能直接抄进 Keil 工程。2. RC522 和 PN532 怎么选IC 卡读卡器的硬件链路与 SPI 接线2.1 射频前端和单片机各管什么很多人第一次接线时会以为单片机在“读卡”其实单片机一根载波都不发。13.56MHz 的交变磁场由射频前端芯片驱动天线线圈产生卡片靠这个磁场取电再用负载调制把数据“反射”回来。单片机做的事情只有三件按 SPI 时序把寄存器写进去、按协议帧把命令塞进 FIFO、读回中断标志和 FIFO 里的数据。分工理清了选型问题就变成两个独立问题——射频前端支持哪些卡型主控能不能稳定跑出 SPI 时序。2.2 常见方案的参数对比模块/卡型协议接口供电支持卡型典型场景MFRC522ISO14443ASPI部分小板带 I2C/UART3.3VMifare Classic 1K/4K、Ultralight、NTAG门禁、考勤、毕业设计PN532ISO14443A/B、Felica、ISO15693I2C/SPI/UART 三选一3.3V卡型更全支持卡模拟与点对点需要读 B 卡或多协议AT24C 系列I2C 两线8 脚直连1.8~5.5V24C01~24C512接触式存储卡非射频SLE4442/4428ISO7816 触点三线5V/3V逻辑加密卡老式 IC 卡表具RC522 的优势是便宜、资料密集、寄存器手册只有几十页PN532 把防冲突和帧封装都做进固件里串口发一条0x4A 01 00就能拿到 UID代价是贵一截而且 SPI 模式下需要先切模式。如果只要读 A 卡 UIDRC522 足够如果手里同时有 B 卡和 15693 标签直接上 PN532 更省事。注意RC522 模块标称 3.3V 供电5V 单片机的 IO 直接怼上去短期能跑长期会缩短模块寿命。常见做法是在 SCK、MOSI、NSS 三根线上各串一个 1k 电阻或者换成 STC8、STC32 这类可跑 3.3V 的单片机。2.3 与 51 单片机的接线表RC522 引脚STC89C52 引脚说明SDA / NSSP1.0片选低有效一次通信期间拉低SCKP1.1时钟软件模拟空闲为低MOSIP1.2主机输出模块输入MISOP1.3模块输出主机输入RSTP1.4复位低有效上电后拉高IRQ不接查询方式够用VCC / GND3.3V / GND单独 LDO 供电更稳这套接法用的是普通 IO 口没有占用 51 的定时器和串口后面串口打印 UID 时不会打架。3. 在 51 单片机上跑通 RC522 的最小驱动寻卡、防冲突、选卡3.1 51 没有硬件 SPI先写软件模拟STC89C52 这类 51 内核没有 SPI 外设只能用 IO 翻转模拟。MFRC522 的 SPI 是模式 0时钟空闲为低、上升沿采样手册给的最高时钟是 10MHz实际用 1MHz 量级最稳因为 51 在 11.0592MHz 晶振下一条指令就要一个机器周期。// 软件模拟 SPI模式 0上升沿采样一次交换 8 位 sbit RC522_NSS P1^0; sbit RC522_SCK P1^1; sbit RC522_MOSI P1^2; sbit RC522_MISO P1^3; sbit RC522_RST P1^4; unsigned char SPI_RW(unsigned char byte) { unsigned char i, recv 0; for (i 0; i 8; i) { RC522_SCK 0; RC522_MOSI (byte 0x80) ? 1 : 0; byte 1; RC522_SCK 1; // 上升沿模块采样 MOSI recv 1; if (RC522_MISO) recv | 0x01; } RC522_SCK 0; return recv; }循环里先拉低时钟再摆数据拉高后读 MISO顺序反了读回来的就是错位数据。如果读 VersionReg 拿到 0x00 或 0xFF先查 NSS 是不是在上电时被拉低了。3.2 寄存器读写与天线开启RC522 的地址是 6 位字节的最高位表示读或写次高位表示地址自增。unsigned char ReadRawRC(unsigned char addr) { unsigned char v; RC522_NSS 0; SPI_RW(((addr 1) 0x7E) | 0x80); // 读最高位置 1 v SPI_RW(0x00); RC522_NSS 1; return v; } void WriteRawRC(unsigned char addr, unsigned char value) { RC522_NSS 0; SPI_RW((addr 1) 0x7E); // 写最高位为 0 SPI_RW(value); RC522_NSS 1; } void PcdAntennaOn(void) { unsigned char v ReadRawRC(0x14); // TxControlReg if (!(v 0x03)) // Tx1RFEn 和 Tx2RFEn 要同时置 1 WriteRawRC(0x14, v | 0x03); }天线这两位是很多“读不到卡”问题的根因只置一位时载波功率不够卡片贴到 1cm 内才偶尔有反应。复位后先读 VersionReg正常值一般是 0x91 或 0x92对不上就别往下调了先把接线和供电理清。3.3 寻卡与防冲突的调用链RC522 的收发都走同一个底层函数把命令和数据写 FIFO、启动命令、轮询中断标志、把结果读回来。// PCD_TRANSCEIVE 0x0CPCD_AUTHENT 0x0E char PcdComMF522(unsigned char command, unsigned char *pIn, unsigned char inLen, unsigned char *pOut, unsigned int *pOutBits) { char status MI_ERR; unsigned char irqEn 0, waitFor 0, lastBits, n; unsigned int i; if (command 0x0E) { irqEn 0x12; waitFor 0x10; } if (command 0x0C) { irqEn 0x77; waitFor 0x30; } WriteRawRC(0x02, irqEn | 0x80); // ComIEnReg允许中断 WriteRawRC(0x04, 0x7F); // ComIrqReg清所有中断标志 WriteRawRC(0x0A, 0x80); // FIFOLevelRegFlushBuffer WriteRawRC(0x01, 0x00); // CommandReg IDLE for (i 0; i inLen; i) WriteRawRC(0x09, pIn[i]); WriteRawRC(0x01, command); if (command 0x0C) WriteRawRC(0x0D, ReadRawRC(0x0D) | 0x80); // StartSend i 2000; // 超时计数按主频调整 do { n ReadRawRC(0x04); i--; } while (i !(n 0x01) !(n waitFor)); WriteRawRC(0x0D, ReadRawRC(0x0D) 0x7F); if (i 0) return MI_ERR; if (ReadRawRC(0x06) 0x1B) return MI_ERR; // ErrorReg 有错 status MI_OK; if (n irqEn 0x01) status MI_NOTAGERR; // 定时器中断 没卡 if (command 0x0C) { n ReadRawRC(0x0A); lastBits ReadRawRC(0x0C) 0x07; *pOutBits lastBits ? (n - 1) * 8 lastBits : n * 8; if (n 0) n 1; if (n 16) n 16; for (i 0; i n; i) pOut[i] ReadRawRC(0x09); } WriteRawRC(0x01, 0x00); return status; }寻卡用 REQA0x26只叫醒空闲卡WUPA0x52连挂起态的卡一起唤醒返回的backBits应该是 0x10即 2 字节。char PcdRequest(unsigned char reqMode, unsigned char *tagType) { char status; unsigned int backBits; WriteRawRC(0x0D, 0x07); // BitFramingReg最后 3 位有效 tagType[0] reqMode; status PcdComMF522(0x0C, tagType, 1, tagType, backBits); if (status ! MI_OK || backBits ! 0x10) return MI_ERR; return MI_OK; }防冲突阶段卡片回 5 字节前 4 字节是 UID第 5 字节是 BCC 校验异或不为 0 就说明通信过程被干扰了。char PcdAnticoll(unsigned char *pSnr) { char status; unsigned char i, bcc 0, buf[2]; unsigned int unLen; WriteRawRC(0x0D, 0x00); buf[0] 0x93; // PICC_ANTICOLL1 buf[1] 0x20; WriteRawRC(0x01, 0x00); status PcdComMF522(0x0C, buf, 2, pSnr, unLen); if (status MI_OK) { for (i 0; i 4; i) bcc ^ pSnr[i]; if (bcc ! pSnr[4]) status MI_ERR; } return status; }主循环里把两步串起来中间加 200ms 延时别用空转死循环否则天线和串口都得不到足够时间。4. Mifare 扇区读写与密钥认证参数怎么设才不锁卡4.1 1K 卡的扇区与块结构Mifare Classic 1K 的 1024 字节被切成 16 个扇区每个扇区 4 个块每块 16 字节。块 0 是厂商块存 UID 和厂商数据只读块 1、2 是数据块块 3 是尾块结构固定为 6 字节密钥 A 4 字节访问位 6 字节密钥 B。出厂默认密钥 A 和密钥 B 通常都是 6 个 0xFF访问位默认FF 07 80 69。参数取值说明扇区号0~15业务上按扇区划分块地址扇区号 × 4 块偏移认证和读写都用块地址认证模式0x60 / 0x610x60 用密钥 A0x61 用密钥 B密钥长度6 字节不足或超出都会认证失败4.2 认证命令的参数含义认证时送进去 12 个字节1 字节认证模式 1 字节块地址 6 字节密钥 4 字节 UID。块地址填错是新手最常踩的坑——想操作扇区 3 的数据地址要填 12 而不是 3。char PcdAuthState(unsigned char authMode, unsigned char addr, unsigned char *pKey, unsigned char *pSnr) { unsigned char status, buf[12]; unsigned int unLen; buf[0] authMode; // 0x60 或 0x61 buf[1] addr; // 块地址不是扇区号 memcpy(buf[2], pKey, 6); memcpy(buf[8], pSnr, 4); status PcdComMF522(0x0E, buf, 12, buf, unLen); if (status ! MI_OK || !(ReadRawRC(0x08) 0x08)) return MI_ERR; return MI_OK; // Status2Reg 的 MFCrypto1On 位必须为 1 }认证通过后卡片内部会保持一个会话状态接下来对同扇区的读写都不用再认证。换到另一个扇区必须重新认证否则读回来全是 0。4.3 读块与写块的返回值判断读块命令 0x30正常返回 16 字节长度是 0x90 位。char PcdRead(unsigned char addr, unsigned char *pData) { unsigned char status, buf[2]; unsigned int unLen; buf[0] 0x30; // PICC_READ buf[1] addr; status PcdComMF522(0x0C, buf, 2, pData, unLen); if (status ! MI_OK || unLen ! 0x90) return MI_ERR; return MI_OK; }写块要分两次交互先发命令和地址卡回 0x0A 表示“我准备好了”再发 16 字节数据卡再回一次 0x0A 才算成功。中间任何一步没回 0x0A 都要当成失败别继续往下走。char PcdWrite(unsigned char addr, unsigned char *pData) { unsigned char status, buf[18]; unsigned int unLen; buf[0] 0xA0; // PICC_WRITE buf[1] addr; status PcdComMF522(0x0C, buf, 2, buf, unLen); if (status ! MI_OK || unLen ! 4 || (buf[0] 0x0F) ! 0x0A) return MI_ERR; memcpy(buf, pData, 16); status PcdComMF522(0x0C, buf, 16, buf, unLen); if (status ! MI_OK || unLen ! 4 || (buf[0] 0x0F) ! 0x0A) return MI_ERR; return MI_OK; }4.4 改密钥的正确顺序尾块只能整块写不能只改其中几个字节。稳妥的顺序是先用当前密钥认证 → 读尾块 16 字节到内存 → 在内存里改密钥 A、密钥 B 或访问位 → 写回 → 立刻用新密钥重新认证一次确认。注意访问位写错会让整个扇区变成只读甚至永久锁死卡就报废了。改之前把原 16 字节完整打印出来留底改的时候只在备份上做替换不要凭记忆拼字节。5. 读卡器调不稳的常见原因天线、供电与调试技巧读卡距离从 3cm 掉到贴着才响应八成不是代码问题。先量供电寻卡瞬间模块电流会冲到 100mA 以上USB 转串口模块引出的 3.3V 往往带不动电压一塌就表现为时好时坏。换独立 LDO 供 3.3V并在模块电源脚旁边并一个 10μF 电解加 0.1μF 瓷片很多“偶发读不到”会直接消失。天线本身是 PCB 走线匹配电容一般在 27pF 附近模块不同批次有偏差不建议自己拆改除非确认是天线被金属遮挡或贴在金属面板背面。5.1 用串口日志缩小故障范围把每一步的结果都打出来比猜快得多。在寻卡前后各加一行输出就能分清是根本没载波、还是载波有但防冲突失败unsigned char ver ReadRawRC(0x37); // 0x91/0x92 说明 SPI 通了 printf(VER%02X\r\n, ver); if (PcdRequest(0x52, tagType) MI_OK) { if (PcdAnticoll(uid) MI_OK) printf(UID%02X%02X%02X%02X BCC%02X\r\n, uid[0], uid[1], uid[2], uid[3], uid[4]); else printf(ANTICOLL FAIL\r\n); // 有卡但校验不过查天线和电源 } else { printf(NO CARD\r\n); // 完全没响应查供电和天线开关 }VER 读不到就先停下其余都是空谈。串口波特率用 960051 在 11.0592MHz 下误差最小。5.2 状态机卡死与掉卡重连单片机上最容易写坏的是“卡一拿走就死在等待里”。收发的超时计数i 2000是按主频估的主频翻倍就要跟着调。更稳的写法是让PcdComMF522在任何分支退出前都把 CommandReg 写回 IDLE、清一次 FIFO这样下一轮寻卡不会被上一轮的残留数据污染。业务层用一个三态循环IDLE 只发 RequestCARD_PRESENT 做认证和读写ERROR 先 Flush 再退回 IDLE每个状态都只做一件事。5.3 密钥和扇区操作的最小验证顺序拿到新卡先别急着写。按这个顺序逐级验证读 VersionReg → 寻卡拿 UID → 用默认密钥FF FF FF FF FF FF认证扇区 0 的块 0 → 读块 0 比对 UID 和厂商数据 → 再碰数据块。前四步全过再动尾块。密钥认证失败时先换密钥 B 试一次很多卡只开放了其中一组两组都失败基本可以判断这张卡被改过密钥没有原始密钥就没有后续。本文还有配套的精品资源点击获取
返回列表