摘要:一文手把手教你利用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 运行效果

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

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

  1. 动手实操:如何用 Python 实现人脸识别,证明这个杨幂是那个杨幂?

    当前,人脸识别应用于许多领域,如支付宝的用户认证,许多的能识别人心情的 AI,也就是人的面部表情,还有能分析人的年龄等等,而这里面有着许多的难度,在这里我想要分享的是一个利用七牛 SDK 简单的实现人 ...

  2. 动手实操(一):如何用七牛云 API 实现相片地图?

    实操玩家: 在苹果手机上,我们只要打开定位服务,拍照后便能在相簿中找到地图,地图上显示着在各地拍摄的相片.网站上这种显示方式也并不少见,例如 Flickr.即将关闭的 Panoramio 等. 作为地 ...

  3. RC522 射频读卡器模块(MINI型)

    一.硬件: 二.[主芯片介绍] MF RC522是应用于13.56MHz非接触式通信中高集成度的读写卡芯片,是NXP公司针对"三表"应用推出的一款低电压.低成本.体积小的非接触式读 ...

  4. 2.动手实操Apache ZooKeeper

    Tips 做一个终身学习的人! 日拱一卒,功不唐捐. 在本节中,我们将讲解如何下载并安装Apache ZooKeeper,以便我们可以直接开始使用ZooKeeper. 本部分旨在通过提供详细的安装和使 ...

  5. 技术实操丨HBase 2.X版本的元数据修复及一种数据迁移方式

    摘要:分享一个HBase集群恢复的方法. 背景 在HBase 1.x中,经常会遇到元数据不一致的情况,这个时候使用HBCK的命令,可以快速修复元数据,让集群恢复正常. 另外HBase数据迁移时,大家经 ...

  6. 二:动手实操SpringBoot-使用Spring Initializr创建项目

    使用 Spring Initializr 初始化 Spring Boot 项目 Spring Initializr 从本质上说就是一个Web应用程序,它能为你构建Spring Boot项目结构. 虽然 ...

  7. IC卡、ID卡、M1卡、射频卡的区别是什么【转】

    本文转载自:https://www.cnblogs.com/najifu-jason/p/4122741.html IC卡.ID卡.M1卡.射频卡都是我们常见的一种智能卡,但是很多的顾客还是不清楚IC ...

  8. IC卡、ID卡、M1卡、射频卡的区别是什么

    IC卡.ID卡.M1卡.射频卡都是我们常见的一种智能卡,但是很多的顾客还是不清楚IC卡.ID卡.M1卡.射频卡的区别是什么,下面我们一起来看看吧. 所谓的IC卡就是集成电路卡,是继磁卡之后出现的又一种 ...

  9. 射频IC卡和IC卡读卡器的成本分析

    当今射频IC卡和IC卡读卡器的种类繁多,很多人问IC卡读卡器多少钱,那么如何在满足我们需求的情况下最大的节省成本呢.下面就各种射频IC卡和IC卡读卡器来分析下各自的成本.                ...

随机推荐

  1. 为什么要使用 rabbitmq?

    1.在分布式系统下具备异步,削峰,负载均衡等一系列高级功能; 2.拥有持久化的机制,进程消息,队列中的信息也可以保存下来. 3.实现消费者和生产者之间的解耦. 4.对于高并发场景下,利用消息队列可以使 ...

  2. 怎么理解 Redis 事务?

    1)事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他客户端发送来的命令请求所打断. 2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执 ...

  3. Elasticsearch 在部署时,对 Linux 的设置有哪些优化方 法?

    1.64 GB 内存的机器是非常理想的, 但是 32 GB 和 16 GB 机器也是很常见的. 少于 8 GB 会适得其反. 2.如果你要在更快的 CPUs 和更多的核心之间选择,选择更多的核心更好. ...

  4. 小技巧之“将Text文件中的数据导入到Excel中,这里空格为分割符为例”

    1.使用场景 将数据以文本导出后,想录入到Excel中,的简便方案, 起因:对于Excel的导出,Text导出明显会更方便些 2.将Text文件中的数据导入到Excel中,这里空格为分割符为例的步骤 ...

  5. Citus 分布式 PostgreSQL 集群 - SQL Reference(手动查询传播)

    手动查询传播 当用户发出查询时,Citus coordinator 将其划分为更小的查询片段,其中每个查询片段可以在工作分片上独立运行.这允许 Citus 将每个查询分布在集群中. 但是,将查询划分为 ...

  6. c的free 为什么不需要知道大小

    malloc malloc函数在运行时分配内存.它需要以字节为单位的大小并在内存中分配那么多空间.这意味着malloc(50)将在内存中分配50个字节.它返回一个void指针 calloc 与mall ...

  7. scanf()函数的原理

    最近使用scanf发现了自己对scanf函数还是不太了解,主要出现在无意中出现的一个错误: scanf正确的写法是,scanf中以什么格式输入变量,则变量的类型就应该是什么格式,如下面scanf输入到 ...

  8. 使用Node.js版本管理器

    使用Node.js版本管理器 完全卸载Node.js 清除Package缓存:npm cache clean --force 卸载Node.js:wmic product where caption= ...

  9. 【uniapp 开发】文字缩略css

    文字超出两行后显示省略号 display: -webkit-box; overflow: hidden; text-overflow: ellipsis; word-wrap: break-word; ...

  10. Linux环境下Eclipse中快捷键不起作用

    在window->Preferences->general->keys中, 找到 content asist 修改下边值 Binding 改成 Alt+/ When 改为 Editi ...