资讯详情

资讯详情

基于Mifare One与MFRC522的RFID公交车刷卡系统设计

简介围绕RFID公交车刷卡系统设计的电子系统设计说明书面向电子信息工程、物联网及单片机课程设计的学习者聚焦公交一卡通场景下的非接触刷卡系统实现可用于课程设计参考、方案复现与报告撰写。压缩包共1个doc文件约649KB内容为一份结构完整的说明书包含引言、系统硬件设计、系统软件设计与总结等章节。已有4299人学习具备一定参考热度。文档围绕Mifare One射频卡与AT89S52单片机展开细化了晶振电路、蜂鸣器提示音、LCD1602显示、DS1302时钟模块及系统总体方案并给出系统初始化、射频卡与读写器通信和软件总流程图。读者可从中获取硬件选型依据、模块电路组织方式与软硬件协同设计思路也能借鉴其章节编排完成类似课设文档适合需要快速搭建RFID刷卡系统框架的读者。1. 从一张刷不上的卡说起RFID 公交车刷卡系统要解决什么早高峰队伍排到车门外前面的乘客把卡贴着读头挪了三次位置才滴一声——这个场景是不少人动手做基于 RFID 公交车刷卡系统设计的最初动机。这套方案要解决的不是能不能读卡而是三件事读卡距离稳定在 3 到 5 厘米内不漏读、一次扣款在一个钱包块里原子完成、断电重上电后时间戳和余额都对得上。它适合正在做电子系统设计、单片机课程设计、电子信息工程毕业设计的同学也适合需要一份低成本读卡终端参考方案的从业者。整套 RFID 刷卡系统方案围绕 Mifare One 卡、AT89S52 主控和 MFRC522 读写芯片展开硬件分成四个模块软件跑三条链路物料成本压到几十元这个量级板子焊完当天就能点亮。2. 方案定型Mifare One、AT89S52 与 MFRC522 的选型取舍选型这一步最容易被跳过也最容易在答辩现场被追问。很多设计说明书写成我选了某某芯片因为它便宜这等于没答。真正站得住的选型逻辑要回答三个问题候选有哪些、淘汰的理由是什么、剩下的这个在什么边界条件下会失效。2.1 射频卡条码、磁卡、接触式 IC 卡为什么都被淘汰把市面上的卡摊开看能进公交场景的其实只有四类。条码成本最低但需要光学对准、怕污损、不能写入票务场景里只能做一次性纸票。磁卡在银行和早期超市用得很多问题是磁条裸露和手机、磁扣放在一起就消磁而且读写头需要机械接触磨损后误读率直线上升。接触式 IC 卡可靠性比磁卡好芯片外露在卡面金属触点位置插拔几百次之后触点氧化、卡片弯折都会导致接触不良早年公共电话亭那种插进去拔出来反复三次的体验就是这么来的。非接触式 IC 卡把芯片和天线线圈一起封在 PVC 里没有外露触点弯折、沾水、沾灰都不影响读卡时不需要对准方向。在这个项目里选的是 Mifare One业内常叫 M1 卡符合 ISO14443A 协议容量 1K 字节。它的存储结构对公交扣款非常友好1K 字节分成 16 个扇区每个扇区 4 个块每块 16 字节每个扇区的最后一块是控制块存 A 密钥、存取控制位和 B 密钥第 0 扇区的第 0 块是厂商固化的只读块存 4 字节 UID 和厂商信息。也就是说一张卡天然能切出十几个互相独立的钱包公交扣一块、食堂扣一块、门禁读一块密钥互不干扰。这一点在答辩时是加分项——它解释了为什么不用更便宜的只读 ID 卡ID 卡只有一个不能改的序列号扣款余额没地方存必须联网查后台离线场景直接失效。卡类型读写距离可写入易损性单卡成本适用判断条码贴读否怕污损折皱极低一次性票磁卡接触是怕磁、怕磨低已淘汰接触式 IC 卡接触是触点氧化、易折中需插拔场景Mifare One3~10 cm是分扇区封装内部耐折耐污低本项目采用2.2 主控AT89S52 够不够用DSP 和 ARM 为什么被否掉主控这一层项目里定的是 AT89S52。它的账面参数是40 引脚 DIP 封装、32 个可编程 I/O、8K 字节可反复擦写的 Flash、256 字节 RAM、3 个 16 位定时计数器、1 个全双工串口兼容标准 MCS-51 指令系统支持在线编程ISP。对这套系统来说真正吃资源的只有三件事软件模拟 SPI 驱动 MFRC522、驱动 LCD1602、跑一个串口和上位机通信。这三件事加起来占用的 RAM 不到 100 字节Flash 不到 6K8K 的余量足够放显示字模和提示语。DSP 和 ARM 在这套系统里是典型的过度设计。它们的主频、浮点能力和 DMA 通道对一个读卡—扣款—显示的流程毫无帮助反而带来三个成本芯片单价翻十倍以上、需要多层板和更严格的电源设计、开发工具链和调试器的学习周期拉长。课程设计或毕设的时间窗口通常只有两三个月把时间花在移植 RTOS 上不如花在把射频时序调稳。有一个参数细节值得单独说系统晶振选的是 11.0592MHz不是常见的 12MHz。原因在串口。51 单片机串口工作在方式 1 时波特率由定时器 1 的溢出率除以 32 再除以 (256-TH1) 得到。11.0592MHz 经过 12 分频后是 921.6kHz这个数能被 9600、4800、19200 等常用波特率整除误差为 0换成 12MHz9600 波特率下的误差接近 7%短帧勉强能通信连续传几十字节就开始丢包。这个取舍是血泪经验换来的——串口乱码十次里有八次是晶振选错。2.3 总体架构四个模块的职责边界与引脚分配系统从功能上切成四块控制模块、收发数据模块、串行通信模块、显示模块。控制模块由单片机和晶振电路组成负责调度整个流程收发数据模块由 MFRC522、13.56MHz 晶振和天线匹配网络组成完成对卡的激励、调制解调和加密运算串行通信模块负责读卡终端和上位机之间的数据交换也承担读卡器与卡之间命令的串行收发显示模块把卡号、余额、时间呈现给乘客。除此之外还有两个从属电路蜂鸣器提示和 DS1302 时钟。MFRC522 内部其实是两个部分。模拟部分驱动天线提供 13.56MHz 的能量载波按寄存器设置对发送数据做调制同时检测并解调卡返回的负载调制信号数字部分通过并口或 SPI 与单片机通信处理 ISO14443A 的帧格式、CRC 和防冲突。天线侧由 LC 低通滤波器和 LC 谐振电路构成谐振点必须落在 13.56MHz这也是后面避坑章节要重点讲的翻车点。引脚分配建议按下面这张表固定下来画原理图和写代码时共用一份定义改一处两边同步单片机引脚连接对象方向说明P1.0 / P1.1 / P1.2 / P1.3MFRC522 NSS / SCK / MOSI / MISO双向软件模拟 SPIP1.4 / P1.5MFRC522 RST / IRQ输出 / 输入硬复位与中断P0.0~P0.7LCD1602 D0~D7输出需外接 10k 排阻上拉P2.0 / P2.1 / P2.2LCD1602 RS / RW / EN输出控制线P2.3蜂鸣器驱动三极管基极输出低电平鸣响P3.0 / P3.1串口 RXD / TXD双向与上位机通信P3.2 / P3.3 / P3.4DS1302 CE / SCLK / I/O输出 / 双向三线时钟接口这套分配的好处是三个外设分别落在 P0、P1、P2 三个端口上写代码时端口操作互不干扰不会出现改显示把读卡搞挂的情况。P2.3 驱动蜂鸣器是原设计明确指定的后面讲驱动电路时还会回到这个引脚。3. 外围电路落地晶振、提示音、显示与时钟的具体参数硬件这一层的难点不在接上而在接对。同样一个 DS1302有人焊上去时间就走有人焊上去读出来全是 0xFF差别往往在两三个元件参数和一个上电时序上。3.1 晶振电路11.0592MHz 配 30pF 负载电容晶振接在单片机的 XTAL1 和 XTAL2 之间两端各挂一只 30pF 的无极性电容到地。这两只电容叫负载电容作用是把晶振的谐振频率拉到标称值同时抑制起振时的过冲。它们的取值不是随便定的晶振厂商给出的负载电容公式大致是 CL C1×C2/(C1C2) Cstray其中 Cstray 是 PCB 走线和芯片引脚带来的杂散电容通常按 3 到 5pF 估。两只 30pF 串联得到 15pF加上 5pF 杂散正好落在 20pF 附近这是绝大多数 11.0592MHz 晶振的标称负载值。电容选大了起振慢甚至不起振选小了频率偏高串口波特率跟着漂。PCB 布局上有几条硬规矩晶振和两只电容尽量贴近芯片走线不超过 1 厘米晶振下方整层铺地不要走任何信号线晶振外壳如果有金属焊盘接地。这些做法在万能板上没法完全满足所以洞洞板搭出来的板子串口误码率天然比打样的 PCB 高答辩演示时如果只能用洞洞板波特率可以降到 4800 换稳定性。下面这段是晶振相关的最小验证代码上电后先让一个 LED 按 1 秒闪烁闪得准说明晶振和定时器都对再去调射频。这个顺序很重要晶振没确认就去调读卡出问题根本分不清是哪一层的锅。#include reg52.h sbit LED P1^7; // 心跳灯验证晶振与定时器 /* 11.0592MHz 下定时器0 方式1 每 1ms 溢出一次 初值 65536 - (11059200/12/1000) 65536 - 921 64615 0xFC67 */ void timer0_init(void) { TMOD 0xF0; // 保留定时器1 的串口配置 TMOD | 0x01; // 定时器0方式116 位 TH0 0xFC; // 高 8 位初值 TL0 0x67; // 低 8 位初值 ET0 1; // 开定时器0 中断 EA 1; // 开总中断 TR0 1; // 启动计数 } void timer0_isr(void) interrupt 1 { static unsigned int ms 0; TH0 0xFC; // 重装初值中断里必须重装 TL0 0x67; if (ms 1000) { // 累计 1000 次即 1 秒 ms 0; LED ~LED; // 翻转心跳灯 } }初值 0xFC67 是按 11.0592MHz、12 分频算出来的如果板上实际焊的是 12MHz一秒会变成 0.92 秒左右肉眼看不出来但累积误差明显用手机秒表对 60 秒就能发现。这个自检动作花五分钟能省掉后面几小时的怀疑人生。3.2 提示音模块为什么蜂鸣器不能直连 I/O 口蜂鸣器接在 P2.3 上但中间必须过一个三极管。原设计的电路逻辑是P2.3 输出高电平时三极管截止蜂鸣器回路断开不响输出低电平时三极管导通回路闭合鸣响。这个低电平导通的行为说明用的是 PNP 管例如常见的 8550发射极接 VCC集电极串蜂鸣器到地基极通过一只 1k 到 4.7k 的限流电阻接 P2.3。为什么不能省掉这只管子51 单片机的 I/O 口灌电流能力标称 20mA 左右但这是单个引脚的极限值整片芯片所有端口加起来还有总电流限制通常 71mA。有源蜂鸣器在 5V 下工作电流 25 到 35mA直连会带来两个后果一是引脚长期超载端口驱动能力衰减二是瞬间拉低电源单片机复位或者程序跑飞表现出来就是一响就死机。加了三极管之后基极电流只要 (5-0.7)/1k ≈ 4.3mA放大倍数 100 时能扛 400mA 以上的集电极电流余量充足。蜂鸣器两端再反并一只 1N4148 做续流关断瞬间的反电动势有地方泄放。sbit BEEP P2^3; // PNP 基极低电平导通 void beep_once(unsigned char ms) { BEEP 0; // 导通鸣响 delay_ms(ms); // 阻塞延时短提示可接受 BEEP 1; // 截止静音 } void beep_ok(void) // 刷卡成功一长声 { beep_once(120); } void beep_fail(void) // 余额不足或认证失败两短声 { beep_once(60); delay_ms(80); beep_once(60); }长声和短声的区分不是为了好看是为了让乘客在嘈杂环境里不看屏幕也能判断结果。车门口人多的时候听觉反馈比视觉反馈有效得多这个细节在验收演示时很讨巧。3.3 LCD1602 显示模块HD44780 兼容带来的移植便利LCD1602 是 16 列 2 行的字符型点阵液晶每个字符位由 5×7 或 5×11 的点阵组成。它的控制器绝大多数基于 HD44780 兼容芯片这套指令集已经成了事实标准所以网上找到的任何一份 HD44780 驱动代码改改端口定义就能直接用这是选它而不是选图形点阵屏的主要理由——省下来的时间全花在射频调试上。接口上有 8 位和 4 位两种模式。8 位模式一次传一个字节速度快、代码直观代价是占 8 根数据线。4 位模式分两次传高半字节和低半字节省 4 根线代价是驱动复杂度上升。这个项目 I/O 够用走 8 位模式接线简单出问题好查。初始化有几条必须遵守的时序上电后等待 15ms 以上再发指令第一条指令发 0x388 位数据、2 行显示、5×7 点阵这条要连发三次中间各留 4.1ms 以上这是 HD44780 上电复位的内部状态机决定的少发一次就会出现上电偶尔不显示的玄学接着发 0x0C 开显示关光标、0x06 设置地址自动加一、0x01 清屏清屏指令执行时间约 1.64ms必须等够。之后每次写数据前先读忙碌标志位 BF数据线 D7 为 1 表示忙或者干脆每条指令后延迟 2ms 图省事。#define LCD_DATA P0 // 数据口P0 必须外接 10k 上拉 sbit RS P2^0; sbit RW P2^1; sbit EN P2^2; void lcd_write_cmd(unsigned char cmd) { LCD_DATA cmd; RS 0; // 0 指令寄存器 RW 0; // 0 写 EN 1; // 使能拉高 delay_us(5); // 满足使能脉宽要求 EN 0; // 下降沿锁存 delay_ms(2); // 等指令执行完 } void lcd_write_data(unsigned char dat) { LCD_DATA dat; RS 1; // 1 数据寄存器 RW 0; EN 1; delay_us(5); EN 0; delay_ms(1); } void lcd_init(void) { delay_ms(20); // 上电等待不能省 lcd_write_cmd(0x38); // 8 位、2 行、5×7 delay_ms(5); lcd_write_cmd(0x38); // 复位期间必须连发三次 delay_ms(5); lcd_write_cmd(0x38); lcd_write_cmd(0x0C); // 开显示、关光标、不闪烁 lcd_write_cmd(0x06); // 写入后地址自增画面不移动 lcd_write_cmd(0x01); // 清屏 delay_ms(2); }对比度是靠 VO 引脚上的 10k 电位器调的调到字符清晰、背景不过黑为止。这一步经常被忽略板子焊完上电看到一排黑方块就以为屏坏了其实只是对比度拧到了极端。3.4 DS1302 时钟模块三线接口、BCD 码与写保护时间显示用 DS1302一款带 31 字节 RAM 的实时时钟芯片能对年月日周时分秒计时有闰年补偿有效到 2100 年工作电压 2.5V 到 5.5V掉电后靠备用纽扣电池维持走时。它和单片机之间是三线同步串行CE有的资料叫 RST做片选SCLK 做时钟I/O 做双向数据。用它有两个必须处理的坑。第一个是写保护。DS1302 的秒寄存器 bit7 是 CH时钟 halt 位控制寄存器 0x8E 的 bit7 是 WP 写保护位上电默认置 1此时所有写操作都被忽略。表现就是代码跑了时间没变。正确流程是先写 0x8E 发 0x00 解除保护写完时间再发 0x80 恢复保护。第二个是数据格式。DS1302 内部存的是 BCD 码读出来 0x25 表示 25 而不是十进制的 37显示前必须做 BCD 到十进制的转换否则屏幕上会出现莫名其妙的数值。sbit DS_CE P3^2; // 片选 sbit DS_SCLK P3^3; // 时钟 sbit DS_IO P3^4; // 双向数据线 void ds_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { // 低位先出 DS_IO dat 0x01; DS_SCLK 1; // 上升沿写入 DS_SCLK 0; dat 1; } } unsigned char ds_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat 1; if (DS_IO) dat | 0x80; // 低位先入逐位左移拼接 DS_SCLK 1; DS_SCLK 0; // 下降沿读出 } return dat; } void ds_write_reg(unsigned char addr, unsigned char val) { DS_CE 0; // 操作前必须拉低再拉高形成完整帧 DS_SCLK 0; DS_CE 1; ds_write_byte(addr); // 先发命令字节 ds_write_byte(val); // 再发数据字节 DS_CE 0; } unsigned char bcd2dec(unsigned char bcd) { return (bcd 4) * 10 (bcd 0x0F); }每次读写都要用 CE 拉低再拉高把一帧框起来中间 SCLK 保持有节拍的翻转。如果 CE 一直保持高电平第二帧的命令会被当成第一帧的继续读出来的数据就全乱了。备用电池建议用可充电的纽扣电池或超级电容普通不可充电电池长期浮充有鼓包风险。4. 软件实现从初始化到钱包扣款的完整链路软件这条线是整套系统里最考验顺序感的部分。初始化的顺序错了后面所有步骤都白费射频状态机的顺序错了卡明明在场上也读不出来。4.1 系统初始化中断、堆栈、串口与 MFRC522 复位上电之后的第一件事不是读卡是把系统状态摆正。具体要做的有关总中断配置堆栈指针初始化串口的定时器和波特率配置中断优先级清各个外设的缓冲区最后开总中断。原设计里还提到中断向量表和重映射操作这在 51 上其实体现为中断入口地址固定、不需要重映射真正需要做的是把用不到的中断源关掉避免干扰。串口的配置是固定的定时器 1 工作在方式 28 位自动重装TH1 TL1 0xFD 对应 9600 波特率11.0592MHz 下误差为 0SCON 0x50 表示方式 1、允许接收。中断选择上串口和 MFRC522 都挂在中断线上要先清标志再使能否则一开中断就进一次空中断。MFRC522 这边的初始化分五步硬复位RST 脚拉低再拉高、软复位写 CommandReg 0x0F、配置定时器TModeReg、TPrescalerReg 决定内部定时精度、配置调制方式TxASKReg 0x40 用 100% ASK 调制这是 ISO14443A 的规定、最后打开天线TxControlReg 的 bit0 和 bit1 置 1。天线没打开时卡贴上去毫无反应很多人在这里怀疑天线焊错了其实是寄存器忘了写。/* MFRC522 寄存器地址节选 */ #define CommandReg 0x01 #define ComIrqReg 0x04 #define ErrorReg 0x06 #define FIFODataReg 0x09 #define FIFOLevelReg 0x0A #define ControlReg 0x0C #define BitFramingReg 0x0D #define ModeReg 0x11 #define TxControlReg 0x14 #define TxASKReg 0x15 #define TModeReg 0x2A #define TPrescalerReg 0x2B void rc522_init(void) { rc522_reset(); // RST 脚拉低 100us 再拉高 rc522_write(CommandReg, 0x0F); // 软复位 delay_ms(50); // 等内部状态机复位完成 rc522_write(TModeReg, 0x8D); // 定时器自动启动TAUTO1 rc522_write(TPrescalerReg, 0x3E);// 分频系数决定超时时间 rc522_write(TxASKReg, 0x40); // 100% ASK 调制 rc522_write(ModeReg, 0x3D); // CRC 初值 0x6363 rc522_antenna_on(); // 打开天线驱动 } void rc522_antenna_on(void) { unsigned char v rc522_read(TxControlReg); if ((v 0x03) ! 0x03) { // 两位都置 1 才真正驱动天线 rc522_write(TxControlReg, v | 0x03); } }TPrescalerReg 和 TModeReg 共同决定射频通信的超时窗口。超时设短了卡还没应答就报错设长了卡片离场后程序还傻等界面卡顿。经验值是把超时定在 25ms 左右城市场景里卡贴近读头后 10ms 内就能完成应答余量足够。4.2 寻卡与防冲突复位应答、防冲突、选卡三步卡片进入读写器天线场区后整个交互按 ISO14443A 的流程往前走三个步骤一步都不能少。第一步是复位应答Request。读写器以固定协议发出寻卡命令卡收到后回一个应答这一步实质是验证场上是否有符合协议的卡。命令字有两个0x26 只寻处于空闲状态的卡0x52 寻包括已进入休眠状态的卡在内的所有卡。公交场景里乘客刷一次就走用 0x52 更稳妥避免上一张卡没真正离场导致下一张寻不到。第二步是防冲突Anticollision。多张卡同时在场时它们的应答会在同一时刻发出波形叠在一起。防冲突机制通过逐位比较的方式把卡片的 UID 一位一位地筛出来最终锁定一张其余卡回到空闲状态等下一轮。命令是 0x93 0x20返回 5 个字节1 字节校验加 4 字节 UID。这一步是这个系统里最能体现多卡场景能力的地方答辩时同时放两三张卡上去演示很有说服力。第三步是选卡Select。用上一步拿到的 UID 发出 0x93 0x70卡回一个 SAK 字节其中包含卡的容量代码这是后续判断扇区数、决定钱包块地址的依据。选卡完成后这张卡才真正进入可以认证的状态。/* 寻卡 防冲突 选卡成功返回 0 并把 UID 写入 uid_buf */ unsigned char picc_request_select(unsigned char *uid_buf) { unsigned char status; unsigned char buf[5]; unsigned char len; /* 1. 寻卡0x52 寻场内所有卡 */ len 1; buf[0] 0x52; status rc522_transceive(0x07, buf, len, buf, len); // 7 位帧 if (status ! 0) return 1; // 场内无卡 /* 2. 防冲突逐位筛出唯一 UID */ len 2; buf[0] 0x93; buf[1] 0x20; status rc522_transceive(0x00, buf, len, buf, len); if (status ! 0 || len ! 5) return 2; // 多卡冲突未解开 memcpy(uid_buf, buf, 4); // 4 字节 UID /* 3. 选卡带 UID 和 CRC 发出返回 SAK */ len 7; buf[0] 0x93; buf[1] 0x70; memcpy(buf[2], uid_buf, 4); buf[6] buf[2] ^ buf[3] ^ buf[4] ^ buf[5]; // BCC 异或校验 status rc522_transceive(0x00, buf, len, buf, len); if (status ! 0) return 3; return 0; // 成功 }注意 transceive 的第二个参数是位长度。寻卡命令只有 7 位short frame位长度传 7寄存器 BitFramingReg 的低 3 位也要同步设 7写成 0 的话卡收到的是 8 位帧直接不认。4.3 三次互相确认扇区密钥校验到底在防什么选定卡之后不能立刻读写必须先过认证。Mifare One 的认证叫三次互相确认Three Pass Authentication名字听着抽象实质是一次挑战应答。过程是这样的读写器指定要访问的扇区号并发出认证命令0x60 用 A 密钥0x61 用 B 密钥随后跟着块地址和 6 字节密钥卡收到后检查密钥是否正确正确则生成一个随机数发给读写器读写器用密钥加密这个随机数回给卡卡验证通过后再发一个随机数给读写器读写器加密回传。三轮走完双方建立加密通道后续的数据块读写全部走加密流。任何一轮对不上卡立即回到空闲状态本次会话作废。这个机制解决的是离线扣款的安全底线卡里存的是密钥而不是明文密钥在认证过程中从不明文传输只能在加密通道里交换数据。换一个扇区就要重新认证一次所以一张卡上不同的应用公交钱包、食堂钱包各自独立攻破一个不影响另一个。在代码层面认证成功返回 0 之后才能调用读写块返回非 0 就必须给出提示并重新寻卡不要原地重试——原地重试的逻辑会让程序卡死在认证失败的分支里表现是刷卡没反应但也不报错。4.4 数据块操作与钱包扣款减值和传输为什么要成对出现Mifare One 对数据块的操作有七种读Read读一个块写Write写一个块初始化钱包Restore 之外还有一个专用格式把块定义成钱包格式加值Increment、减值Decrement改变钱包数值存储Restore把块内容搬到内部数据寄存器传输Transfer把数据寄存器内容写回块中止Halt让卡停止工作。公交扣款的完整链路是认证通过 → 读当前钱包块拿到余额 → 判断余额是否够一次票价 → 不够则蜂鸣报警并显示余额不足 → 够则发减值命令减值额度写入数据寄存器 → 发传输命令把新余额落到卡里 → 蜂鸣一声、显示新余额、时间戳通过串口上报 → 发中止命令让卡离场。这里的关键是减值和传输必须成对。减值命令只是把新数值算好放进卡内的数据寄存器并没有真正写入 EEPROM只有传输命令才会写进去。如果只减值不传输卡拿开再贴回来余额还是老的如果先传输再减值等于什么都没做。更隐蔽的情况是减值成功、传输失败卡被提前拿开卡内余额没变但上位机那边如果已经记了一笔账就平不上了。稳妥的做法是减值、传输都返回成功之后才输出扣款成功提示同时把这次交易写进本地的未确认队列等下次同一张卡再刷时对账。钱包块的数据格式是固定的 16 字节前 4 字节是数值小端低位在前第 5 字节是这 4 字节的按位取反接着 4 字节是备份值再 1 字节备份取反最后 4 字节是块地址和地址反码交替。这个冗余结构是为了让卡在写入过程中掉电时还能靠反码校验恢复出正确值。所以钱包块千万不要用普通写命令去改必须走减值/加值加传输的路径否则格式一乱这张卡就废了。/* 钱包扣款成功返回 0余额不足返回 1通信失败返回 2 */ unsigned char wallet_pay(unsigned char *uid, unsigned char block, unsigned int fare, unsigned int *balance) { unsigned char buf[16]; /* 1. 认证该扇区密钥用 A 密钥 */ if (rc522_auth(0x60, block, default_key_a, uid) ! 0) return 2; /* 2. 读钱包块解析 4 字节小端余额 */ if (rc522_read_block(block, buf) ! 0) return 2; *balance buf[0] | (buf[1] 8) | (buf[2] 16) | (buf[3] 24); /* 3. 余额判断不足直接返回不动卡 */ if (*balance fare) return 1; /* 4. 减值新值先进数据寄存器 */ if (rc522_value(0xC0, block, fare) ! 0) // 0xC0 Decrement return 2; /* 5. 传输把数据寄存器真正写进 EEPROM */ if (rc522_value(0xB0, block, 0) ! 0) // 0xB0 Transfer return 2; *balance - fare; return 0; }这套流程里第 4 步和第 5 步之间不能插入任何阻塞操作比如刷屏、等串口发送完成否则卡被拿走的概率大幅上升。显示和串口上报全部放到扣款完成之后再做宁可界面晚 100ms 更新也不能让卡在临界区里。5. 避坑与排查这套刷卡系统最容易翻车的五个地方下面这五条都是调试现场真实出现频率最高的按现象、原因、解决三段式整理。每一条都能独立定位不需要从头查起。5.1 现象LCD 上电显示一排黑方块或乱码程序其他部分正常原因基本锁定在 P0 口。51 单片机的 P0 口是开漏输出结构内部没有上拉电阻作为通用 I/O 输出高电平时实际处于高阻态拉不出电流。LCD1602 的数据线接在 P0 上没有上拉就只能靠外部电路把电平抬起来抬不起来时 LCD 收到的数据是乱的初始化指令都发不对屏幕上自然是一排黑块。解决办法是在 P0.0 到 P0.7 上并一只 10k 的排阻到 VCC。这只排阻在原理图上极其容易漏画因为它不像芯片那样显眼。已经焊好的板子补救也简单拿一只 10k 排阻飞线到 5V 即可。另外顺手检查一下 VO 引脚的电位器有没有拧到合适位置黑块和对比度过度也经常被混淆。5.2 现象串口助手收到的全是乱码或者读卡时好时坏原因有两个层次。表层是波特率不匹配深层是晶振频率和代码里的常量对不上。很多参考资料里写外部晶体振荡频率 20-25MHz还有把 Flash 容量写成 18K 字节的这类参数是从别的板子上抄来的或者是笔误照着写就会翻车。AT89S52 的实际 Flash 是 8K 字节标准配置就是 11.0592MHz 或 12MHz 晶振20MHz 以上的描述对应的是别的系列。排查顺序是先看板上晶振外壳上印的频率再核对代码里定时器初值是不是按这个频率算的最后用示波器量 XTAL2 脚的波形看频率和幅值是否正常幅值一般在 VCC 的一半以上。没有示波器就用串口自发自收做回环测试短接 TXD 和 RXD发什么收什么说明串口配置对收不到就是波特率或晶振的问题。这一步做完再动射频代码能省大量时间。5.3 现象卡必须贴着天线才读得到稍微偏一点就没反应这是射频部分最典型的症状原因通常不在代码在天线的匹配网络和供电。MFRC522 的天线由 LC 低通滤波器和 LC 谐振电路组成谐振点必须调在 13.56MHz偏差 5% 以上读卡距离就掉一半。匹配电容的取值跟天线线圈的电感直接相关线圈多做一圈少做一圈电容就得跟着改照抄别人的参数往往不灵。其次是环境干扰。天线正下方如果铺了大面积地平面涡流会把磁场吃掉板子旁边有开关电源或者金属外壳同样会削弱场强。处理办法是把天线区域的地平面挖空天线离板边至少 5mm电源入口加 100nF 和 10uF 并联去耦必要时给天线加一层铁氧体屏蔽片。还有一个容易忽略的点是供电电流读卡瞬间电流能冲到 100mAUSB 供电或劣质电源适配器带不动电压跌落会导致读卡时好时坏换一个稳定的 5V 电源往往立刻见效。5.4 现象DS1302 时间不走、走两天差几小时或者读出来是 0xFF分三种情况。时间完全不走在第一次上电时最常见原因是写保护位没清对 0x8E 写 0x00 这一步漏了所有写操作全被丢弃。走时不准多半是 32.768kHz 晶振的问题这颗晶振频率低、驱动能力弱对负载电容和布线比主晶振敏感得多走线稍长就停振或者起振后频率偏低表现出来就是每天慢几分钟。读出来是 0xFF 则是通信层面的问题CE、SCLK、I/O 三根线接错、上拉缺失或者读写时序里 CE 没有形成完整的低-高-低帧数据线一直处于悬空状态被读成高电平。处理顺序是先确认 0x8E 写保护解除再确认备用电池电压在 2.5V 以上然后量 32.768kHz 晶振有没有起振最后用逻辑分析仪抓一遍三线时序看命令字节和数据字节是否按低位在前的顺序发出。时序抓一遍问题基本就现原形了。5.5 现象同一张卡连着刷两次扣了两次钱或者一次都没扣成功这个问题最伤因为它直接关系到钱的正确性。原因往往在按键或刷卡触发的去抖上。乘客把卡放在读头上不动程序每 200ms 循环寻一次卡只要卡还在场就会重复进入扣款流程连刷两三次就扣两三次。另一种情况是减值成功但传输失败卡上没扣上位机却记了一笔。解决思路是引入状态机而不是简单的循环。每张卡用 UID 做标记同一 UID 在离场之前不重复处理读到卡后置一个在场标志扣款完成后必须检测到一次寻卡失败卡离场才清除标志下次同一张卡进场才允许再扣。同时给扣款流程加一个交易序号本地记未确认队列下次这张卡再刷时先对账再扣新的。这个改动不大但它是整套系统从能演示跨到敢交付的分水岭。6. 验收与答辩怎么证明这套刷卡系统真的能刷验收现场最怕的不是功能少是演示到一半卡住。我一般按下面这张表走一遍全部通过再上台。验收项操作方法通过标准心跳与时钟上电观察指示 LED与手机秒表对 60 秒误差在 1 秒以内单卡读卡正常卡贴天线中心再逐步偏移3~5cm 内稳定读到 UID多卡防冲突同时放 2 到 3 张卡在天线上只锁定一张不报错不死机正常扣款余额充足时刷一次蜂鸣一长声余额正确减少余额不足把余额扣到低于票价再刷蜂鸣两短声显示提示余额不变断电恢复扣款后断电 10 秒再上电余额与时间均正确保留重复刷卡同一张卡贴住不动 10 秒只扣一次串口上报连接上位机接收交易记录每次扣款一条卡号金额时间齐全答辩讲的时候重点不是念参数是讲清楚每一个为什么。为什么用 Mifare One 不用 ID 卡——因为要离线存余额为什么主控用 AT89S52 不用 ARM——因为外设需求已经被 32 个 I/O 和 8K Flash 覆盖成本差十倍为什么晶振是 11.0592MHz——因为串口波特率要整数分频为什么减值和传输必须成对——因为一个是算、一个是写。把这四个为什么讲透比罗列十页元器件清单有分量。演示环节建议刻意展示两个边界一是同时放三张卡演示防冲突二是把余额扣到不足再刷一次。顺利路径谁都能跑通边界路径才说明这套设计是想过的。演示前把所有卡的余额重置到固定值别指望现场调现场紧张的时候手会抖。从那以后我每次交板子之前都强制自己先跑一遍这张验收表尤其是断电恢复和重复刷卡这两项——它们不会在演示的前三分钟暴露但一定会被评委问。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →