ZigBee案例笔记 -- RFID卡片读写(模拟饭卡)

RFID模拟饭卡应用

      • RFID(射频识别技术)
      • RFID通讯协议
      • RFID发展历史
      • RFID操作流程说明
        • RFID卡片读写流程
        • RFID寻卡
        • RFID防碰撞
        • RFID选卡
        • RFID卡密验证
        • RFID读卡
        • RFID写卡
        • 读写数据流程
      • RFID饭卡模拟案例
        • 驱动代码
        • 串口协议
        • 饭卡操作
        • 案例结果
        • 优化建议

RFID(射频识别技术)

RFID,全称为"射频识别技术"(Radio Frequency Identification),是一种非接触式自动识别技术。
它由读写器和标签(也叫芯片)两个主要组成部分组成。读写器是RFID系统中的中心,是负责发射电磁波和接收标签信号的设备。读写器可以读取标签中存储的信息,也可以将信息写入标签中存储。标签(芯片)是RFID系统中最基本的元素,是实现物体识别的核心部分。它由射频芯片、天线和封装材料组成,是一种小巧、灵活、可靠的数据存储设备。标签中存储的信息可以是物品的ID、生产日期、生产厂家、价格等。
由于RFID技术具有自动化、高效、非接触等优点,它在物流、仓库管理、资产跟踪、身份识别等领域得到广泛应用,正在成为物联网时代的重要组成部分。在这里插入图片描述
RFID系统的工作原理如下:当标签进入读写器的射频场时,标签内的射频芯片接收来自读写器的电磁波,并通过接收的能量激活芯片中的电路,使其发出回复信号。读写器接收到标签发送的信号后,对其进行解码,就可以读取标签中存储的信息。读写器还可以向标签中写入信息,实现信息的更新。
在RFID实验中常用的RFID读写器如下图所示。所用的芯片是RC522。
在这里插入图片描述

RFID通讯协议

ISO 14443是一种非接触式IC卡的通讯协议,由国际标准化组织(ISO)制定,广泛应用于支付、门禁、公共交通、身份认证等场景。
ISO 14443协议定义了RFID标签与读写器之间的近距离(一般在10cm以内)非接触式通信规范,标签内置天线,利用读写器发射的射频信号进行供电和数据通信。
目前ISO 14443共分为四部分规范:

  • ISO 14443-A:最常使用的协议,支持4位、7位、10位的唯一卡序列号,多用于银行卡、公交卡、门禁等领域。

  • ISO 14443-B:支持中、高频的工作频率,多用于读写器、智能卡等应用场景。

  • ISO 14443-C:只用于芯片的漏接触式接口规范,多用于电子钱包、金融安全等领域。

  • ISO 14443-D: 与ISO 14443-A和ISO 14443-B不同,该规范定义了一种数据格式而不是通信协议,它主要用于近距离无线供电和数据通信场景。

RFID发展历史

RFID直接继承了雷达的概念,并由此发展出一种生机勃勃的AIDC新技术——RFID技术。1948年哈里.斯托克曼发表的“利用反射功率的通讯”奠定了射频识别RFID的理论基础。在20世纪中,无线电技术的理论与应用研究是科学技术发展最重要的成就之一。RFID技术的发展可按10年期划分如下:

  • 1941~1950年:雷达的改进和应用催生了RFID技术,1948年奠定了RFID技术的理论基础。
  • 1951~1960年:早期RFID技术的探索阶段,主要处于实验室实验研究。
  • 1961~1970年:RFID技术的理论得到了发展,开始了一些应用尝试。
  • 1971~1980年:RFID技术与产品研发处于一个大发展时期,各种RFID技术测试得到加速。出现了一些最早的RFID应用。
  • 1981~1990年:RFID技术及产品进入商业应用阶段,各种规模应用开始出现。
  • 1991~2000年:RFID技术标准化问题日趋得到重视,RFID产品得到广泛采用,RFID产品逐渐成为人们生活中的一部分。
  • 2001~至今:标准化问题日趋为人们所重视,RFID产品种类更加丰富,有源电子标签、无源电子标签及半无源电子标签均得到发展,电子标签成本不断降低,规模应用行业扩大。RFID技术的理论得到丰富和完善。单芯片电子标签、多电子标签识读、无线可读可写、无源电子标签的远距离识别、适应高速移动物体的RFID正在成为现实。

RFID操作流程说明

案例中使用的RFID读写器是市面常用的RC522(如上述图片),引脚顺序基本是固定的,与ZigBee芯片的连接引脚如下表所示(ZigBee连接引脚并非固定,可按实际连接引脚而定)

