
简介这份文档面向电子信息工程、自动化等专业学生及RFID应用入门者围绕公交车刷卡系统的硬件与软件实现展开可用于课程设计、毕业设计选题参考和电子系统设计训练。文档共1个doc文件压缩包约649KB内容以系统设计说明书为主结构涵盖射频卡选择、AT89S52单片机、晶振电路、蜂鸣器提示音、LCD1602显示、DS1302时钟模块以及系统总体方案并给出初始化、射频卡与读写器通信和软件总流程图。读者可据此梳理非接触式IC卡读写系统的基本架构、模块划分与软硬件协同思路理解低成本、技术成熟器件的选型依据并对照各模块电路连接和程序流程形成整体方案也可将其作为交通一卡通、身份识别等场景的参考。目前已有4299人学习适合需要快速建立RFID刷卡系统整体认识并查漏补缺的读者。1. 从一张公交卡说起这份 RFID 公交车刷卡系统设计到底能落地什么把《基于RFID公交车刷卡系统设计》这份电子系统设计说明书翻到硬件框图那一页很多人会先盯着 MFRC522 和 AT89S52 的连线但真正决定这套东西能不能落地的是公交场景下的刷卡节奏卡贴上去要快、扣费要准、掉电不能乱。它给出的是一套低成本非接触式 IC 卡读卡器的完整设计路径从 M1 卡选型、13.56MHz 射频前端到 LCD 显示、DS1302 时钟和串口上传都有对应章节。适合做嵌入式入门复现、门禁/考勤/公交刷卡类小系统二次开发的人也适合手里有现成模块、想把读卡流程和钱包扣费逻辑一次理顺的工程师。2. 硬件选型与电路参数Mifare One、AT89S52、MFRC522 怎么配2.1 为什么选非接触式 IC 卡磁卡、接触式 IC 卡与 M1 卡的对比公交刷卡的第一约束不是“技术先进”而是“卡不能坏、刷得快、成本压得住”。条码怕折怕污磁卡怕磁怕磨接触式 IC 卡芯片外露插拔多了容易坏非接触式 IC 卡把芯片和线圈封在卡片里靠近读卡器就能完成能量取电和半双工通信适合频繁、短时、免维护的场合。这份文档选的是 M1 卡S50属于 13.56MHz 非接触式逻辑加密卡常见容量 1KB分成 16 个扇区每个扇区 4 个块每块 16 字节0 扇区 0 块是厂商块存 UID 和厂商信息不能当普通数据块写。卡类型读写方式典型距离环境适应性成本适合场景边界条码/二维码光学几厘米到几十厘米怕污损、怕折低一次性票、移动支付依赖屏幕或纸票磁卡接触式磁头约 0.52cm怕磁、怕磨低老式门禁、储值卡磁条易失效接触式 IC 卡触点0cm芯片外露易折中公共电话、插卡式设备插拔磨损非接触式 IC 卡射频约 15cm封装内抗污抗折中低公交、门禁、考勤逻辑加密卡安全性有限M1 卡的价值在“脱机小额扣费”读卡器不需要实时联网卡内钱包块先扣交易记录再通过串口上传。它的边界也要说清楚逻辑加密卡不是高安全金融卡密钥体系和防冲突机制适合低风险、低金额、可接受事后对账的场景如果业务要求强匿名、强防复制应换 CPU 卡或更安全的非接触式卡方案。文档把 M1 卡作为射频卡选型和公交刷卡的低成本、快速通行目标是匹配的。2.2 AT89S52 最小系统与 11.0592MHz 晶振的参数账主控选 AT89S52核心原因是“够用、便宜、资料多”。它片内 8KB Flash、256B RAM、32 个双向 I/O、3 个 16 位定时/计数器、1 个全双工串口兼容 MCS-51 指令系统适合这种读卡、显示、扣费、串口上传的低速控制任务。相比 DSP、ARM它跑不动复杂协议栈但 MFRC522 的寄存器操作和 M1 卡的认证流程都是短帧命令8 位机完全能扛。真正需要注意的是最小系统EA 接高电平、复位电路 10μF 电容配 10k 电阻、P0 口作为数据总线时加 10k 上拉排阻、晶振靠近单片机引脚放置。晶振选 11.0592MHz 不是玄学而是为了串口波特率。51 单片机定时器 1 工作在方式 2 时波特率由晶振频率和重装值决定11.0592MHz 能被 9600、19200 等常用波特率整除误差小串口和上位机联调时不容易出现半帧、乱码。晶振两端各接一只 30pF 负载电容到地电容值会影响起振裕量和频率偏差板子布局不好时先换 22pF33pF 试。下面这段只做最小系统验证烧进去后串口助手能收到连续字节说明晶振、复位、串口和下载电路都通了。#include reg52.h void UartInit(void) { SCON 0x50; // 串口方式18位UART允许接收 TMOD 0x0F; // 清除定时器1模式位 TMOD | 0x20; // 定时器1方式28位自动重装 TH1 0xFD; // 11.0592MHz、9600bps、SMOD0 的常用重装值 TL1 0xFD; TR1 1; // 启动定时器1 ES 1; // 使能串口中断 EA 1; // 开总中断 } void UartSendByte(unsigned char dat) { SBUF dat; while (!TI); // 等待发送完成 TI 0; } void main(void) { unsigned char i 0; UartInit(); while (1) { UartSendByte(i); // 简单延时避免串口助手刷得太快 { unsigned int d 60000; while (d--); } } }这段代码的参数要会改TH10xFD对应 11.0592MHz、9600bps、SMOD0如果波特率翻倍到 19200重装值要改如果晶振换成 12MHz9600bps 的误差会变大串口容易丢字节。SCON0x50是方式 1、允许接收如果只用查询发送可以不开ES。代码后的验证方法是先量 P3.0/P3.1 对地电压再短接 TXD/RXD 做回环最后接 USB 转串口模块共地。最小系统不过关后面射频部分再调都是白费。2.3 MFRC522 射频前端与 13.56MHz 天线匹配MFRC522 是 13.56MHz 非接触式读写芯片支持 ISO14443A/B常见模块通过 SPI、I2C 或 UART 和主控通信。这份设计里它负责三件事驱动天线发载波、对卡返回信号解调、通过寄存器与单片机交互。AT89S52 没有硬件 SPI常见做法是用普通 I/O 软件模拟 SPINSS 片选、SCK 时钟、MOSI 主出从入、MISO 主入从出、RST 复位。电平要特别注意MFRC522 是 3.3V 器件AT89S52 是 5V 系统I/O 直连有损坏风险稳妥做法是加电平转换至少用电阻分压处理单片机到模块的输入线模块到单片机的 MISO 一般可直连但也要确认模块是否带电平转换。信号示例引脚方向说明NSSP1.0单片机→模块片选低有效SCKP1.1单片机→模块SPI 时钟软件模拟MOSIP1.2单片机→模块寄存器地址/数据MISOP1.3模块→单片机读回数据RSTP1.4单片机→模块复位低有效3.3V独立 LDO电源纹波尽量小靠近模块加 0.1μFGND共地电源天线地要短而粗天线部分是这套设计里最容易翻车的地方。13.56MHz 天线本质是 LC 谐振回路模块上的匹配电容和天线电感决定谐振频率天线附近放金属、LCD 排线、电池都会改变谐振点表现就是读卡距离从 4cm 掉到 1cm甚至要用手碰一下才响应。常见做法是先用成品模块调通协议再考虑自己画天线自己画时先用网络分析仪或示波器看载波幅度不要一上来就改匹配电容。读卡距离不是越大越好距离拉大后容易误读旁边卡片公交场景反而要控制在场强边界内。2.4 LCD1602、DS1302、蜂鸣器的接口分配与上电顺序显示用 LCD1602两行每行 16 字符基于 HD44780 兼容控制芯片初始化命令表比较固定0x38设 8 位数据、两行、5×7 点阵0x0C开显示、关光标0x06写一个字符后地址加 10x01清屏。数据口接 P0 时要加上拉排阻RS、RW、E 接普通 I/O如果只写不读RW 可以直接接地省掉忙标志判断但初始化后要加足够延时。V0 对比度接 10k 电位器调不到合适对比度时显示会是一排黑块不要误判为程序跑飞。时钟用 DS1302三线接口CE、SCLK、I/O外接 32.768kHz 晶振和备用电池。它按 BCD 码存秒、分、时、日、月、周、年上电后要先清秒寄存器最高位 CH否则时钟可能停走。写保护寄存器要先关写保护再改时间。蜂鸣器用 P2.3 经三极管驱动P2.3 低电平时三极管导通蜂鸣器响基极串 1k 电阻感性蜂鸣器两端反并一只二极管。上电顺序建议先 5V 最小系统、再 3.3V 射频模块、最后插卡测试每加一个模块就用串口打印一行状态别等全焊完再查。模块型号关键参数接口注意点主控AT89S528KB Flash、256B RAM、32 I/O最小系统P0 上拉、EA 接高射频MFRC52213.56MHz、ISO14443A/B软件 SPI3.3V 供电电平转换显示LCD160216×2 字符8 位并口V0 调对比度RW 可接地时钟DS130232.768kHz、BCD 码三线清 CH 位备用电池提示有源蜂鸣器5V/3.3V 视型号P2.3 经三极管低电平响加基极电阻3. 软件初始化与读卡通信从复位应答到三次互相认证的完整时序3.1 系统初始化中断、串口、定时器与 MFRC522 复位软件初始化不是把寄存器从头到尾写一遍而是按“先让单片机活着、再让射频芯片可控、最后让业务状态机跑起来”的顺序做。单片机侧先关总中断配串口、定时器、外部中断再开总中断MFRC522 侧先拉低 RST 再拉高写CommandReg的PCD_RESETPHASE命令读VersionReg确认返回值常见模块返回0x91或0x92读不到就查 SPI 线序和片选。接着配置定时器、ASK 调制、接收增益、天线使能最后开天线。寄存器/命令常见值作用备注CommandReg0x01命令寄存器写0x0F软复位TModeReg0x8D定时器模式常见自动重装配置TPrescalerReg0x3E定时器预分频影响超时时间TReloadRegH/L0x00/0x1E定时器重装按需调整TxASKReg0x40100% ASK 调制M1 卡常用ModeReg0x3D发送/接收模式CRC 预设TxControlReg0x03天线使能置位后开天线RFCfgReg0x70接收增益可试 0x400x70初始化失败时不要先怀疑卡先看VersionReg。读不到版本号问题在 SPI 时序、片选、供电或复位读得到版本号但寻不到卡问题在天线、场强或协议命令。串口初始化、定时器节拍和 MFRC522 复位要分开验证串口能打印“RFID OK”再往下走。注意 MFRC522 的寄存器写法和普通 SPI 器件不同地址字节最高位表示读写低 6 位是寄存器地址软件模拟时先发地址再收发数据片选在整个帧期间保持低电平。3.2 复位应答与防冲突多张卡同时进场怎么选一张卡进入读卡器场区后先通过复位应答告诉读写器“我是什么类型”。M1 卡属于 ISO14443A读卡器发REQA0x26或WUPA0x52卡回ATQA两字节然后进入防冲突循环读卡器发SEL0x93/0x95/0x97加NVB0x20 表示只发命令0x70 表示命令加完整 UID卡回 UID 和 CRC。多张卡同时在场时UID 的某些位会冲突MFRC522 的CollReg能检测冲突位软件按位逐次筛选直到选出一张卡并返回SAK。选中后其他卡进入空闲状态等下一次寻卡。阶段命令/动作方向说明寻卡REQA 0x26读卡器→卡请求卡进入就绪态应答ATQA2 字节卡→读卡器表明卡类型和位帧防冲突SEL 0x93 NVB 0x20读卡器→卡开始选 UID 第一级选卡SEL 0x93 NVB 0x70 UID CRC读卡器→卡选中唯一卡确认SAK卡→读卡器表明卡已选中休眠HALT 0x50读卡器→卡当前卡暂停等下一轮防冲突做不好现场表现是“两张卡叠在一起时读不出来”或“反复读到同一张又跳另一张”。常见做法是每次寻卡只选一张业务层记录 UID 并做 3 秒冷却避免同一个人连续刷卡重复扣费。MFRC522 有硬件 CRC 和位冲突检测但软件仍要处理超时发命令后等ComIrqReg的接收中断或定时器中断超时就复位命令寄存器不要死等。读卡距离内有多张卡时天线场强不要调得过大场强越强旁边卡越容易被唤醒防冲突压力越大。下面这段是寻卡和读 UID 的骨架代码用来验证 SPI 和防冲突流程。不同模块的引脚宏要按实际接线改ReadRawRC、WriteRawRC、PcdComMF522是底层寄存器读写和命令收发函数。unsigned char PcdRequest(unsigned char req_code, unsigned char *tag_type) { unsigned char status; unsigned int backBits; // 清中断标志准备接收 WriteRawRC(ComIrqReg, 0x7F); // 写入寻卡命令和参数 WriteRawRC(FIFOLevelReg, 0x80); // 清 FIFO WriteRawRC(FIFODataReg, req_code); // REQA 或 WUPA WriteRawRC(CommandReg, PCD_TRANSCEIVE); WriteRawRC(BitFramingReg, 0x07); // 发送 7 位短帧 WriteRawRC(CommandReg, PCD_TRANSCEIVE | 0x80); // 启动发送 // 等待接收完成或超时 status WaitForRecv(backBits, 2000); if (status MI_OK backBits 0x10) { tag_type[0] ReadRawRC(FIFODataReg); tag_type[1] ReadRawRC(FIFODataReg); return MI_OK; } return MI_ERR; }逻辑说明req_code传0x26是寻卡传0x52是唤醒BitFramingReg0x07表示短帧 7 位这是 ISO14443A 短帧格式WaitForRecv里轮询ComIrqReg的RxIRq位超时返回错误。参数上2000是轮询次数不是毫秒实际超时时间要按主频和循环开销折算如果串口打印发现经常超时先降 SPI 时钟、缩短杜邦线再查天线。读 UID 后再走PcdAnticoll和PcdSelect把 UID 缓存到业务层不要每次扣费都重新寻卡。3.3 三次互相认证扇区密钥和块地址怎么传M1 卡的数据按扇区保护每个扇区有密钥 A 和密钥 B认证通过后才能读写信扇区内的块。认证命令是AUTHENT1A0x60或AUTHENT1B0x61后面跟块地址和 6 字节密钥。这里的块地址不是随便填认证针对的是扇区传扇区首块地址即可比如扇区 1 的首块是 4扇区 2 的首块是 8。读卡器发认证命令后卡回令牌读卡器再回认证信息卡再确认这就是常说的“三次互相确认”。认证成功后Status2Reg的MFCrypto1On位置位后续通信加密换扇区要重新认证。步骤发送内容说明10x60/0x61 块地址 密钥A/B 6字节读卡器发起认证2卡返回令牌卡确认收到认证请求3读卡器返回认证信息完成密钥校验4卡返回确认Status2Reg加密位置位认证失败的现场表现很统一寻卡成功、选卡成功一到读块就返回错误读回数据全 0 或全0xFF。先查密钥是不是默认的FF FF FF FF FF FF再查用的是 A 密钥还是 B 密钥最后查块地址是不是填成了数据块而不是扇区首块。如果卡被改过密钥又没记录基本没有后悔药只能换卡。调试阶段建议先用默认密钥、只读不写确认认证链路通了再改密钥改密钥时先写备份扇区再写新密钥别把自己锁在外面。注意 0 扇区 0 块是厂商块认证可以写入会失败不要拿它当钱包块。3.4 钱包块操作读、写、加值、减值与传输公交扣费的核心是钱包块。M1 卡的钱包块有固定格式第 03 字节是数值小端第 47 字节是数值反码第 811 字节再存一遍数值第 12 字节存块地址第 13 字节存地址反码第 1415 字节保留。读钱包用READ0x30读一个块写钱包用WRITE0xA0写一个块加值用INCREMENT0xC1减值用DECREMENT0xC0存储用RESTORE0xC2传输用TRANSFER0xB0。关键点加值/减值只改卡内部数据寄存器必须再发TRANSFER才会写回钱包块只发减值不发传输余额不会变。操作命令作用是否落块读块0x30读一个 16 字节块否写块0xA0写一个 16 字节块是加值0xC1数值加否需 Transfer减值0xC0数值减否需 Transfer存储0xC2块内容存入寄存器否传输0xB0寄存器内容写回块是扣费流程建议写成状态机认证通过→读钱包块→校验钱包格式→判断余额→减值→传输→回读校验→蜂鸣器→LCD→串口上传。钱包格式校验要看反码和地址反码格式不对就不要硬扣先提示“卡片未初始化”。初始化钱包时按“值反码值地址反地址”写满 16 字节再读回比对。公交场景票价固定扣费金额小但写块失败不能吞掉回读不一致就重试一次仍失败则记录卡号和错误码让上位机对账。注意不要用WRITE直接改钱包数值M1 卡的钱包命令自带备份和传输机制直接写块容易把反码写错。4. 公交刷卡业务闭环一次扣费从寻卡到串口上传的落地拆解4.1 一次完整刷卡的时序与状态机硬件调通后业务层不要把寻卡、认证、扣费、显示、上传写成一条直线现场一卡顿就全乱。常见做法是状态机IDLE等待卡、SEEK寻卡、ANTICOLL防冲突、SELECT选卡、AUTH认证、READ读余额、CHECK判断、DECREMENT减值、TRANSFER传输、DISPLAY显示、BEEP提示、UPLOAD上传、COOLDOWN冷却然后回IDLE。每个状态带超时超时回IDLE并清射频命令寄存器避免一次失败把后续刷卡全卡死。状态动作成功条件失败处理SEEK发 REQA/WUPA收到 ATQA超时回 IDLEANTICOLL防冲突读 UIDUID 完整重试 3 次SELECT选卡收到 SAK复位射频AUTH认证扇区加密位置位提示认证失败READ读钱包块格式校验通过提示未初始化CHECK余额判断余额≥票价提示余额不足DECREMENT减值命令返回成功重试一次TRANSFER传输落块回读一致记录错误DISPLAY/BEEP显示与提示完成不影响交易UPLOAD串口上传帧校验通过缓存重发状态机的好处是每个环节都能单独打日志。串口打印不要只打“成功/失败”要打状态、UID、块地址、余额、错误码。现场排查时看日志能区分是卡没进场、防冲突失败、认证失败还是写块失败。冷却状态用定时器节拍计数同一 UID 在 3 秒内不重复扣费如果业务要求“一人一卡”冷却时间可以更长但别把正常连续刷卡误判成重复。注意状态机里不要放长延时LCD 刷新和蜂鸣器用非阻塞节拍否则寻卡响应会变慢。4.2 扣费逻辑与钱包块数据结构扣费逻辑要围绕钱包块写不要围绕显示值写。显示值只是读出来给人看的真正扣的是块里的数值。假设票价 2 元钱包单位按“分”存余额 1000 表示 10.00 元读钱包后先校验反码和地址反码再比较余额是否大于等于 200满足则发DECREMENT参数传扣减值和块地址成功后发TRANSFER再回读钱包块确认数值变了。余额不足时只提示不扣费不要先扣再退款脱机场景退款逻辑很麻烦。字节偏移内容示例说明03数值小端C8 00 00 00200 分47数值反码37 FF FF FF用于校验811数值备份C8 00 00 00再存一遍12块地址04钱包所在块13地址反码FB地址校验1415保留00 00按卡型使用钱包初始化时地址字节要填钱包块的实际地址反地址是地址按位取反。扣费后回读先看数值是否等于“原值-票价”再看反码是否匹配不匹配就判写块异常记录 UID 和块地址不要连续重试太多次避免把卡写坏。实际业务里还会遇到“余额够但写块失败”的情况这时卡内余额没变显示层不能提前显示扣费成功正确顺序是传输成功后再刷 LCD。串口上传帧里带交易前余额、交易后余额和错误码上位机对账时能还原每一次扣费。4.3 LCD1602 与 DS1302 的协同显示LCD1602 显示内容要克制第一行显示卡号后 8 位或“欢迎乘车”第二行显示余额和时间扣费成功时第二行短暂显示“扣款 2.00 余额 8.00”1 秒后恢复时间。LCD 初始化按0x38、0x0C、0x06、0x01的顺序发清屏后延时 2ms 以上如果 RW 接地写命令后要加延时不能连续猛写。显示字符前把数值转成 ASCII金额按分转元别在中断里做浮点运算。DS1302 读时间时先读秒再读分、时、日、月、周、年判断秒是否变化变化则重读一次避免读到跨秒的脏数据。LCD 命令含义使用时机0x388 位数据、两行、5×7上电初始化0x0C开显示、关光标初始化0x06写入后地址加 1初始化0x01清屏初始化/刷新0x80addr设置 DDRAM 地址定位显示DS1302 的 BCD 码要转十进制再显示写时间时反过来转 BCD。上电初始化要清秒寄存器 bit7 的 CH 位否则振荡器可能停。备用电池电压不够时掉电后时间会归零现场表现是“每次断电时间都回到 00:00”。如果 LCD 显示乱码但串口打印正常先查 LCD 数据线顺序和 RS/RW/E 时序再调 V0 对比度如果时间乱跳先查 32.768kHz 晶振和负载电容再查备用电池。显示和时钟不参与扣费决策但它们是现场判断系统是否正常的第一眼依据。4.4 串口上传帧格式与上位机联调串口上传用于对账和抓日志不建议在刷卡主流程里同步等上位机应答。帧格式要定长、带头尾和校验帧头0xAA 0x55卡号 4 字节交易金额 2 字节时间 6 字节年、月、日、时、分、秒状态 1 字节校验和 1 字节帧尾0x0D 0x0A。波特率 9600、8 数据位、无校验、1 停止位和 11.0592MHz 晶振匹配。上位机用串口助手切 HEX 显示先看帧头对不对再看校验和最后解析金额和时间。现场干扰大时帧之间加 20ms 间隔别连续猛发。字段长度示例说明帧头2AA 55固定卡号412 34 56 78UID 低 4 字节金额2C8 00小端单位分时间618 06 01 12 30 00BCD 或十进制需约定状态100成功、01余额不足错误码校验1前面字节累加和低 8 位帧尾20D 0A方便文本查看void UartSendFrame(unsigned char *uid, unsigned int amount, unsigned char *time6, unsigned char status) { unsigned char i, sum 0; UartSendByte(0xAA); sum 0xAA; UartSendByte(0x55); sum 0x55; for (i 0; i 4; i) { UartSendByte(uid[i]); sum uid[i]; } UartSendByte(amount 0xFF); sum (amount 0xFF); UartSendByte((amount 8) 0xFF); sum ((amount 8) 0xFF); for (i 0; i 6; i) { UartSendByte(time6[i]); sum time6[i]; } UartSendByte(status); sum status; UartSendByte(sum); // 校验和不含帧尾 UartSendByte(0x0D); UartSendByte(0x0A); }逻辑说明uid是 4 字节卡号amount是本次交易金额单位分小端发送time6是 6 字节时间status是业务状态。校验和只累加帧头到状态字节接收端按同样规则算不一致丢帧。参数上金额用unsigned int避免负数如果票价可能退款本文还有配套的精品资源点击获取