ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RFID 刷卡消费实验:扣款金额超过余额时,为什么有时成功变负数,有时却提示余额不足?

RFID 刷卡消费实验:扣款金额超过余额时,为什么有时成功变负数,有时却提示余额不足? 一、问题的发现在做 RFID 刷卡消费充值实验时我遇到了一个令人困惑的现象实验过程中卡内余额一度变成了负数但后续操作又一切正常串口和 TFT 屏显示的结果看起来都没问题。一开始我以为是接触不良或者卡片没放稳但反复核对每一步操作后发现这个负数余额是稳定可复现的而且根源就在第一次扣费操作里。这篇博客从实验的初始状态出发完整复盘每一步操作和余额变化结合代码逐层拆解找出背后的真正原因。二、实验环境与初始状态实验平台是 STM32L4 RC522 模块四个按键分别对应KEY1 扣费 10 元KEY2 扣费 50 元KEY3 充值 100 元KEY4 初始化卡。卡内余额存储在 M1 卡的 Sector 1 Block 0 数值块中操作结果同时输出到串口和 TFT 屏。代码中钱包初始值定义为uint8_t Wallet_vlaue[4] {0x0a}; // 钱包初始值 10元执行 KEY4 初始化卡后RC522_Write_Date()将按数值块格式打包好的 16 字节模板写入卡片初始余额为10 元。此时卡片就绪实验正式开始。三、实验过程完整复盘第一步按 KEY2 扣费 50 元按下 KEY2key2_end_handle()将RCC522_Work_Mode设为 2串口输出*****key2按下请将RFID卡靠近感应区当前工作模式为扣费金额50元*****将卡靠近感应区RC522_Handle()每秒执行一次完成寻卡、防碰撞、选卡、密码认证四步通信后读取卡内余额为 10 元。程序进入 KEY2 扣费分支执行余额判断if(Card_Money 5) // 10 5 为假不拦截注意这里的判断条件是 5而实际扣费金额是 50 元。10 元余额不小于 5所以没有被拦截直接进入else分支调用RC522_Deduction_Recharge()发送PICC_DECREMENT命令减值 50 元RFID卡扣费中... RFID卡扣费成功 RFID卡当前余额:-40元余额计算10 - 50 -40 元。TFT 屏底部同步显示当前金额 -40元蜂鸣器鸣叫 400 毫秒。这一步是整个实验中问题的爆发点扣费金额50 元远大于余额10 元但由于余额判断阈值错误系统照常执行了扣费余额被透支为负数。第二步按 KEY3 充值 100 元按下 KEY3RCC522_Work_Mode被设为 3串口输出*****key3按下请将RFID卡靠近感应区当前工作模式为充值金额100元*****刷卡后读取余额为 -40 元。KEY3 是充值分支代码中没有余额判断直接执行PICC_INCREMENT加值 100 元RFID卡充值中... RFID卡充值成功 RFID卡当前余额:60元余额计算-40 100 60 元。TFT 屏显示当前金额 60元。这一步把负数余额拉回了正数表面上看一切正常但实际上 60 元这个结果是建立在之前 -40 元异常状态的基础上的。如果只看这一步的结果根本发现不了前面已经发生过透支。第三步再次按 KEY3 充值 100 元再次按下 KEY3刷卡后读取余额为 60 元执行充值 100 元RFID卡充值中... RFID卡充值成功 RFID卡当前余额:160元余额计算60 100 160 元。TFT 屏显示当前金额 160元。第四步按 KEY2 扣费 50 元按下 KEY2刷卡后读取余额为 160 元。余额判断160 5为假不拦截执行扣费 50 元RFID卡扣费中... RFID卡扣费成功 RFID卡当前余额:110元余额计算160 - 50 110 元。TFT 屏显示当前金额 110元。这一步余额充足扣费正常是一次没有触发问题的正常操作。第五步按 KEY1 扣费 10 元按下 KEY1RCC522_Work_Mode被设为 1刷卡后读取余额为 110 元。进入 KEY1 分支余额判断if(Card_Money 0) // 110 0 为假不拦截执行PICC_DECREMENT减值 10 元RFID卡扣费中... RFID卡扣费成功 RFID卡当前余额:100元余额计算110 - 10 100 元。TFT 屏显示当前金额 100元。完整余额变化链把五步操作连起来从初始 10 元到最终 100 元的完整链路是10 元初始化→ 扣 50 元 →-40 元异常透支→ 充 100 元 →60 元→ 充 100 元 →160 元→ 扣 50 元 →110 元→ 扣 10 元 →100 元最终其中第一步扣费 50 元是唯一触发异常的操作其余四步均为正常操作。如果没有第一步的异常扣费从初始 10 元直接开始后面的操作结果应该是10 100 100 - 50 - 10 150 元而不是实验中得到的 100 元。两者相差正好 50 元就是第一步被错误扣除的那笔钱。四、现象总结从完整复盘可以提炼出一个规律当卡内余额高于某个阈值时即使扣款金额大于余额系统也会执行扣款余额变成负数当卡内余额低于或等于某个阈值时系统直接提示余额不足拒绝执行。而且 KEY1 和 KEY2 的阈值不一样。这说明问题不在硬件也不在 RC522 模块而在软件的余额判断逻辑里。接下来深入代码看看。五、代码层面的分析核心逻辑都在handle.c的RC522_Handle()函数中。在完成寻卡、防碰撞、选卡、认证四步通信后程序读取卡内余额Card_Money (uint32_t)(card_money[0] | (card_money[1] 8) | (card_money[2] 16) | (card_money[3] 24));M1 卡数值块的前 4 字节以小端模式存储余额这里按位或合并成一个 32 位整数。注意Card_Money的类型是int32_t所以它可以表示负数——当数值块中的值被扣减到 0 以下时就会以补码形式呈现为负数。KEY1 的扣费逻辑if(RCC522_Work_Mode 1) // 扣款模式一扣10元 { if(Card_Money 0) { printf(\r\n*****余额不足请充值*****\r\n); Gui_Printf_Display((uint8_t *)*****余额不足请充值*****); } else { printf(RFID卡扣费中...\r\n); Value_Change(Deduction_Value1, HEXvalue[0]); status RC522_Deduction_Recharge(PICC_DECREMENT, Sector_Addr_Date[RC522_SECTOR]SECTOR_BLOCKNUM, HEXvalue); } }KEY1 的余额判断条件是Card_Money 0。也就是说只要余额大于 0不管是 1 元还是 5 元都会进入else分支执行扣费 10 元。M1 卡的PICC_DECREMENT减值命令本身不做余额校验它只是简单地把数值块中的值减去指定金额所以 5 元减 10 元就变成了 -5 元操作返回MI_OK程序打印扣费成功。只有当余额已经小于等于 0 时才会被if(Card_Money 0)拦截提示余额不足。KEY2 的扣费逻辑else if(RCC522_Work_Mode 2) // 扣款模式二扣50元 { if(Card_Money 5) { printf(\r\n*****余额不足请充值*****\r\n); Gui_Printf_Display((uint8_t *)*****余额不足请充值*****); } else { printf(RFID卡扣费中...\r\n); Value_Change(Deduction_Value2, HEXvalue[0]); status RC522_Deduction_Recharge(PICC_DECREMENT, Sector_Addr_Date[RC522_SECTOR]SECTOR_BLOCKNUM, HEXvalue); } }问题更明显了。KEY2 扣的是 50 元但余额判断条件却是Card_Money 5。这意味着只要余额大于等于 5 元哪怕只有 6 元也会执行扣费 50 元结果变成 -44 元。只有当余额小于 5 元时才会被拦截。实验中第一步就是这种情况余额 10 元扣 50 元10 不小于 5执行扣费变成 -40 元。六、根本原因把两个分支放在一起对比根本原因就清楚了余额判断的阈值与实际扣款金额不匹配。KEY1 扣 10 元判断条件是 0正确的写法应该是 10。当前的条件只拦截了非正余额却放行了所有正数余额导致 1~9 元的余额被扣 10 元后变成负数。KEY2 扣 50 元判断条件是 5这个阈值和 50 元完全对不上大概率是代码编写时的笔误——可能是从 KEY1 复制过来后只改了扣款金额忘了改判断阈值也可能是把 50 误写成了 5。正确的写法应该是 50。此外M1 卡的PICC_DECREMENT硬件命令本身不具备余额保护机制它只是执行减法运算是否允许透支完全取决于上层软件的判断。所以一旦软件判断条件有漏洞负数余额就必然出现。还有一个值得注意的细节是Card_Money的类型。代码中声明为int32_t但读取时做了uint32_t强制转换再赋值。当余额为正时没有问题但当数值块中的值被扣减到 0 以下时M1 卡存储的是补码按uint32_t解析后是一个很大的数再赋值给int32_t就会被解释为负数。这个类型设计本身让负数余额能够被正确显示出来但也意味着如果上层不做拦截负数就会一直累积。七、修复方案知道了原因修复就很简单。核心原则是余额判断阈值必须等于实际扣款金额。KEY1 的修复if(RCC522_Work_Mode 1) { if(Card_Money Deduction_Value1) // 改为 10而不是 0 { printf(\r\n*****余额不足请充值*****\r\n); Gui_Printf_Display((uint8_t *)*****余额不足请充值*****); } else { // 执行扣费10元 } }KEY2 的修复else if(RCC522_Work_Mode 2) { if(Card_Money Deduction_Value2) // 改为 50而不是 5 { printf(\r\n*****余额不足请充值*****\r\n); Gui_Printf_Display((uint8_t *)*****余额不足请充值*****); } else { // 执行扣费50元 } }直接引用金额变量Deduction_Value1和Deduction_Value2作为判断阈值而不是硬编码数字这样以后修改金额时判断条件会自动跟随避免再次出现不一致的问题。更进一步可以把余额判断和扣费操作封装成一个统一的函数避免每个分支重复写判断逻辑uint8_t Deduction_If_Sufficient(int32_t balance, uint32_t amount, uint8_t *pValue) { if(balance amount) { return MI_ERR; // 余额不足 } Value_Change(amount, pValue); return RC522_Deduction_Recharge(PICC_DECREMENT, 块地址, pValue); }这样两个扣费分支都调用同一个函数判断逻辑只有一份从根本上杜绝了阈值不一致的问题。九、总结RFID 实验中扣款金额超过余额时出现两种不同结果根本原因是代码中两个扣费分支的余额判断阈值与实际扣款金额不匹配KEY1 扣 10 元却只判断 0KEY2 扣 50 元却判断 5。当余额高于错误阈值但低于实际扣款金额时M1 卡的 DECREMENT 指令照常执行减法余额变成负数并显示扣费成功只有当余额低于错误阈值时才会被软件拦截并提示余额不足。实验中从初始 10 元开始第一步按 KEY2 扣 50 元就触发了这个问题余额变成 -40 元后续充值和扣费操作把这个异常状态掩盖在了正数结果之下最终得到的 100 元与理论值 150 元相差正好 50 元。修复方法是将判断条件改为与实际扣款金额一致并建议封装统一的扣费函数避免重复代码带来的不一致。这个小小的 bug 让我深刻体会到嵌入式系统的稳定性不仅取决于硬件连接和通信协议更取决于业务逻辑中每一个判断条件的严谨性。细节决定成败在代码世界里尤其如此。
返回列表