在这里插入图片描述

本篇章 Mifare522 Module 使用说明书

1.1 硬件描述

MIFARE522_MODULE 实物图如图 1 所示
在这里插入图片描述
图 1 MIFARE522_MODULE 实物图
该模块的供电电压为直流 5~9V,UART TTL 电平输出。接线简单,图 1 中 J2 为接线引脚,J1 为生产编程引脚(用户不需要理会)。J2 的引脚描述如表 1 所示。
表 1 J2 的引脚描述

在这里插入图片描述
机械结构图如图 2 所示,单位为毫米。
在这里插入图片描述

1.2 通信协议

本模块以命令——响应的方式工作,在系统中模块是处于从属地位,不会主动发出数据(自动检测卡片除外)。通常主机首先发出命令,然后等待模块响应。
通信控制符描述如表 2 所示。
表 2 通信控制符表
在这里插入图片描述
UART 接口一帧的数据格式为 1 个起始位,8 个数据位、无奇偶校验位、1 个停止位,波特率固定为 9600。

1.2.1数据帧 [1帧=6byte+N]

数据总是以一帧为单位进行通信的,一帧的数据格式如下:
在这里插入图片描述
网络层字段说明如表 3所示:
表 3 数据帧各字段说明表
在这里插入图片描述
数据帧接收规则:
一帧的结束一定是 ETX,但接收到0x03 则不一定是帧结束;
帧长必须不小于 6 字节,最大不能超过54 字节,且帧长必须等于信息长度加6;
BCC 计算必须正确。
无论是主机还是从机所接收的数据必须符合以上规则,否则从机不会执行任何命令,也不会有任何错误响应,主机也必须丢弃这帧数据,以找出错误原因,从而纠正错误。

1.3 应用命令详述

1.3.1 设备控制类命令 设备控制类命令((((CmdType = 1))))

设备控制类命令总汇如表 4所示。
表 4 设备控制类命令一览表
在这里插入图片描述

[1]读设备信息(Cmd = A)

声明: INT8U GetDvcInfo(INT8U *DvcInfo);
主机命令
命令类型(CmdType): 0x01
命令 (Command): ‘A’
数据长度(Length): 0
数据信息(Info): none

例如:
数据帧
在这里插入图片描述
假设所有的例子中包序号SEQ 为0;

从机应答
状态 (Status): 0
数据长度(Length): 14
数据信息(Info): ’ZLG522S V1.06’
例如:
数据帧
在这里插入图片描述

[2]配置读卡芯片(Cmd = B)

声明: INT8U PCDConfig();
主机命令
命令类型(CmdType): 0x01
命令 (Command): ‘B’
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧
在这里插入图片描述

3. 关闭读卡芯片(Cmd = C)

声明: INT8U PCDClose();
主机命令:
命令类型(CmdType): 0x01
命令 (Command): ‘C’
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述
在这里插入图片描述

1.3.2 ISO14443A 类命令 类命令((((CmdType = 2))))

ISO14443A类命令总汇如表 5所示。
表 5 ISO14443A 类命令一览表
在这里插入图片描述
前4 条命令(命令A—D)是ISO14443A 标准定义的命令,只要符合该标准的卡都应能 发出响应;中间4条命令(命令E—H)为Mifare1 卡的专用命令,只有先进行验证(命令E、F)成功之后才能进行。

[1]请求(Cmd = A)

声明: INT8U PiccRequest(INT8U Req_Code,INT8U *TagType);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘A’
数据长度(Length): 1
数据信息(Info): 请求模式(1 字节): 0x26——IDLE 0x52——ALL
例如:请求天线范围内所有的卡
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 2
数据信息(Info): 请求应答ATQ(2 字节,低字节在前)
在这里插入图片描述
表 6例举了各种类型的卡返回的ATQ。
表 6 返回 ATQ 一览表
在这里插入图片描述
例如:S50 卡返回的ATQ
数据帧:
在这里插入图片描述
==说明 ==

卡进入天线后,从射频场中获取能量,从而得电复位,复位后卡处于IDLE 模式,用两种请求模式的任一种请求时,此时的卡均能响应;若对某一张卡成功进行了挂起操作(Halt命令或DeSelect*命令),则进入了Halt 模式,此时的卡只响应ALL(0x52)模式的请求,除非将卡离开天线感应区后再进入。*注:DeSelect 为ISO14443-4 命令。 
另外,对Mifare1 卡连续进行请求操作,总是一次成功,一次失败,循环往复。

