☰
基于PSoC的RFID读卡与UART串口输出实战指南
2026/10/15 19:44:51 网站建设 项目流程

简介:本资源面向嵌入式开发初学者与电子工程师,提供一套基于PSoC的RFID-UART读写器完整工程实现,帮助读者掌握可编程片上系统与射频识别技术的结合应用。压缩包共271个文件,约2.4MB,以C源码与头文件为核心,辅以汇编启动文件、库文件、工程配置及烧录输出文件,覆盖从底层驱动到上层通信的完整代码链路。资源围绕PSoC架构配置、RFID标签读写机制、UART串行通信参数设定及主机端数据交互展开,涉及天线设计、固件编程与通信调试等关键环节,可应用于库存管理、物流追踪、门禁控制等场景。目前已有203人学习下载,适合希望深入理解RFID读写器软硬件协同设计、积累PSoC项目实战经验的开发者参考借鉴。

1. 从一块 PSoC 板子说起:RFID 读卡到串口输出到底怎么落地

手头有一块 PSoC 开发板,想接个 RFID 读卡模块,把卡号通过 UART 吐给上位机——这个需求听起来简单,但真动手时你会发现几个绕不开的问题:PSoC 的 UART 组件怎么配、RFID 模块的通信协议是 SPI 还是 UART、卡号怎么从原始字节转成可读字符串、中断优先级怎么排。这套「基于 PSoC 的 RFID-UART 读写」资源包,解决的就是从读卡到串口输出这条完整链路。它适合已经摸过 PSoC Creator 或 PSoC Designer、对串口通信有基本概念、但还没把 RFID 读卡流程跑通的嵌入式开发者。如果你正在做门禁、考勤、物料追踪这类需要读卡再上传的小系统,这套东西能帮你省掉从零翻数据手册的时间。下面按「硬件链路怎么搭 → 固件怎么写 → 坑在哪 → 怎么验证」的顺序拆开讲。

2. 硬件链路与通信协议:先搞清楚数据从哪来到哪去

2.1 RFID 模块选型与接口模式

市面上常见的 13.56MHz 读卡模块,按接口分主要有三种:SPI、UART、I2C。这套资源走的是 UART 路线,原因很直接——PSoC 的 UART 组件配置简单,不需要额外写时序驱动,中断收数据就行。常见做法是选一款支持 UART 输出的读卡芯片模组,比如基于 RC522 或 PN532 的串口版本,模块上电后主动或被动输出卡号帧。

这里有个选型边界要注意:SPI 接口的模块速率高、适合高频轮询,但需要自己写寄存器操作;UART 接口的模块通常内置了协议栈,你拿到的是已经解析好的帧数据,开发快但灵活性低。如果你只是读卡号,UART 模块足够;如果要读写卡内扇区数据、做密钥认证,SPI 更合适。这套资源定位在「读卡 + 串口转发」,所以 UART 是合理选择。

模块与 PSoC 的连接一般四根线:VCC、GND、TX、RX。注意模块的 TX 接 PSoC 的 RX,模块的 RX 接 PSoC 的 TX,交叉接法。供电电压要确认,3.3V 和 5V 混接是烧模块的常见原因。

2.2 PSoC UART 组件配置要点

在 PSoC Creator 里拖一个 UART(SCB)组件出来,几个关键参数必须对齐:

参数项典型值说明
Baud Rate9600 或 115200必须与 RFID 模块一致
Data Bits8标准配置
ParityNone多数模块无校验
Stop Bits1标准配置
RX Buffer Size64 或更大要能容纳一帧卡号数据
InterruptRX FIFO Not Empty收到数据即触发

波特率不匹配是最常见的「收不到数据」原因。模块出厂默认 9600 的居多,但有些模组可以通过配置卡改成 115200。我一般先用 9600 跑通,再根据数据量决定要不要提速。

2.3 数据帧格式与解析逻辑

RFID 模块通过 UART 吐出来的数据通常是一串十六进制字节。以常见的 Mifare 卡为例,模块读到卡后会输出类似0x02 0x0A 0x04 0xXX 0xXX 0xXX 0xXX 0x03这样的帧:帧头、长度、卡号字节、校验、帧尾。不同模块的帧格式不一样,有的带起始符和结束符,有的直接裸发卡号。

