news 2026/10/22 13:22:09

RFID公交卡充值实战:从M1卡读写到余额回写全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RFID公交卡充值实战:从M1卡读写到余额回写全流程

简介:面向RFID技术课程学习者与物联网专业学生的教学演示PPT,以公交卡充值为场景,系统讲解RFID系统组成、阅读器与标签的无线交互、电磁波激活天线及刷卡充值全流程。压缩包共1个pptx演示文稿,大小约2.17MB,内容紧凑,适合课堂展示、实验预习或课后复习。目前已有92人学习浏览。PPT按步骤梳理系统初始化、阅读器使能、识别公交卡、蜂鸣器提示、ID显示、充值前后余额查询等环节,并针对公交卡存储块1第1字节blockdata[0]的数值增加,说明充值金额可固定也可自定义的修改方式;同时配有程序流程设计和主要实现代码,体现从硬件初始化到数据块改写再到显示更新的完整逻辑,便于理解RFID标签数据存储与读写器交互原理,适合作为实验参考或课程报告素材。

1. RFID公交卡充值:一个藏在PPT里的完整闭环

很多刚接触射频识别(RFID)的开发者,第一反应是去啃ISO/IEC 14443协议栈,或者纠结M1卡的加密算法,结果被扇区密钥和认证流程劝退。而这份《射频识别技术-RFID公交卡充值》讲义走的是另一条路:它把整套M1卡操作浓缩成一个“刷卡→响铃→显示ID→读余额→充值→再读余额”的闭环,代码量不大,却覆盖了RFID阅读器初始化、防碰撞识别、数据块读写和LCD回显的全部关键动作。对想快速跑通一套RFID应用demo的从业者来说,这个资源比直接读芯片手册友好得多——它讲清楚了“公交卡余额为什么存在块1第1个字节”,以及“想让充10块还是充50块,改哪个参数”。适合正在做门禁、食堂卡、校园卡或仓储盘点系统,想从协议层往业务层跨一步的人。

2. 读懂M1卡的存储结构:为什么余额偏偏存在块1第1个字节

2.1 M1卡的扇区、块与字节寻址

Mifare Classic 1K卡是公交卡最常用的芯片之一,它内部有16个扇区,每个扇区4个块,每个块16个字节。块0是厂商固化的UID和厂商数据,扇区的块3是密钥和访问控制位,真正能给业务用的就是每扇区的块1、块2以及其余扇区的块0。讲义里提到的“块1中的第1个字节”指的就是扇区0的块1偏移量为0的那个字节,用代码表示就是 blockdata[0]。

常见做法是把余额定义成整数元,用一个字节存储时上限是255,如果卡片面额可能超过这个值,就需要扩展到两个字节,或者把单位从“元”改成“角”。我在实际项目里就吃过这个亏,当时额度设成单字节存储,结果充到256就溢出归零,查了半天才发现是数据类型的问题。

2.2 为什么公交卡读写不需要“格式化”

M1卡出厂时默认密钥是FFFFFFFFFFFF(6字节全F),很多开发板自带的库函数默认就是用这个密钥做认证。公交卡系统虽然在实际部署时会改密钥,但教学版的读写流程往往跳过密钥管理这一层,直接认证、读块、写块。好处是跑通逻辑很快,坏处是你得知道:真实环境里第一步永远是先做密钥认证,否则读到的数据全是加密后的乱码。

2.3 充值前后两读的意义

正常的充值流程是“先读旧余额→写新余额→再读验证”。讲义里把“充值前余额显示”和“充值后余额显示”都列为独立步骤,这不是冗余,而是一个闭环验证:写卡成功了没有、写进去的字节对不对,不重新读一遍是看不出来的。很多翻车案例都是写完卡直接返回成功,结果卡里余额根本没变。

3. 把PPT里的流程变成可抄的代码:初始化、识别与回显

3.1 系统初始化和LCD初始化