CC2530引脚RC522引脚
P1_4RST
P1_5MISO
P1_6MOSI
P1_7SCK
P2_0SDA
3.3V3.3V
GNDGND

RFID卡片读写流程

RFID卡片一般的操作步骤为寻卡、防碰撞、选卡、卡密验证、读卡/写卡
在这里插入图片描述

RFID寻卡

首先看看寻卡的指令说明,长度为7,命令类型0x02,Cmd为‘A’(0x41),默认数据信息为0x52请求检测范围内所有符合类型的卡片
在这里插入图片描述
根据不同寻找到的不同类型卡片返回的ATQ也不一样,在返回的数据帧中,卡片类型的2个字节,低字节在前,高字节在后,如下
在这里插入图片描述

驱动代码如下,参数1为寻卡方式,一般是0x52寻符合14443A标准的卡,参数2为存放返回的2个字节卡片类型的数组:

/
//功    能:寻卡
//参数说明: req_code[IN]:寻卡方式
//                0x52 = 寻感应区内所有符合14443A标准的卡
//                0x26 = 寻未进入休眠状态的卡
//          pTagType[OUT]:卡片类型代码
//                0x4400 = Mifare_UltraLight
//                0x0400 = Mifare_One(S50)
//                0x0200 = Mifare_One(S70)
//                0x0800 = Mifare_Pro(X)
//                0x4403 = Mifare_DESFire
//返    回: 成功返回MI_OK
/
char PcdRequest(unsigned char req_code,unsigned char *pTagType)
{char status;  
//   uint i;unsigned int  unLen;unsigned char ucComMF522Buf[MAXRLEN]; ClearBitMask(Status2Reg,0x08);	//清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况WriteRawRC(BitFramingReg,0x07);	//	发送的最后一个字节的 七位SetBitMask(TxControlReg,0x03);	//TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号ucComMF522Buf[0] = req_code;		//存入 卡片命令字status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,1,ucComMF522Buf,&unLen);	//寻卡    if ((status == MI_OK) && (unLen == 0x10))	//寻卡成功返回卡类型 {    *pTagType     = ucComMF522Buf[0];*(pTagType+1) = ucComMF522Buf[1];}else{   status = MI_ERR;}return status;
}

RFID防碰撞

防碰撞指令长度为8,命令类型0x02,Cmd(命令)为‘B’(0x42),防碰撞等级默认为第一级防碰撞0x93(低字节在前,高字节在后发送)
在这里插入图片描述
RFID卡片应答,如果防碰撞指令发出成功收到应答,则可获取到卡片的唯一序列号,如图得到的4字节序列号0x8e6e8610,在传输过程中同样给是低字节在前,高字节在后
在这里插入图片描述
在这里插入图片描述
驱动代码如下,传参为存放序列号的4字节数组

/
//功    能:防冲撞
//参数说明: pSnr[OUT]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/  
char PcdAnticoll(unsigned char *pSnr)
{char status;unsigned char i,snr_check=0;unsigned int  unLen;unsigned char ucComMF522Buf[MAXRLEN]; ClearBitMask(Status2Reg,0x08);		//清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位WriteRawRC(BitFramingReg,0x00);		//清理寄存器 停止收发ClearBitMask(CollReg,0x80);			//清ValuesAfterColl所有接收的位在冲突后被清除ucComMF522Buf[0] = 0x93;	//卡片防冲突命令ucComMF522Buf[1] = 0x20;status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,2,ucComMF522Buf,&unLen);//与卡片通信if (status == MI_OK)		//通信成功{for (i=0; i<4; i++){   *(pSnr+i)  = ucComMF522Buf[i];			//读出UIDsnr_check ^= ucComMF522Buf[i];}if (snr_check != ucComMF522Buf[i]){   status = MI_ERR;    }}SetBitMask(CollReg,0x80);return status;
}

RFID选卡

选卡指令,长度11,命令类型0x02,Cmd(命令)为‘C’(0x43),将上一个指令拿到的UID(卡片序列号)发出,进行选卡(读卡/写卡前的操作 - 选卡)
在这里插入图片描述
RFID卡片应答,如果选择的卡片类型是S50卡,则会收到ATQ为0x08的数据帧
在这里插入图片描述
在这里插入图片描述
驱动代码,传参为上一个指令读出来的4字节卡片序列号

