简介:面向电子信息工程、物联网与交通一卡通方向的课程设计参考文档,围绕基于RFID的公交车刷卡系统展开,适合需要完成电子系统设计、撰写设计说明书或进行硬件选型与软件流程梳理的学生及初阶开发者。压缩包内为1个doc文件,约649KB,集中呈现一份完整的电子系统设计说明书,目录涵盖研究背景、系统硬件设计、系统软件设计与总结等章节。硬件部分具体讨论Mifare One非接触式IC卡、AT89S52单片机、晶振电路、蜂鸣器提示音、LCD1602显示及DS1302时钟模块,并给出系统总体设计方案;软件部分包括初始化模块、射频卡与读写器通信设计及总流程图。已有4299人学习下载,可作为课程设计报告结构、模块电路说明与软件流程组织的对照材料,也便于了解RFID公交刷卡系统从选型到方案集成的完整思路。
1. 从一次刷不上卡说起:这套 RFID 公交刷卡系统设计要解决什么
公交车上最难受的一次是后面排了七八个人,卡贴上去半秒没反应,司机说再贴一次,还是没反应,最后掏零钱。这套基于 RFID 的公交车刷卡系统设计,就是围绕这样一个很具体的问题展开的:卡片在 13.56MHz 载波下被唤醒、防冲突挑出一张、三次认证通过、钱包块减掉一次票价、LCD 打出余额和时间、蜂鸣器响一声,六个动作要在一秒左右走完。整份方案面向电子信息工程专业的课程设计与毕业设计,也适合要把读卡模块塞进考勤机、储物柜、班车计费的从业者。硬件走的是 AT89S52 加 MFRC522 这条低成本成熟路线,软件用单片机 C 语言写,模块边界划得比较清楚,换卡型或者换显示都不用大改。
2. 硬件选型与电路落地:Mifare One、AT89S52 和五个外围模块怎么定
硬件这一章的判断标准只有两条:一是能不能在低成本下稳定跑起来,二是学生手上有没有条件焊、有条件测。器件清单看着简单,每一项背后都有一笔账要算,选错的代价往往不是多花钱,而是调试阶段找不到方向。
2.1 射频卡选型:为什么在条码、磁卡、接触式 IC 卡里挑非接触式
市面上流通的卡大致四类:条码、磁卡、接触式 IC 卡、非接触式 IC 卡。条码和磁卡在超市和银行用得多,成本极低,但条码怕污损、磁卡怕磁场,公交这种高频次、户外、单手操作的场景都不合适。接触式 IC 卡在公共电话时代很常见,问题是芯片外露,插拔几百次之后触点磨损,卡片弯折就容易报废。
非接触式 IC 卡把芯片和天线线圈一起封进卡片内部,没有外露触点,不怕灰尘、不怕轻微弯折,刷卡时不用对准方向,隔着几厘米就能完成能量耦合和数据交换,这个特性对公交场景几乎是刚需。方案里选的是 Mifare One 卡,也就是常说的 S50,1K 字节容量、16 个扇区、每扇区 4 个块,工作频率 13.56MHz,符合 ISO14443A 协议。它的钱包块格式是现成的,做扣费不用自己从头设计余额结构。
选它还有一个现实原因:这张卡的扇区权限、密钥体系、块地址规则在教材和开源例程里都能查到,学生做课设时不至于卡在"卡里到底存了什么"这种黑匣子问题上。卡片内部的存储划分通常这样安排:
| 块地址 | 用途 | 存放内容 | 访问权限 |
|---|---|---|---|
| 扇区 0 块 0 | 厂商固化区 | 序列号 UID、厂商数据 | 只读,不可改 |
| 扇区 0 块 1~2 | 卡户信息 | 卡号、持卡人标识、发卡日期 | 密钥 A 可读 |
| 扇区 1 块 4 | 电子钱包 | 余额值 + 反码 + 备份地址 | 密钥 A/B 读写 |
| 扇区 1 块 5 | 交易记录 | 最近一次扣费时间、金额 | 密钥 A/B 读写 |
| 扇区 1 块 7 | 控制块 | 密钥 A、访问位、密钥 B | 不可直接读 |
钱包块不能随便拿一个数据块当余额用,它必须是"值块"格式:4 字节小端余额、4 字节按位取反的校验、4 字节备份地址。这样卡片硬件在执行加值、减值指令时会自己做一致性检查,写坏了能立刻发现。我一般会在块 4 做完扣费后,回读一次余额和反码,两者按位取反对不上就判定本次交易失败,这比事后查账省事得多。
2.2 主控与最小系统:AT89S52 的 I/O 账和 11.0592MHz 晶振
主控选 AT89S52,理由不是它先进,而是够用、便宜、资料全。它是 40 引脚封装,32 个双向 I/O 口,片内 8KB 可反复擦写的 Flash、256 字节 RAM、3 个 16 位定时计数器、1 个全双工串口,兼容标准 MCS-51 指令系统,支持在线编程。对比 DSP 和 ARM,后两者性能强但价格高、外围电路复杂,对一个只需要"读卡、显示、计时、串口上传"的系统来说属于性能溢出,调试成本反而更高。
真正要吃透的是 I/O 分配。32 个口看着多,实际一排下来很紧张:LCD1602 的数据线要占 8 位(P0 口,还得外接 10K 排阻做上拉,因为 P0 是开漏输出),RS、RW、E 三根控制线占 3 位;MFRC522 如果走并口要 8 位数据加若干控制线,走 SPI 只要 4~5 根;DS1302 三线;蜂鸣器一根;串口占 P3.0、P3.1 两根。全部铺开基本把端口吃干净,所以方案里读卡模块优先走 SPI 或者复用总线,把 P2 口留给控制信号。
晶振选 11.0592MHz 而不是常见的 12MHz,这是一个容易被忽略但很关键的点。串口工作在方式 1 时,波特率由定时器 1 的溢出率分频得到,11.0592MHz 能被 9600、19200 这些标准波特率整除,误差接近 0;用 12MHz 算出来是 9600 的 1.08 倍左右,短帧通信勉强能忍,连续传几十字节就开始丢。晶振两端各接一个 30pF 的无极性电容到地,这两个是负载电容,容量影响谐振频率和输出幅度,取值偏离标称值会让起振变慢甚至不起振,画板时电容要尽量靠近晶振引脚。
复位电路用经典的阻容复位加分压,RST 引脚保持两个机器周期以上的高电平即可复位。这里有个小细节:如果读卡器插在车上的供电环境里,上电瞬间电源爬升慢,阻容复位可能不到位,通常的做法是并一个手动复位按键,调试阶段能省掉反复拔插电源的麻烦。
2.3 外围三件套:蜂鸣器驱动、LCD1602 接口与 DS1302 三线时序
蜂鸣器看着最简单,翻车率却不低。单片机 I/O 口的灌电流和拉电流能力有限,直接驱动蜂鸣器声音发虚甚至带不动,所以中间加一级三极管做开关。电路逻辑是:P2.3 输出高电平时三极管截止,蜂鸣器回路断开不响;输出低电平时三极管导通,回路闭合发声。也就是说这个蜂鸣器是低电平触发的,写代码时别把电平搞反,否则表现是"该响的时候不响,不该响的时候一直响"。三极管的基极要串一个 1K~4.7K 的限流电阻,防止把单片机口拉爆。
LCD1602 是 16×2 的字符型液晶,内部控制器兼容 HD44780 指令集,初始化序列写对了,市面大部分同规格模块都能直接换。它的忙标志位 BF 在读写时会占用数据线,简单做法是每次写指令后给 1~2ms 延时,不查忙标志,代码短、可靠性也够。初始化代码大致长这样:
/* LCD1602 初始化:8 位数据线、两行显示、5x7 点阵 */ void LCD1602_Init(void) { DelayMs(15); /* 上电后等待内部复位完成 */ LCD_WriteCmd(0x38); /* 功能设置:8 位接口,两行,5x7 点阵 */ DelayMs(5); LCD_WriteCmd(0x38); /* 数据手册要求连续发两次 */ LCD_WriteCmd(0x0C); /* 开显示,关光标,不闪烁 */ LCD_WriteCmd(0x06); /* 写入后地址自动加 1,画面不移动 */ LCD_WriteCmd(0x01); /* 清屏 */ DelayMs(2); /* 清屏指令执行时间较长,必须等 */ }这段的关键在时序而不是指令本身。0x38 连发两次是数据手册的硬性要求,第一次用来把模块从可能的 4 位模式拉回 8 位模式;0x01 清屏指令内部执行约 1.64ms,期间不接受新指令,延时不够就会出现"清屏后第一行显示乱码"这种很玄学的现象。
DS1302 负责给每笔交易打时间戳,它对公交扣费很重要——同一张卡一天刷几次、有没有异常重复刷卡,全靠时间判断。这颗芯片工作电压 2.5V~5.5V,能对年月日周时分秒计时,带闰年补偿,通过三线(RST、CLK、DAT)与单片机同步通信。读一个字节的时序是低位在前,写命令字之后逐位在时钟上升沿输出:
/* 从 DS1302 读一个字节,addr 为命令字(如 0x81 读秒) */ unsigned char DS1302_ReadByte(unsigned char addr) { unsigned char i, dat = 0; DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; /* 拉高 RST 启动一次传输 */ DS1302_WriteByte(addr); /* 先发命令字,最低位先出 */ for (i = 0; i < 8; i++) { /* 数据同样低位在前 */ dat >>= 1; if (DS1302_DAT) dat |= 0x80; DS1302_CLK = 1; /* 上升沿让芯片输出下一位 */ DS1302_CLK = 0; } DS1302_RST = 0; /* 拉低结束本次传输 */ return dat; }读秒、分、时分别用 0x81、0x83、0x85 这三个命令字,读回来的是 BCD 码,要转成十进制才能送到 LCD 显示。这一步转码漏掉,屏幕上就会出现"13 点 45 分"显示成"0x13 点 0x45 分"的怪值。DS1302 还有一点要注意:首次上电时寄存器里的值是不确定的,程序里要做一次"掉电标志判断 + 初始化写入",否则时间会从一个随机值开始走。
3. 总体架构设计:四大模块的职责边界与信号流
把器件堆在一起不叫系统,把职责切清楚、把信号流画明白,后面写代码和排查才有依据。这套方案从功能上切成四块:控制模块、发送接收数据模块、串行通信模块、显示模块。切法看起来朴素,但它对应的是四条完全不同的调试路径,出问题时能立刻定位到哪一层。
3.1 控制、收发、串行通信、显示四个模块怎么切
控制模块由单片机和晶振电路构成,是整个系统的心脏,负责时序调度、协议状态机、按键与中断响应。它不直接碰射频信号,只对读卡模块下发命令、读回结果,这个边界一定要守住,否则射频部分的时序抖动会污染主循环。
发送接收数据模块以 MFRC522 为核心,配 13.56MHz 晶振和天线。它自己分成模拟和数字两部分:模拟部分负责驱动天线、提供 13.56MHz 能量载波、按寄存器设置对发送数据做调制,同时检测并解调卡片返回的信号;数字部分通过并口或 SPI 与单片机通信,把"我要寻卡""我要读块 4"这类请求翻译成射频动作。这种内外分工的好处是,单片机不用关心载波怎么发,只关心命令和状态码。
串行通信模块承担两件事:一是读卡器与上位机(比如车载终端或调试用的 PC)之间的数据上传,二是射频卡信息的修改与下发。用 RS232 电平或者 TTL 串口都行,波特率定在 9600,帧格式 8 位数据、1 位停止、无校验,够用且抗干扰余量大。
显示模块负责把卡号、余额、时间打在 LCD1602 上。两行十六字符看着少,实际排版要提前规划:第一行放卡号和状态,第二行放余额与时间,超过 16 字符的卡号要截断或者滚动显示。模块职责表大致如下:
| 模块 | 核心器件 | 输入 | 输出 | 主要调试手段 |
|---|---|---|---|---|
| 控制模块 | AT89S52 + 晶振 | 中断、串口命令 | 各模块控制信号 | 示波器看晶振起振、串口打日志 |
| 收发数据模块 | MFRC522 + 天线 | 单片机命令 | 卡型、UID、块数据 | 读寄存器版本号、示波器看载波 |
| 串行通信模块 | 串口 + 电平转换 | 卡数据、交易记录 | 上行帧 | 串口助手抓帧 |
| 显示模块 | LCD1602 | 卡号、余额、时间 | 字符画面 | 单独跑显示测试程序 |
这张表在实际项目里的价值是:任何一次故障,先判断落在哪一行,再决定拿什么工具测。曾经遇到"刷卡后显示正常但上位机收不到数据",按表一查就是串行模块,最后发现是波特率重装值按 12MHz 算的,跟 11.0592MHz 的板子对不上。
3.2 13.56MHz 载波链路:模拟部分与数字部分的分工
射频链路的能量路径值得单独说,因为它是整个系统里最容易被当成黑匣子的一段。读写器并不"读"卡,它先发一个 13.56MHz 的载波,卡片天线通过电感耦合从载波里取电,把内部电路激活,然后再用负载调制的方式把数据"反射"回来。也就是说,卡片是无源的,读写器的天线既是发射天线也是接收天线。
MFRC522 的能量载波由 13.56MHz 晶振提供,天线部分主要由 LC 低通滤波器和 LC 谐振电路组成。低通滤波器滤掉载波的高次谐波,谐振电路把天线调谐到 13.56MHz 上,让辐射效率最高。这两个网络的元件参数不能随手取值,天线线圈的电感量、匹配电容的容量决定了谐振点,偏了就会出现"贴着能读、离两厘米就不行"的现象。
数字部分负责的事情相对单纯:通过并口和中断与单片机通信,把待发送的数据做调制前的打包,把接收到的信号解调成字节流。方案里 MFRC522 的中断请求和串口中断都映射到 IRQ 模式,单片机用中断方式响应"卡已进入场区""数据接收完成"这类事件,主循环则做业务逻辑。这样分工之后,主循环不会被射频时序拖住,LCD 刷新和串口发送也不会因为等卡而卡顿。
3.3 串行通信与接口预留:让读卡器变成可复用模块
这套系统如果只做成一个能刷卡的盒子,价值有限;做成一个"带串口输出的读卡模块",就能塞进更多场景。设计时预留三样东西:一是标准的 TTL 串口排针,方便接 USB 转串口模块直接连电脑;二是电源输入做宽压处理,加一级稳压到 5V,避免车上电压波动直接打进单片机;三是把读卡结果做成固定格式的帧,比如"帧头 + 卡号 4 字节 + 余额 2 字节 + 时间 6 字节 + 校验 + 帧尾"。
固定帧格式的好处是可测试。调试阶段用串口助手直接看帧,不用每次都盯着 LCD;上位机侧只要按帧解析,换一块板子也不用改协议。串口的初始化代码如下,参数改一行就能适配别的波特率:
/* 串口初始化:方式 1,8 位数据,9600bps @ 11.0592MHz */ void Uart_Init(void) { SCON = 0x50; /* 方式 1,允许接收 */ TMOD |= 0x20; /* 定时器 1 工作在方式 2,8 位自动重装 */ TH1 = 0xFD; /* 11.0592MHz 下 9600bps 的重装值 */ TL1 = 0xFD; TR1 = 1; ES = 1; /* 开串口中断 */ EA = 1; /* 开总中断 */ }TH1 取 0xFD 是按 11.0592MHz、波特率 9600、SMOD 为 0 算出来的,改成 19200 就把重装值改成 0xFA 左右,具体值要用公式复核一遍。很多人图省事直接抄 0xF3,结果波特率对不上,收到的全是乱码,还以为是硬件虚焊。
4. 软件流程实现:从初始化到一次完整扣费的代码路径
软件部分的核心不是写多少行,而是把"一次刷卡"拆成可验证的若干步,每步都有明确成功标志和失败出口。整个流程是:系统初始化 → 寻卡 → 防冲突 → 选卡 → 三次认证 → 读钱包 → 判断余额 → 扣费 → 写回 → 显示与提示 → 串口上传。
4.1 系统初始化:中断向量、串口参数与外设时钟
初始化是所有玄学问题的源头。系统要正常运行,必须先把中断向量表建立起来,再做外设配置:串口波特率与帧格式、定时器工作方式、MFRC522 与串口的中断优先级分配。MFRC522 和串口都基于中断工作,配置顺序错了会出现"卡能寻到但串口收不到"或者"两个中断互相打断"。
初始化的骨架包括:单片机时钟与看门狗配置、I/O 口方向设定(LCD 数据口输出、蜂鸣器口输出、DS1302 三线方向切换)、LCD1602 初始化、DS1302 上电时间校准、串口初始化、MFRC522 复位与寄存器配置。MFRC522 复位后建议先读一次版本寄存器,返回值是 0x91 或 0x92 才说明芯片活着,这一步相当于给自己留一个排查问题的办法,后续任何异常都能先排除"芯片根本没工作"。
/* 系统初始化主流程,按依赖顺序排列 */ void System_Init(void) { EA = 0; /* 先关总中断,避免配置过程被中断打断 */ Uart_Init(); /* 串口:9600bps,允许接收 */ LCD1602_Init(); /* 液晶:8 位、两行、关光标 */ DS1302_Init(); /* 时钟:判断掉电标志,必要时写入初始时间 */ PcdReset(); /* MFRC522 软复位 */ if (PcdReadReg(VersionReg) != 0x91 && PcdReadReg(VersionReg) != 0x92) { LCD_ShowString(0, 0, "RC522 ERR"); /* 版本号不对,直接报错 */ while (1); } PcdAntennaOn(); /* 打开天线,开始发射 13.56MHz 载波 */ EA = 1; /* 外设配置完毕,再开总中断 */ }顺序上有两个讲究:EA 最后才打开,防止半配置状态下进中断读到脏数据;天线在芯片自检通过后才打开,避免自检失败还一直发载波,白白耗电还可能干扰旁边电路。
4.2 寻卡与防冲突:MFRC522 的命令序列
寻卡是问"场区里有没有卡",防冲突是问"有好几张卡时选哪一张"。这两步的返回值含义完全不同,混在一起写是新手最容易犯的错。
寻卡命令用 0x52(寻天线场内所有卡)或 0x26(只寻未休眠的卡)。发出去之后,能正常工作的卡会返回 2 字节应答,用 ATQA 表示卡的类型和容量。返回值长度必须是 16 位(2 字节),少了说明没有卡或者通信失败。
/* 寻卡:req_code 常用 0x52,tag_type 返回 2 字节卡型 */ unsigned char PcdRequest(unsigned char req_code, unsigned char *tag_type) { unsigned char status; unsigned int backBits; PcdWriteReg(BitFramingReg, 0x07); /* 短帧:最后字节只发 7 位 */ status = PcdComMF522(PCD_TRANSCEIVE, &req_code, 1, tag_type, &backBits); if (status != MI_OK || backBits != 0x10) { status = MI_ERR; /* 应答不足 2 字节,判为失败 */ } return status; }防冲突这一步,读卡器发 0x93 0x20,场区内所有卡同时应答自己的 UID,多个卡同时回会造成位碰撞,芯片通过逐位判定把冲突位定下来,最终只留下序列号最小的那张卡继续通信,其余的回到空闲状态等下一轮。返回值是 5 字节,前 4 字节是 UID,第 5 字节是异或校验值,必须校验通过才算选卡成功。
这里有个实操细节:卡片被选中之后不会一直保持选中状态,中间如果隔太久没发指令,卡会自己回到空闲。所以选卡、认证、读块这三步要连着走完,中间不要插 LCD 刷屏或者长延时。我见过把显示刷新放在认证前面的写法,结果十次里有一两次认证失败,查了半天以为是天线问题。
4.3 三次认证与钱包块:加值减值的块结构
Mifare One 的每个扇区都有自己的密钥,读写前必须对目标扇区做认证。认证过程是三次互相确认:读卡器发认证请求,卡片回一个随机数,读卡器用密钥加密后送回,卡片验证通过才开放该扇区的后续读写。认证通过后建立的是"本次会话"的加密通道,换扇区要重新认证。
钱包块的操作有固定的六条指令:读块、写块、初始化钱包、加值、减值、存储、传输、中止。其中加值和减值不是直接改数,而是先把目标值装进数据寄存器,再由芯片算出反码和备份,最后用传输指令一次性写进块里。这个设计的意义是:即使写入过程中断电,卡片也能通过反码判断出数据被破坏,把这次交易作废而不是记成半个余额。
扣费的业务逻辑要写成"先判断后执行":读块 4 拿到余额,比较本次票价,余额不足就提示"余额不足"并直接退出,不要先进减值再判断。流程上宁可多读一次块,也不要让卡片进入半扣费状态。完成减值后回读一次,把余额和反码做按位取反比对,一致才算交易成功,然后把时间和金额写进块 5 的交易记录区,最后才刷新 LCD 和蜂鸣器。
安全上还有一个容易漏的点:密钥不能写死在主程序里明晃晃地放在那儿,至少要拆成几个常量拼起来,或者放在单独的密钥文件里按扇区索引。密钥 A 用于读,密钥 B 用于写,控制块的访问位要设成密钥 A 不可写,避免误操作把整张卡锁死。控制块一旦写坏,那个扇区就永久报废,卡片的血泪经验基本都出在这里。
4.4 显示、时钟与提示音的联动:一个状态机收口
刷卡不是单一动作,是一串带先后顺序的状态迁移。把这串动作写成状态机,比写成一堆顺序 if 要稳得多,也方便后面加"重复刷卡间隔限制""余额不足提示""黑名单卡拒绝"这类功能。
状态划分建议是:IDLE(等卡)→ CARD_FOUND(寻到卡)→ AUTH_OK(认证通过)→ BALANCE_OK(余额足够)→ DONE(扣费完成)→ ERROR(任一环节失败)。每个状态只做一件事,做完立刻迁移,失败统一跳 ERROR 并在 LCD 上打出两位错误码。错误码用两位数字表示:01 表示无卡应答、02 表示防冲突失败、03 表示认证失败、04 表示余额不足、05 表示写块失败。有了这套码,现场排查不用猜。
/* 单次刷卡业务的状态机主干(简化) */ unsigned char Bus_Tick(void) { switch (state) { case IDLE: if (PcdRequest(0x52, tag_type) == MI_OK) state = CARD_FOUND; break; case CARD_FOUND: if (PcdAnticoll(uid) == MI_OK && PcdSelect(uid) == MI_OK) state = AUTH_OK; else state = ERROR, err_code = 0x02; break; case AUTH_OK: if (PcdAuthState(0x60, 4, key_a, uid) != MI_OK) state = ERROR, err_code = 0x03; /* 认证失败最常见原因是密钥错 */ else if (ReadBalance(&balance) && balance >= FARE) state = BALANCE_OK; else state = ERROR, err_code = 0x04; break; case BALANCE_OK: if (DeductFare(FARE) == MI_OK) state = DONE; else state = ERROR, err_code = 0x05; break; case DONE: BeepOnce(1); /* 成功只响一声,短促 */ LCD_ShowBalance(balance - FARE); Uart_SendFrame(uid, balance - FARE, DS1302_ReadTime()); state = IDLE; break; case ERROR: BeepOnce(3); /* 失败连响三声,区分度高 */ LCD_ShowError(err_code); state = IDLE; break; } return state; }显示和提示音的联动规则要提前定死,不要临时改:成功短响一声、LCD 第二行右侧显示新余额;失败连响三声、第一行显示错误码。这样用户在车上不用看屏幕也知道成没成,调试时听声音就能判断走到哪一步。DS1302 的读取放在 DONE 状态里做,因为只有交易成功才需要时间戳,失败交易不需要占用时钟总线。
5. 避坑与排查:调试阶段最容易翻车的五个点
这一章列的是实际调试里出现频率最高的五类问题,基本照着现象查就能定位。
5.1 卡贴上去毫无反应,LCD 也不刷新
现象是卡片靠近天线,蜂鸣器不响、屏幕不变、串口没有输出。原因通常是射频链路根本没工作,而不是卡的问题。先查三处:MFRC522 的版本寄存器读出来是不是 0x91 或 0x92,不是就说明 SPI 或并口接线错了、复位时序不对;再看天线是否使能,寄存器里天线驱动位没打开,芯片就是不发载波;最后用示波器量天线两端有没有 13.56MHz 正弦波,有波形但幅度很小,多半是匹配电容取值不对或者天线线圈虚焊。解决顺序固定为:先自检芯片、再开天线、最后调匹配网络,跳过前两步直接调天线属于白费功夫。
5.2 能寻到卡但认证一直失败
现象是寻卡返回成功、UID 也能读出来,一到认证就返回错误。原因八成是密钥或者扇区号写错了。Mifare One 的密钥 A 和密钥 B 是两组独立密码,认证命令字 0x60 对应密钥 A、0x61 对应密钥 B,块地址和扇区号也不是一回事——块 4 属于扇区 1,认证时传的扇区号必须换算正确。还有一种情况是这张卡之前被别的程序改过密钥,出厂默认密钥已经失效。解决办法是先写一个只做认证不做扣费的最小测试程序,把 16 个扇区逐个用默认密钥试一遍,能通过的记下来,再回到业务代码里对照。
5.3 扣费偶尔成功偶尔失败,余额对不上
现象是十次刷卡里有两次余额没变,或者余额变成了一个离谱的大数。原因通常是钱包块格式不对,或者减值流程没走完整。钱包块必须是值块格式,写块指令直接往里塞普通数据,芯片不会认它是钱,后续减值就失效。另外减值之后必须紧接传输指令把数据寄存器内容写进块,中间被打断就会留下半成品。解决上,扣费统一走"读余额 → 减值 → 回读校验"三步,回读时把余额和反码做按位取反比对,不一致就把本次交易标记失败并且不进交易记录,宁可让用户重刷一次,也不要写进一个错误余额。
5.4 LCD 显示乱码或者只亮不显示
现象是背光亮着但屏幕上全是黑块或者随机字符。原因一般有三类:初始化序列不完整,特别是 0x38 只发了一次;清屏指令后的延时不够,后续指令被丢弃;对比度调节电位器没调,VO 引脚电压不对,字符其实是显示出来了但看不见。排查方法是先单独跑一个只显示固定字符串的测试程序,把硬件问题和业务代码隔离开。如果固定字符串能显示,说明液晶没问题,乱码就出在数据格式上,比如把 BCD 码直接当 ASCII 送进去了。
5.5 串口上行数据乱码或丢帧
现象是串口助手收到的全是问号或者字节对不上。最常见的原因是波特率不匹配,板子按 11.0592MHz 算的重装值,上位机却设成了别的波特率;其次是电平不匹配,单片机的 TTL 电平直接接 RS232 接口,没有经过电平转换芯片,信号根本识别不了;还有一种容易被忽略的是共地问题,两块板子只连了信号线没连地线,通信时好时坏。解决办法是先把波特率、数据位、停止位、校验位四项在两边对齐,再用示波器量 TX 引脚上的波形宽度,一位一位对时间,9600bps 下一位约 104 微秒,量出来差很多就是晶振或者重装值的问题。
6. 验收与答辩:怎么证明这套读卡器真的能用
验收这一步,最忌讳的是只演示"刷一下屏幕变了"。评审看的不是你演示得顺不顺,而是这套系统在异常情况下表现如何。我一般会把验收拆成功能项、边界项、异常项三组,每项都有明确的通过判据,现场按表走,通不过的当场说明原因而不是绕过。
| 验收项 | 操作 | 通过判据 |
|---|---|---|
| 寻卡距离 | 卡片从 5cm 外缓慢靠近 | 3cm 内稳定响应,不出现反复识别 |
| 多卡防冲突 | 同时放 3 张卡在场区内 | 只选中一张,UID 唯一且可复现 |
| 正常扣费 | 余额充足卡刷一次 | 余额准确减票价,蜂鸣器短响一声,LCD 显示新余额 |
| 余额不足 | 用低于票价的卡刷 | 不扣费,连响三声,显示错误码 04 |
| 重复刷卡 | 同一张卡连续刷两次 | 第二次按设定的间隔时间拒绝或正常处理,行为一致 |
| 断电恢复 | 扣费过程中断开电源再上电 | 余额要么是原值要么是新值,不出现中间值 |
| 时间戳 | 刷一次后读交易记录 | 时间与实际时间误差在分钟级以内 |
| 串口上传 | 接串口助手抓帧 | 帧头、卡号、余额、时间、校验全部正确 |
答辩时最容易被追问的三个问题,提前准备好答案会从容很多。第一个是"为什么不用 ARM 或者现成的读卡模块",答成本和技术成熟度,说清楚这个系统的瓶颈在天线和卡片协议,不在主控算力。第二个是"多张卡同时进入场区怎么办",答防冲突机制按 UID 逐位判定,最终只保留一张,未选中的回到空闲等待。第三个是"扣费断电了余额会不会错",答钱包块的值格式带反码校验,回读比对不过就判交易失败,这是卡片层面提供的原子性,不是软件硬凑的。
现场演示还有一个实用技巧:把错误码做成一份小卡片带在身上,出错时直接看屏幕两位码对照,不用当场翻代码。我在第一次答辩时就吃过这个亏,屏幕打出一个自己都忘了含义的码,站在那儿现想,越急越想不起来。从那以后我每次做带状态机的项目,都强制把错误码表打印出来贴在板子背面,验收和演示时先看码再说话。这套流程走顺了,剩下的就是把这套硬件、代码和验收表照着改一遍的事。希望帮到你。
本文还有配套的精品资源,点击获取