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