[2] 防碰撞(Cmd = B)

声明: INT8U PiccAnticoll(INT8U Sel_Code,INT8U Bcnt,INT8U *PiccSnr);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘B’
数据长度(Length): 若位计数=0,则长度=2
若位计数≠0,则长度=6
数据信息(Info): 选择代码(1 字节):
0x93——第一级防碰撞
0x95——第二级防碰撞
0x97——第三级防碰撞
位计数(1 字节): 已知的序列号的长度
序列号(4 字节)(若位计数≠0)
例如:第一级防碰撞
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 4
数据信息(Info): UID(4 字节,低字节在先),若UID 不完整,则最低字节
为级联标志0x88,需要进行更高一级的防碰撞。
例如:返回序列号0x8e6e8610
在这里插入图片描述
说明
符合ISO14443A 标准卡的序列号都是全球唯一的,正是这种唯一性,才能实现防碰撞的算法逻辑,若有若干张卡同时在天线感应区内则这个函数能够找到一张序列号较大的卡来操作。实际上由于天线辐射的磁场能量有限,同时在天线感应区内的所有卡都要从辐射场中吸收,因此同时在天线感应区内的卡不能太多,否则辐射场能量被平分,没有一张卡能获得足够的能量来正常工作。 位计数为已知的序列号的位数,若位计数=0,则序列号的所有位都要从本函数获得;若位计
数≠0,则序列号中有已知的序列号的值,表示要获得序列号的前位计数位为序列号中所示的卡的其余位的值。位计数必须小于32,若位计数等于32,则可直接用选择命令,选择一张已知序列号
的卡。

[3] 选择(Cmd = C)

声明: INT8U PiccSelect(INT8U Sel_Code,INT8U *PiccSnr,INT8U *Sak);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘C’
数据长度(Length): 5
数据信息(Info): 选择代码(1 字节):
0x93——第一级防碰撞
0x95——第二级防碰撞
0x97——第三级防碰撞
UID(4 字节):前一个防碰撞命令返回的UID(包含级联志)
例如:第一级选择,UID 为0x8e6e8610
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 1
数据信息(Info): 选择应答 SAK
在这里插入图片描述
b2:Cascade 位,表示UID 是否完整,
若b2=0,表示UID 完整;
若b2=1,表示UID 不完整,还有部分UID 未读出。
表 7例举了各种类型的卡第一级选择返回的SAK
表 7 返回 SAK 一览表
在这里插入图片描述
例如:选择 S50 卡应答
在这里插入图片描述
说明
卡的序列号长度有三种:4 字节、7 字节和10 字节。4 字节的只要用一级选择即可得到完整的序列号,如Mifare1 S50 S70 等;7 字节的要用二级选择才能得到完整的序列号,前一级所得到的序列号的最低字节为级联标志0x88,在序列号内只后3 字节可用,后一级选择能得到4 字节序列号,两者按顺序连接即为7 字节序列号,如UltraLight 和DesFire 等;10 字节的以此类推,但至今还未发现此类卡。
在程序中可用 SAK.2 位来判断是还有序列号未读出,如 if(SAK & 0x04){…}。

[4]暂停(Cmd = D)

声明: INT8U PiccHalt();
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘D’
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述

说明
此命令将使所选择的卡进入Halt 状态,在Halt 状态下,卡将不响应读卡器发出的IDLE模式的请求,除非将卡复位或离开天线感应区后再进入。但它会响应读卡器发出的ALL 请求。

[5]直接密码证实(Cmd = F)

声明: INT8U PiccAuthKey(INT8U KeyAB,INT8U *PiccSnr,INT8U *Key,INT8U Block);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘F’
数据长度(Length): 12
数据信息(Info): 密钥AB(1 字节):
0x60——密钥A
0x61——密钥B
卡序列号(4 字节)
密钥(6 字节)
卡块号(1 字节): S50:0——63
S70:0——255
例如:用密钥“0xFF 0xFF 0xFF 0xFF 0xFF 0xFF”证实序列号为0x8e6e8610的卡的块4的密钥A
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述

[6] 读(Cmd = G)

