基于RFID与MFRC522的公交车刷卡系统设计与调试

发布时间:2026/11/2 9:08:57
基于RFID与MFRC522的公交车刷卡系统设计与调试 简介面向电子信息工程、自动化等专业学生及嵌入式入门者的电子系统设计文档围绕基于RFID的公交车刷卡系统展开可用于课程设计、毕业设计选题与小型刷卡终端方案参考。压缩包仅含1个doc文件约649KB以文字与图表形式呈现完整设计说明书便于直接查阅、摘录和二次整理。内容从研究背景与总体方案入手硬件部分涵盖Mifare One射频卡选型、AT89S52单片机、晶振电路、蜂鸣器提示音、LCD1602显示及DS1302时钟模块软件部分给出初始化、射频卡与读写器通信及总流程图并讨论可靠性与成本权衡。已有4299人学习适合需要快速理解RFID刷卡系统组成、梳理硬件模块与软件流程的读者参考也能为物流管理、身份识别等相近应用提供设计思路。1. 公交车前门那块读卡器拆开就是这几百行早高峰有人把卡贴在读卡器上,机器滴一声,余额减两块,闸机放行——这套动作背后是一颗 13.56MHz 的读头在几十毫秒内完成寻卡、防冲突、三次认证、减值、传输五步。这份 2017 级电子系统设计说明书拆的就是这套东西,题目叫基于 RFID 公交车刷卡系统设计:主控 AT89S52、读头 MFRC522、卡片 Mifare One、走时 DS1302、显示 LCD1602、提示音用蜂鸣器,从选型、电路、初始化流程一直写到射频卡与读写器的通信时序。它适合两类人看。一类是想从零抄一版能读能写、能扣钱的 51 读卡器的新手,硬件清单和流程图都齐;另一类是手里已经有 MFRC522 模块、但卡贴上去死活没反应的熟手,天线匹配和防冲突那几段值得对着示波器重看一遍。RFID 公交车刷卡不是什么新题目,难的是把射频链路、卡片协议和扣费逻辑串成一条不断电的闭环。2. 硬件选型与器件清单一块 51 板子上怎么摆下整条刷卡链路这份设计说明书最值钱的部分不是代码,是选型理由。做过课设的人都知道,题目给个公交车刷卡系统,方案能写十页,但真正落到 BOM 表上,每一个器件都要回答为什么不是另一个。这一章按说明书里的硬件章节顺序,把射频卡、主控、时钟、提示音、显示五个决定点拆开讲,最后给一张能直接照着采购的模块职责表。2.1 射频卡选型为什么是 Mifare One 而不是磁卡或接触式 IC 卡说明书里把市面上的卡分成四类:条码、磁卡、接触式 IC 卡、非接触式 IC 卡。结论很清楚——条码和磁卡在超市、银行用得多,成本低但易复制、怕折;接触式 IC 卡早年公共电话上用,芯片外露,插拔几百次之后触点氧化、卡面折断都是常事。最后落到非接触式 IC 卡,芯片和线圈一起封在 PVC 里,不怕水不怕折,读卡距离几厘米,这正是公交场景要的形态。具体型号选了 Mifare One,也就是圈内常说的 M1 卡,兼容 ISO14443A 协议,工作频率 13.56MHz,容量 1K 字节,分 16 个扇区、每扇区 4 个块。这个选择的后果要提前想清楚:1K 容量对公交扣费完全够用,一个扇区拿来做钱包块,一个扇区存卡号与开户信息,剩下的留给扩展;但 M1 的加密算法是私有的 Crypto1,只做课设和原型没问题,真要商用得掂量一下密钥体系和卡片克隆风险。我一般会把卡型选择写成一句可验证的话:读卡器能不能稳定读到 UID、能不能对指定扇区做认证、能不能对值块做加减。这三件事只要有一件做不到,卡型就得换。2.2 主控与时钟AT89S52 加 11.0592MHz 的组合账说明书选 AT89S52 的理由写得很实在:成本低、功能够、生态成熟,DSP、ARM 性能够但价格高、当时资料少。AT89S52 是 40 引脚、32 个双向 I/O、8K Flash、256 字节 RAM、3 个 16 位定时器、1 个全双工串口,兼容标准 MCS-51 指令集,支持在线编程,反复擦写能压低开发成本。真正体现经验的是晶振那一节:11.0592MHz,而不是教科书上常见的 12MHz。原因只有一个——串口。51 的串口工作在方式 1 时,波特率由定时器 1 的溢出率除以 32 再除以(2^SMOD)得到,11.0592MHz 恰好能被 9600、19200 这些常用波特率整除,误差为 0;换成 12MHz,9600 波特率下误差接近 7%,连续收几十个字节就会累积到错位,表现就是上位机界面上偶尔蹦出一两个乱码字符。这种问题查起来很玄学,换晶振就没了。晶振旁边两个 30pF 的无极性电容是负载电容,分别接在 XTAL1、XTAL2 与地之间。它的作用是配合晶体本身的负载电容参数,让谐振频率落在标称值上,同时影响起振速度和幅度。容量选小了起振慢甚至不起振,选大了频率偏移,一般按晶体规格书给的 CL 值反推,30pF 是这套板子上的经验值。2.3 外围三件套蜂鸣器、LCD1602、DS1302 的接法提示音模块用蜂鸣器,说明书里的电路逻辑写得很清楚:单片机 I/O 直接驱动能力不够,中间加一级三极管。P2.3 输出高电平时三极管截止,蜂鸣器回路断开不响;输出低电平时三极管导通,回路闭合发声。这里有个容易被忽略的好处——51 复位瞬间所有 I/O 默认输出高电平,如果电路设计成高电平响,上电那一下会嘀一声误报,低电平驱动正好避开。软件上只要在初始化里先把 P2.3 置高,后续按响 100ms、停 50ms的节奏给脉冲就行。显示用 LCD1602,16 字符 2 行的字符型点阵模块,控制器基本都是 HD44780 兼容芯片。它的价值在于控制指令通用:清屏 0x01、光标归位 0x02、显示开 0x0C、功能设置 0x38(8 位数据、2 行、5×7 点阵),这几条指令写对,换任何一家 1602 屏都能跑。接线时别忘 RW 引脚要么接地写模式,要么由单片机控制,悬空是最常见的上电显示一排黑块原因。时钟用 DS1302,低功耗、带 31 字节 RAM、支持年月日周时分秒计时和闰年补偿,工作电压 2.5V 到 5.5V,三线接口(复位 CE、时钟 SCLK、数据 I/O)与单片机同步通信,还支持突发模式一次读写多个字节。公交车刷卡系统里时间的用处有两个:交易流水打时间戳,以及运营方按时间段做票价策略。没有时钟芯片,掉电之后流水时间全是乱的,事后对账根本没法看。2.4 系统总体框图四个模块的职责边界说明书把系统分成控制模块、发送接收数据模块、串行通信模块、显示模块四块。这种切法是对的,因为它按谁负责什么划界,而不是按谁挨着谁划界。落到板上就是下面这张表:模块核心器件职责对外接口控制模块AT89S52 11.0592MHz 晶振调度流程、跑协议状态机、管超时全部 I/O收发数据模块MFRC522 13.56MHz 晶振 天线产生载波、调制发送、解调接收SPI 片选、SCK、MOSI、MISO、RST、IRQ串行通信模块单片机 UART与上位机/上位系统交换卡号、金额、流水TXD、RXD显示与提示LCD1602、蜂鸣器显示余额与状态、给用户反馈8 位并口、单根 I/OMFRC522 内部又分模拟和数字两部分:模拟部分驱动天线,提供 13.56MHz 能量载波,按寄存器设置调制发送数据,同时检测并解调卡片返回的信号;数字部分通过并口/SPI 和中断与单片机打交道。天线那一段由 LC 低通滤波器和 LC 谐振电路组成,这一块是整套硬件里最容易翻车的地方,下一章单独讲。3. 射频链路拆解MFRC522 和 13.56MHz 天线之间到底发生了什么很多人以为读卡器是单片机发个命令,卡就把卡号送回来,实际上从单片机到卡片中间隔着三层:寄存器层、射频层、协议层。这一章按信号流动的方向走一遍,把说明书里那张系统框图中发送接收数据模块拆开,顺便把天线匹配这个高频坑说透。3.1 寄存器层MFRC522 的模拟部分与数字部分怎么配合MFRC522 是 NXP 的 13.56MHz 非接触读卡芯片,支持 ISO14443A/B、Mifare 系列。它的寄存器分成几组:命令寄存器(CommandReg)决定当前执行哪个动作,中断寄存器(CommIrqReg)报告收发完成、定时器超时、CRC 错误,错误寄存器(ErrorReg)记录冲突、协议错误,FIFO 相关寄存器负责数据进出。单片机要做的其实很简单:把数据写进 FIFO、配置位速率和帧格式、置位开始发送、等中断、读 FIFO、查错误。以 SPI 模拟为例,51 没有硬件 SPI,只能用 I/O 口按时序打。常见写法是这样:/* 51 模拟 SPI 访问 MFRC522 寄存器CS 低有效CPOL0 CPHA0 */ static void rc522_write_reg(uint8_t addr, uint8_t val) { uint8_t i; RC522_CS 0; /* 片选拉低选中芯片 */ addr (addr 1) 0x7E; /* 写操作地址左移一位bit0 0 */ for (i 0; i 8; i) { /* 先发地址字节MSB 在前 */ RC522_SCK 0; RC522_MOSI (addr 0x80) ? 1 : 0; addr 1; RC522_SCK 1; /* 上升沿被从机采样 */ } for (i 0; i 8; i) { /* 再发数据字节 */ RC522_SCK 0; RC522_MOSI (val 0x80) ? 1 : 0; val 1; RC522_SCK 1; } RC522_CS 1; /* 释放片选 */ }这段代码的关键在两处。一是地址字节的 bit0:写操作置 0、读操作置 1,地址左移一位是因为寄存器地址只占 7 位,最高位留给读写标志,写错这一位会出现写进去了但读不出来的假象。二是时序,SCK 空闲为低(CPOL0),数据在 SCK 上升沿被芯片采样(CPHA0),每条指令之间 SCK 至少要拉低再拉高一次。如果 SPI 速率调得太快,或者杜邦线超过 20cm,读 VersionReg(0x37)会返回 0x00 或 0xFF,这是连线问题的第一诊断点。上电之后的第一件事是软复位:CommandReg 写 0x0F,等 50ms,再读 VersionReg 确认芯片在线,正常应读到 0x91 或 0x92。读到别的值,先别怀疑代码,查供电和地线。3.2 射频层LC 低通加谐振,天线不调的后果就是贴上去也不读MFRC522 的 TX1、TX2 输出的是 13.56MHz 的方波驱动信号,直接接线圈会辐射大量谐波,所以中间要串 LC 低通滤波;线圈本身再和并联/串联电容构成谐振回路,谐振在 13.56MHz 时天线两端电压最高、场强最强,读卡距离才够。这一块的参数由线圈电感量决定,而线圈电感量又由匝数、直径、线宽决定,所以换了天线就必须重算电容,不能照抄。说明书里提到如果对天线系统进行优化还可以达到更大范围,这句话的实操含义是:用示波器差分探头量 TX1/TX2 上的载波幅度,或者量天线两端电压,调匹配电容让幅度达到最大且波形不削顶。调之前先把天线远离金属台面,PCB 板下面垫一层绝缘,不然你调的永远是被金属拉偏的谐振点。我见过最典型的翻车是读卡距离只有 5mm,离近了能读、稍微抬一点就掉,最后发现是匹配电容焊错了一个数量级。软件层面也要配合:TxControlReg(0x14)的 TX1RFEn、TX2RFEn 两位必须置 1,天线才真正输出载波,很多初始化代码漏了这一步,表现就是寄存器读写全正常、卡片毫无反应。3.3 协议层一次刷卡从 REQA 到 UID 的完整时序卡片进入场区后不是立刻把卡号喊出来,而是走一套 ISO14443A 的严格流程,说明书里归成五步:复位应答、防冲突、选择卡片、三次互相确认、数据块操作。挨个说。复位应答(ATQA):读卡器发 REQA(0x26)或 WUPA(0x52),场区内所有卡用 ATQA 应答。这一步本质是验证卡型,确认对面是 ISO14443A 的卡还是别的协议。如果读卡器发的是 WUPA,处于 HALT 状态的卡也会被唤醒,这在公交场景里很重要——乘客刷完卡没拿走,下一次贴上来还能读。防冲突:多张卡同时在场时,各自的 UID 会在同一时刻回传造成冲突。MFRC522 的 CollReg 会记录冲突发生在哪一位,读卡器据此逐位筛选,最后选中一张。未被选中的卡回到空闲模式等下一轮。这一步的工程价值在于:公交车上乘客的卡可能隔着包、隔着口袋,场区内同时存在两三张卡是常态,没有防冲突就会出现刷卡失败请重试。选择卡片:读卡器发 SEL(0x93)加级联级别和已知 UID 位,被选中的卡返回完整 UID 和 SAK(卡容量代码),SAK 低 3 位能看出卡片类型。到这一步,读卡器才真正拿到 4 字节或 7 字节 UID。三次互相确认:选定卡之后,读卡器指定要访问的扇区号,发送认证命令(0x60 认证 KeyA,0x61 认证 KeyB)加块地址和 6 字节密钥,双方用 Crypto1 做三轮质询应答。认证通过后才能建立加密流通信。注意换扇区必须重新认证,不存在认证一次全场通行。后面才是数据块操作:读块、写块、初始化钱包、加值、减值、存储、传输、中止。这条链路里任何一步超时,整个流程就得从头开始,所以工程代码里必须有超时计数,不能让主循环卡死在等中断上。3.4 一条容易被忽略的工程约束:读卡间隔与 Halt卡被读完之后,标准做法是发一次 Halt 命令让它进入暂停态,避免下一轮寻卡又把它选中,导致同一次刷卡被重复扣费。如果卡片一直贴在读卡器上不走,主循环里要有同一 UID 在 N 秒内不重复处理的去抖逻辑,以及超过 N 秒后主动 Halt 的处理。这一条在说明书里没展开,但凡是做过刷卡设备的,基本都在这上面翻过车。4. 软件分层初始化、寻卡、防冲突、扣费的状态机怎么写说明书把软件部分拆成系统初始化、射频卡与读写器通信、软件总流程图三块。这个划分适合写文档,但真正落代码时我更习惯按驱动层—协议层—业务层三层切:驱动层管 SPI 和寄存器,协议层管寻卡认证读写,业务层管扣费、显示、串口上报。下面按这个顺序给可抄的骨架,代码都是 51 C,按这套板子的常见写法补全。4.1 上电初始化:时钟、串口、定时器、中断的先后顺序初始化顺序错了会出现很难查的随机故障,比如串口收到一半数据丢失、定时器中断进不去。稳妥的顺序是:先关全局中断,再配时钟和外设时钟分频,然后配串口和定时器,最后开全局中断。说明书里提到的使能 PLL 但不连接、设置分频比、等待锁定、再连接是 ARM 平台的流程,51 上没有 PLL,对应的是晶振起振稳定加软件延时,但分步使能、别一步到位的思路是一样的。void SystemInit(void) { EA 0; /* 先关全局中断避免配置过程中被打断 */ P2 0xFF; /* 蜂鸣器接 P2.3上电先置高防止误响 */ UartInit(); /* 串口 9600bps用于上位机通信 */ Timer0Init(); /* 定时器0 做 1ms 基准供超时计数 */ Lcd1602Init(); /* LCD 初始化含 15ms 上电延时 */ DS1302Init(); /* 时钟芯片初始化写控制字解除写保护 */ RC522Init(); /* 读头软复位 寄存器配置 开天线 */ EA 1; /* 外设就绪后再开全局中断 */ } void UartInit(void) { SCON 0x50; /* 方式18 位数据允许接收 */ TMOD 0x0F; /* 清定时器1 控制位 */ TMOD | 0x20; /* 定时器1 工作在方式28 位自动重装 */ TH1 0xFD; /* 11.0592MHz 下 9600bpsSMOD0 */ TL1 0xFD; TR1 1; /* 启动定时器1产生波特率 */ ES 1; /* 开串口中断 */ }TH1 0xFD这个值是可以算出来的:11.0592MHz / 12 / 32 / (256 - 0xFD) ≈ 9600,误差 0。如果板上焊的是 12MHz 晶振,同样写 0xFD 得到的是 10000 左右,误差 4% 以上,短帧勉强能用,长帧必丢。TMOD那两行要先清后或,直接赋值会把定时器 0 的配置一起冲掉——这是 51 新手最常见的自伤。RC522 的初始化里必须包含这几步:软复位、TModeReg 和 TPrescalerReg 配置内部定时器、TReloadRegH/L 设置超时值、TxASKReg 设 0x40 开启 100% ASK 调制、ModeReg 设 0x3D、最后 TxControlReg 写 0x03 打开两路天线驱动。少任何一步,后面的寻卡函数都会返回超时。4.2 寻卡与防冲突:一次完整读 UID 的代码骨架协议层最好用一个小的状态机封装,别在业务代码里直接操作寄存器。下面是寻卡加获取 UID 的核心函数,按 MFRC522 常见驱动风格写:#define PICC_REQA 0x26 /* 寻卡命令只唤醒空闲态卡片 */ #define PICC_WUPA 0x52 /* 唤醒命令HALT 态卡片也能响应 */ #define PICC_SEL 0x93 /* 选卡命令级联级别1 */ /* 返回 0 表示成功读出 4 字节 UID非 0 为错误码 */ uint8_t PcdReadUid(uint8_t *uid) { uint8_t atqa[2]; uint8_t buf[9]; uint8_t len; if (PcdRequest(PICC_REQA, atqa) ! 0) /* 第一步复位应答 */ return 1; if (PcdAnticoll(buf) ! 0) /* 第二步防冲突拿 5 字节 */ return 2; /* buf[0] 是异或校验值 */ buf[0] PICC_SEL; /* 第三步选卡 */ buf[1] 0x70; /* NVB 0x70已知完整 UID */ memcpy(buf[2], buf[0], 0); /* 实际代码这里拷 5 字节 UID */ len 7; if (PcdTransceive(buf, len) ! 0) return 3; memcpy(uid, buf[0], 4); /* 取出 4 字节 UID */ return 0; }三个函数的分工要说清楚。PcdRequest负责发 REQA 或 WUPA 并等 2 字节 ATQA,超时返回错误,是判断场区内有没有卡的第一道门。PcdAnticoll调用 MFRC522 的防冲突命令,芯片自动完成逐位筛选,返回 5 字节:4 字节 UID 加 1 字节异或校验。PcdTransceive是通用收发函数,发什么收什么,CRC 由芯片自动附加或由软件补。实际写的时候,PcdRequest里要清空 FIFO、清中断标志、把命令写进 FIFODataReg、CommandReg 写 0x0C(Transceive)、然后把 BitFramingReg 的 StartSend 位置 1 启动发送,再轮询 CommIrqReg 等 RxIRq 或 TimerIRq。等中断的地方务必加超时,不然卡拿走的瞬间读卡器会永远卡在那里。防冲突返回的 5 字节里,最后一字节是所有 UID 字节的异或,不校验这个值,偶尔会读到看起来正常但实际错位的 UID,表现出来就是同一个用户有时能扣费有时提示未开户。4.3 三次认证与扇区密钥:别把密钥写死在业务代码里拿到 UID 之后要访问钱包块,必须先认证。Mifare One 的认证命令是 0x60(KeyA)或 0x61(KeyB),后面跟块地址和 6 字节密钥。厂商默认密钥是 6 个 0xFF,很多教学板卡出厂不改,所以演示阶段一切正常,一旦卡片发卡时改过密钥,代码里写死 0xFF 就直接认证失败。/* 认证指定扇区key 为 6 字节sector 由块地址右移 2 位得到 */ uint8_t PcdAuthSector(uint8_t block, uint8_t *key) { uint8_t buf[12]; uint8_t len; buf[0] 0x60; /* 0x60 用 KeyA 认证 */ buf[1] block; /* 认证目标是该块所在扇区 */ memcpy(buf[2], key, 6); /* 6 字节密钥出厂常为 FF*6 */ len 8; return PcdTransceive(buf, len); /* 成功返回 0失败通常是 2 或超时 */ }这里的参数block不是随便给的,认证作用于整个扇区,但你得告诉芯片你要访问哪个块,它据此推算扇区号。地址算错一格,认证的是隔壁扇区,密钥对也失败。密钥管理上我的建议是:哪怕课设也把密钥放进单独的card_key.h,按卡种分数组,业务层只传索引,后面换密钥不用满工程搜0xFF。还有一条常见的误解:认证成功之后只有当前扇区可读写,换扇区必须重新认证;而且认证状态在卡片被 Halt 或离开场区后立即失效,下次刷卡要重来一遍。4.4 钱包块操作:加值、减值、传输,以及那个必须做的回读Mifare One 的值块有固定格式,不是随便存个整数。一个值块 16 字节,结构是:4 字节值(小端) 4 字节值取反 4 字节值 1 字节块地址 1 字节块地址取反 2 字节保留。这套冗余设计是为了容错,任何一段损坏都能用其他段恢复。所以你不能用普通写块去改余额,必须用专门的加值/减值命令,让芯片自己维护这四段的一致性。扣费流程的标准姿势是:认证 → 读当前值块 → 判断余额是否够 → 减值(Decrement)→ 传输(Transfer)→ 回读校验。/* 公交扣费从钱包块扣 amount 分返回 0 成功 */ uint8_t WalletDebit(uint8_t block, uint32_t amount) { uint32_t balance; if (PcdReadValue(block, balance) ! 0) /* 先读余额 */ return 1; if (balance amount) /* 余额不足不发起减值 */ return 2; if (PcdValueOp(PICC_DECREMENT, block, amount) ! 0) return 3; /* 减值结果进内部寄存器 */ if (PcdValueOp(PICC_TRANSFER, block, 0) ! 0) return 4; /* 传输寄存器写回卡内 */ if (PcdReadValue(block, balance) ! 0) /* 回读确认真的写进去了 */ return 5; return 0; }最关键的细节是TRANSFER这一步不能省。减值命令只是把新值放进卡片的内部数据寄存器,不执行传输,卡内的值块一个字节都没变。断电、移卡、下一位乘客刷卡,余额还是原来的。这个坑在课设答辩现场非常经典——演示时看着余额减了,重新读一次又回去了。回读校验也别省,它是唯一能确认这笔交易真的落到卡上的手段。业务层还要配一张本地流水表,记录 UID、时间、金额、交易序号;51 的 RAM 只有 256 字节,放不下多少条,常见做法是写到外挂的 24C02 或者干脆通过串口实时上报给上位机。真要做成产品,掉电保护和流水补传是绕不过去的。4.5 主循环:显示、提示音、串口上报怎么不互相阻塞主循环的结构建议是查询 短处理,不要在里面写长延时。void main(void) { uint8_t uid[4]; SystemInit(); while (1) { if (PcdReadUid(uid) 0) { /* 有卡且读到 UID */ if (!IsSameCardInCooldown(uid)) { /* 同卡 3 秒内不重复处理 */ Beep(100); /* 蜂鸣器响 100ms */ LcdShowUid(uid); HandleTransaction(uid); /* 扣费 流水 串口上报 */ PcdHalt(); /* 置卡片为暂停态 */ } } LcdRefreshClock(); /* 非阻塞刷新时间显示 */ } }IsSameCardInCooldown是去抖,防止一张卡贴着不走被连扣。PcdHalt让卡进入暂停态,下一轮 REQA 不会选中它,乘客拿走再贴回来才会被 WUPA 唤醒。LcdRefreshClock内部用 1ms 定时器做计数,每 500ms 才真正刷一次屏,不然 LCD 刷太频繁会闪。串口上报建议用中断发送加环形缓冲,主循环只负责把帧塞进缓冲,不要在中断里做长逻辑。5. 避坑与排查:调试台上最容易翻车的 5 个坑这一章是按现象 → 原因 → 解决记的现场笔记,顺序大致按调试时遇到的先后排。每条都对应这套 51 MFRC522 M1 的组合,换个平台道理也通。5.1 现象:寄存器读写全正常,卡片贴上去毫无反应原因排查下来通常有三个。第一,没开天线——TxControlReg(0x14)的 TX1RFEn、TX2RFEn 没置位,芯片内部一切正常但天线不出载波,读 VersionReg 依然是 0x91,特别有迷惑性。第二,天线匹配没调,载波出来了但场强太弱,读卡距离只有几毫米。第三,天线附近有金属或大面积覆铜,谐振点被拉偏。解决的办法是分层验证:先用万用表确认 3.3V 供电和地去耦电容,再读 VersionReg 确认 SPI 通,然后单独写一段代码只开天线、循环发 REQA,用示波器看天线两端有没有 13.56MHz 的载波。载波有但读不到卡,就往匹配电容上调;载波都没有,回去查 TxControlReg 和 RST 引脚电平。我一般会把只开天线不寻卡做成一个独立测试函数,出问题先跑它。5.2 现象:同一张卡,有时能读到有时读不到,UID 还偶尔不一样原因多半在防冲突环节。MFRC522 的防冲突结果里带一个异或校验字节,代码里如果不校验,偶尔会拿到错位的 UID;另外,多张卡同时在场时如果不做完整的防冲突循环,选中的卡可能每次不同。还有一种情况是电源纹波大,天线驱动瞬间拉低电压导致芯片复位,表现也是时好时坏。解决:防冲突返回的 5 字节必须做异或校验,不通过就重来一次;多卡场景循环调用寻卡直到拿到稳定 UID;电源侧在天线驱动旁边并 10uF 加 0.1uF,USB 供电改成独立稳压。另外读卡成功后一定要发 Halt,不然同一张卡会在连续几轮里被反复选中,日志看起来就像读到了两张卡。5.3 现象:扣费显示成功,重新读卡余额又变回去了原因就是前面说的:只发了 Decrement 没发 Transfer。减值结果暂存在卡片内部数据寄存器,不传输不回写,卡内值块没动。另一种变体是发了 Transfer 但没回读,卡片在传输瞬间被拿走,交易其实没落盘,而界面已经提示成功。解决:把减值 → 传输 → 回读比对写成不可拆分的三步,任何一步失败都按交易失败处理并提示重刷;业务层记录交易序号,回读的余额必须等于期望值才算成功。这一点在演示前一定要用刷完立刻拿开的方式测十次,而不是慢慢放着不动。5.4 现象:LCD1602 上电显示一排黑块,或者只显示一行原因有三个常见来源:初始化延时不够,HD44780 上电后需要 15ms 以上才接受指令,很多人只写个几微秒的循环;RW 引脚悬空或者接错,导致芯片一直处于读状态;对比度引脚 V0 直接接地,显示对比度调到极端。解决:初始化里老老实实延时 15ms,再发 0x38、0x0C、0x01 三条基础指令,每条之间留 2ms;RW 要么直接接地,要么代码里每次操作前先拉低;V0 接一个 10K 电位器分压,调到字符清晰不重影。显示乱码还有一种可能是数据口接反了高低四位,这个用万用表逐根对一遍就能确认,别硬猜。5.5 现象:串口上位机收到的卡号偶尔多一位或少一位,重启又正常原因是波特率误差或帧处理没做边界保护。11.0592MHz 配 0xFD 是准的,但如果板上实际焊的是 12MHz 晶振,误差就会累积,长帧必错。另一种情况是中断里收数据没做环形缓冲,主循环正在刷 LCD 时来的字节被覆盖。解决:先量晶振实际频率,确认是 11.0592 而不是 12;串口接收用环形缓冲,中断只负责入队,解析放到主循环;帧格式加帧头 0xAA、长度、卡号、金额、校验和,校验不过直接丢帧重发。调试阶段可以让设备每秒主动上报一次心跳帧,上位机那边一看有没有心跳就知道链路通不通,比盲猜快得多。6. 二次开发:把课设读卡器改成能跑扣费原型的三步这份说明书的定位是电子系统设计,硬件和流程都对,但要拿去做真正的公交扣费原型,还差三块:可维护的代码分层、可追溯的交易记录、可验证的异常路径。照着改三步就能跑起来。第一步,把驱动从业务里剥出来。新建bsp_rc522.c/h、bsp_lcd1602.c/h、bsp_ds1302.c/h,业务层只调PcdReadUid、PcdAuthSector、WalletDebit三个接口,寄存器操作一律不进main.c。这么做最直接的好处是换平台便宜:后面想换 STM32,只要重写rc522_write_reg和rc522_read_reg两个函数,上层一行不用动。我通常会把寄存器地址写成宏,把命令码写成枚举,方便对照数据手册。第二步,把交易记录做成结构体加环形队列,串口帧格式固定下来。typedef struct { uint8_t uid[4]; /* 卡号取自防冲突结果 */ uint32_t amount; /* 本次扣费金额单位分 */ uint32_t balance; /* 交易后余额来自回读 */ uint8_t time[7]; /* DS1302 读出的年月日时分秒 */ uint16_t seq; /* 交易序号掉电可恢复 */ } TradeRecord;结构体别用位域,51 上对齐和访问都麻烦;amount用分做单位避免浮点;seq从外部 EEPROM 里读出来递增,掉电重启能续上,对账时靠它定位丢单。串口帧建议0xAA 长度 UID(4) 金额(4) 余额(4) 时间(7) 序号(2) 校验(1),一共 23 字节,9600 波特率下 25ms 左右发完,不会拖慢主循环。第三步,把异常路径当成主路径来测。写一个测试清单:余额不足、卡在中途拿走、同一张卡连刷五次、场区内两张卡、读卡器断电重启后继续刷、上位机不接时设备是否还能独立工作。每一条都要在代码里有明确分支和提示音节奏,比如成功一声长响、余额不足两声短响、认证失败三声短响。验证手段不用太高级,串口打印加逻辑分析仪抓 SPI 时序就够,逻辑分析仪能直接看出 StartSend 有没有发出去、FIFO 里到底有没有数据,比我当年盯着 LED 猜效率高太多。从那以后我每次上电前都会先用万用表量一遍天线匹配电容有没有虚焊、再跑一次只开天线不寻卡的空转测试,确认载波稳了才敢接业务代码——这个习惯是从一次现场演示连续三次读不出卡之后养成的,当时折腾到凌晨才发现是匹配电容焊盘裂了。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询