/
//功    能:选定卡片
//参数说明: pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/
char PcdSelect(unsigned char *pSnr)
{char status;unsigned char i;unsigned int  unLen;unsigned char ucComMF522Buf[MAXRLEN]; ucComMF522Buf[0] = PICC_ANTICOLL1;ucComMF522Buf[1] = 0x70;ucComMF522Buf[6] = 0;for (i=0; i<4; i++){ucComMF522Buf[i+2] = *(pSnr+i);ucComMF522Buf[6]  ^= *(pSnr+i);}CalulateCRC(ucComMF522Buf,7,&ucComMF522Buf[7]);ClearBitMask(Status2Reg,0x08);status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,9,ucComMF522Buf,&unLen);if ((status == MI_OK) && (unLen == 0x18)){   status = MI_OK;  }else{   status = MI_ERR;    }return status;
} 

RFID卡密验证

卡密验证指令长度18,命令类型0x02,Cmd(命令)为‘F’(0x46),指令内容需要发送验证密钥A/B、卡片序列号、密钥(默认密钥为6个0xFF)、需要操作的块号
在这里插入图片描述
关于块号,以上图的S50卡来说,S50卡(RFID卡片)内部分为16个扇区,每个扇区为4块,总16个扇区 64块按绝对地址为0~63,每块大小为16字节。块0里面存放了厂商代码,已经固化,不可以更改。每个扇区的块3存放了该扇区块前三块的权限管理,建议不要对扇区块3进行操作。结构如下图所示
在这里插入图片描述
每个扇区的块3为控制块,包括了密码A(6字节)、存取控制(4字节)、密码B(6字节),结构如下

A0A1A2A3A4A5   FF078069   B0B1B2B3B4B5

注意:新卡默认出厂的块密码都是0xFF 0xFF 0xFF 0xFF 0xFF 0xFF,不建议修改(RFID有修改卡密的指令),毕竟忘了某个块的密码相当于这个块已经失去操作功能了

RFID卡片应答,卡密验证成功返回0,失败返回其他
在这里插入图片描述
驱动代码,传参A/B密钥、块地址(块号)、卡密、RFID序列号

/
//功    能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
//                 0x60 = 验证A密钥
//                 0x61 = 验证B密钥 
//          addr[IN]:块地址
//          pKey[IN]:密码
//          pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/               
char PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{char status;unsigned int  unLen;unsigned char i,ucComMF522Buf[MAXRLEN]; ucComMF522Buf[0] = auth_mode;ucComMF522Buf[1] = addr;for (i=0; i<6; i++){    ucComMF522Buf[i+2] = *(pKey+i);   }for (i=0; i<6; i++){    ucComMF522Buf[i+8] = *(pSnr+i);   }//   memcpy(&ucComMF522Buf[2], pKey, 6); //   memcpy(&ucComMF522Buf[8], pSnr, 4); status = PcdComMF522(PCD_AUTHENT,ucComMF522Buf,12,ucComMF522Buf,&unLen);if ((status != MI_OK) || (!(ReadRawRC(Status2Reg) & 0x08))){   status = MI_ERR;   }return status;
}

RFID读卡

读卡指令长度7,命令类型0x02,Cmd(命令)为‘G’(0x47),指令内容为指定要读的块号(S50卡块号范围为0 - 63)
在这里插入图片描述
RFID卡片应答,读卡成功返回对应卡块号保存的16字节数据
在这里插入图片描述
驱动代码,传参为卡片块地址(块号)

/
//功    能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
//          pData[OUT]:读出的数据,16字节
//返    回: 成功返回MI_OK
/ 
char PcdRead(unsigned char addr,unsigned char *pData)
{char status;unsigned int  unLen;unsigned char i,ucComMF522Buf[MAXRLEN]; ucComMF522Buf[0] = PICC_READ;ucComMF522Buf[1] = addr;CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);if ((status == MI_OK) && (unLen == 0x90))//   {   memcpy(pData, ucComMF522Buf, 16);   }{for (i=0; i<16; i++){    *(pData+i) = ucComMF522Buf[i];   }}else{   status = MI_ERR;   }return status;
}

RFID写卡

写卡指令长度23,命令类型0x02,Cmd(命令)为‘H’(0x48),命令内容为要写入的块号及16字节数据
在这里插入图片描述
在这里插入图片描述
RFID卡片应答,写入成功返回0,失败返回其他
在这里插入图片描述
驱动代码,传参为1字节块号,16字节写入的数据

/
//功    能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
//          pData[IN]:写入的数据,16字节
//返    回: 成功返回MI_OK
/                  
char PcdWrite(unsigned char addr,unsigned char *pData)
{char status;unsigned int  unLen;unsigned char i,ucComMF522Buf[MAXRLEN]; ucComMF522Buf[0] = PICC_WRITE;ucComMF522Buf[1] = addr;CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A)){   status = MI_ERR;   }if (status == MI_OK){//memcpy(ucComMF522Buf, pData, 16);for (i=0; i<16; i++){    ucComMF522Buf[i] = *(pData+i);   }CalulateCRC(ucComMF522Buf,16,&ucComMF522Buf[16]);status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,18,ucComMF522Buf,&unLen);if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A)){   status = MI_ERR;   }} return status;
}