解析的核心思路是状态机:等待帧头 → 收长度 → 收数据 → 收校验 → 校验通过后提取卡号。下面是一段典型的解析代码框架:

// RFID UART 帧解析状态机(简化版) #include "project.h" #define FRAME_HEADER 0x02 #define FRAME_TAIL 0x03 #define MAX_FRAME_LEN 16 typedef enum { STATE_IDLE, // 等待帧头 STATE_LEN, // 接收长度字节 STATE_DATA, // 接收数据 STATE_TAIL // 等待帧尾 } ParseState; static ParseState state = STATE_IDLE; static uint8_t buf[MAX_FRAME_LEN]; static uint8_t idx = 0; static uint8_t expectLen = 0; // 在 UART RX 中断中逐字节调用 void RFID_ParseByte(uint8_t byte) { switch (state) { case STATE_IDLE: if (byte == FRAME_HEADER) { state = STATE_LEN; idx = 0; } break; case STATE_LEN: expectLen = byte; if (expectLen > MAX_FRAME_LEN) { state = STATE_IDLE; // 长度异常,复位 } else { state = STATE_DATA; } break; case STATE_DATA: buf[idx++] = byte; if (idx >= expectLen) { state = STATE_TAIL; } break; case STATE_TAIL: if (byte == FRAME_TAIL) { // 一帧完整数据到手,提取卡号 RFID_OnFrameReceived(buf, expectLen); } state = STATE_IDLE; break; default: state = STATE_IDLE; break; } }

这段代码的逻辑说明:状态机从 IDLE 开始,检测到帧头后进入长度接收状态;长度字节决定后续收多少数据;数据收满后等帧尾;帧尾正确才认为一帧有效。参数方面,FRAME_HEADER和FRAME_TAIL要根据你实际模块的协议文档改,MAX_FRAME_LEN设成你模块最大帧长加余量。中断里只做收字节和状态切换,卡号处理放到主循环或回调里,避免中断里做耗时操作。

2.4 卡号转字符串与 UART 发送

拿到卡号字节后,通常要转成十六进制字符串再通过另一个 UART 发给上位机。转换逻辑不复杂,但要注意大小写和分隔符格式:

// 将卡号字节转为 "A1B2C3D4" 格式字符串并通过 UART 发送 void RFID_SendCardID(const uint8_t *card, uint8_t len) { char out[32]; uint8_t pos = 0; for (uint8_t i = 0; i < len; i++) { // 高四位 uint8_t hi = (card[i] >> 4) & 0x0F; uint8_t lo = card[i] & 0x0F; out[pos++] = hi < 10 ? ('0' + hi) : ('A' + hi - 10); out[pos++] = lo < 10 ? ('0' + lo) : ('A' + lo - 10); } out[pos++] = '\r'; out[pos++] = '\n'; UART_PutArray((uint8_t *)out, pos); // 阻塞发送,数据量小可接受 }

参数说明:card是卡号字节数组,len是字节数(Mifare 卡通常 4 字节或 7 字节)。输出格式选了「大写十六进制 + 回车换行」,方便上位机按行读取。如果你的上位机要求小写或带冒号分隔,改out的填充逻辑即可。UART_PutArray是阻塞发送,卡号数据量小,阻塞几十微秒可以接受;如果主循环还有其他实时任务,改成中断发送或 DMA。

3. 固件工程搭建:从 PSoC Creator 到可烧录的 hex

3.1 工程创建与组件摆放

打开 PSoC Creator,新建工程,选对你的芯片型号。这一步别选错,芯片型号不对后面编译能过但烧录会报错。工程建好后,在 TopDesign 里放这些组件:

  • UART (SCB):一个用于接 RFID 模块(RX 为主),一个用于接上位机(TX 为主)。如果芯片只有一个 SCB,可以用 UART 半双工或软件模拟一个 TX。
  • Clock:给 UART 提供时钟源,通常 24MHz 或 48MHz,UART 组件内部会分频。
  • Pin:RX、TX 引脚分配到实际物理管脚,对照开发板原理图来。
  • Interrupt:UART RX 中断,优先级设成中等,别设最高——除非你只做这一件事。

