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.rhkb.cn/news/116583.html

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

相关文章

Cordova Android 生成的 APK 中添加代码混淆

要在 Cordova Android 生成的 APK 中添加代码混淆&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 在项目根目录下&#xff0c;找到 platforms/android/ 目录&#xff0c;进入该目录。 2. 打开 build.gradle 文件&#xff0c;并在 android { ... } 部分添加以下代码&…

关于两个不同数据库的两张表建立数据库链接,关联查询数据

一、数据库链接 数据库链接&#xff08;database link&#xff09;是用于跨不同数据库之间进行连接和数据传输的工具或方法。它允许在一个数据库中访问另一个数据库中的对象和数据。 二、具体操作 以Oracle数据库为例 --1.建立链接tjpt CREATE DATABASE LINK tjpt CONNECT…

go语言--锁

锁的基础&#xff0c;go的锁是构建在原子操作和信号锁之上的 原子锁 原子包实现协程的对同一个数据的操作&#xff0c;可以实现原子操作&#xff0c;只能用于简单变量的简单操作&#xff0c;可以把多个操作变成一个操作 sema锁 也叫信号量锁/信号锁 核心是一个uint32值&#…

基于单片机的串行通信发射机设计

一、项目介绍 串行通信是一种常见的数据传输方式&#xff0c;允许将数据以比特流的形式在发送端和接收端之间传输。当前实现基于STC89C52单片机的串行通信发射机&#xff0c;通过红外发射管和接收头实现自定义协议的数据无线传输。 二、系统设计 2.1 单片机选择 在本设计中&…

前端基础2——CSS样式

文章目录 一、使用方式1.1 内联方式1.2 内部方式1.3 外部导入方式&#xff08;推荐&#xff09; 二、选择器类型2.1 元素选择器2.2 ID选择器2.3 类选择器2.4 派生选择器 三、常用属性3.1 内边距和外边距3.2 文本3.3 边框3.4 背景3.5 定位3.6 浮动3.7 字体3.8 其他属性 四、案例…

MySQL 数据库常用命令大全(完整版)

文章目录 1. MySQL命令2. MySQL基础命令3. MySQL命令简介4. MySQL常用命令4.1 MySQL准备篇4.1.1 启动和停止MySQL服务4.1.2 修改MySQL账户密码4.1.3 MySQL的登陆和退出4.1.4 查看MySQL版本 4.2 DDL篇&#xff08;数据定义&#xff09;4.2.1 查询数据库4.2.2 创建数据库4.2.3 使…

DataTable扩展 列转行方法(2*2矩阵转换)

源数据 如图所示 // <summary>/// DataTable扩展 列转行方法&#xff08;2*2矩阵转换&#xff09;/// </summary>/// <param name"dtSource">数据源</param>/// <param name"columnFilter">逗号分隔 如SDateTime,PM25,PM10…

docker desktop安装es 并连接elasticsearch-head:5

首先要保证docker安装成功&#xff0c;打开cmd&#xff0c;输入docker -v&#xff0c;出现如下界面说明安装成功了 下面开始安装es 第一步&#xff1a;拉取es镜像 docker pull elasticsearch:7.6.2第二步&#xff1a;运行容器 docker run -d --namees7 --restartalways -p 9…

ShardingSphere——压测实战

摘要 Apache ShardingSphere 关注于全链路压测场景下&#xff0c;数据库层面的解决方案。 将压测数据自动路由至用户指定的数据库&#xff0c;是 Apache ShardingSphere 影子库模块的主要设计目标。 一、压测背景 在基于微服务的分布式应用架构下&#xff0c;业务需要多个服…

WebVR — 网络虚拟现实

推荐&#xff1a;使用 NSDT编辑器 快速搭建3D应用场景 虚拟现实设备 随着Oculus Rift和许多其他生产设备即将上市&#xff0c;未来看起来很光明——我们已经有足够的技术来使VR体验“足够好”&#xff0c;可以玩游戏。有许多设备可供选择&#xff1a;像Oculus Rift或HTC Vive这…