读写数据流程

S50卡片读写数据流程图
在这里插入图片描述

RFID饭卡模拟案例

驱动代码

案例使用S50卡进行块数据写入读出和串口打印,主要驱动代码如下(操作部分的代码在上面),SPI连接引脚在variable.h中定义

#include"variable.h"
#include"rc522.h"
#include"UART.h"void SPIWriteByte(uchar infor)
{... 
}unsigned char SPIReadByte()
{...
}/
//功    能:读RC632寄存器
//参数说明:Address[IN]:寄存器地址
//返    回:读出的值
/
unsigned char ReadRawRC(unsigned char Address)
{...
}
/
//功    能:写RC632寄存器
//参数说明:Address[IN]:寄存器地址
//          value[IN]:写入的值
/
void WriteRawRC(unsigned char Address, unsigned char value)
{  ...
}/
//功    能:置RC522寄存器位
//参数说明:reg[IN]:寄存器地址
//          mask[IN]:置位值
/
void SetBitMask(unsigned char reg,unsigned char mask)  
{...
}/
//功    能:清RC522寄存器位
//参数说明:reg[IN]:寄存器地址
//          mask[IN]:清位值
/
void ClearBitMask(unsigned char reg,unsigned char mask)  
{...
} /
//开启天线  
//每次启动或关闭天险发射之间应至少有1ms的间隔
/
void PcdAntennaOn(void)
{...
}/
//关闭天线
/
void PcdAntennaOff(void)
{...
}/
//功    能:复位RC522
//返    回: 成功返回MI_OK
/
void PcdReset(void)
{...
}//
//设置RC632的工作方式 
//
void M500PcdConfigISOType(unsigned char type)
{...
}/
//功    能:通过RC522和ISO14443卡通讯
//参数说明:Command[IN]:RC522命令字
//          pInData[IN]:通过RC522发送到卡片的数据
//          InLenByte[IN]:发送数据的字节长度
//          pOutData[OUT]:接收到的卡片返回数据
//          *pOutLenBit[OUT]:返回数据的位长度
/
char PcdComMF522(unsigned char Command, 		//RC522命令字unsigned char *pInData, 		//通过RC522发送到卡片的数据unsigned char InLenByte,		//发送数据的字节长度unsigned char *pOutData, 		//接收到的卡片返回数据unsigned int  *pOutLenBit)		//返回数据的位长度
{...
}/
//功    能:寻卡
//参数说明: req_code[IN]:寻卡方式
//                0x52 = 寻感应区内所有符合14443A标准的卡
//                0x26 = 寻未进入休眠状态的卡
//          pTagType[OUT]:卡片类型代码
//                0x4400 = Mifare_UltraLight
//                0x0400 = Mifare_One(S50)
//                0x0200 = Mifare_One(S70)
//                0x0800 = Mifare_Pro(X)
//                0x4403 = Mifare_DESFire
//返    回: 成功返回MI_OK
/
char PcdRequest(unsigned char req_code,unsigned char *pTagType)
{...
}/
//功    能:防冲撞
//参数说明: pSnr[OUT]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/  
char PcdAnticoll(unsigned char *pSnr)
{...
}
/
//用MF522计算CRC16函数
/
void CalulateCRC(unsigned char *pIndata,unsigned char len,unsigned char *pOutData)
{...
}
/
//功    能:选定卡片
//参数说明: pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/
char PcdSelect(unsigned char *pSnr)
{...
}/
//功    能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
//                 0x60 = 验证A密钥
//                 0x61 = 验证B密钥 
//          addr[IN]:块地址
//          pKey[IN]:密码
//          pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/               
char PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{...
}/
//功    能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
//          pData[IN]:写入的数据,16字节
//返    回: 成功返回MI_OK
/                  
char PcdWrite(unsigned char addr,unsigned char *pData)
{...
}
/
//功    能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
//          pData[OUT]:读出的数据,16字节
//返    回: 成功返回MI_OK
/ 
char PcdRead(unsigned char addr,unsigned char *pData)
{...
}/
//功    能:命令卡片进入休眠状态
//返    回: 成功返回MI_OK
/
char PcdHalt(void)
{
...
}void IC_CMT(uchar *UID,uchar *KEY,uchar RW,char *Dat)
{...
}

串口协议

代码中通过串口发送协议指令来对RFID卡片进行操作,串口通讯协议帧组成如下
在这里插入图片描述

  • 寻卡指令 0x02 -> RFID协议命令类型 0x02(ISO14443A 类命令 CmdType = 2)
  • 寻卡方式 0x52 -> RFID寻卡模式 0x52 (请求天线范围内所有的卡)
  • 块读写指令
    – 00:读块数据(读卡,读取饭卡金额)
    – 01:块数据加(写卡,饭卡充值)
    – 02:块数据减(写卡,饭卡扣费)
  • 块地址 XX (块号,范围 0 - 63)
  • 数据 XX (需要写入的数据 - 饭卡充值或扣钱金额,读卡时默认为00)

当串口收到数据时,进行单字节校验,将串口传输数据保存在数组RevBuffer中

#pragma vector = URX0_VECTOR__interrupt void UART0_ISR(void)
{uchar tmp;URX0IF = 0;               //清除中断tmp = U0DBUF;            //接收数据// 头校验if((tmp == PACK_HEAD) && (PackFlag == HEAD_VERIFY)){count=0;RevBuffer[count++]=tmp;PackFlag = CMD_VERIFY;}// 指令校验else if((tmp == 0x02) && (PackFlag == CMD_VERIFY)){RevBuffer[count++]=tmp;PackFlag = TYPE_REQUEST;}// 无线平台校验(ZigBee)else if((tmp == 0x52) && (PackFlag == TYPE_REQUEST)){RevBuffer[count++]=tmp;PackFlag = ORDER_RECE;}// 模块类型与buff接收else if((count < sizeof(RevBuffer)) && (PackFlag == ORDER_RECE)){RevBuffer[count++]=tmp;if(count == sizeof(RevBuffer))PackFlag = HEAD_VERIFY;}
}

饭卡操作

在main函数中初始化RFID引脚并while循环RFID卡片靠近检测

void main()
{Initial();PcdReset();M500PcdConfigISOType('A');  //设置工作方式while(1){iccardcode();             //IC卡检测}
}

iccardcode()函数中,变量cmd获取串口指令进行RFID操作,当RevBuffer[1]为2时(收到串口指令),进行寻卡操作,寻卡成功(有RFID卡片移动到检测范围内)则修改RevBuffer[1]值,准备进入下一步防碰撞操作,在两次寻卡失败后串口打印信息,清空串口接收数组,退出switch

void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 2: // Request 寻卡status= PcdRequest(RevBuffer[2],&RevBuffer[3]);if(status != MI_OK){status= PcdRequest(RevBuffer[2],&RevBuffer[3]);if(status != MI_OK)				{// 寻卡失败 退出UartSend_String("PcdRequest Wrong !!!",sizeof("PcdRequest Wrong !!!"));memset(RevBuffer,0,sizeof(RevBuffer));break;}}  RevBuffer[1]=3;	RevBuffer[2]=status;break;
...}
}