组件摆完后点 Build,让 PSoC Creator 生成 API 头文件。生成的函数名类似UART_Start()、UART_GetChar()、UART_PutArray(),具体前缀取决于你给组件起的名字。

3.2 中断服务函数与主循环分工

中断和主循环的分工原则:中断里只做「收字节 + 喂状态机」,主循环做「帧处理 + 业务逻辑 + 发送」。这样中断执行时间短,不会阻塞其他中断。

// UART RX 中断服务函数 CY_ISR(UART_RX_ISR) { // 清除中断标志 UART_ClearRxInterruptSource(UART_INTR_RX_NOT_EMPTY); // 读出所有 FIFO 中的字节,喂给解析状态机 while (UART_GetRxBufferSize() > 0) { uint8_t byte = UART_GetByte(); RFID_ParseByte(byte); } } // 主循环 int main(void) { CyGlobalIntEnable; // 开全局中断 UART_Start(); // 启动 UART UART_RX_ISR_StartEx(UART_RX_ISR); // 注册中断 for (;;) { // 主循环可以处理其他任务 // 卡号处理在 RFID_OnFrameReceived 回调里完成 CyDelay(10); } }

逻辑说明:中断里用while把 FIFO 读空,避免遗漏字节。RFID_OnFrameReceived是你在解析状态机里调用的回调,在里面做卡号转字符串和发送。主循环目前是空的,实际项目里可以加看门狗喂狗、LED 指示、按键扫描等。

3.3 烧录与串口助手验证

编译通过后,用 MiniProg 或 KitProg 烧录。烧录成功后,打开串口助手(波特率与你的发送 UART 一致),把卡靠近 RFID 模块,应该能看到卡号字符串输出。

如果没输出,按这个顺序排查:先确认 RFID 模块的 TX 有没有数据(用示波器或逻辑分析仪看波形),再确认 PSoC 的 RX 中断有没有进(在中断里翻转一个 GPIO 用示波器看),最后确认发送 UART 的波特率和接线。这个排查顺序能帮你快速定位问题在哪一段。

4. 避坑与常见问题:那些让我熬夜的翻车现场

4.1 波特率不匹配导致收到乱码

现象:串口助手收到一堆乱码,或者偶尔收到几个正确字符但大部分是垃圾。

原因:RFID 模块的波特率和 PSoC UART 组件配置的波特率不一致。模块出厂可能是 9600,但你在 PSoC 里配了 115200;或者模块被配置卡改过波特率,你没同步改。

解决:先用示波器测模块 TX 引脚上一位数据的宽度,反推波特率。或者拿 USB-TTL 直接接模块,用串口助手从 9600 开始逐个试,确认模块实际波特率后再改 PSoC 配置。改完重新编译烧录。

4.2 中断优先级冲突导致丢数据

现象:卡放上去,有时候能读到,有时候读不到;或者读到的卡号缺字节。

原因:UART RX 中断优先级设得太低,被其他中断(比如定时器中断)抢占,FIFO 溢出丢字节。或者中断里处理时间太长,下一次中断来的时候前一次还没退出。

解决:把 UART RX 中断优先级调到中等偏上,确保它不会被无关中断长时间阻塞。中断服务函数里只做读 FIFO 和喂状态机,不做字符串转换和发送。如果 FIFO 还是溢出,把 RX Buffer Size 调大,或者提高波特率减少单字节接收时间。

4.3 电平不匹配烧毁模块

现象:模块接上后不工作,摸上去发烫,或者直接冒烟。

原因:RFID 模块是 3.3V 供电,你接了 5V;或者 PSoC 的 TX 是 5V 电平,模块 RX 只耐受 3.3V。

解决:接线前一定确认模块的供电电压和 IO 电平。3.3V 模块接 3.3V,5V 模块接 5V。如果 PSoC 和模块电平不一致,加电平转换电路或用电阻分压。烧过的模块基本救不回来,这是血泪经验。