JasperReport定义变量后打印PDF变量为null以及整个pdf文件为空白

问题1: JasperReport打印出来的整个pdf文件为空白文件&#xff1b; 问题2&#xff1a;JasperReport定义变量后打印PDF变量为null&#xff1b; 问题1原因是因为缺少数据源JRDataSource JasperFillManager.fillReport(jasperReport, params,new JREmptyDataSource());如果你打印…

Go 使用 Gorm 将操作信息集成到链路跟踪 Jaeger,进行增删改查使用举例,并做可视化UI界面展示(附源码)

Go 使用 Gorm 将操作信息集成到链路跟踪 Jaeger,进行增删改查使用举例(附源码)。 为了增强程序的可观测性,方便问题定位,在发起数据库操作请求时我们也可以调用代码统一集成链路跟踪的能力,Jaeger 是当今比较流行的选择。使用 Gorm 来将操作信息集成到 Jaeger 中。 全面…

Autofac中多个类继承同一个接口,如何注入?与抽象工厂模式相结合

多个类继承同一个接口,如何注入&#xff1f;与抽象工厂模式相结合 需求: 原来是抽象工厂模式,多个类继承同一个接口。 现在需要使用Autofac进行选择性注入。 Autofac默认常识: Autofac中多个类继承同一个接口,默认是最后一个接口注入的类。 解决方案&#xff1a;(约定大于配…

使用远程桌面软件改善工作与生活的平衡

在当今高度互联的世界中&#xff0c;我们的工作和个人生活之间的界限变得越来越模糊。在本文中&#xff0c;我们将探讨像 Splashtop 这样的远程桌面工具如何成为实现和谐工作与生活平衡不可或缺的一部分。 在当今的背景下理解工作与生活的平衡 工作与生活的平衡不仅仅是一个时…

【C++进阶】模板进阶

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

Ansible学习笔记5

copy模块&#xff1a;&#xff08;重点&#xff09; copy模块用于对文件的远程拷贝&#xff08;如把本地的文件拷贝到远程主机上。&#xff09; 在master的主机上准备一个文件&#xff0c;拷贝文件到group1的所有主机上。 这个用的频率非常高&#xff0c;非常有用的一个模块…

pdf加密如何解除?这样解除加密很简单

pdf加密如何解除&#xff1f;有时&#xff0c;我们可能会收到一些加密的PDF文件&#xff0c;它们不允许我们对其进行编辑或打印。这时&#xff0c;我们需要使用PDF解密工具&#xff0c;以便能够轻松地解除PDF加密并对其进行编辑。那么接下来就给大家介绍一下pdf加密解除的方法。…

11.物联网lwip,网卡原理

一。LWIP协议栈内存管理 1.LWIP内存管理方案 &#xff08;1&#xff09;堆heap 1.灰色为已使用内存 2.黑色为未使用内存 3.紫色为使用后内存 按照某种算法&#xff0c;把数据放在内存块中 &#xff08;2&#xff09;池pool 设置内存池&#xff0c;设置成大小相同的内存块。 2…

JVM调优指令参数

常用命令查找文档站点&#xff1a;https://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html -XX:PrintFlagsInitial 输出所有参数的名称和默认值&#xff0c;默认不包括Diagnostic和Experimental的参数。可以配合 -XX:UnlockDiagnosticVMOptions和-XX:UnlockEx…

NoSQL数据库介绍+Redis部署

目录 一、NoSQL概述 1、数据的高并发读写 2、海量数据的高效率存储和访问 3、数据库的高扩展和高可用 二、NoSQL的类别 1、键值存储数据库 2、列存储数据库 3、文档型数据库 4、图形化数据库 三、分布式数据库中的CAP原理 1、传统的ACID 1&#xff09;、A--原子性 …