基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

您所在的位置:网站首页 rfid电子钱包设计方案怎么做 基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

2024-07-14 15:56| 来源: 网络整理| 查看: 265

大家好,又见面了,我是你们的朋友全栈君。

目录

前言

STM32F103ZET6单片机

RC522

相关引脚连接

准备工作

Mifare卡

读卡过程

最终实现功能

代码

RC522.C代码

RC522.H

main.c

led.h

前言

本人也是正在学习单片机知识的萌新一枚,在这里记录下自己完成这个小设计的过程跟大家分享一下,也请大家指出我哪里还有不足可以改进的地方。秉着和大家一起学习进步发布了这篇文章

STM32F103ZET6单片机

我使用的单片机是正点原子版的STM32F1精英版,型号是ZET6。32系列的单片机功能比较完整,基本所有的小设计都可以使用32完成,而且现在市面上使用32系列的人也是不在少数,所以推荐大家使用这款单片机去完成各种实验和设计。

下面是我的STM32F1的实物图

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

下面是STM32F103ZET6的原理图

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

由于本博客旨在跟大家分享设计电子钱包相关功能的过程,所以MCU系列相关的我不再做多的介绍,有对STM32系列单片机不懂的或者对STM32单片机感兴趣的小伙伴可以去网上查阅相关资料。

RC522

MF RC522 是应用于13.56MHz 非接触式通信中高集成度读写卡系列芯片中的一员。是NXP 公司针对“三表”应用推出的一款低 电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携 式手持设备研发的较好选择。RC522使用的SPI协议,所以它的对应引脚输入输出应该是MISO和MOSI。

RC522相对来说也是一款功能很齐全的模块了,它相当于531来说,价格相对更便宜。下面是我的RC522实物图

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计相关引脚连接

下面是STM32F103相关引脚的定义

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

下面是RC522相关引脚的定义

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

根据上面两个表我们可以知道两个模块的相关的引脚连接应该是

RC522 STM32

3.3V ———- 3.3V

RST ———- PB0

GND ———- GND

IRQ ———- 悬空

MISO ——- PA6

MOSI ——- PA7

SCK ———- PA5

SDA ———- PA4

这样的话,硬件部分我们就设置完成了,接下来就是代码部分了,再讲代码部分之前,这里详细的给大家讲一下我在设计这个的过程中遇到的一些问题和调试过程,供大家参考,不要犯跟我一样的错误。

准备工作

在我们连接好硬件之后,我们得去思考一个问题,它怎么能去识别卡里面的信息呢,卡里面都是怎么定义的,才能被RC522的天线给识别到。因为我使用的是M1卡,所以这里我给大家介绍下M1卡。

Mifare卡

过多的文字描述我也不多说,给大家介绍下Mifare卡的比较重要的知识

Mifare卡的工作频率为13.56MHz,每张卡都有16个扇区,每个扇区4块,每块6个字节,以块为单位。每个扇区都有独立的一组密码以及访问控制位,如下图

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

由上图我们可以知道,每张卡的0扇区0块区存储的是我们的卡号以及厂商信息,是出厂的时候定死了的,不可更改,剩下的所有块我们都可以人为的进行修改。我们还可以看到,每个扇区的第3块区,也就是尾块,前6位是密钥A,后6位是密钥B,第6,7,8字节就是访问控制位,通过修改每个个尾块的访问控制位,我们可以修改该扇区的各个块为数值块或者数据块或者传输配置状态。我们要做的电子钱包就需要设置为数值块。这里要说明下传输配置状态也具有数值块功能。

下面给大家着重讲一下访问控制状态的配置,这里我当时学的时候也是搞了好久才搞懂。

我们先看下访问控制位字节6,7,8是如何定义的。

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

这里的_b代表取反,意思就是与对应的Cxy是相反的值。这里的Cxy,x表示访问控制位1,2,3。y表示块0,1,2,3.

接下来我们再来看看访问控制位C1,C2,C3如何使用的

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

举个例子,假如此时我的访问控制位此时为 FF 07 80。拆成二进制可以表示为

1111 1111

0000 0111

1000 0000

此时我们可以对照访问控制位的表,以块0为例,C10,C20,C30=000,对照表可以理解为,验证密钥A或者密钥B成功后,块0可读,可写,可增值,可减值。

但是尾块的访问控制表和其他块的不一样,它有着单独的访问控制表

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

在这个表里面我们可以看出,密钥A在任何时候都不可读,其他相应的位都可以通过我们手动去修改它的访问控制位来达到读写的效果。

在这里给大家说一下我实验中遇到的问题,因为密钥A是不可读的,所以你在打印尾块的时候密钥A打印出来的会全是0,并不是密钥A实际的值为0

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计代码语言:javascript复制unsigned char DATA2[16]= {0xff,0xff,0xff,0xff,0xff,0xff,0xef,0x07,0x81,0x69,0xff,0xff,0xff,0xff,0xff,0xff};