// 串口和LCD初始化 void System_Init(void) { UART_Init(9600); // 调试串口波特率,看卡片UID用 LCD_Init(); // 1602或OLED初始化 LCD_Clear(); LCD_ShowString(0, 0, "RFID Bus Card"); }

这里 UART_Init 的波特率建议先设成9600,等确认通信稳定后再往上调。LCD初始化里最坑的是I2C地址,常见的有0x27和0x3F,不对时屏幕只亮不显示。

3.2 RFID阅读器初始化:电源、天线和寻卡

// 以RC522为例 void RFID_Reader_Init(void) { RC522_Init(); // SPI接口初始化,CS引脚拉高 RC522_AntennaOn(); // 开天线,必须有这一句,否则读不到卡 RC522_SetReg(RegMode, 0x3D); // 设置为发送和接收使能 } uint8_t RFID_PollCard(void) { uint8_t uid[4] = {0}; uint8_t len = 4; uint8_t status = RC522_Request(PICC_REQIDL, uid); // 寻卡 if (status == MI_OK) { status = RC522_Anticoll(uid); // 防碰撞,拿UID } return status; }

RC522_Request 是寻卡指令,PICC_REQIDL 表示寻所有空闲状态的卡;RC522_Anticoll 用来做防碰撞,当有多张卡同时靠近时拿到其中一张的UID。天线开关是最容易被忽略的:RC522_AntennaOn 不调用,卡片放在天线正上方也毫无反应,这个坑我帮别人排查过不少次。

3.3 蜂鸣器提示与UID显示

