首页 / 资讯中心 / 文章详情

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

RFID公交卡充值实战:从M1卡读写到余额回写全流程 ★ FEATURED ARTICLE
简介面向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卡出厂时默认密钥是FFFFFFFFFFFF6字节全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_AUTHENT1AKeyA认证还是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显示给用户看。这一步看起来多花了几毫秒但在排查“卡没写进去”“写错了块”“金额对不上”这类问题时帮我把排查时间从小时级压缩到了分钟级。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站