文章目录
- 前言
- 一、基本了解
- 二、M1卡和模块的通信流程
- 三、RC522模块
- 四、M1卡
- 1.主要指标
- 2.块3结构:
- 3.密码
- 4.M1卡命令
- 五、STC8A8K调试
- 1.调试
- 1.模块在使用的过程中,一会可以检测到,一会就检测不到?
- 2.在写卡和钱包同时操作的时候,不能够操作钱包?
- 3.在给钱包初始化数值的时候,验证密码正确,写入失败?
- 4.在进行充值或扣款的过程中,用串口打印的数据含有abcdef,怎样解决?
- 5.我想让钱包在充值的时候充值个5毛钱的,怎么办?
- 6.在操作充值和扣款的过程中一旦数比较大就出错,数小就成功?
- 2.实验界面
- 六、STM32调试
- 1.定义数组
- 2.操作流程
- 1.先初始化一个钱包
- 2.往钱包中进行充值
- 3.进行扣款
- 3.总结
前言
早在很久之前就接触过了MFRC522这个模块了,记得还想还是大二上学期的时候,在那一个暑假,我正在学STC8单片机,一个星期天,闲来无事就研究了一下MFRC522模块,当时算是大概了解了一下, 也都通信了,实现了钱包功能,能够扣款和充值。然后就搁置了,没想到,今天又用上了,在使用的时候发现有点问题,以前学的东西都又还回去了。翻博客的时候没有找到,但是在资料中找到了这篇文章,应该是以前写的,但是忘记上传的,今天弄好程序之后,就简单的上传一下吧,也算是为以后做准备。
一、基本了解
我看了一遍之后,发现并没有什么特别有用的信息。
PCD是接近式卡(也就是模块)。PICC是接近式耦合设备(M1卡)。在通信过程中实际上是使用PCD命令控制RC522发出PICC命令与卡进行交互。
通俗点说就是模块和M1卡进行通信,那我们就明白了方向,操作模块是一部分,操作M1卡是一部分.
二、M1卡和模块的通信流程
MF_RC522初始化—寻卡—防冲突—选卡—对要操作的块所在的扇区进行密码验证—操作卡(读、写、加值、减值、转移、恢复)
三、RC522模块
就是操作一些命令集和寄存器
这里随便贴一些:
在手册里面都有,直接抄就行了。还有一些基本的函数
寻卡 ——防冲突——选卡——验证密码等都是网上流传的经典,直接拿来用就行。
在最后我会给出程序代码的连接。这里就不贴了,太占地方。
四、M1卡
1.主要指标
1容量为 8K 位 EEPROM
2分为 16 个扇区,每个扇区为4块,每块16个字节,以块为存取单位
3每个扇区有独立的一组密码及访问控制
4每张卡有唯一序列号,为32 位,没有重复的两张Mifare卡
5具有防冲突机制,支持多卡操作
6工作频率:13.56MHZ
7通信速率:106KBPS
8读写距离:10cm以内(与读写器有关)
这里要了解的是M1卡的容量是8Kb也就是1KB 共分为16个扇区,每个扇区4块,每块16B,每个扇区的4块分为0-4块,总共64块,总的容量也可按绝对地址编号分为0-63,绝对地址0块用于存放厂商代码,已固化,不可更改。块0-2为数据块,块3为控制块。
2.块3结构:
3.密码
出厂值:AB的密码都是FF FF FF FF FF FF 六个字节的FF默认控制FF078069
每个扇区的密码和存取控制都是独立的,可以根据实际需求设定各自的密码以及存取控制。存取控制为四个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制的每个块都有相应的三个控制位,定义如下:
块0:C10 C20 C30
块1:C11 C21 C31
块2:C12 C22 C32
块3:C13 C23 C33
三个控制位以正和反两种形式存在于存取控制字节中,决定了该块的访问权。
如进行减值操作必须验证 KEYA,进行加值操作必须验证 KEYB,等等
存取控制(4 字节,其中字节 9 为备用字节)这里以默认控制字和密码为例:
从图上可以看出,默认密码对块0块1块2都是000 对块三是001.
数据块(块0块1块2)的存取控制表:
由图可知,在默认情况下 ,数据块验证密码A或密码B正确后可执行所有操作。
控制块的存取控制表:
默认情况下001 对密码A任何情况下都不可读,在验证密码A或密码B正确后可进行其他所有操作。
4.M1卡命令
M1卡也有一些命令:
还有一些操作:写一个块,读一个块,扣款和充值,备份钱包等
这些操作也在代码里。也就不贴了。
五、STC8A8K调试
1.调试
以下操作都是在STC8A8K开发板下执行:
下面说一些我在调试的时候遇到的问题:
1.模块在使用的过程中,一会可以检测到,一会就检测不到?
可能是模块供电的电压不够,换个供电电源试试。
2.在写卡和钱包同时操作的时候,不能够操作钱包?
要注意每次进行卡片操作的时候都要进行一遍基本操作。
我已经打包成了一个函数,方便调用。
/*********************************
* @函数名:MFRC522_Base
* @描 述:通信基础函数
* @说 明:
* @参 数:无
* @返回值:成功返回MI_OK
*********************************/
char MFRC522_Base()
{
char status;
status = MFRC522_Find_Card(PICC_Request_all, RC522_Receive_Buffer);//寻全部卡
if (status == MI_OK) //寻卡成功
{
status = MFRC522_Avoid_Collision(RC522_Receive_Buffer);//防冲撞
if (status == MI_OK) //防冲撞成功
{
status = MFRC522_Select_Card(RC522_Receive_Buffer);//选定卡片
if (status == MI_OK) //选卡成功
{
status = MFRC522_Verify_Password(PICC_Auth_1b, 7, DefaultKey, RC522_Receive_Buffer);//验证卡片密码
// if (status == MI_OK)//验证密码成功
// {
// return status; //status = MI_OK //都成功,可以继续下一步
// }
}
}
}
return status; //status = MI_OK //都成功,可以继续下一步
3.在给钱包初始化数值的时候,验证密码正确,写入失败?
先理解钱包的数据块的结构,
M1卡的某一块写为如下格式,则该块为钱包,可接收扣款和充值命令
4字节金额(低字节在前)+4字节金额取反+4字节金额+1字节块地址+1字节块地址取反+1字节块地址+1字节块地址取反
所以可得,在写入数据的时候,要按格式写入,如下:
uchar code init_Wallet[16]=
{0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x04,~0x04,0x04,~0x04};
4.在进行充值或扣款的过程中,用串口打印的数据含有abcdef,怎样解决?
这是因为用串口打印的数据时16进制,所以打印的数据是含有abcd的。
解决:写一个16进制转化为10进制的函数:
/*********************************
* @函数名:Transform_16_10
* @描 述:16进制装换成10进制
* @说 明:
* @参 数:num 要转换的数据
* @返回值:无
*********************************/
uint Transform_16_10(uint num)
{
uint temp,result;
temp = num&0x000f;//获取最后一位
result = temp;
temp = (num>>4)&0x000f;//获取倒数第二位
result += temp*16;
temp = (num>>8)&0x000f;
result += temp*16*16;
temp = (num>>12)&0x000f;//获取最高位
result += temp*16*16*16; //最后的结果
return result; //十六进制转换为十进制
}
我这里是转化的16位的,只取钱包的前两个字节,65535 ,我觉得足够用。
再问:我转化之后用printf(“余额为%hx元”,money);输出结果为什么还是不对?
这是因为 h是表示16位,但是x是表示的16进制的无符号数,要想输出一个整数,不妨试试printf(“余额为%hd元”,money); //输出10进制
5.我想让钱包在充值的时候充值个5毛钱的,怎么办?
可以把钱包的前两个字节00ff 转化成10进制255 我们用最后一位表示5表示小数,前两位表示整数,只需要在输出的时候printf(“余额为%.1f元”,num/10.0); //输出10进就可以啦
6.在操作充值和扣款的过程中一旦数比较大就出错,数小就成功?
这是因为我在操作的时候,每次扣款和充值的数组
Add_Reduce_Wallet[0] = Key_Operate_Value;//要充值的金额
status=MFRC522_Operate_Wallet(com,block,Add_Reduce_Wallet);//进行充值和扣款功能
这两句就操作了,可以看出,这里的Key_Operate_Value虽然是uint型的,但是你写入的只是一个字节,Add_Reduce_Wallet[0] 所以你输入的数不能大于25.5,。
下面我贴几个自己封装的函数:
读出钱包的数据,并转化成实际的金额
/*********************************
* @函数名:MFRC522_Read_Wallet
* @描 述:读出钱包的金额
* @说 明:
* @参 数:block 钱包的位置(第几块)
* @返回值:成功返回MI_OK
*********************************/
char MFRC522_Read_Wallet(uchar block)
{
uchar status = MI_ERR;
uint money; //最后的结果
float num;
status = MFRC522_Read_M1_Data(block, RC522_Receive_Buffer);//读块
if(status == MI_OK) //读数据成功
{
//一个块中的16个字节的前4个字节是金额
// money = RC522_Receive_Buffer[3]; //最高位
// money <<= 8;//扩大8倍
// money += RC522_Receive_Buffer[2];
// money <<= 8;//扩大8倍
//32位太大,这里只取16位
money = RC522_Receive_Buffer[1];
money <<= 8;//扩大8倍
money += RC522_Receive_Buffer[0];
num = Transform_16_10(money);//把十六进制转换为10进制
// printf("余额为%hd元",money); //输出10进制
printf("余额为%.1f元",num/10.0); //输出10进制
putchar_End();
}
return status;
}
钱包充值和扣款,只需调用这一个函数
/*********************************
* @函数名:MFRC522_Wallet_Add_Reduce_Money
* @描 述:钱包充值和扣款
* @说 明:
* @参 数: com 是充值还是扣款 命令 PICC_Decrement 扣款 PICC_Increment 充值 block 钱包的位置(第几块)
* @返回值:无
*********************************/
char MFRC522_Wallet_Add_Reduce_Money(uchar com,uchar block)
{
uchar status;
status = MFRC522_Base();//寻卡-防冲突-选卡-验证密码
if(status == MI_OK)
{
//Send_String("请输入要充值的金额:\n");
Add_Reduce_Wallet[0] = Key_Operate_Value%256;//要充值的金额
Add_Reduce_Wallet[1] = Key_Operate_Value/256;//高位
status = MFRC522_Operate_Wallet(com,block,Add_Reduce_Wallet);//进行充值和扣款功能
if(status == MI_OK) //充值成功 或扣款成功
{
if(com == PICC_Decrement) //扣款
Send_String("扣款成功\n");
else
Send_String("充值成功\n");
status = MFRC522_Read_Wallet(4); //读出钱包的金额 //读出成功会自动打印余额
// if(status != MI_OK) //如果不成功
// {
// while(!(status==MI_OK)) //一直读 直到读出成功
// status = MFRC522_Read_Wallet(4); //读出钱包的金额 //读出成功会自动打印余额
// }
}
}
return status;
}
确保充值和扣款只执行一次
/*********************************
* @函数名:Ensure_Once_Execute
* @描 述:确保一次执行 扣款和充值只执行一次
* @说 明:
* @参 数:无
* @返回值:无
*********************************/
void Ensure_Once_Execute(uchar com,uchar block)
{
uchar status;
status = MI_ERR; //第一次初始化错误 ,确保至少执行一次
while(status!=MI_OK) //不成功 ,进去 确保有一次是成功的
{
status = MFRC522_Wallet_Add_Reduce_Money(com,block); //扣款 ,第4块为钱包
}
}
在连接中,我会给出两个代码,一个是测试版,一个是实用版。测试版就是对模块进行测试的,所有的信息都在主函数里,寻卡,防冲撞 ,选卡,验证密码,读卡,写卡,充值等都是一步一步执行的,适合于新手。在调试的时候把后面的注释掉,前一步操作成功,再执行后一步,直到最后成功。程序中每一步都有串口打印提示信息,方便调试。
实用版就是对钱包的操作,充值和扣款.不多说,直接上图.
2.实验界面
初始界面
扣款
充值:
六、STM32调试
今天是4月22日早上8点钟,今天也是学校运动会的日子,我们大三既不出观众也不上课,nice,今天7点醒的,7点15起来的,可能昨天实在是太困了吧。
闲话少说,开始搞。
关于STM32的调试,我昨天从STC8移植了过来,中间碰到了一点问题,这里简单说明一下。
1.定义数组
uchar RC522_Receive_Buffer[20]; //接收数据的数组
uchar DefaultKey[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; //默认AB密码
//钱包初始化:
uchar Init_Wallet[16] = {0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x04,~0x04,0x04,~0x04};
/*M1卡的某一块写为如下格式,则该块为钱包,可接收扣款和充值命令
4字节金额(低字节在前)+4字节金额取反+4字节金额+1字节块地址+1字节块地址取反+1字节块地址+1字节块地址取反 */
uchar Add_Reduce_Wallet[4] = {0x00,0,0,0}; //低字节在前
从上面我们可以知道,M1卡的默认密码是6个FF,我们从上面也可以知道控制权限,读写权限。基本上都是验证A密码或B密码之后就可以执行一切操作。
DefaultKey:默认的密码 AB都一样
Init_Wallet:初始化钱包数据,可以把某一块作为一个钱包使用
Add_Reduce_Wallet:这个存放的是要扣款(/充值)的金额数据
2.操作流程
1.先初始化一个钱包
我们如果要使用钱包功能,那么我们就需要把M1卡的某一块作为一个钱包使用,即我们需要往这个块写入指定的信息格式。
函数已经封装好,我们在调用的时候只需要执行下面一条语句:
MFRC522_Init_Wallet(0x04); // 把第4块作为钱包使用
要注意这一句话只需要执行一次,并且后续也不需要再执行。
关于这个初始化钱包函数,我们可以修改Init_Wallet这个数组的前4个字节来直接初始化一个金额,我这里默认是0,我们统一用下面这种方法进行写入。
2.往钱包中进行充值
在我们初始化钱包之后我们还需要对钱包中的金额进行充值,我们一般初始化一个比较大的金额进去,比如下面这条语句:
Ensure_Once_Execute(PICC_Increment,0x04,10000);//充值100元
为什么我这里明明写的是10000,为什么注册却是100元呢,这是因为我们默认保留两位小数,所以呢整体要除以100,这一点大家一定要注意。
3.进行扣款
我们使用了钱包功能,那我们肯定要消费,由于我们的函数都封装好了,所以我们直接拿来使用就好了,只需要执行下面这一条指令:
Ensure_Once_Execute(PICC_Decrement,0x04,5);//扣款 每次0.05元
这一句表示我们每次扣款5分钱,也就是说我们每次刷一次卡,就扣5分钱,这一个数值可以是不固定的,只需要把5换成一个变量,每次改变这个变量,那么每次刷卡扣钱都不一样了。
3.总结
基本流程大概就是这样,一般的基本操作我们也只需要调用这两个函数,实现对这两个函数的操作就可以任意的去充值和消费,
更多其他的功能欢迎大家前去扩展。