这一篇记录物联网方向的RFID 系列:读取、写入、刷卡消费充值,同样是学校的智联场景实训箱(STM32L431 + Keil MDK5 + HAL 库)。RFID是物联网感知层里 "身份识别" 的代表技术,门禁、校园卡、食堂刷卡、公交卡全是它。这篇不贴大段代码,重点讲核心逻辑和真正踩过的坑。
一、实验环境与硬件
项目 | 配置 |
主控芯片 | STM32L431VCTx(Cortex-M4,主频 80MHz) |
RFID 芯片 | RC522(NXP MFRC522,13.56MHz,ISO14443A) |
卡类型 | Mifare 1(S50),默认密码 FF FF FF FF FF FF |
RC522 接口 | 硬件 SPI3(分频 64)+ 复位 PD0 + 片选 PA15 |
人机交互 | TFT LCD + 4 个按键 + 蜂鸣器 + 串口 2(115200) |
知识铺垫:M1 卡内部结构 =16 个扇区 × 每扇区 4 块,每块 16 字节。每扇区的块 0/1/2 是数据块,块 3 是控制块(存密码和访问控制位)——块 3 千万别乱写,这是后面重点坑之一。
二、实验一:RFID 读取功能
功能:把卡放到感应区,读出卡类型和 4 字节 UID 卡号,LCD + 串口显示,蜂鸣器提示。
核心代码(读卡四步流程):
static void RC522_Read_ID(void) { uint8_t card_id_arr[4] = {0}; // 卡ID缓存 uint8_t card_type_arr[2] = {0}; // 卡类型缓存 status = RC522_Search_Cards(PICC_REQALL, card_type_arr); // ① 寻卡 if(status == MI_OK) { status = MI_ERR; status = RC522_Anticoll(card_id_arr); } // ② 防冲撞 if(status == MI_OK) { status = MI_ERR; status = RC522_Select_Cards(card_id_arr); } // ③ 选卡 if(status == MI_OK) // ④ 合成卡ID和类型 { Card_Id = (card_id_arr[0] << 24) | (card_id_arr[1] << 16) | (card_id_arr[2] << 8) | card_id_arr[3]; Card_Type = (card_type_arr[0] << 8) | card_type_arr[1]; RC522_Sleep(); // 让卡进入睡眠,防止一直重复触发 } }实验现象:刷卡 → 蜂鸣器响 400ms → 串口打印:
Card type = S50(0x0400) Card ID = 1268881799LCD 同步显示 Card Type = Mifare_One(S50) 和 Card ID = ...。卡类型判断表:0x0400=M1(S50)、0x0200=S70、0x4400=UltraLight、0x4403=DESFire。
⚠️重点坑(必看):寻卡 → 防冲撞 → 选卡这三步一步都不能省。防冲撞(Anticoll)是解决 "两张卡同时放在感应区" 时的冲突问题 —— 没有它,两张卡一起放会读不出任何一张。新手最容易把三步简化成一步 "寻卡就读",然后发现读卡偶尔失败还找不到原因。
另外:读完后要调 RC522_Sleep() 让卡睡眠,不然卡一直贴在感应区会每秒重复触发,串口刷屏。
三、实验二:RFID 写入功能
功能:向卡扇区 1 的块 0/1/2写入数据(key1→块 0 写 "tangrenjie",key2→块 1 写 "dongfangxueyuan",key3→块 2 写 0x30313233,key4→清空三块),写完读回验证。
核心代码(验证密码 + 写数据 + 读回):
// ① 验证扇区密码(默认 FFFFFFFFFFFF),不通过则无法读写 status = RC522_Verification_Password(PICC_AUTHENT1A, Sector_Addr_Date[1], Pass_Word, card_id); // ② 向扇区1的块0写入16字节数据 status = RC522_Write_Date(Sector_Addr_Date[1], Test_date1); // "tangrenjie" if(status != MI_OK) { printf("数据写入失败\r\n"); return; } // ③ 读回块0/1/2,确认写入成功 for(i = 0; i < 3; i++) { status = RC522_Read_Date(Sector_Addr_Date[1] + i, card_date); memcpy(&BlockData[i][0], card_date, 16); }实验现象:按 key1 → 刷卡 → "数据写入成功",读回显示 块0:tangrenjie 块1:dongfangxueyuan 块2:0123;按 key4 刷卡后三块全部清零。
⚠️重点坑(必看):每个扇区的块 3 是密码控制块,写数据永远只能写块 0/1/2。块 3(绝对地址 3,7,11,15,...,63)存的是密码 A/B 和访问控制位,有固定的数据格式,一旦乱写进去,整个扇区直接废掉—— 密码对不上、读写全部失败,这张卡的这个扇区就再也用不了了。原厂代码注释里特意用大写警告了这一点,是真的会发生的。
另外:写数据前必须先 RC522_Verification_Password 验证密码(默认全 F),密码错会导致写入静默失败,现象就是 "写数据失败" 刷屏。
四、实验三:RFID 应用设计・刷卡消费充值
这个实验最 "物联网":把卡当成一张电子钱包。key1 扣 2 元、key2 扣 12 元、key3 充值 200 元、key4 初始化新卡,余额存在卡的数值块里,刷卡即读写,不联网也能用 —— 这就是 M1 卡在食堂 / 超市的玩法。
背景知识:M1 卡数据块有两种模式 ——数据保存模式(普通读写,实验二用的)和数值模式(专门存金额,支持初始化 / 加值 / 减值指令)。数值块格式固定:4字节数值(小端) + 4字节反码 + 4字节数值 + 1字节地址 + 3字节地址反码。
核心代码(充值 + 扣费):
// ① 新卡先初始化:生成数值块格式写入(初始金额 10 元) Wallet_Data_Init(); // ② 十进制金额转成 4 字节小端 Value_Change(&Deduction_Value1, &HEXvalue[0]); // 扣费 2 元 // ③ 执行减值/加值指令 status = RC522_Deduction_Recharge(PICC_DECREMENT, // 扣费 Sector_Addr_Date[1] + 0, HEXvalue); status = RC522_Deduction_Recharge(PICC_INCREMENT, // 充值 Sector_Addr_Date[1] + 0, HEXvalue); // ④ 读余额(小端合成) Card_Money = card_money[0] | (card_money[1] << 8) | (card_money[2] << 16) | (card_money[3] << 24);实验现象:key4 初始化新卡(余额 10 元)→ key3 刷卡充值 200 → 余额 210 → key1 刷卡扣 2 → 208 → key2 刷卡扣 12 → 196,串口全程打印 RFID卡当前余额:xxx元,LCD 同步显示,蜂鸣器提示。
⚠️重点坑(必看):余额判断的数值是模板没改干净的雷。看代码:
if(RCC522_Work_Mode == 1) // 扣费2元
if(Card_Money <= 0) // ← 应该是 < 2!
if(RCC522_Work_Mode == 2) // 扣费12元
if(Card_Money < 5) // ← 应该是 < 12!
扣 2 元判断的是 <= 0、扣 12 元判断的是 < 5—— 余额 5 元时扣 12 元照样成功,余额直接变负数。这是因为工程模板默认金额是 1 元和 5 元,改金额时判断条件没跟着改。演示到 "余额不足" 环节时会翻车,记得改成 Card_Money < 2 / Card_Money < 12。
另外两个提醒:①新卡必须先 key4 初始化,否则刷卡会提示 "此卡未初始化";② 初始化 / 扣费 / 充值之后要重新读一次余额再显示,别用旧值。
五、三个实验对比(含总结)
实验 | 核心功能 | 关键 API | 存储位置 | 总结 |
B01 读取 | 读卡类型 + UID 卡号 | RC522_Search_Cards → Anticoll → Select_Cards | 只读,不动卡内数据 | 入门第一步,四步流程要背熟;注意防冲撞和多卡冲突 |
B02 写入 | 向扇区 1 块 0/1/2 写数据 | Verification_Password + Write_Date + Read_Date | M1 数据块(16 字节 / 块) | 密码验证是读写前提;块 3 控制块千万别碰,写坏扇区直接废 |
B03 刷卡消费 | 充值 / 扣费 / 余额 / 初始化 | Deduction_Recharge(PICC_INCREMENT/DECREMENT) | M1 数值块(固定格式) | 数值块有专用格式和加值 / 减值指令;余额判断要按真实金额改(模板坑) |
总结:三个实验是层层递进的 —— 读卡是基础,写卡是 "能存数据",消费是 "把数据用起来",正好对应物联网 "感知 → 存储 → 应用" 的链路。
六、重点坑速查
- 余额 5 元扣 12 元竟然成功:余额判断是模板默认值没改(Card_Money < 5 应为 < 12),金额和判断条件要同步修改。
- 某扇区读写全部失败,卡 "废了":把数据写进了块 3(密码控制块)。只写块 0/1/2,块 3 绝对不动。
- 提示 "此卡未初始化":新卡没有数值块格式,先按 key4 初始化再使用。
- 读卡偶尔失败、两张卡都读不出:少了防冲撞或流程被简化,寻卡→防冲撞→选卡三步要齐全。
- 卡贴着感应区串口刷屏:处理完没让卡睡眠,记得调 RC522_Sleep()。
七、总结
RFID 三个实验做下来,最深的感受:
- RFID 的 "身份" 是硬件的:每张 M1 卡的 4 字节 UID 出厂就烧死了,读卡就是 "认卡不认人",这是它跟二维码最大的区别 —— 所以门禁、公交卡至今离不开它。
- 卡内存储要分清 "数据块" 和 "数值块":普通读写用数据块,存金额要用数值块指令(加值 / 减值),混用会出各种莫名其妙的问题。
- 细节决定成败:块 3 不能写、新卡先初始化、余额判断改金额要同步改 —— 这三个坑全是 "照抄模板没细看" 造成的。
- 物联网的套路:传感器篇是 "采集数据",RFID 篇是 "识别身份 + 存数据",后面再接上 WiFi 上云,就是完整的物联网应用了。
如果帮到了你,欢迎点赞收藏,评论区交流!🫡
本文基于学校智联场景实训箱(STM32L431 + Keil MDK5 + HAL 库)真实工程代码整理,代码已实测跑通。