可以看到,我们在写尾块的时候写进去的密钥A是12个F,可是读出来的却全是0,这让我刚开始的时候误以为它的密钥A全是0,导致我后面写读写控制条件的时候把密钥A全写成了0,以至于写坏了3个扇区才发现问题的所在,希望大家不要犯和我一样的错误。

接下来说一说数值块的定义,要设置一个块为数值块,它的格式是非常严格的,必须按照数值块的格式去写,才能调用充值和扣款的功能,数值块的格式如下

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

这里需要注意的是,钱包值里面放的数据是小端模式,比如说你的钱包值为0x00000123,在数值块里面就得写为 23 01 00 00 。以这种格式存放数据。后面的地址值表示你当前块的地址。

读卡过程

在简单介绍了Mifare卡之后,我们来介绍下它到底是如何实现读卡的过程的。

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

由上图可知,操作的过程简单来说可以分解为

寻卡—防冲撞—选卡—三轮认证—操作卡。

在我的实验里面,我把选卡的操作加了一个if条件,检查到是我定义的卡号的时候才执行后面的三轮认证以及操作卡的过程,不是我的卡则串口打印此卡没有认证。

定义的卡号如下

代码语言:javascript复制unsigned char card_0[4]= {153,240,20,16};//我自己的卡 unsigned char card_2[4]= {121,209,130,123};//水卡

验证是否为自己的卡的代码条件如下

代码语言:javascript复制if((SN[0]==card_0[0])&&(SN[1]==card_0[1])&&(SN[2]==card_0[2])&&(SN[3]==card_0[3]))

这里的SN为寻卡宣选到的ID存放的数组。

接下来说一下三轮认证,也就是验证密钥A和密钥B的过程,这里需要注意的是,只能操作验证过的扇区,比如我验证了第二扇区,则只能对第二扇区的块进行相关的操作。

最终实现功能

要完成简单的电子钱包充值扣款的功能,我这里设置了两个按键,KEY0和KEY1,分别控制着充值和扣款的功能。我设置的单次充值扣款1元,并且所剩金额不够扣款的话会提示扣款失败。

按键的初始化如下

代码语言:javascript复制#define KEY0 PEin(4)//PE4按键 #define KEY1 PEin(3)//PE3按键

然后我们设置单次按键的充值或者扣款的金额,我这里设置的每次为1块钱。

代码语言:javascript复制Add_Money[0] = 0x01; //为充值金额 //金额转化 status = PcdValue(PICC_INCREMENT, adr3_0, Add_Money);

PcdValue函数里第一个参数表示充值或者扣款的功能,这是在头文件里面定义的地址。第二个参数表示钱包的地址,第三个参数表示单次操作的金额。

充值扣款的定义如下

代码语言:javascript复制#define PICC_DECREMENT 0xC0 //扣款 #define PICC_INCREMENT 0xC1 //充值

接下来看看运行过程中的串口输出

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

可以看到现在打印出了261块钱,这261块钱是怎么来的呢,我们可以看到钱包值为05 01 00 00

但是存放格式是小端格式存放的,实际值是0x00000105,也就是261。然后我们可以看到它提示我们按下KEY0充值,按下KEY1扣款。我们看看效果

充值效果,单次充值1元

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

扣款效果,单次扣除1元

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

我后面又添加了一段函数,使得扣款扣到0后,会提示扣款失败。更加贴合实际情况

基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计

PS:此图是借用的他人的图,功能类型(因为我当时没设备)

代码

下面把相关主要代码给大家参考