4.4 帧解析状态机卡死

现象:一开始能读卡,跑一段时间后就不响应了,重启又正常。

原因:状态机在某个状态收到异常字节后没有复位机制。比如在 STATE_DATA 收到超过MAX_FRAME_LEN的字节,idx越界写 buf,或者状态机停在 STATE_TAIL 等一个永远不来的帧尾。

解决:给状态机加超时复位。在 STATE_LEN 和 STATE_DATA 里判断长度是否合法,非法就回 IDLE。另外加一个字节计数器,超过一定数量还没收到帧尾就强制复位。上面代码里expectLen > MAX_FRAME_LEN的判断就是干这个的,但还不够,建议再加超时。

4.5 发送 UART 阻塞导致响应慢

现象:读卡后要等很久才输出卡号,或者连续读卡时丢卡。

原因:UART_PutArray是阻塞发送,如果发送波特率低、数据量大,主循环被卡住,期间新的卡号帧来了处理不及时。

解决:卡号数据量小(十几个字节),9600 波特率下发完也就十几毫秒,一般能接受。如果确实影响响应,改成中断发送或 DMA。另一个办法是提高发送波特率到 115200,发送时间缩短到一毫秒左右。

5. 进阶技巧:用 DMA 和环形缓冲把吞吐量拉上去

前面讲的方案在低频读卡场景(比如门禁,一秒读一次)完全够用。但如果你要做高频轮询、连续读多张卡,或者主循环还有其他重任务,阻塞发送和中断逐字节处理就会成为瓶颈。我一般会做两个升级:RX 用环形缓冲 + DMA,TX 用 DMA 发送。

环形缓冲的思路是:UART RX 中断只负责把字节塞进环形缓冲,主循环从缓冲里取数据喂状态机。这样中断执行时间极短,不怕被抢占。环形缓冲的实现不复杂,两个指针(读指针、写指针)加一个数组,写指针在中断里动,读指针在主循环里动。

// 环形缓冲(简化版,假设缓冲区大小为 2 的幂) #define RING_BUF_SIZE 128 #define RING_BUF_MASK (RING_BUF_SIZE - 1) static volatile uint8_t ringBuf[RING_BUF_SIZE]; static volatile uint16_t ringWr = 0; // 中断里写 static volatile uint16_t ringRd = 0; // 主循环里读 // 中断里调用:写入一个字节 void RingBuf_Put(uint8_t byte) { uint16_t next = (ringWr + 1) & RING_BUF_MASK; if (next != ringRd) { // 缓冲区未满 ringBuf[ringWr] = byte; ringWr = next; } // 满了就丢弃,或者置溢出标志 } // 主循环里调用:取出一个字节,返回 0 表示无数据 uint8_t RingBuf_Get(uint8_t *byte) { if (ringRd == ringWr) return 0; // 空 *byte = ringBuf[ringRd]; ringRd = (ringRd + 1) & RING_BUF_MASK; return 1; }

参数说明:RING_BUF_SIZE设成 2 的幂,这样用位与代替取模,速度快。128 字节对卡号帧来说绰绰有余。ringWr和ringRd用volatile修饰,防止编译器优化出错。中断里只调RingBuf_Put,主循环里循环调RingBuf_Get喂状态机。

TX 侧用 DMA 的话,把要发送的字符串放到一个数组里,配置 DMA 通道从内存搬到 UART TX FIFO,启动 DMA 后 CPU 就可以去干别的。PSoC Creator 里有 DMA 组件,配置源地址、目的地址、传输长度,触发源选 UART TX FIFO 空。具体配置步骤在 PSoC Creator 的 DMA 组件数据手册里有详细说明,这里不展开。

验证 DMA 和环形缓冲是否生效,我一般用两个方法:一是用 GPIO 翻转配合示波器,看中断执行时间有没有缩短;二是连续快速刷卡,看丢卡率有没有下降。从那以后我每次做串口通信项目,都强制走一遍「环形缓冲 + DMA」的检查清单,哪怕当前需求用不上,也先把框架搭好,后面加功能不用返工。

希望帮到你。

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

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

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

立即咨询