成功寻卡后RevBuffer[1]值为3,调用PcdAnticoll()函数开始防碰撞操作,防碰撞操作成功时保存RFID序列号,修改RevBuffer[1]值,准备进入下一步选卡操作,通过串口16进制转ASC码打印,操作失败时串口打印错误信息,清空串口数组并退出

void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 3: // 防冲突 读卡的系列号 MLastSelectedSnrstatus = PcdAnticoll(&RevBuffer[3]);if(status != MI_OK){// 防碰撞失败 退出UartSend_String("PcdAnticoll Wrong !!!",sizeof("PcdAnticoll Wrong !!!"));memset(RevBuffer,0,sizeof(RevBuffer));break;}// 保存卡片IDmemcpy(MLastSelectedSnr,&RevBuffer[3],4);RevBuffer[1]=4;RevBuffer[2]=status;// 串口打印卡片IDUartSend_String("ID: ",4);   /****16进制转ASC码********/for(uchar i=0;i<4;i++){Card_Id[i*2]=asc_16[RevBuffer[i+3]/16];Card_Id[i*2+1]=asc_16[RevBuffer[i+3]%16];        }  UartSend_String(Card_Id,8); UartSend('\t');break;...}
}

当RevBuffer[1]值为4时,开始进行RFID选卡,选择上一步保存的RFID序列号,选择成功时修改RevBuffer[1]值为5准备下一步验证卡密,选卡失败则串口打印错误信息,清空串口数组,退出