void Card_Detected_Beep(void) { BEEP_On(); delay_ms(100); BEEP_Off(); } void LCD_ShowCardID(uint8_t *uid) { char buf[12]; sprintf(buf, "ID:%02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); LCD_ShowString(1, 0, buf); }

蜂鸣器响100毫秒就够了,时间太长会显得像报警。UID显示用%02X格式化,能保证0A这类值显示成两位十六进制。注意防碰撞读出来的UID长度可能是4字节也可能是7字节,取决于卡型和RC522固件版本,7字节时第1字节是级联标识,直接拼接会显示错。

4. 充值落库:余额读取、金额累加与回写

4.1 读取余额:块认证与数据暂存

uint8_t Read_Balance(uint8_t *blockdata) { uint8_t status; status = RC522_Auth(PICC_AUTHENT1A, 1, DefaultKey, uid); // 认证块1 if (status != MI_OK) return status; status = RC522_Read(1, blockdata); // 读出块1共16字节 if (status != MI_OK) return status; return MI_OK; }

认证函数的参数里,1是块号,DefaultKey是6字节数组。这里要用PICC_AUTHENT1A(KeyA认证)还是1B,取决于卡片出厂配置和块3的访问控制位,教学卡多数支持A方式。Read_Balance里有个隐藏细节:即使你只关心blockdata[0],也必须一次性读出16字节,RC522的读命令按块操作,不支持单独读某字节。

4.2 充值写卡:固定金额与自定义金额

uint8_t Recharge(uint8_t amount) { uint8_t blockdata[16] = {0}; uint8_t status; status = Read_Balance(blockdata); if (status != MI_OK) return status; uint8_t old_balance = blockdata[0]; uint8_t new_balance = old_balance + amount; if (new_balance < old_balance) { printf("Overflow!\r\n"); return ERROR; } blockdata[0] = new_balance; status = RC522_Write(1, blockdata); // 整块写回 if (status != MI_OK) return status; return MI_OK; }

amount 就是讲义里说的“充值金额数值可以固定也可以自定义”。固定金额的话直接在函数里传10或50;自定义则可以从串口接收数据,或者用按键模块输入。溢出的判断很重要——块1第1字节是uint8_t,余额255时再充10就变成9,这是M1卡单字节存储的硬边界。教学版这么写没问题,生产系统应该至少用两字节存余额。

4.3 数据块写回时的三个隐藏陷阱

RC522_Write 是整块16字节写入,读出来时原封不动、只改第1个字节再写回,这是安全做法。如果只初始化一个全新数组就写,会把其他字节的清零,导致卡内其他数据丢失。第三方库的 RC522_Write 实现不同,有的是 command + address 连续写,写完后有个等待卡片空闲的时序,没等够时间就进行下一次操作会偶发失败。还有一点:同一张卡连续写两次,最好间隔50毫秒以上,M1卡内部写操作需要时间,连续快速写容易出现第二次操作无响应。

4.4 充值后余额的二次读取

uint8_t Read_Balance_After_Recharge(void) { uint8_t blockdata[16] = {0}; uint8_t status = Read_Balance(blockdata); printf("New Balance: %d\r\n", blockdata[0]); return status; }

二次读取最好重新走一遍寻卡和认证流程,不要复用充值前的认证状态。RC522的状态寄存器在写卡后可能被改写,直接再读容易拿到错误结果。LCD上显示充值后余额时,建议同时显示“充值金额”和“新余额”,方便肉眼比对验证。

5. 避坑清单:RFID充值场景下的五个常见翻车点

5.1 现象:卡片贴近天线无任何反应

原因:天线没有开启,或SPI接线中CS引脚被占用。

解决:先调用 RC522_AntennaOn,再用万用表确认CS引脚电平在通信时确实有跳变。最常见的情况是开发板上的CS引脚被LCD或者SD卡模块复用,初始化时被拉高拉低冲突了。

5.2 现象:能寻到卡,但认证失败

原因:默认密钥不是FFFFFFFFFFFF,或者卡是UID可写卡(比如UID卡、CUID卡),认证时序和原装M1有差异。

解决:先打印一下卡的类型,很多RC522库的 PICC_IsNewCardPresent 返回成功后,可以继续调用 PICC_GetType 排查。真遇到破解卡,换原装M1测试,或者根据卡的厂商文档调整库的防碰撞时序。

5.3 现象:写卡返回MI_OK,但重新读出还是旧值

原因:写操作后没有重新认证,或者写的是新块但没有清掉内部缓存;也有一部分是RC522天线供电不足,写操作的电流要求比读要高。

解决:充值后强制重新执行一遍“寻卡→防碰撞→认证→读块”完整流程。另外给RC522模块单独供3.3V电源,别从杜邦线上取电,天线功率不够时读写表现极不稳定。

5.4 现象:余额显示乱码或负数

原因:把字节数据按有符号整型打印了,或者余额用了两个字节存储但字节序没处理好。

解决:统一用%u打印,blockdata[0]声明成uint8_t。两字节存储时,高字节在前(大端)还是低字节在前(小端),在读写两端必须保持一致,我一般会在卡片数据区的第0字节固定写入一个标识位确认字节序。

5.5 现象:蜂鸣器响但LCD不显示

原因:LCD的I2C地址没配对,或者LCD背光引脚默认关闭。

解决:先用I2C扫描工具跑一遍,确认设备地址是0x27还是0x3F,再检查背光控制寄存器。很多1602模块的背光默认由I2C扩展芯片的P3引脚控制,需要额外写一个开背光的命令才能看到内容。

6. 把demo往真实系统拖:多扇区存储、金额加密与下行验证

6.1 从单字节余额到多扇区业务卡

讲义里的充值逻辑是“块1第1字节加值”,这在教学上成立,但真实公交卡会涉及“卡内余额、最近交易记录、卡类型、有效期”多类数据。建议按扇区分层:扇区0放UID和卡类型,扇区1放余额和充值记录,扇区2放密钥和访问控制。余额用两个字节存储,单位用“分”而不是“元”,这样可以覆盖0到655.35元。

我在一次模拟项目X里,就是因为余额溢出问题导致卡片没法坐车,最后把blockdata[0]单字节改成两字节,同时兼容了旧卡的单字节数据格式——用卡类型字节识别新旧版本,再做差异化读取。

6.2 余额字段加简单校验:先求校验和再写卡

uint8_t blockdata[16] = {0}; blockdata[0] = old_balance + amount; blockdata[1] = blockdata[0] ^ 0x5A; // 简单异或校验
uint8_t Check_Balance(uint8_t *blockdata) { if ((blockdata[0] ^ 0x5A) != blockdata[1]) return ERROR; return MI_OK; }

校验字节写在余额字节旁边,读卡时先校验再使用。这能挡住一部分“数据位翻转”导致的余额错乱,也能在读卡流程里尽早发现卡片异常,不至于把脏数据写回去。复杂一点可以用CRC8,但1K卡存储空间紧张,异或或CRC8足够教学和大多数内网场景用。

6.3 一个值得养成的复盘习惯

从那以后,我每次做完一个RFID写入动作,都会强制走一遍“写后即读,读后即显”的闭环:写卡后必须把目标块读出来,和预期的字节数组逐个比对,比对结果通过串口打印出来确认,再让LCD显示给用户看。这一步看起来多花了几毫秒,但在排查“卡没写进去”“写错了块”“金额对不上”这类问题时,帮我把排查时间从小时级压缩到了分钟级。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 13:21:19

无标定动态视觉伺服:不依赖标定/速度/深度的机械手闭环控制

简介&#xff1a;本资源是一篇聚焦机器人视觉伺服前沿方向的博士论文全文&#xff0c;面向自动化、机器人学、控制理论等领域的研究生与科研人员&#xff0c;重点解决机械手在缺乏精确系统标定条件下的动态视觉伺服控制难题。论文系统梳理了无标定视觉伺服的技术脉络&#xff0…

作者头像 李华
网站建设 2026/10/22 13:19:17

LTE负荷均衡MLB参数配置实战:双载波负荷不均与MML命令详解

简介&#xff1a;这是面向4G/5G网络优化工程师与技术人员的LTE负荷均衡&#xff08;MLB&#xff09;技术文档&#xff0c;系统讲解Intra-LTE与Inter-RAT两类负荷均衡原理、测量-判决-执行三阶段流程&#xff0c;以及异频切换开关、MLB算法开关、触发模式等关键参数配置&#xf…

作者头像 李华
网站建设 2026/10/22 13:17:50

无线网络技术考点解析:从WLAN组网到MANET路由

简介&#xff1a;这份PDF试题集围绕无线网络技术核心考点&#xff0c;面向网络工程、通信技术专业的备考学生、教师及自学者。内容按知识点模块化梳理&#xff0c;集中整理了无线局域网、无线个域网、无线城域网及移动自组网络等典型选择题&#xff0c;覆盖无线路由器、无线网桥…

作者头像 李华
网站建设 2026/10/22 13:17:40

5G手机天线设计与量产工艺全解析:从LDS到毫米波天线的工程实践

简介&#xff1a;《5G手机天线技术及工艺详谈》是一份面向通信工程师、手机结构设计人员、天线射频研发者及5G终端硬件爱好者的技术解析文档&#xff0c;围绕“5G真要来了&#xff0c;手机天线有什么不同”这一核心问题展开。全篇以1个docx文档交付&#xff0c;压缩包体积仅23K…

作者头像 李华
网站建设 2026/10/22 13:14:33

人类时空钟表律——钟表即人类最小时空·纯时间=手机显示

BSD 人类时空钟表律——钟表即人类最小时空&#xff08;时空结构而非纯时间&#xff09;传统权威灯下黑实证电脑本地显示时间才是纯时间Step294L0 层&#xff1a;总本源层 六系核心公理&#xff08;元理论级&#xff09; ​★ 命题来源​&#xff1a;作者突然发现—— ① ​钟…

作者头像 李华
网站建设 2026/10/22 13:10:42

C++智能指针 shared_ptr与unique_ptr

shared_ptr与unique_ptr的转换关系 不能将shared_ptr转换为unique_ptrunique_ptr可以转换为shared_ptr&#xff08;通过std::move&#xff09;常见的使用方式 将你的函数返回unique_ptr 是一种常见的设计模式&#xff0c;这样可以提高代码的复用度&#xff0c;因为unique_ptr…

作者头像 李华