PSoC与RC522实现RFID-UART读卡器:从SPI时序到M1卡UID上报

发布时间:2026/10/19 9:14:01
PSoC与RC522实现RFID-UART读卡器:从SPI时序到M1卡UID上报
简介基于PSoC的RFID-UART读写项目资料包是一套面向嵌入式开发者、电子工程学生及物联网入门者的完整工程包解决基于可编程片上系统构建RFID读写器并实现UART串行通信的问题。资料以PSoC为主控通过配置内部UART模块完成与外部主机的数据交换覆盖RFID标签读取、编码传输、指令接收等核心流程适用于库存管理、物流追踪、门禁控制等典型物联网场景的参考与二次开发。资源共271个文件压缩包2.4MB主要包含C/H源码、A51汇编启动文件、HEX固件、LIB静态库以及cyprj、cydwr、cysch等工程与原理图文件从固件代码到硬件设计一应俱全可直接导入PSoC Creator环境查看和编译。已有203人学习使用包内项目结构完整、模块划分清晰能帮助使用者快速掌握PSoC内部资源调配、RFID工作原理、UART波特率及数据帧配置、主机端通信协议设计等关键技能是一份适合边学边做的实战参考资料。1. 一个读卡器工程拆开之后PSoC 与 RFID-UART 的完整落地链路当你想给工位机加一张刷卡的功能搜到的资料多半停在“RC522 模块接线”这一步。我拿到这个工程时的第一反应是终于有人把 PSoC 上的 RFID-UART 读写做成了能编译、能烧录、能直接改的完整工程而不是又一张原理图。这个工程解决的实际问题是M1 卡的 UID 从 RC522 的天线读出来经过 PSoC 的 SPI 外设进入寄存器再打包成自定义帧从 UART 串口发出去。它适合正在做门禁、工具借还、设备盘点这类读卡上报场景的人也适合想弄明白 PSoC 的 SPI 和 UART 是怎么串起来的新手。接下来按硬件链路、工程结构、核心代码、踩坑记录的顺序往下拆最后给三个我能想到的改造方向。看完你就能把板子跑起来跑起来之后也知道从哪里下手改。2. 硬件链路与选型RFID 卡号到串口的每一段2.1 一条卡号要经过的完整链路先别急着看代码。把整条链路从物理层到应用层过一遍后面读代码的时候就不会迷路。第一段是射频部分M1 卡靠近天线后通过 13.56MHz 电磁场耦合获得能量RC522 内部的模拟前端负责解调、编码和校验。第二段是数字部分RC522 把卡号相关的数据放进自己的 FIFO 和寄存器等待主机来取。第三段是传输部分PSoC 的 SPI_Master 组件以 8 位模式访问 RC522 的寄存器一个字节一个字节地把 FIFO 里的内容读出来。第四段是应用部分PSoC 的主循环里跑寻卡、防碰撞、认证、读块这些流程把 UID 整理成一个 4 字节数组。最后一段才是输出UART 把自定义帧发出去上位机按帧解析。这里有个容易忽略的分层PSoC 读到的其实是 RC522 的寄存器状态而不是直接“看到”卡片的数据。所以任何一次读卡失败都得先确认是射频层没响应还是 SPI 层读回来的是无效值还是 UART 层把数据传丢了。排查顺序永远从物理层往上走。这个工程把链路简化成了很标准的五层结构我配置完 PSoC 的外设之后剩下的工作基本就是写中间三层的代码。2.2 为什么选 PSoC 和 RC522 的组合先说 RC522。它是一颗 13.56MHz 的非接触读写芯片支持 ISO14443A 协议的 M1 卡。它和主控之间可以用 SPI、I2C、UART 三种方式通信这个工程用的是 SPI原因很简单速率够、时序直白、网上成熟的参考代码最多。再说 PSoC。这块芯片和普通 MCU 最大的区别在于外设是“可重构”的UART、SPI、定时器这些数字外设不是固定焊死在某个引脚上的而是在图形化配置工具里拖出来、连好线、再映射到任意 GPIO。这带来一个实际好处——布线的时候看到哪个引脚顺眼就用哪个不会因为 UART_TX 恰好被某个功能占用而被迫改板子。更要紧的是PSoC 的外设之间可以在内部连接。UART 的 TX 可以接到一个数字输出引脚这个引脚在原理图上看起来就是一个“引脚”组件真正占用的是一块内部数字逻辑。相比用 GPIO 模拟 SPI 时序的做法PSoC 的 SPI_Master 组件发完一个字节后硬件会自动处理移位和时钟主循环不会被位冲撞的死循环拖住。这也是我推荐这套组合的原因RC522 负责射频域的脏活累活PSoC 负责协议处理和串口输出两边都不需要软件去模拟时序。2.3 引脚分配与供电细节打开工程的引脚约束文件能看到这张接线表不同版本具体引脚可能不完全一样但映射关系是一致的PSoC 引脚RC522 丝印方向说明P0[0]MOSI输出SPI 主机数据输出P0[1]MISO输入从机数据回读P0[2]SCK输出SPI 时钟P0[4]CS / NSS输出片选低电平有效P0[5]RST输出复位P0[6]IRQ输入卡事件中断本工程可不接VCCVCC电源模块供电 3.3VGNDGND电源共地这里有一个容易翻车的点PSoC 的 IO 是 3.3V 电平RC522 模块务必选 3.3V 供电的版本不要把 5V 模块直接怼上去。选 CS 引脚时要选默认输出高电平的引脚避免上电瞬间片选误触发。RST 必须由 PSoC 控制而不是直接拉死否则上电时序不干净后面的第 5 章会专门讲这个问题。模块的天线线圈外围已经配好匹配电容正常连上 3.3V 就能工作。如果发现读卡距离很近先不要怀疑模块坏了多半是天线驱动寄存器配置的问题我在 4.2 会给出具体寄存器值。2.4 为什么不直接用串口读卡模块市面上有一种集成读卡头主控只需要发一串指令就能拿到卡号比 RC522 方案少写一大截代码。那这个工程为什么还要从 RC522 裸芯片开始做核心原因是协议可控性。集成读卡头的协议通常是厂家定死的帧格式、轮询周期、指令集都改不了。你在门禁场景里可能只需要卡号但到了“刷卡 写卡号到扇区 上报剩余次数”这种场景厂家模块就做不到了。而这个工程用的是芯片级方案RC522 的每个寄存器都暴露在 SPI 上寻卡、防碰撞、选卡、认证、读写块每一步都能自己控制。另外从成本角度看RC522 芯片方案比集成模块便宜不少。对批量出设备的项目来说省下的钱够改善天线匹配和外壳了。还有一点是调试效率集成模块出问题就是个黑匣子只能来回发指令猜RC522 方案则可以用逻辑分析仪直接抓 SPI 波形确认每个寄存器命令的返回值故障定位快很多。做嵌入式开发的人应该都懂能抓到波形的问题都不是问题。2.5 上电时序与复位控制的设计思路PSoC 的 GPIO 外设比 RC522 的电源稳定早得多这是很多读卡器上电不稳定的根源。这个工程的做法是把 RST 脚挂在 PSoC 的普通 GPIO 上然后在初始化函数里手动拉低、拉高给 RC522 一个干净的复位沿。验证这个时序是否可靠的方法是用示波器同时测 RC522 的 VCC 和 RST 两个点RST 的上升沿至少要滞后 VCC 稳定 10ms太早了芯片可能还在内部复位太晚了又会拖慢启动速度。我一般会在初始化里先拉低 RST然后延时 10ms 再释放实测这个窗口对 RC522 来说很宽容。如果你自己画的板子把 RST 直接接在 VCC 上那么每次上电能不能正常工作基本看运气。3. 工程搭建与资源配置从新建 PSoC 工程到进入主循环3.1 工程文件结构与职责划分拿到工程后先看目录结构PSoC Creator 的工程和普通单片机工程不太一样它带一套自动生成的驱动代码RfidUart.cydsn/ main.c // 主循环和业务逻辑 MFRC522.c / MFRC522.h // RC522 驱动封装 TopDesign.cysch // 原理图设计拖组件、连线都在这里 RfidUart.cydwr // 引脚分配与电气配置 Generated_Source/ // PSoC 自动生成的外设驱动不要手改main.c 里只做三件事启动外设、轮询刷卡、发送帧。MFRC522.c 是重点它把 RC522 的寄存器操作全部封装成了函数main.c 不需要关心 SPI 的底层细节。TopDesign.cysch 是 PSoC 的特色UART、SPI、时钟这些外设组件在这个原理图里以图形方式摆放和连线。Generated_Source 目录是编译前由工具生成的里面的代码不应该手改因为每次改动 TopDesign 或引脚分配它都会被重新生成覆盖。如果你要做二次开发大部分改动集中在 main.c 和 MFRC522.c只在需要新增外设时才动 TopDesign。3.2 TopDesign 里的三个关键组件打开 TopDesign.cysch能看到工程里其实只放了三个组件但每个的配置都影响最终能不能跑通。第一个是 UART 组件配置为 9600 波特率、8 数据位、无校验、1 停止位TX 映射到 P1[0]RX 映射到 P1[1]。9600 在调试阶段是比较稳的选择PSoC 内部时钟即使有点误差也不容易乱码。如果后面要高速传输再改成 115200 并配合外部晶振就行。第二个是 SPI_Master 组件配置为 1Mbps、8 位数据、MSB first、SPI Mode 0也就是 CPOL0、CPHA0。RC522 的数据手册推荐的就是这个模式很多网上示例用的是 Mode 1读回来的数据就不对这个坑在第 5 章会展开。这里有个小细节SPI 的片选信号不要用 SPI_Master 组件自带的 SS 引脚而是用普通 GPIO 手动控制这样读卡流程里可以随时拉高拉低不跟组件的状态机绑定。第三个是 Clock 组件作为 SPI 和 UART 的时钟源头。PSoC 的时钟树比较灵活UART 波特率靠时钟分频出来所以时钟源选什么、分频系数是多少直接影响串口准不准。我的习惯是把时钟树里的一个 Clock 组件设为 1MHz 给 SPI 用UART 组件内部自己再做分频这样两个外设互不干扰。配置完成后打开 .cydwr 文件在 Pins 页签里把每个组件端口映射到具体引脚保存后重新编译即可。这里唯一的建议是引脚映射尽量让 SPI 的四根线集中在相邻引脚方便布线也方便逻辑分析仪夹探头。3.3 初始化顺序和主循环骨架外设配置好之后main.c 的骨架是这样的int main(void) { CyGlobalIntEnable; UART_Start(); SPI_Master_Start(); MFRC522_Init(); // RC522 复位 配置寄存器 开天线 UART_UartPutString(RFID Reader Ready\r\n); for (;;) { uint8_t uid[4] {0}; if (ReadCardUid(uid) MI_OK) { SendCardFrame(uid); // 按自定义帧格式从 UART 发出 } CyDelay(100); } }这段代码有固定的启动顺序先开全局中断再启动 UART 和 SPI最后才调用 MFRC522_Init。MFRC522_Init 里面要写 RC522 的寄存器如果 SPI 还没 Start写进去的全是无效值。CyDelay(100) 是主循环的轮询间隔100ms 对刷卡场景足够卡片在天线上停留的时间通常超过 300ms不会漏读间隔再短反而容易在上电瞬间产生电磁干扰。读卡成功的判断用 ReadCardUid 的返回值这个函数在 4.3 会详细拆。它在内部做了寻卡和防碰撞卡离开天线时立刻返回失败主循环就继续空转。3.4 编译、烧录和运行观察PSoC Creator 里直接点编译第一次编译会花比较长时间因为要生成整套外设驱动代码。如果引脚配置有冲突编译会直接报“Pin assignment conflict”到 .cydwr 里改掉冲突引脚就行。烧录用 SWD 接口接 SWDIO、SWDCLK、VCC、GND 四根线上电后程序自动运行。运行观察很简单打开串口助手波特率设 9600显示方式切到 Hex刷卡后应该收到 8 个字节比如 AA 55 01 04 6B 2D 3E 1F前两个是帧头第三个是命令字中间四个是卡号最后一个是校验和。如果刷了几次都没输出先用万用表确认模块供电和共地再用逻辑分析仪看 SPI 波形——不要怀疑代码先怀疑硬件这是我调这类板子最深刻的体会。4. 核心模块拆解寻卡、防碰撞、读块与 UART 帧实现4.1 RC522 寄存器读写底层RC522 和 PSoC 的通信全部走寄存器读写这层是所有上层功能的地基。读一个寄存器实际上是在 SPI 上先发地址字节再发一个空字节把数据移回来uint8_t RC522_ReadReg(uint8_t reg) { uint8_t value 0; RC522_CS_LOW; // 拉低片选 SPI_Master_WriteByte(((reg 1) 0x7E) | 0x80); // 地址左移 读标志 SPI_Master_WriteByte(0x00); // 产生时钟同时读回 value SPI_Master_ReadByte(); // 取回 MISO 数据 RC522_CS_HIGH; // 拉高片选 return value; }这里有两个关键点。第一RC522 的 SPI 地址格式是 A6..A0 七个地址位加上一个读写标志位所以要先把寄存器地址左移一位再按读写操作加上 0x80 或 0x00。第二SPI 是同步协议主机每发一个字节的同时会从 MISO 移入一个字节所以第二次 WriteByte(0x00) 的目的不是发数据而是产生时钟脉冲让 RC522 把数据从 MISO 送出来。这个“写一个空字节来读数据”的套路是 SPI 外设和 I2C 最大的不同刚上手的人经常在这里卡住。写寄存器就简单很多把地址加上写标志再紧接着发送数据字节即可。不同 PSoC 系列的 SPI 组件 API 细节略有差异但核心都是这个思路移植到别的型号时以组件数据手册为准。4.2 RC522 初始化每个寄存器为什么这么写void MFRC522_Init(void) { RC522_RST_Write(0); CyDelayUs(2); RC522_RST_Write(1); CyDelayUs(10); RC522_WriteReg(CommandReg, 0x0F); // 软复位 CyDelayUs(30); RC522_WriteReg(TModeReg, 0x8D); // 定时器自动开启、自动重载 RC522_WriteReg(TPrescalerReg, 0x3E); // 定时器分频 RC522_WriteReg(TReloadRegH, 0x00); // 重载值高位 RC522_WriteReg(TReloadRegL, 0xB4); // 重载值低位 RC522_WriteReg(TxAskReg, 0x60); // 天线驱动电流 RC522_WriteReg(BitFramingReg, 0x07); // 帧起始位置设为第 7 位 RC522_WriteReg(TxControlReg, 0x83); // 打开天线 }初始化做的第一件事是给 RST 一个干净的复位沿先拉低 2us再拉高 10us。接下来写 CommandReg0x0F 是软复位命令芯片会复位内部状态机所以后面要延时 30us 等它复位完成。TModeReg、TPrescalerReg、TReloadReg 这三组寄存器配的是 RC522 内部定时器它负责控制寻卡和防碰撞时发射信号的窗口宽度。0x8D、0x3E、0x00B4 这几个值来自数据手册的推荐配置直接照抄就行。TxAskReg 里的 0x60 决定天线驱动能力配得太小读卡距离会缩到一两厘米配得太大反而容易让卡片过载。BitFramingReg 的 0x07 表示发送帧从第 7 位开始这是发寻卡命令前的标准准备。最后 TxControlReg 写 0x83 把天线打开这一步不写前面的配置全白搭。这些值不是玄学都是从 RC522 数据手册里摘出来的推荐配置。如果换成 PSoC 4 的其他型号只要 RC522 周围元器件不变这套值可以原样沿用。4.3 读卡主流程寻卡、防碰撞、选卡、读块读一次卡的完整流程分为三步寻卡、防碰撞、选卡。寻卡是问一句“附近有没有卡”防碰撞是在多张卡同时在场时挑出其中一张并拿到 UID选卡是告诉卡片“我就要跟你通信”。uint8_t ReadCardUid(uint8_t *uid) { uint8_t status; uint8_t tagType[2]; RC522_WriteReg(CommandReg, PCD_IDLE); // 停止当前命令 status RC522_Request(PICC_REQIDL, tagType); // 寻卡 if (status ! MI_OK) return status; status RC522_Anticoll(uid); // 防碰撞拿到 4 字节 UID if (status ! MI_OK) return status; RC522_ClearBitMask(CollReg, 0x80); // 清掉防碰撞标志 return MI_OK; }RC522_Request 的实现是把寻卡命令 0x52PICC_REQIDL写进 FIFO然后启动发射接收等待卡片应答。卡片应答后RC522_Anticoll 会往 FIFO 写入 0x93 和 0x20触发防碰撞仲裁最终把 4 字节 UID 按顺序放进 FIFO函数逐个读出来再计算 BCC 校验。防碰撞返回的 UID 是 4 字节这是 M1 卡的固定格式。如果你要兼容某些 UID 长度为 7 字节的特殊卡这个函数就得改成循环读取工程只面向标准 M1 卡所以直接按 4 字节处理。清 CollReg 标志这步容易被忽略——上次防碰撞的状态位不清掉下一次读卡可能在错误状态下开始。如果要读卡上的数据比如门禁卡的用户编号存在某个块里就要在防碰撞之后加上认证和读块两步uint8_t key[6] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; status RC522_Auth(PICC_AUTHENT1A, 1, key, uid); if (status MI_OK) { status RC522_Read(1, buffer); // 读出 block 1 的 16 字节 } RC522_StopCrypto();M1 卡的 block 0 是厂商只读区存着厂商代码和 UID不能改。用户数据一般写在 block 1 到 3。默认出厂的 Key A 和 Key B 全是 0xFF如果卡的密码被改过RC522_Auth 就会失败返回的 status 不是 MI_OK。每次认证完一定要调 RC522_StopCrypto() 退出加密状态否则下一张卡的时序会乱掉这是刷第二张卡失败的经典原因。4.4 UART 帧格式与主循环上报卡号读出来了怎么发给上位机这个工程的方案是自定义一个 8 字节帧字节位置内容说明frame[0]0xAA帧头 1frame[1]0x55帧头 2frame[2]0x01命令字0x01 表示刷卡上报frame[3..6]UID[0..3]卡号4 字节frame[7]SUM前 7 字节的异或校验和void SendCardFrame(const uint8_t *uid) { uint8_t frame[8]; uint8_t sum 0; frame[0] 0xAA; frame[1] 0x55; frame[2] 0x01; // 命令字刷卡上报 memcpy(frame[3], uid, 4); // 卡号放中间 for (uint8_t i 0; i 7; i) { sum ^ frame[i]; // 异或校验 } frame[7] sum; for (uint8_t i 0; i 8; i) { UART_UartPutChar(frame[i]); } }帧头用两个字节0xAA 0x55 在上位机那边容易做同步。命令字是为后续扩展预留的比如改成 0x02 表示写卡完成上报。校验用异或和是因为帧短、调试期够用正式产品建议换成 CRC16我第 6 章会讲。主循环里还有一个容易忽略的细节——防重复上报。如果卡片一直贴在天线上寻卡会一直成功不加限制的话上位机会被同样的卡号刷屏。工程里的做法是缓存上次的卡号并记录上报时间uint32_t lastUid 0; uint32_t lastTick 0; if (ReadCardUid(uid) MI_OK) { uint32_t now ((uint32_t)uid[0] 24) | ((uint32_t)uid[1] 16) | ((uint32_t)uid[2] 8) | uid[3]; if (now ! lastUid || CyGetTickCount() - lastTick 300) { SendCardFrame(uid); lastUid now; lastTick CyGetTickCount(); } }这段逻辑的意思是换了新的卡立刻上报还是同一张卡至少间隔 300ms 才会上报一次。如果产品需求变成“卡一直放着每 5 秒上报一次心跳”把 300 改成 5000 就行框架不用动。5. 常见问题排查调 RC522 时踩过的五个坑以下五条都是实际调板时遇到过的按现象、原因、解决三步写好。排查时先用万用表量供电再用逻辑分析仪抓 SPI两个工具能解决八成问题。5.1 读卡无反应天线端电压异常现象上电后刷卡完全没反应万用表量天线线圈两端电压只有几十毫伏。原因模块供电接的是 5V而 PSoC 的 IO 是 3.3V两边没共地或者 RC522 一直处于复位状态天线驱动根本没打开。解决把模块 VCC 统一改为 3.3V并确认 GND 和 PSoC 共地。再用示波器测 TX1/TX2 引脚正常读卡时会出现调制波形。如果一直没波形检查 TxControlReg 是不是被写回了 0x00。5.2 SPI 读回来全是 0x00 或 0xFF现象上位机偶尔能收到卡号但大部分是 0x00 0x00 0x00 0x00或者一直收 0xFF。原因SPI 相位配置不对。RC522 数据手册推荐的是 Mode 0也就是 CPOL0、CPHA0但很多第三方示例工程按 CPHA1 配置MISO 采样点落在数据位边沿上读回来的值自然不对。解决用逻辑分析仪抓 SCK 和 MISO看数据在时钟的哪个相位上稳定下来然后把 SPI_Master 组件的 CPOL/CPHA 切到 Mode 0保存重新编译。另外确认片选信号要覆盖整个字节传输不要用软件按位去拉片选。5.3 串口乱码PSoC 的时钟误差现象串口助手能看到 0xAA 0x55但后面的字节偶发错误把波特率降到 4800 就恢复正常。原因PSoC 的 UART 时钟来自内部振荡器时误差可能到正负 3%。9600 波特率下容差足够但如果上位机那端的 USB 转串口芯片用的是便宜晶振两边误差叠加就会临界。解决在时钟树里给 UART 接上 PLL 或外部晶振而不是直接用内部振荡器。调试时先固定 9600不要一上来就 115200等数据稳定了再往上提。5.4 上电偶发卡死必须手动复位才恢复现象每次重新上电第一次刷卡大概率无反应按一下板子上的复位键之后就正常了。原因RC522 的 RST 脚如果悬空上电后内部状态机处于不确定状态或者 PSoC 上电后立即调用 MFRC522_Init而 RC522 的电源还没稳定寄存器写不进去。解决把 RST 脚接到 PSoC 引脚初始化时先拉低再拉高延时 10ms 后再写寄存器。main 函数里把 UART_Start、SPI_Master_Start、MFRC522_Init 的顺序固定住不要提前初始化。5.5 连续刷两张卡第二张卡号丢帧现象前一张卡拿走马上刷第二张上位机收到了第一张第二张的帧整个缺失或者卡号只有一半。原因查询式 UART 发送期间主循环还在轮询读卡。RC522 的防碰撞仲裁正在进行时UART 发送 8 个字节要占用几个毫秒这几毫秒内 RC522 已经把第二张卡的通信流程跑了一半FIFO 被覆盖。解决读卡成功后先关闭天线等 UART 发送完再重新打开天线或者把 UART 发送放到中断里让读卡状态机不被阻塞。简单粗暴的做法是刷卡成功后加 100ms 冷却延时。6. 二次开发把只读卡器升级成可写卡、可低功耗唤醒的设备6.1 从只读卡号到读写扇区工程现在只上报卡号但 RC522 的能力不止于此。要写卡先调 RC522_Auth 认证块密码再把数据组织成 16 字节写入目标块uint8_t key[6] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (RC522_Auth(PICC_AUTHENT1A, 1, key, uid) MI_OK) { RC522_Write(1, blockData); // blockData 是 16 字节数据 RC522_StopCrypto(); }注意 block 0 是厂商只读区不要写从 block 1 开始写。写完后调 RC522_Read 读回来校验养成“写后读”的习惯能挡住大部分低级错误。6.2 协议升级加长度字段和 CRC16异或校验在调试期够用产品化建议改成帧头 0xA5 0x5A加 1 字节长度、1 字节命令、N 字节数据、2 字节 CRC16。把长度放在固定偏移上位机解析时先按长度切帧再算 CRC丢帧和错帧都能识别。UART 接收端用中断加状态机收帧收到完整帧置标志位主循环再处理。6.3 低功耗刷卡唤醒PSoC 的休眠能力比普通 MCU 强可以把 RC522 的 IRQ 接到 PSoC 的外部中断引脚。平时芯片进入休眠卡片靠近时 RC522 产生 IRQ 唤醒 PSoC读卡后再回去睡待机电流能从毫安级降到微安级。注意休眠前要把 UART 和 SPI 停掉唤醒后按顺序重新 Start。从那以后我每次接新的无线模块都强制自己先把 SPI 波形抓出来、把时钟误差算一遍再开始写业务代码。希望帮到你。本文还有配套的精品资源点击获取