void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 4:	// 选择卡 Select Cardstatus=PcdSelect(MLastSelectedSnr);if(status!=MI_OK){// 选卡失败 退出UartSend_String("PcdSelect Wrong !!!",sizeof("PcdSelect Wrong !!!"));memset(RevBuffer,0,sizeof(RevBuffer));break;}RevBuffer[1]=5;RevBuffer[2]=status;			break;...}
}

当RevBuffer[1]的值为5时,进行卡密验证,默认密码是6个0xFF。卡密验证成功后判断RevBuffer[7]是否为0,RevBuffer[7]为0将RevBuffer[1]修改为8(读卡),RevBuffer[7]为其他值则将RevBuffer[1]修改为9(写卡)。密码验证失败时串口打印错误信息,清空串口数组,退出

uchar DefaultKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 5: // Key loading into the MF RC500's EEPROMstatus = PcdAuthState(0x60, RevBuffer[8], DefaultKey, MLastSelectedSnr);// 校验卡密码if(status!=MI_OK){UartSend_String("PcdAuthState Wrong !!!",sizeof("PcdAuthState Wrong !!!"));memset(RevBuffer,0,sizeof(RevBuffer));break;}// 数据查询/数据修改 判断if(RevBuffer[7] == 0x00)RevBuffer[1]=8;elseRevBuffer[1]=9;RevBuffer[2]=status;			break;...}
}

当RevBuffer[1]的值为8时,进行RFID读卡操作,当读卡成功时,保存当前所读取的块数据,串口打印块数据(饭卡卡内金额),清除串口数组并退出;当读卡失败时,串口打印错误信息,清除串口数组并退出

void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 8:     // Read the mifare card// 读卡status=PcdRead(RevBuffer[8],&CValue[0]);if(status==MI_OK){// 保存块数据memcpy(LastCValue,&CValue[0],1);// 将块数据转换成数字字符打印UartSend_String("Card Value: ",12);...// 清空串口数组,退出memset(RevBuffer,0,sizeof(RevBuffer));break;}else{// 读卡失败 退出UartSend_String("PcdRead Wrong !!!\n",sizeof("PcdRead Wrong !!!\n"));memset(RevBuffer,0,sizeof(RevBuffer));break;}	break;...}
}

当RevBuffer[1]的值为9时,进行RFID写卡操作,块数据大小为16字节,在案例中只用了第1个字节作为数据存储,而且设定数据范围是0~200,当写卡导致块数据超过设定范围时,串口打印报错,清除串口数组并退出

#define MAX_VALUE 0xC8
#define MIX_VALUE 0x00void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 9: // Write the mifare card// 写卡  if((RevBuffer[7] == 0x01) && ((RevBuffer[9] + LastCValue[0]) > MAX_VALUE)){// 充值金额超过设定上限(200) 报错 退出UartSend_String("error,more than 200\n",sizeof("error,more than 200\n"));memset(RevBuffer,0,sizeof(RevBuffer));break;}if((RevBuffer[7] == 0x02) && ((LastCValue[0] - RevBuffer[9]) < MIX_VALUE)){// 卡内金额不足以扣除(<0) 报错 退出UartSend_String("error,less than 0\n",sizeof("error,less than 0\n"));memset(RevBuffer,0,sizeof(RevBuffer));break;}...}
}

当块数据修改在允许范围时,串口打印块数据变动内容,计算块数据更改后的值写入RevBuffer[9],修改RevBuffer[1]为8,将块数据通过串口打印出来