声明: INT8U PiccRead(INT8U Block,INT8U *Bfr);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘G’
数据长度(Length): 1
数据信息(Info): 卡块号(1 字节): S50:0——63
S70:0——255
例如:读块4 数据
数据帧:
在这里插入图片描述
从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 16
数据信息(Info): 块数据(16 字节)
例如:
数据帧:
在这里插入图片描述
说明
在验证成功之后,才能读相应的块数据,所验证的块号与读块号必须在同一个扇区内,Mifare1 卡从块号0 开始按顺序每4 个块1 个扇区。
若要对一张卡中的多个扇区进行操作,在对某一扇区操作完毕后,必须进行一条读命令才能对另一个扇区直接进行验证命令,否则必须从请求开始操作。

[7]写(Cmd = H)

声明: INT8U PiccWrite(INT8U Block,INT8U *Bfr);
主机命令:
命令类型(CmdType): 0x02
命令 (Command): ‘H’
数据长度(Length): 17
数据信息(Info): 卡块号(1 字节): S50:0~63
S70:0~255
数据(16 字节)
例如:写块4 数据
数据帧
在这里插入图片描述

从机应答
状态 (Status): 0——成功,其它——失败
数据长度(Length): 0
数据信息(Info): none
例如:
数据帧:
在这里插入图片描述
说明
对卡内某一块进行验证成功后,即可对同一扇区的各个进行写操作(只要访问条件允许),其中包括位于扇区尾的密码块,这是更改密码的唯一方法。
在这里插入图片描述

源码编写【串口通信存在于所有编程非常重要】

main.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <strings.h>
#include <linux/input.h>
#include "rfid.h"
int open_serial();

int main(void)
{
	int ret, i;
	
	//打开串口文件
	int fd = open_serial();
	
	/*初始化串口*/
	init_tty(fd);
	
	timeout.tv_sec = 1;
	timeout.tv_usec = 0;
	
	while(1)
	{	
		//发送A命令
		ret = PiccRequest(fd);
		if(ret == -1)  //若是请求超时退出,必须要关闭串口后,重新打开才能再次读取数据
		{
			usleep(500000);
			close(fd);
			
			//打开串口文件
			fd = open_serial();
			/*初始化串口*/
			init_tty(fd);
			timeout.tv_sec = 1;
			timeout.tv_usec = 0;
			continue;
		}	
		
		else//(ret == 0)
		{
			printf("ok!\n");
		}
		//发送B命令 和获取卡号
		ret = PiccAnticoll(fd);
		
		//若获取的cardid为0,或获取id超时,则需重新发送'A'命令
		if(cardid == 0 || ret == -1) continue; 
		else if(ret == 0)
		{
			printf("card ID = %x\n", cardid);  //打印cardid号
			usleep(500000);
			//break;
		}
	}
	close(fd);
	return 0;
}

int open_serial()
{
	int fd;

	fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NONBLOCK);
	if (fd < 0)
	{
		printf("Open /dev/ttySAC1 fail!\n");
		return -1;
	}
	return fd;
}



rfid.h

#ifndef __RFID_H__
#define __RFID_H__

#include <stdio.h>
#include <fcntl.h> 
#include <unistd.h>
#include <termios.h> 
#include <sys/types.h>
#include <sys/select.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#define DEV_PATH   "/dev/ttySAC2"
volatile unsigned int cardid ;
// unsigned int buf[6] ;
struct timeval timeout;

void init_tty(int fd);
unsigned char CalBCC(unsigned char *buf, int n);
int PiccRequest(int fd);
int PiccAnticoll(int fd);


#endif

4rfid_test.c

/************************************************
*  * note: Smart Home RFID test program
*  * date: 2012-07-14
************************************************/
#include "rfid.h"



/* 设置窗口参数:9600速率 */
void init_tty(int fd)
{    
	//声明设置串口的结构体
	struct termios termios_new;
	
	//先清空该结构体
	bzero( &termios_new, sizeof(termios_new));
	
	//	cfmakeraw()设置终端属性,就是设置termios结构中的各个参数。
	cfmakeraw(&termios_new);
	
	//设置波特率
	//termios_new.c_cflag=(B9600);
	cfsetispeed(&termios_new, B9600);
	cfsetospeed(&termios_new, B9600);
	
	//CLOCAL和CREAD分别用于本地连接和接受使能,因此,首先要通过位掩码的方式激活这两个选项。    
	termios_new.c_cflag |= CLOCAL | CREAD;
	
	//通过掩码设置数据位为8位
	termios_new.c_cflag &= ~CSIZE;
	termios_new.c_cflag |= CS8; 
	
	//设置无奇偶校验
	termios_new.c_cflag &= ~PARENB;
	
	//一位停止位
	termios_new.c_cflag &= ~CSTOPB;
	//tcflush(fd,TCIFLUSH);
	
	// 可设置接收字符和等待时间,无特殊要求可以将其设置为0
	termios_new.c_cc[VTIME] = 10;
	termios_new.c_cc[VMIN] = 1;
	
	// 用于清空输入/输出缓冲区
	tcflush (fd, TCIFLUSH);
	
	//完成配置后,可以使用以下函数激活串口设置
	tcsetattr(fd,TCSANOW,&termios_new);

}


