動手實操丨RC522射頻卡模組與IC卡完成充值消費查詢的技術實現思路
摘要:一文手把手教你利用RC522射頻卡模組與IC卡完成充值消費查詢的技術實現思路。
本文分享自華為雲社區《RC522射頻卡模組與IC卡完成充值消費查詢的技術實現思路》,作者:DS小龍哥。
一、IC卡介紹
常用的IC卡一般是M1卡,也稱為S50卡,購買RC522刷卡模組送的白卡,藍色鑰匙扣、公交卡、地鐵卡都是S50卡。S50卡內部有16個分區,每分區有AB兩組密碼,總容量為8Kbit。
第0個扇區第0塊用於存放廠商程式碼,意見固話,不可更改。
每個扇區的塊0、塊1、塊2為數據塊,可以用於存儲數據。數據塊可以進行讀寫操作。
每個扇區的塊3為控制塊,包括了密碼A、存儲控制、密碼B。具體結構如下:
每個扇區的密碼和控制位都是獨立的,可以根據實際需求設定各自的密碼及存取控制。存取控制為4個位元組,共32位,扇區中的每個塊(包括數據和控制塊)存取條件是由密碼和存取控制共同決定的,在存取控制中每個塊都有一個相應的三個控制位。
重點總結:
(1)M1卡分為16個扇區,每個扇區由4塊(0、1、2、3)組成。在實際操作時,將16個扇區分為64個塊,按絕對地址編號為0-63進行訪問,也就是程式里需要填塊的位置時,範圍是0~63。
(2)每個塊的大小是16位元組,每個扇區里有3個數據塊,數據塊可以存放自己的自定義數據。
二、一卡通消費機實現原理
2.1 封裝核心函數
(1)主要的硬體:單片機選擇STM32,刷卡模組採用RC522。
(2)實現核心思路:為了方便存儲數據,對數據進行管理,保證程式的通用性,將IC卡的所有資訊都存放在IC卡上。包括:激活狀態、卡所屬人資訊,金額等。
所以在程式里定義了一個結構體:
#pragma pack(1) //這個結構體大小為16個位元組,剛好存放到 IC卡的一個塊裡面 typedef struct CARD_INFO { u8 stat; //卡狀態. 66表示此卡已經激活 其他值表示此卡未激活 // 88表示此卡掛失,無法再進行消費 u32 money; //金額. 第一次激活卡,就將金額清0 u8 phone[11];//可以存放電話號碼,ID,標識符之類的數據 }CARD; extern u8 IC_Card_uid[4];
並封裝了兩個底層函數: 接下來的所有對卡的操作只需要調用下面函數即可。
//讀取卡號 u8 IC_Card_uid[4]; /* card_uid :卡的id號外部5位元組數組 data : 讀出來的一個塊,16位元組數據 addr : 塊號,從4開始 數據存放的地址。每個扇區的0、1、2塊是存放數據。3是存放密碼。 一般填:0、1、2 、4、5、6、8、9、10 數據一般格式:u8 SJ[16]={255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; //寫入的金額; */ u8 IC_Card_Read(CARD *rdata) { u8 KEY[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; //白卡的出廠密碼 u8 status; /*1. 尋卡*/ status = search_card(IC_Card_uid); /*2. 驗證卡密碼*/ if(MI_OK==status) { print_CardNnmber(IC_Card_uid); status = RC522_PcdAuthState(PICC_AUTHENT1A, 3, KEY, IC_Card_uid); //驗證卡片密碼 形參參數:驗證方式,塊地址,密碼,卡序列號 } /*3. 讀出數據*/ if(MI_OK==status) { status = RC522_PcdRead(1,(u8*)rdata); //從第addr塊讀出數據值。 } return status; } /* 功能:寫數據到指定塊 參數: u8 addr :數據存放的地址。每個扇區的0、1、2塊是存放數據。3是存放密碼。 一般填:0、1、2 、4、5、6、8、9、10 數據一般格式:u8 SJ[16]={255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; //寫入的金額; */ u8 IC_Card_Write(CARD *wdata) { u8 KEY[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; //白卡的出廠密碼 u8 status; /*1. 尋卡*/ status = search_card(IC_Card_uid); /*2. 驗證卡密碼*/ if(MI_OK==status) { status = RC522_PcdAuthState(PICC_AUTHENT1A, 3, KEY, IC_Card_uid); //驗證卡片密碼 形參參數:驗證方式,塊地址,密碼,卡序列號 } /*3. 寫數據到卡*/ if(MI_OK==status) { status = RC522_PcdWrite(1, (u8*)wdata); //寫數據到第addr塊,data入的數據值。 } return status; }
2.2 編寫案例介面
為了方便理解整體的設計思路,下面針對幾個常見的操作編寫了函數介面測試Demo。
void Activation_CardInformation(void); //對卡激活-將卡狀態設置為66 void Unlock_CardInformation(void); //對卡解鎖--去除掛失狀態。將卡狀態設置為66 void locking_CardInformation(void); //對卡掛失。將卡狀態設置為88 void Consumption_CardInformation(void); //消費. 消費就是減少金額. void Recharge_CardInformation(void); //對卡進行充值. 充值就是累加金額 void Query_CardInformation(void); //查詢卡的詳細資訊,通過串口列印
源程式碼如下:
#include "app.h" /* 函數功能: 查詢卡的詳細資訊,通過串口列印 */ void Query_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("用戶資訊:%s\r\n",data.phone); printf("餘額:%d\r\n",data.money); } else if(data.stat==88) { printf("此卡已掛失.請先解鎖.\r\n"); } //卡沒有激活 else { printf("此卡沒有激活.\r\n"); } //複位--釋放選中的卡片 RC522_PcdReset(); } } /* 函數功能: 對卡進行充值. 充值就是累加金額 */ void Recharge_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("用戶資訊:%s\r\n",data.phone); printf("充值前的餘額:%d\r\n",data.money); //累加金額 data.money+=100; //充值100塊 //重新寫入到卡里 RC522_PcdWrite(1, (u8*)&data); //寫數據到第addr塊,data入的數據值。; printf("充值後的餘額:%d\r\n",data.money); } //卡已經掛失 else if(data.stat==88) { printf("此卡已掛失.請先解鎖後再充值.\r\n"); } //卡沒有激活 else { printf("此卡沒有激活.請先激活後再充值.\r\n"); } //複位--釋放選中的卡片 RC522_PcdReset(); } } /* 函數功能: 消費. 消費就是減少金額. */ void Consumption_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("用戶資訊:%s\r\n",data.phone); printf("消費前的餘額:%d\r\n",data.money); //消費金額,假如:我要消費10元,先判斷卡里有沒有10元,沒有就不能消費. printf("即將消費10元...\r\n"); //餘額足夠才能消費 if(data.money>=10) { data.money-=10; //減去10塊 //重新寫入到卡里 RC522_PcdWrite(1, (u8*)&data); //寫數據到第addr塊,data入的數據值。; printf("消費後的餘額:%d\r\n",data.money); } else { printf("餘額不足,消費失敗...\r\n"); } } //卡已經掛失 else if(data.stat==88) { printf("此卡已掛失.請先解鎖後再進行消費流程.\r\n"); } //卡沒有激活 else { printf("此卡沒有激活.請先激活後再進行消費流程.\r\n"); } //複位--釋放選中的卡片 RC522_PcdReset(); } } /* 函數功能: 對卡掛失。將卡狀態設置為88 */ void locking_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("用戶資訊:%s\r\n",data.phone); //設置掛失狀態 data.stat=88; //重新寫入到卡里 RC522_PcdWrite(1, (u8*)&data); //寫數據到第addr塊,data入的數據值。; printf("此卡已成功掛失.\r\n"); } //卡已經掛失 else if(data.stat==88) { printf("此卡已掛失.\r\n"); } //卡沒有激活 else { printf("此卡沒有激活.請先激活.\r\n"); } //複位--釋放選中的卡片 RC522_PcdReset(); } } /* 函數功能: 對卡解鎖--去除掛失狀態。將卡狀態設置為66 */ void Unlock_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("此卡已解鎖.\r\n"); } //卡已經掛失 else if(data.stat==88) { //設置解鎖狀態 data.stat=66; //重新寫入到卡里 RC522_PcdWrite(1, (u8*)&data); //寫數據到第addr塊,data入的數據值。; printf("此卡已成功解鎖.\r\n"); } //卡沒有激活 else { printf("此卡沒有激活.請先激活.\r\n"); } //複位--釋放選中的卡片 RC522_PcdReset(); } } /* 函數功能: 對卡激活-將卡狀態設置為66 激活卡也叫註冊卡。可以寫入一些用戶資訊到卡里. */ void Activation_CardInformation() { CARD data; if(IC_Card_Read(&data)==MI_OK) { //判斷卡是否已經激活 if(data.stat==66) { printf("此卡已激活,不需要重複激活.\r\n"); } //卡已經掛失 else if(data.stat==88) { printf("此卡已激活,並掛失,鎖定,請先解鎖...\r\n"); } //卡沒有激活 else { //設置解鎖狀態 data.stat=66; strncpy((char*)data.phone,"473608901",sizeof(data.phone)-1); //重新寫入到卡里 // IC_Card_Write(&data); /*3. 寫數據到卡*/ RC522_PcdWrite(1, (u8*)&data); //寫數據到第addr塊,data入的數據值。 printf("此卡已成功激活.用戶資訊:%s\r\n",data.phone); } //複位--釋放選中的卡片 RC522_PcdReset(); } }
2.3 編寫操作介面
為了方便測試功能,在LCD螢幕上繪製了幾個矩形,觸控螢幕點擊分別執行對應的功能。
#include "app.h" /* RC522射頻模組外部的介面: *1--SDA <----->PB5--片選腳 *2--SCK <----->PB4--時鐘線 *3--MOSI<----->PA12--輸出 *4--MISO<----->PA11--輸入 *5--懸空 *6--GND <----->GND *7--RST <----->PA8--複位腳 *8--VCC <----->VCC */ int main() { USARTx_Init(USART1,72,115200); LCD_Init(); LCD_Clear(BLACK); XPT2046_TouchInit(); RC522_Init(); // DisplayString(0,0,16,"12345jkdbdfvdfvdfv7364837340hdxsmsks3743934ndvdfv",BLACK,WHITE); // // POINT_COLOR=0x00FF; //設置畫筆顏色 // LCD_DrawLine(0,0,200,50); //畫線 // //顏色填充 LCD_Fill(0,0,120,105,RED); //顏色填充 LCD_Fill(120,0,239,105,RED); //顏色填充 LCD_Fill(0,105,120,210,RED); //顏色填充 LCD_Fill(120,105,239,210,RED); //顏色填充 LCD_Fill(0,210,120,320,RED); //顏色填充 LCD_Fill(120,210,239,320,RED); DisplayString(0,0,16,"Activation",BLACK,WHITE); DisplayString(120,0,16,"Query",BLACK,WHITE); DisplayString(0,105,16,"Recharge",BLACK,WHITE); DisplayString(120,105,16,"Consumption",BLACK,WHITE); DisplayString(0,210,16,"locking",BLACK,WHITE); DisplayString(120,210,16,"Unlock",BLACK,WHITE); while(1) { //掃描觸控螢幕坐標 if(XPT2046_ReadXY()) { printf("x=%d,y=%d\r\n",xpt2046_touch.x,xpt2046_touch.y); printf("x0=%d,y0=%d\r\n",xpt2046_touch.x0,xpt2046_touch.y0); // 對卡激活- if(xpt2046_touch.x>0&&xpt2046_touch.x<120&& xpt2046_touch.y>0&&xpt2046_touch.y<105) { printf("---- 對卡激活-Demo----\r\n"); //充值Demo Activation_CardInformation(); //顏色填充 LCD_Fill(0,0,120,105,WHITE); DisplayString(0,0,16,"Activation",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(0,0,120,105,RED); DisplayString(0,0,16,"Activation",BLACK,WHITE); } //查詢Demo else if(xpt2046_touch.x>120&&xpt2046_touch.x<240&& xpt2046_touch.y>0&&xpt2046_touch.y<105) { printf("----運行查詢Demo----\r\n"); //查詢Demo Query_CardInformation(); //顏色填充 LCD_Fill(120,0,239,105,WHITE); DisplayString(120,0,16,"Query",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(120,0,239,105,RED); DisplayString(120,0,16,"Query",BLACK,WHITE); } //充值Demo else if(xpt2046_touch.x>0&&xpt2046_touch.x<120&& xpt2046_touch.y>105&&xpt2046_touch.y<210) { printf("----運行充值Demo----\r\n"); //充值Demo Recharge_CardInformation(); //顏色填充 LCD_Fill(0,105,120,210,WHITE); DisplayString(0,105,16,"Recharge",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(0,105,120,210,RED); DisplayString(0,105,16,"Recharge",BLACK,WHITE); } //消費Demo else if(xpt2046_touch.x>120&&xpt2046_touch.x<240&& xpt2046_touch.y>105&&xpt2046_touch.y<210) { printf("----運行消費Demo----\r\n"); //消費Demo Consumption_CardInformation(); //顏色填充 LCD_Fill(120,105,239,210,WHITE); DisplayString(120,105,16,"Consumption",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(120,105,239,210,RED); DisplayString(120,105,16,"Consumption",BLACK,WHITE); //等待觸控螢幕鬆開 } //掛失Demo else if(xpt2046_touch.x>0&&xpt2046_touch.x<120&& xpt2046_touch.y>210&&xpt2046_touch.y<320) { printf("----運行掛失Demo----\r\n"); //掛失Demo locking_CardInformation(); //顏色填充 LCD_Fill(0,210,120,320,WHITE); DisplayString(0,210,16,"locking",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(0,210,120,320,RED); DisplayString(0,210,16,"locking",BLACK,WHITE); } //解鎖Demo else if(xpt2046_touch.x>120&&xpt2046_touch.x<240&& xpt2046_touch.y>210&&xpt2046_touch.y<320) { printf("----運行解鎖Demo----\r\n"); //解鎖Demo Unlock_CardInformation(); //顏色填充 LCD_Fill(120,210,239,320,WHITE); DisplayString(120,210,16,"Unlock",BLACK,WHITE); //等待觸控螢幕鬆開 while(XPT2046_PEN==0){} //顏色填充 LCD_Fill(120,210,239,320,RED); DisplayString(120,210,16,"Unlock",BLACK,WHITE); } } delay_ms(10); } }
2.4 運行效果