void iccardcode()
{	     unsigned char cmd;unsigned char status;cmd = RevBuffer[1];switch(cmd){...case 9: // Write the mifare card// 写卡 ... //(判断是否超范围)if(RevBuffer[7] == 0x01){// 打印充值数值UartSend_String("add ",4);... //串口打印充值的金额// 更新块数据(卡内金额)进行写入RevBuffer[9] = LastCValue[0] + RevBuffer[9];status=PcdWrite(RevBuffer[8],&RevBuffer[9]);}else if(RevBuffer[7] == 0x02){// 打印扣除数值UartSend_String("deduct ",7);... //串口打印扣除的金额数// 更新块数据(卡内金额)进行写入RevBuffer[9] = LastCValue[0] - RevBuffer[9];status=PcdWrite(RevBuffer[8],&RevBuffer[9]);}RevBuffer[1]=8;RevBuffer[2]=status;			break;...}
}

案例结果

程序正常运行后,打开串口调试助手,修改波特率为115200,其他默认,打开串口,根据RFID实验的串口通讯协议,主要的操作内容为后3个字节,针对某个块地址的数据读写(注意块0及每个扇区的块3都不可进行操作)
在这里插入图片描述
饭卡余额查询(块数据读取),案例中操作的块为0x10,即块号16,扇区5的块0,块数据查询的指令如下

FE 02 52 00 00 00 00 00 10 00

读卡时需要将RFID卡片置于读卡器上方1cm处,选择串口发送助手发送格式为16进制发送,发送查询指令可以收到RFID的ID卡号及指定块保存的饭卡余额数据
在这里插入图片描述
RFID写卡则分为给饭卡充值或饭卡扣费,串口的读写指令01为给饭卡充值,每次写入都能从串口打印看到充入数值和卡内当前金额,如充入金额的结果会超过设定的200,则会进行报错提示,充值指令如下

FE 02 52 00 00 00 00 01 10 1E

实验结果如图
在这里插入图片描述

串口读写指令02为饭卡扣费,每次写入都能从串口打印看到扣费数值和卡内当前金额,如扣费金额的结果会的低于设定的0,则会进行报错提示,扣费指令如下

FE 02 52 00 00 00 00 02 10 30

实验结果如图
在这里插入图片描述

优化建议

分享的部分代码仅作为参考,对于案例的优化部分,在串口通讯协议的地方,0x02的命令类型和0x52的标准卡查询为固定的帧内容,RFID的序列号也无需加在协议帧中,在代码中防碰撞读出序列号后可保存在某个数组中继续进行后续操作,优化后的串口指令部分为
在这里插入图片描述
至于代码方面也可以按不同需求简化代码,减少代码量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/65020.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言入门 Day_12 一维数组

目录 前言 1.创建一维数组 2.使用一维数组 3.易错点 4.思维导图 前言 存储一个数据的时候我们可以使用变量&#xff0c; 比如这里我们定义一个记录语文考试分数的变量chinese_score&#xff0c;并给它赋值一个浮点数&#xff08;float&#xff09;。 float chinese_scoe…

详细介绍如何基于ESP32实现低功耗的电子纸天气显示器--附完整源码

实现界面展示 这是一款天气显示器,由支持 wifi 的 ESP32 微控制器和 7.5 英寸电子纸(又名电子墨水)显示器供电。当前和预测的天气数据是从 OpenWeatherMap API 获取的。传感器为显示屏提供准确的室内温度和湿度。 该项目在睡眠时消耗约 14μA,在约 10 秒的清醒期…

GitHub打不开解决方法——授人以渔

打不开GitHub的原因之一&#xff0c;DNS地址解析到了无法访问的ip。&#xff08;为什么无法访问&#xff1f;&#xff09; 1、打开GitHub看是哪个域名无法访问&#xff0c;F12一下 2、DNS解析看对应的域名目前哪个IP可以访问 DNS解析的网址&#xff1a; &#xff08;1&#x…

上海的正西边有哪些城市

背景 上海一路向西&#xff0c;来一趟拉萨之行&#xff0c;那么上海出现&#xff0c;所经过的那么多城市&#xff0c;哪些是在上海的正西边呢&#xff1f; 画一幅地图 基于这个背景需求&#xff0c;我们需要拿来一幅地图&#xff0c;一看便知。下面的python代码生成了一幅地…

Ubuntu升级Cmake、gcc、g++

背景 最近要安装llvm&#xff0c;我选择的是从源码安装&#xff0c;所以要使用Cmake进行构建项目。但是服务器上的Cmake、gcc、g的版本都太低了&#xff0c;不符合要求&#xff0c;所以要对此进行升级。在本博客中采用的升级方法不一定是最好的方法&#xff08;因为我也是参考…

跨数据中心Multi-Fabric解决方案:L2和L3网络的高效连接和扩展

云数据中心里&#xff0c;为什么需要DCI互通&#xff1f; 云化数据中心&#xff0c;网络资源通过虚拟化技术形成资源池&#xff0c;实现业务与物理网络解耦&#xff0c;通过网络虚拟化&#xff0c;物理网络资源可以被分成多个虚拟网络资源&#xff0c;从而提高网络资源的使用效…

操作系统的发展和分类

注意&#xff1a;每个阶段的主要优点都是解决了上个阶段的缺点 1.手工操作阶段 概括&#xff1a;一个用户在一段时间内独占全机&#xff0c;导致资源利用率极低&#xff0c;用户输入指令给机器&#xff0c;然后机器运行响应给用户。 2.批处理阶段 2.1单道批处理系统 优点&…

【LeetCode】85.最大矩形

题目 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1&quo…

图解 STP

网络环路 现在我们的生活已经离不开网络&#xff0c;如果我家断网&#xff0c;我会抱怨这什么破网络&#xff0c;影响到我刷抖音、打游戏&#xff1b;如果公司断网&#xff0c;那老板估计会骂娘&#xff0c;因为会影响到公司正常运转&#xff0c;直接造成经济损失。网络通信中&…

基于Matlab利用IRM和RRTstar实现无人机路径规划(附上源码+数据+说明+报告+PPT)

无人机路径规划是无人机应用领域中的关键问题之一。本文提出了一种基于IRM&#xff08;Informed RRTstar Method&#xff09;和RRTstar&#xff08;Rapidly-exploring Random Tree star&#xff09;算法的无人机路径规划方法&#xff0c;并使用Matlab进行实现。该方法通过结合I…

设计模式行为型-状态模式

文章目录 简介状态模式基础定义状态接口或抽象类实现具体状态类 上下文类与状态转换上下文类的定义和作用状态转换及触发条件 状态模式的优势与适用性优点一&#xff1a;可维护的代码优点二&#xff1a;清晰的状态管理适用场景一&#xff1a;对象拥有多个状态适用场景二&#x…

【Unity】常见的角色移动旋转

在Unity 3D游戏引擎中&#xff0c;可以使用不同的方式对物体进行旋转。以下是几种常见的旋转方式&#xff1a; 欧拉角&#xff08;Euler Angles&#xff09;&#xff1a;欧拉角是一种常用的旋转表示方法&#xff0c;通过绕物体的 X、Y 和 Z 轴的旋转角度来描述物体的旋转。在Un…

区块链技术与应用 - 学习笔记1【引言】

大家好&#xff0c;我是比特桃。本系列主要将我之前学习区块链技术时所做的笔记&#xff0c;进行统一的优化及整合。其中大量笔记源于视频课程&#xff1a;北京大学肖臻老师《区块链技术与应用》公开课。肖老师的课让我找回了求知若渴般的感觉&#xff0c;非常享受学习这门课的…

内存管理方式

内存管理 一、C/C内存分布1、内存空间的介绍2、示例题目3、示例题目图解 二、C语言动态内存管理方式1、代码2、介绍 三、C内存管理方式1、概念2、代码3、代码所代表的意义 四、new和delete操作自定义类型1、代码2、运行结果3、特点 五、operator new与operator delete函数1、概…

Go的数据结构-hashmap

开放寻址法和拉链法 runtime.hamp bucket的数据结构 bucket的指针指向这里 map初始化&#xff1a;make 和字面量 make初始化 新建一个hamp结尾体&#xff0c;计算大B&#xff0c;创建一个桶数组 字面量初始化 map的并发解决 sync.map

无涯教程-JavaScript - QUARTILE函数

QUARTILE函数取代了Excel 2010中的QUARTILE.INC函数。 描述 该函数返回数据集的四分位数。四分位数通常用于销售和调查数据中,以将人群分为几类。 语法 QUARTILE (array,quart)争论 Argument描述Required/OptionalArrayThe array or cell range of numeric values for whi…

怎么提取视频中的音乐保存到本地?其实方法很简单

当你想要使用视频中的音乐时&#xff0c;你可以考虑将它从视频中提取出来。这可以用于制作音频样本集&#xff0c;制作铃声或其他音频素材&#xff0c;或者向其他人展示视频的音乐部分而无需显示视频本身。如果你是一位音乐制作人员&#xff0c;你可能会需要一些特定类型的音效…

CP Autosar-Ethernet配置

文章目录 前言一、Eth层级结构介绍二、Autosar实践2.1 ETH Driver2.2 Eth InterfaceEth Interface Autosar配置2.3 TcpIp模块Eth TcpIp Autosar配置2.4 SoAdEth SoAd配置前言 因汽车E/E架构和功能的复杂度提升而带来的对车辆数据传输带宽提高和通讯方式改变(基于服务的通讯-S…

JavaScript(函数,作用域和闭包)

目录 一&#xff0c;什么是函数1.1&#xff0c;常用系统函数1.2&#xff0c;函数声明 1.3&#xff0c;函数表达式二&#xff0c;预解析2.1&#xff0c;函数自调用 2.2&#xff0c;回调函数三&#xff0c;变量的作用域3.1&#xff0c;隐式全局变量 四&#xff0c;作用域与块级作…

Seaborn绘制热力图的子图

Seaborn绘制热力图的子图 提示&#xff1a;如何绘制三张子图 绘制的时候&#xff0c;会出现如下问题 &#xff08;1&#xff09;如何绘制1*3的子图 &#xff08;2&#xff09;三个显示条&#xff0c;如何只显示最后一个 提示&#xff1a;下面就展示详细步骤 Seaborn绘制热力…