動手實操丨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 運行效果

 

點擊關注,第一時間了解華為雲新鮮技術~