/*计算校验和*/
unsigned char CalBCC(unsigned char *buf, int n)
{
	int i;
	unsigned char bcc=0;
	for(i = 0; i < n; i++)
	{
		bcc ^= *(buf+i);
	}
	return (~bcc);
}

//发送A命令
int PiccRequest(int fd)
{
	unsigned char WBuf[128], RBuf[128];
	int  ret, i,len=0;
	fd_set rdfd;
	
	memset(WBuf, 0, 128);  //数组清空
	memset(RBuf,0,128);
	
	
	WBuf[0] = 0x07;	//帧长= 7 Byte
	WBuf[1] = 0x02;	//包号= 0 , 命令类型
	WBuf[2] = 0x41;	//命令= 'A'
	WBuf[3] = 0x01;	//信息长度
	WBuf[4] = 0x52;	//请求模式:  ALL=0x52
	WBuf[5] = CalBCC(WBuf, WBuf[0]-2);		//校验和
	WBuf[6] = 0x03; 	//结束标志
	
	while(1)
	{
		FD_ZERO(&rdfd);
		FD_SET(fd,&rdfd);
		tcflush (fd, TCIFLUSH);  
		
		write(fd, WBuf, 7);
		
	
		ret = select(fd + 1,&rdfd, NULL,NULL,&timeout);  //监测文件描述符的变化
		switch(ret)
		{
			case -1:
				perror("select error\n");
				break;
			case  0:
				len++;   //3次请求超时后,退出该函数
				if(len == 3)
				{
					len=0;
					return -1;
				}
				
				//printf("Request timed out.\n");
				break;
			default:
				usleep(10000);
				ret = read(fd, RBuf, 8);
				
				if(ret < 0)
				{
					printf("len = %d, %m\n", ret, errno);
					break;
				}
				
				//printf("RBuf[2]:%x\n", RBuf[2]);
				if(RBuf[2] == 0x00)	 	//应答帧状态部分为0 则请求成功
				{
					return 0;
				}
				break;
		}
		
		usleep(100000);
		
	}
	
	return -1;
}


/*防碰撞,获取范围内最大ID*/
int PiccAnticoll(int fd)
{
	//printf("fd = %d\n", fd);

	unsigned char WBuf[128], RBuf[128];
	int ret=1, i,len=0;
	fd_set rdfd;
	
	memset(WBuf, 0, 128);
	memset(RBuf,1,128);
	
	WBuf[0] = 0x08;	//帧长= 8 Byte
	WBuf[1] = 0x02;	//包号= 0 , 命令类型= 0x01
	WBuf[2] = 0x42;	//命令= 'B'
	WBuf[3] = 0x02;	//信息长度= 2
	WBuf[4] = 0x93;	//防碰撞0x93
	WBuf[5] = 0x00;	//位计数0
	WBuf[6] = CalBCC(WBuf, WBuf[0]-2);		//校验和
	WBuf[7] = 0x03; 	//结束标志
	
	 
	while(1)
	{
		
		tcflush (fd, TCIFLUSH);
		FD_ZERO(&rdfd);
		FD_SET(fd,&rdfd);
		
		write(fd, WBuf, 8);
		
		ret = select(fd + 1,&rdfd, NULL,NULL,&timeout);
		switch(ret)
		{
			case -1:
				perror("select error\n");
				break;
			case  0:
				len++;
				if(len == 10)
				{
					len=0;
					return -1;
				}
				perror("Timeout:");
				break;
				
			default:
				usleep(10000);
				ret = read(fd, RBuf, 10);
				if (ret < 0)
				{
					printf("ret = %d\n", ret);
					break;
				}
				if (RBuf[2] == 0x00) //应答帧状态部分为0 则获取ID 成功
				{
					cardid = (RBuf[4]<<24) | (RBuf[5]<<16) | (RBuf[6]<<8) | RBuf[7];
					
					return 0;
				}
		}

	}
	
	return -1;
}

在这里插入图片描述