简介:本资源面向嵌入式开发初学者与电子工程师,提供一套基于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 Rate | 9600 或 115200 | 必须与 RFID 模块一致 |
| Data Bits | 8 | 标准配置 |
| Parity | None | 多数模块无校验 |
| Stop Bits | 1 | 标准配置 |
| RX Buffer Size | 64 或更大 | 要能容纳一帧卡号数据 |
| Interrupt | RX 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」的检查清单,哪怕当前需求用不上,也先把框架搭好,后面加功能不用返工。
希望帮到你。
本文还有配套的精品资源,点击获取