戰(zhàn):從M1卡讀寫到余額回寫全流程)
簡介面向RFID技術(shù)課程學(xué)習(xí)者與物聯(lián)網(wǎng)專業(yè)學(xué)生的教學(xué)演示PPT以公交卡充值為場景系統(tǒng)講解RFID系統(tǒng)組成、閱讀器與標(biāo)簽的無線交互、電磁波激活天線及刷卡充值全流程。壓縮包共1個(gè)pptx演示文稿大小約2.17MB內(nèi)容緊湊適合課堂展示、實(shí)驗(yàn)預(yù)習(xí)或課后復(fù)習(xí)。目前已有92人學(xué)習(xí)瀏覽。PPT按步驟梳理系統(tǒng)初始化、閱讀器使能、識(shí)別公交卡、蜂鳴器提示、ID顯示、充值前后余額查詢等環(huán)節(jié)并針對(duì)公交卡存儲(chǔ)塊1第1字節(jié)blockdata[0]的數(shù)值增加說明充值金額可固定也可自定義的修改方式同時(shí)配有程序流程設(shè)計(jì)和主要實(shí)現(xiàn)代碼體現(xiàn)從硬件初始化到數(shù)據(jù)塊改寫再到顯示更新的完整邏輯便于理解RFID標(biāo)簽數(shù)據(jù)存儲(chǔ)與讀寫器交互原理適合作為實(shí)驗(yàn)參考或課程報(bào)告素材。1. RFID公交卡充值一個(gè)藏在PPT里的完整閉環(huán)很多剛接觸射頻識(shí)別RFID的開發(fā)者第一反應(yīng)是去啃ISO/IEC 14443協(xié)議?;蛘呒m結(jié)M1卡的加密算法結(jié)果被扇區(qū)密鑰和認(rèn)證流程勸退。而這份《射頻識(shí)別技術(shù)-RFID公交卡充值》講義走的是另一條路它把整套M1卡操作濃縮成一個(gè)“刷卡→響鈴→顯示ID→讀余額→充值→再讀余額”的閉環(huán)代碼量不大卻覆蓋了RFID閱讀器初始化、防碰撞識(shí)別、數(shù)據(jù)塊讀寫和LCD回顯的全部關(guān)鍵動(dòng)作。對(duì)想快速跑通一套R(shí)FID應(yīng)用demo的從業(yè)者來說這個(gè)資源比直接讀芯片手冊友好得多——它講清楚了“公交卡余額為什么存在塊1第1個(gè)字節(jié)”以及“想讓充10塊還是充50塊改哪個(gè)參數(shù)”。適合正在做門禁、食堂卡、校園卡或倉儲(chǔ)盤點(diǎn)系統(tǒng)想從協(xié)議層往業(yè)務(wù)層跨一步的人。2. 讀懂M1卡的存儲(chǔ)結(jié)構(gòu)為什么余額偏偏存在塊1第1個(gè)字節(jié)2.1 M1卡的扇區(qū)、塊與字節(jié)尋址Mifare Classic 1K卡是公交卡最常用的芯片之一它內(nèi)部有16個(gè)扇區(qū)每個(gè)扇區(qū)4個(gè)塊每個(gè)塊16個(gè)字節(jié)。塊0是廠商固化的UID和廠商數(shù)據(jù)扇區(qū)的塊3是密鑰和訪問控制位真正能給業(yè)務(wù)用的就是每扇區(qū)的塊1、塊2以及其余扇區(qū)的塊0。講義里提到的“塊1中的第1個(gè)字節(jié)”指的就是扇區(qū)0的塊1偏移量為0的那個(gè)字節(jié)用代碼表示就是 blockdata[0]。常見做法是把余額定義成整數(shù)元用一個(gè)字節(jié)存儲(chǔ)時(shí)上限是255如果卡片面額可能超過這個(gè)值就需要擴(kuò)展到兩個(gè)字節(jié)或者把單位從“元”改成“角”。我在實(shí)際項(xiàng)目里就吃過這個(gè)虧當(dāng)時(shí)額度設(shè)成單字節(jié)存儲(chǔ)結(jié)果充到256就溢出歸零查了半天才發(fā)現(xiàn)是數(shù)據(jù)類型的問題。2.2 為什么公交卡讀寫不需要“格式化”M1卡出廠時(shí)默認(rèn)密鑰是FFFFFFFFFFFF6字節(jié)全F很多開發(fā)板自帶的庫函數(shù)默認(rèn)就是用這個(gè)密鑰做認(rèn)證。公交卡系統(tǒng)雖然在實(shí)際部署時(shí)會(huì)改密鑰但教學(xué)版的讀寫流程往往跳過密鑰管理這一層直接認(rèn)證、讀塊、寫塊。好處是跑通邏輯很快壞處是你得知道真實(shí)環(huán)境里第一步永遠(yuǎn)是先做密鑰認(rèn)證否則讀到的數(shù)據(jù)全是加密后的亂碼。2.3 充值前后兩讀的意義正常的充值流程是“先讀舊余額→寫新余額→再讀驗(yàn)證”。講義里把“充值前余額顯示”和“充值后余額顯示”都列為獨(dú)立步驟這不是冗余而是一個(gè)閉環(huán)驗(yàn)證寫卡成功了沒有、寫進(jìn)去的字節(jié)對(duì)不對(duì)不重新讀一遍是看不出來的。很多翻車案例都是寫完卡直接返回成功結(jié)果卡里余額根本沒變。3. 把PPT里的流程變成可抄的代碼初始化、識(shí)別與回顯3.1 系統(tǒng)初始化和LCD初始化// 串口和LCD初始化 void System_Init(void) { UART_Init(9600); // 調(diào)試串口波特率看卡片UID用 LCD_Init(); // 1602或OLED初始化 LCD_Clear(); LCD_ShowString(0, 0, RFID Bus Card); }這里 UART_Init 的波特率建議先設(shè)成9600等確認(rèn)通信穩(wěn)定后再往上調(diào)。LCD初始化里最坑的是I2C地址常見的有0x27和0x3F不對(duì)時(shí)屏幕只亮不顯示。3.2 RFID閱讀器初始化電源、天線和尋卡// 以RC522為例 void RFID_Reader_Init(void) { RC522_Init(); // SPI接口初始化CS引腳拉高 RC522_AntennaOn(); // 開天線必須有這一句否則讀不到卡 RC522_SetReg(RegMode, 0x3D); // 設(shè)置為發(fā)送和接收使能 } 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 表示尋所有空閑狀態(tài)的卡RC522_Anticoll 用來做防碰撞當(dāng)有多張卡同時(shí)靠近時(shí)拿到其中一張的UID。天線開關(guān)是最容易被忽略的RC522_AntennaOn 不調(diào)用卡片放在天線正上方也毫無反應(yīng)這個(gè)坑我?guī)蛣e人排查過不少次。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毫秒就夠了時(shí)間太長會(huì)顯得像報(bào)警。UID顯示用%02X格式化能保證0A這類值顯示成兩位十六進(jìn)制。注意防碰撞讀出來的UID長度可能是4字節(jié)也可能是7字節(jié)取決于卡型和RC522固件版本7字節(jié)時(shí)第1字節(jié)是級(jí)聯(lián)標(biāo)識(shí)直接拼接會(huì)顯示錯(cuò)。4. 充值落庫余額讀取、金額累加與回寫4.1 讀取余額塊認(rèn)證與數(shù)據(jù)暫存uint8_t Read_Balance(uint8_t *blockdata) { uint8_t status; status RC522_Auth(PICC_AUTHENT1A, 1, DefaultKey, uid); // 認(rèn)證塊1 if (status ! MI_OK) return status; status RC522_Read(1, blockdata); // 讀出塊1共16字節(jié) if (status ! MI_OK) return status; return MI_OK; }認(rèn)證函數(shù)的參數(shù)里1是塊號(hào)DefaultKey是6字節(jié)數(shù)組。這里要用PICC_AUTHENT1AKeyA認(rèn)證還是1B取決于卡片出廠配置和塊3的訪問控制位教學(xué)卡多數(shù)支持A方式。Read_Balance里有個(gè)隱藏細(xì)節(jié)即使你只關(guān)心blockdata[0]也必須一次性讀出16字節(jié)RC522的讀命令按塊操作不支持單獨(dú)讀某字節(jié)。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 就是講義里說的“充值金額數(shù)值可以固定也可以自定義”。固定金額的話直接在函數(shù)里傳10或50自定義則可以從串口接收數(shù)據(jù)或者用按鍵模塊輸入。溢出的判斷很重要——塊1第1字節(jié)是uint8_t余額255時(shí)再充10就變成9這是M1卡單字節(jié)存儲(chǔ)的硬邊界。教學(xué)版這么寫沒問題生產(chǎn)系統(tǒng)應(yīng)該至少用兩字節(jié)存余額。4.3 數(shù)據(jù)塊寫回時(shí)的三個(gè)隱藏陷阱RC522_Write 是整塊16字節(jié)寫入讀出來時(shí)原封不動(dòng)、只改第1個(gè)字節(jié)再寫回這是安全做法。如果只初始化一個(gè)全新數(shù)組就寫會(huì)把其他字節(jié)的清零導(dǎo)致卡內(nèi)其他數(shù)據(jù)丟失。第三方庫的 RC522_Write 實(shí)現(xiàn)不同有的是 command address 連續(xù)寫寫完后有個(gè)等待卡片空閑的時(shí)序沒等夠時(shí)間就進(jìn)行下一次操作會(huì)偶發(fā)失敗。還有一點(diǎn)同一張卡連續(xù)寫兩次最好間隔50毫秒以上M1卡內(nèi)部寫操作需要時(shí)間連續(xù)快速寫容易出現(xiàn)第二次操作無響應(yīng)。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; }二次讀取最好重新走一遍尋卡和認(rèn)證流程不要復(fù)用充值前的認(rèn)證狀態(tài)。RC522的狀態(tài)寄存器在寫卡后可能被改寫直接再讀容易拿到錯(cuò)誤結(jié)果。LCD上顯示充值后余額時(shí)建議同時(shí)顯示“充值金額”和“新余額”方便肉眼比對(duì)驗(yàn)證。5. 避坑清單RFID充值場景下的五個(gè)常見翻車點(diǎn)5.1 現(xiàn)象卡片貼近天線無任何反應(yīng)原因天線沒有開啟或SPI接線中CS引腳被占用。解決先調(diào)用 RC522_AntennaOn再用萬用表確認(rèn)CS引腳電平在通信時(shí)確實(shí)有跳變。最常見的情況是開發(fā)板上的CS引腳被LCD或者SD卡模塊復(fù)用初始化時(shí)被拉高拉低沖突了。5.2 現(xiàn)象能尋到卡但認(rèn)證失敗原因默認(rèn)密鑰不是FFFFFFFFFFFF或者卡是UID可寫卡比如UID卡、CUID卡認(rèn)證時(shí)序和原裝M1有差異。解決先打印一下卡的類型很多RC522庫的 PICC_IsNewCardPresent 返回成功后可以繼續(xù)調(diào)用 PICC_GetType 排查。真遇到破解卡換原裝M1測試或者根據(jù)卡的廠商文檔調(diào)整庫的防碰撞時(shí)序。5.3 現(xiàn)象寫卡返回MI_OK但重新讀出還是舊值原因?qū)懖僮骱鬀]有重新認(rèn)證或者寫的是新塊但沒有清掉內(nèi)部緩存也有一部分是RC522天線供電不足寫操作的電流要求比讀要高。解決充值后強(qiáng)制重新執(zhí)行一遍“尋卡→防碰撞→認(rèn)證→讀塊”完整流程。另外給RC522模塊單獨(dú)供3.3V電源別從杜邦線上取電天線功率不夠時(shí)讀寫表現(xiàn)極不穩(wěn)定。5.4 現(xiàn)象余額顯示亂碼或負(fù)數(shù)原因把字節(jié)數(shù)據(jù)按有符號(hào)整型打印了或者余額用了兩個(gè)字節(jié)存儲(chǔ)但字節(jié)序沒處理好。解決統(tǒng)一用%u打印blockdata[0]聲明成uint8_t。兩字節(jié)存儲(chǔ)時(shí)高字節(jié)在前大端還是低字節(jié)在前小端在讀寫兩端必須保持一致我一般會(huì)在卡片數(shù)據(jù)區(qū)的第0字節(jié)固定寫入一個(gè)標(biāo)識(shí)位確認(rèn)字節(jié)序。5.5 現(xiàn)象蜂鳴器響但LCD不顯示原因LCD的I2C地址沒配對(duì)或者LCD背光引腳默認(rèn)關(guān)閉。解決先用I2C掃描工具跑一遍確認(rèn)設(shè)備地址是0x27還是0x3F再檢查背光控制寄存器。很多1602模塊的背光默認(rèn)由I2C擴(kuò)展芯片的P3引腳控制需要額外寫一個(gè)開背光的命令才能看到內(nèi)容。6. 把demo往真實(shí)系統(tǒng)拖多扇區(qū)存儲(chǔ)、金額加密與下行驗(yàn)證6.1 從單字節(jié)余額到多扇區(qū)業(yè)務(wù)卡講義里的充值邏輯是“塊1第1字節(jié)加值”這在教學(xué)上成立但真實(shí)公交卡會(huì)涉及“卡內(nèi)余額、最近交易記錄、卡類型、有效期”多類數(shù)據(jù)。建議按扇區(qū)分層扇區(qū)0放UID和卡類型扇區(qū)1放余額和充值記錄扇區(qū)2放密鑰和訪問控制。余額用兩個(gè)字節(jié)存儲(chǔ)單位用“分”而不是“元”這樣可以覆蓋0到655.35元。我在一次模擬項(xiàng)目X里就是因?yàn)橛囝~溢出問題導(dǎo)致卡片沒法坐車最后把blockdata[0]單字節(jié)改成兩字節(jié)同時(shí)兼容了舊卡的單字節(jié)數(shù)據(jù)格式——用卡類型字節(jié)識(shí)別新舊版本再做差異化讀取。6.2 余額字段加簡單校驗(yàn)先求校驗(yàn)和再寫卡uint8_t blockdata[16] {0}; blockdata[0] old_balance amount; blockdata[1] blockdata[0] ^ 0x5A; // 簡單異或校驗(yàn)uint8_t Check_Balance(uint8_t *blockdata) { if ((blockdata[0] ^ 0x5A) ! blockdata[1]) return ERROR; return MI_OK; }校驗(yàn)字節(jié)寫在余額字節(jié)旁邊讀卡時(shí)先校驗(yàn)再使用。這能擋住一部分“數(shù)據(jù)位翻轉(zhuǎn)”導(dǎo)致的余額錯(cuò)亂也能在讀卡流程里盡早發(fā)現(xiàn)卡片異常不至于把臟數(shù)據(jù)寫回去。復(fù)雜一點(diǎn)可以用CRC8但1K卡存儲(chǔ)空間緊張異或或CRC8足夠教學(xué)和大多數(shù)內(nèi)網(wǎng)場景用。6.3 一個(gè)值得養(yǎng)成的復(fù)盤習(xí)慣從那以后我每次做完一個(gè)RFID寫入動(dòng)作都會(huì)強(qiáng)制走一遍“寫后即讀讀后即顯”的閉環(huán)寫卡后必須把目標(biāo)塊讀出來和預(yù)期的字節(jié)數(shù)組逐個(gè)比對(duì)比對(duì)結(jié)果通過串口打印出來確認(rèn)再讓LCD顯示給用戶看。這一步看起來多花了幾毫秒但在排查“卡沒寫進(jìn)去”“寫錯(cuò)了塊”“金額對(duì)不上”這類問題時(shí)幫我把排查時(shí)間從小時(shí)級(jí)壓縮到了分鐘級(jí)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取