RC522.C代码 代码语言:javascript复制#include "sys.h" #include "rc522.h" #include "delay.h" #include "usart.h" #include "string.h" /******************************* *连线说明: *1--SDA PA4 *2--SCK PA5 *3--MOSI PA7 *4--MISO PA6 *5--悬空 *6--GND GND *7--RST PB0 *8--VCC VCC ************************************/ /*全局变量*/ unsigned char CT[2];//卡类型 unsigned char SN[4]; //卡号 unsigned char date[16]; //存放数据 unsigned char date1_0[16]; //扇区1块0存放数据 unsigned char date1_3[16]; //扇区1块3存放数据 unsigned char date2_3[16]; //扇区2块3存放数据 unsigned char date2_0[16]; //扇区2块0存放数据 unsigned char date3_0[16]; //扇区3块0存放数据 unsigned char date3_3[16]; //扇区3块3存放数据 unsigned char date4_3[16]; //扇区4块3存放数据 unsigned char card0_bit=0; unsigned char card1_bit=0; unsigned char card2_bit=0; unsigned char card3_bit=0; unsigned char card4_bit=0; unsigned char total=0; // 替换成自己卡的UID unsigned char card_0[4]= {153,240,20,16};//我自己的卡 unsigned char card_2[4]= {121,209,130,123};//水卡 unsigned char Add_Money[4] = {0,0,0,0}; //充钱金额 unsigned char sub_Money[4] = {0,0,0,0}; //扣款金额 u8 KEY_A[6]= {0xff,0xff,0xff,0xff,0xff,0xff}; u8 KEY_B[6]= {0xff,0xff,0xff,0xff,0xff,0xff}; // 置零用 unsigned char DATA0[16]= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; unsigned char DATA1[16]= {0xff,0xff,0xff,0xff,0xff,0xff,0xef,0x07,0x81,0x69,0xff,0xff,0xff,0xff,0xff,0xff};//改尾块设置扇区0为数据块 unsigned char DATA2[16]= {0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x0c,0xf3,0x0c,0xf3};//设置3扇区0块区为数值块结构,钱包余额初始为0 unsigned char status; unsigned char adr2_0=0x08;// 第2扇区0区块(第9块) unsigned char adr1_0=0x04;// 第1扇区0区块(第5块) unsigned char adr1_3=0x07;// 第1扇区3区块(第8块) unsigned char adr2_3=0x0B;// 第2扇区3区块(第12块) unsigned char adr3_0=0x0C;// 第3扇区0区块(第13块) unsigned char adr3_3=0x0F;// 第3扇区3区块(第16块) unsigned char adr4_3=0x13;// 第4扇区3区块(第20块) unsigned char adr5_3=0x17;// 第5扇区3区块(第24块) #define RC522_DELAY() delay_us( 20 ) void RC522_Handle(void) { u8 i = 0; status = PcdRequest(PICC_REQALL,CT);//寻卡 // printf("\r\nstatus>>>>>>%d\r\n", status); if(status==MI_OK)// 寻卡成功 { status=MI_ERR; status = PcdAnticoll(SN);// 防冲撞 获得UID 存入SN //ShowID(SN); } else printf("Please swipe the card\r\n"); if (status==MI_OK)// 防冲撞成功 { status = MI_ERR; ShowID(SN); // 串口打印卡的ID号 UID // 判断是否为自己的卡,是自己的卡才执行后面的操作。 if((SN[0]==card_0[0])&&(SN[1]==card_0[1])&&(SN[2]==card_0[2])&&(SN[3]==card_0[3])) { card0_bit=1; printf("\r\nwelcome \r\n"); status = PcdSelect(SN); / if(status == MI_OK)//选卡成功 { status = MI_ERR; // 验证A密钥 块地址 密码 SN // 注意:此块地址只需要指向某一扇区就可以了,且只能对验证过的扇区进行读写操作 status = PcdAuthState(KEYA, adr3_3, KEY_A, SN); if(status == MI_OK)//验证成功 { printf("PcdAuthState(A) success\r\n"); } else { printf("PcdAuthState(A) failed\r\n"); } // 验证B密钥 块地址 密码 SN status = PcdAuthState(KEYB, adr3_3, KEY_B, SN); if(status == MI_OK)//验证成功 { printf("PcdAuthState(B) success\r\n"); } else { printf("PcdAuthState(B) failed\r\n"); } } if(status == MI_OK)//验证成功 { status = MI_ERR; // 读取M1卡一块数据 块地址 读取的数据 注意:因为上面验证的扇区是3扇区,所以只能对3扇区的数据进行读写,超出范围读取失败。 status = PcdRead(adr3_0, date3_0); if(status == MI_OK)//读卡成功 { // printf("RFID:%s\r\n", RFID); printf("date3_0:"); for(i = 0; i < 16; i++) { printf("%02x", date3_0[i]); } printf("\r\n"); printf("now you have %d money\r\n",date3_0[0]+date3_0[1]*256+date3_0[2]*256*256+date3_0[3]*256*256*256);//打印输出你现在有多少钱 printf("Press the KEY0 to recharge\r\n");//按下KEY0充值 printf("Press the KEY1 to charge\r\n");//按下KEY1扣款 } else { printf("PcdRead() failed\r\n"); } } delay_ms(10000); // if(KEY0==0) { Add_Money[0] = 0x01; //为充值金额 //金额转化 status = PcdValue(PICC_INCREMENT, adr3_0, Add_Money); if (status == MI_OK)//充值成功 { status=MI_ERR; status=PcdRead(adr3_0,date3_0); if(status==MI_OK)//读卡成功 { printf("date3_0:"); for(i = 0; i < 16; i++) { printf("%02x", date3_0[i]); } printf("\r\n"); printf("recharge success\r\n"); printf("now you have %d money\r\n",date3_0[0]+date3_0[1]*256+date3_0[2]*256*256+date3_0[3]*256*256*256); } else printf ("PcdRead failed\r\n"); } else printf ("PcdValue failed\r\n"); } delay_ms(10000); if(KEY1==0) { sub_Money[0] = 0x01; //为扣款金额 //金额转化 if((date3_0[0]+date3_0[1]*256+date3_0[2]*256*256+date3_0[3]*256*256*256)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3