写点什么

动手实操丨 RC522 射频卡模块与 IC 卡完成充值消费查询的技术实现思路

  • 2022 年 5 月 05 日
  • 本文字数:7453 字

    阅读完需:约 24 分钟

本文分享自华为云社区《​​​​​​​​​​​​​​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 运行效果





点击关注,第一时间了解华为云新鲜技术~

发布于: 2022 年 05 月 05 日阅读数: 3
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
动手实操丨RC522射频卡模块与IC卡完成充值消费查询的技术实现思路_stm32_华为云开发者社区_InfoQ写作社区