1.基本知识介绍

        MFRC522是一个高集成的非接触式读写卡芯片,此发送模块利用调制和解调的原理,将他们都集成到各种非接触式通信方法和协议中。可以有不用的通信协议进行通信,如SPI,IIC,UART。里面有各种寄存器,详细见下图:

该图取自RC522数据手册,可见该模块芯片里面有着非常多不同功能的寄存器,我们采用通信接口和对应的通信协议进行通信,来对这些寄存器中的每一个位或者某些位进行修改来实现功能,在命令寄存器中我们还需要写入一些命令来实现相应的功能,常用的命令如下表:

我在实现通信时,采用的是SPI通信接口,所以对SPI接口进行说明,首先我们来了解一下RC522用SPI进行通信时的硬件引脚:

包括了时钟线,主机输出从机输入,主机输入从机输出,片选引脚,在进行于主机通信时引脚一定要连接正确。

下面我们来看看主机与RC522进行通信的字节序,首先看看读数据:

接着看看写数据:

可见在写数据时,MISO引脚是不参与数据交互的,有了这个字节顺序后我们就可以用SPI接口来实现跟RC522的通信啦。

2.软件程序功能的实现

               首先我们要实现通信就要实现底层的通信程序,下面就是用软件来实现SPI通信的底层程序:

#include "stm32f10x.h"                  // Device header
#include "SPI.h"


//SPI初始化——软件SPI
void SPI_init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB,ENABLE);//开启APB2总线上的GPIOA和GPIOB

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;                      //W25Q64片选引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_8;        //SPI时钟线    SPIDO
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;                      //RC522片选引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;                      //SPI DI引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}

//SPI开始时序
//mode 0:片选为W25Q64
//mode 1:片选为RC522
void SPI_Cs_Start(uint8_t mode)
{
	if(mode == 0)
	{
		SPI_W25Q64_Cs(0);
	}
	else if(mode == 1)
	{
		SPI_RC522_Cs(0);
	}
}

//SPI停止时序
//mode 0:片选为W25Q64
//mode 1:片选为RC522
void SPI_Cs_Stop(uint8_t mode)
{
	if(mode == 0)
	{
		SPI_W25Q64_Cs(1);
	}
	else if(mode == 1)
	{
		SPI_RC522_Cs(1);
	}		
}

//SPI交换一个字节数据时序
uint8_t SPI_SwapByte(uint8_t byte)
{
	uint8_t temp = 0x00,i;
	for(i = 0;i < 8;i++)
	{
//		SPI_DO(byte & (0x80>>i));
		if(byte & (0x80>>i)){SPI_DO(1);}
		else{SPI_DO(0);}
		SPI_SCK(1);
		if(SPI_DI==1){temp |= (0x80>>i);}
		SPI_SCK(0);
	}
	return temp;
}

包括了SPI的初始化,以及开始,停止和数据的发送和接收程序。有了这个底层代码之后我们就可以来编写RC522的功能函数啦:

//在指定地址写入一个字节数据
void RC522_WriteByte(uint8_t address,uint8_t data)
{
	address = (address<<1)&0x7E;
	SPI_Cs_Start(1);
	SPI_SwapByte(address);
	SPI_SwapByte(data);
	SPI_Cs_Stop(1);
}

//在指定地址读出一个字节数据
uint8_t RC522_ReadByte(uint8_t address)
{
	uint8_t temp;
	address = ((address<<1)&0x7E) | 0x80;
	SPI_Cs_Start(1);
	SPI_SwapByte(address);
	temp = SPI_SwapByte(0xFF);
	SPI_Cs_Stop(1);
	return temp;
}

//RC522复位
void RC522_Reset(void)
{
	RC522_WriteByte(RC522_REG_COMMAND,RC522_PCD_RESETPHASE);
	Delay_ms(1);
	while(RC522_ReadByte(RC522_REG_COMMAND)&0x10);
}

//设置寄存器的值,通过将寄存器中某一位置1,来实现该寄存器下的功能  mask:位数据掩码
void RC522_SetRegBit(uint8_t address,uint8_t mask)
{
	uint8_t temp;
	temp = RC522_ReadByte(address);
	RC522_WriteByte(address,temp|mask);
}

//设置寄存器的值,通过将某寄存器中的某一位置0,来实现该寄存器下的功能
void RC522_ResetRegBit(uint8_t address,uint8_t mask)
{
	uint8_t temp;
	temp = RC522_ReadByte(address);
	RC522_WriteByte(address,temp& ~mask);
}

//RC522初始化设置
void RC522_Init(void)
{
	//硬件设置
	SPI_init();
	//复位
	RC522_Reset();
	//定时器设置
	RC522_WriteByte(RC522_REG_T_MODE, 0x8D);
	RC522_WriteByte(RC522_REG_T_PRESCALER, 0x3E);
	RC522_WriteByte(RC522_REG_T_RELOAD_H, 0x00);
	RC522_WriteByte(RC522_REG_T_RELOAD_L, 0x1E);
	//传输设置
	RC522_WriteByte(RC522_REG_TX_AUTO, 0x40);
	RC522_WriteByte(RC522_REG_MODE, 0x3D);
	//天线开启
	RC522_SetRegBit(RC522_REG_TX_CONTROL, 0x03);
}

//卡通信数据交流函数
//command    RC522_PCD_TRANSCEIVE: 发送并接收        RC522_PCD_AUTHENT: 认证
//sendData: 要发送的数据缓冲区  sendLen: 要发送的数据长度  
//backData: 接收数据的缓冲区  backLen: 接收到的数据长度
//返回值:状态
uint8_t RC522_ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen,
                     uint8_t *backData, uint16_t *backLen)
{
		uint8_t status = RC522_ERROR;
		int8_t irqEn = 0x00;                                // 中断使能寄存器值
		uint8_t waitIRq = 0x00;                             // 等待的中断标志
		uint8_t lastBits;                                   // 最后接收到的位数
		uint8_t n;                                          // 临时变量
		uint16_t i;                                         // 循环计数器
	
	//根据命令设置中断使能和等待的中断标志
    switch (command)
    {
    case RC522_PCD_AUTHENT:  // 认证命令
        irqEn = 0x12;        // 使能错误中断(EIE)和空闲中断(IDLEIE)
        waitIRq = 0x10;      // 等待空闲中断(IDLEIRq)
        break;
    case RC522_PCD_TRANSCEIVE: // 发送并接收命令
        irqEn = 0x77;        // 使能定时器中断、错误中断、空闲中断、接收中断、发送中断
        waitIRq = 0x30;      // 等待接收中断(RxIRq)或空闲中断(IDLEIRq)
        break;
    default:
        break;
    }
		
		//中断寄存器使能
		RC522_WriteByte(RC522_REG_COMIEN,irqEn | 0x80);     //允许中断请求
		RC522_ResetRegBit(RC522_REG_COMIRQ,0x80);             //清除所有中断请求位为后面的中断做准备
		
		//FIFO缓冲区初始化
		RC522_SetRegBit(RC522_REG_FIFO_LEVEL, 0x80);
		
		//取消所有正在执行的命令
		RC522_WriteByte(RC522_REG_COMMAND, RC522_PCD_IDLE);
		
		//将要发送的数据写入FIFO
		for(i = 0;i<sendLen;i++)
		{
			RC522_WriteByte(RC522_REG_FIFO_DATA, sendData[i]);
		}
		
		//执行命令
		RC522_WriteByte(RC522_REG_COMMAND, command);
		
		//判断命令,如果是发送命令则启动发送
		if(command == RC522_PCD_TRANSCEIVE)
		{
			RC522_SetRegBit(RC522_REG_BIT_FRAMING, 0x80);
		}
		
		//等待命令完成或超时
		i=5000;
		do
		{
			n = RC522_ReadByte(RC522_REG_COMIRQ);
			i--;
		}
		while((i!=0) && !(n&0x01) && !(n&waitIRq));//i计数超时,定时器中断产生,其他中断产生都退出循环
		
		//停止发送
		RC522_ResetRegBit(RC522_REG_BIT_FRAMING, 0x80);
		
		if(i != 0)//超时判断
		{
			if(!(RC522_ReadByte(RC522_REG_ERROR) & 0x1B))//错误判断
			{
				status = RC522_OK;
				if(n & waitIRq & 0x01)//无卡判断
				{
					status = RC522_NO_CARD;
				}
				if(command == RC522_PCD_TRANSCEIVE)//发送接收命令,处理接收到的数据
				{
					n  = RC522_ReadByte(RC522_REG_FIFO_LEVEL);            //获取FIFO中的数据位数
					lastBits = RC522_ReadByte(RC522_REG_CONTROL)&0x07;    //获取最后接收的数据位
					if(lastBits)//计算接收到的数据位数
					{
						*backLen = (n - 1)*8 + lastBits;
					}
					else
					{
						*backLen = n*8;
					}
					//确保数据位数是有效的
					if(n == 0)
					{
						n=1;
					}
					if(n > RC522_MAX_LEN)
					{
						n = RC522_MAX_LEN;
					}
					for(i=0;i<n;i++)
					{
						backData[i] = RC522_ReadByte(RC522_REG_FIFO_DATA);
					}
				}
			}
			else
			{
				status = RC522_ERROR;
			}
		}
		else
		{
			status = RC522_TIMEOUT;
		}
		return status;
}

//功能:寻卡
//req_mode: 寻卡模式  RC522_PCD_REQIDL: 寻未休眠卡  RC522_PCD_REQALL: 寻所有卡
// TagType: 返回卡片类型
//0x4400 = Mifare_UltraLight
//0x0400 = Mifare_One(S50)
//0x0200 = Mifare_One(S70)
//0x0800 = Mifare_Pro(X)
//0x4403 = Mifare_DESFire
//返回值:状态
uint8_t RC522_Request(uint8_t req_mode ,uint8_t* TagType)
{
	uint8_t status;
	uint16_t backBits;   //返回位数用于判断
	
	RC522_WriteByte(RC522_REG_BIT_FRAMING,0x07);//配置
	
	TagType[0] = req_mode;
	
	status =RC522_ToCard(RC522_PCD_TRANSCEIVE,TagType,1,TagType,&backBits);
	if((status != RC522_OK) || (backBits != 0x10))
	{
		status = RC522_ERROR;
	}
	return status;
}

//功能:防冲撞,读卡的序列号
//serNum:返回卡的序列号(4个字节)
//返回值:状态
uint8_t RC522_Anticoll(uint8_t* serNum)
{
	uint8_t status,i,serNumCheck = 0;
	uint16_t unLen;
	
	RC522_WriteByte(RC522_REG_BIT_FRAMING,0x00);//配置TxLastBits=0
	
	serNum[0] = RC522_PCD_ANTICOLL;
	serNum[1] = 0x20;                 //0x20:用于构成完整的命令帧,设置了防冲撞等级,指示了卡的序列号为4个字节,启用了CRC校验和计算
	
	status =RC522_ToCard(RC522_PCD_TRANSCEIVE,serNum,2,serNum,&unLen);
	
	if(status == RC522_OK)
	{
		for(i=0;i<4;i++)
		{
			serNumCheck ^= serNum[i];
		}
		if(serNumCheck != serNum[i])
		{
			status = RC522_ERROR;
		}
	}
	return status;
}


//选卡
//serNUM:卡的序列号(4个字节)
//返回值:卡的容量(0x08为1K,0x18为4K)
uint8_t RC522_SelectTag(uint8_t* serNum)
{
	uint8_t i,status,size;
	uint16_t RecvBits;
	uint8_t buffer[9];
	
	buffer[0] = RC522_PCD_SELECTTAG;
	buffer[1] = 0x70;                     //用于构成完整的数据帧,指定UID(卡片序列号)长度和启用CRC检验和计算
	
	for(i=0;i<5;i++)
	{
		buffer[i+2] = serNum[i];
	}
	
	RC522_CaluCRC(buffer, 7, &buffer[7]); // 计算CRC
	
	status = RC522_ToCard(RC522_PCD_TRANSCEIVE,buffer,9,buffer,&RecvBits);
	if((status == RC522_OK) && (RecvBits == 0x18))
	{
		size = buffer[0];
	}
	else
	{
		size = 0;
	}
	return size;
}

//计算CRC
//pIndata,输入数据   len,数据长度   pOutdata,输出数据,计算出的值
void RC522_CaluCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
	uint8_t i,n;
	
	RC522_ResetRegBit(RC522_REG_DIVIRQ, 0x04);
	RC522_SetRegBit(RC522_REG_FIFO_LEVEL, 0x80);
	
	for(i=0;i<len;i++)
	{
		RC522_WriteByte(RC522_REG_FIFO_DATA, pIndata[i]);
	}
	
	RC522_WriteByte(RC522_REG_COMMAND, RC522_PCD_CALCCRC);
	
	i=0xFF;
	do
	{
		n = RC522_ReadByte(RC522_REG_DIVIRQ);
		i--;
	}
	while((i!=0) && !(n&0x04));
	
	pOutData[0] = RC522_ReadByte(RC522_REG_CRC_RESULT_L);
    pOutData[1] = RC522_ReadByte(RC522_REG_CRC_RESULT_H);
	
}

我们通过实现在指定地址读写一个字节的数据和将寄存器中每一位置1和设为0的功能函数实现,来进行对一些卡操作层面上的功能进行实现,如卡初始化,寻卡,防冲撞等功能。并且我们将上面RC522的全部寄存器地址和一些常用的命令以及刷卡的状态进行了宏定义,方便我们在程序当中更好的理解,下面给出头文件中的宏定义。

#define RC522_MAX_LEN 64  // FIFO缓冲区最大长度
/* RC522寄存器定义 */
// 命令寄存器
#define RC522_PCD_IDLE              0x00    // 取消当前命令
#define RC522_PCD_AUTHENT           0x0E    // 验证密钥
#define RC522_PCD_RECEIVE           0x08    // 接收模式
#define RC522_PCD_TRANSMIT          0x04    // 发送模式
#define RC522_PCD_TRANSCEIVE        0x0C    // 发送并接收模式
#define RC522_PCD_RESETPHASE        0x0F    // 复位
#define RC522_PCD_CALCCRC           0x03    // CRC计算

/* RC522命令集 */
#define RC522_PCD_REQIDL           0x26    // 寻天线区内未进入休眠状态的卡
#define RC522_PCD_REQALL           0x52    // 寻天线区内全部卡
#define RC522_PCD_ANTICOLL         0x93    // 防冲撞
#define RC522_PCD_SELECTTAG        0x93    // 选卡
#define RC522_PCD_AUTHENT1A        0x60    // 验证A密钥
#define RC522_PCD_AUTHENT1B        0x61    // 验证B密钥
#define RC522_PCD_READ             0x30    // 读块
#define RC522_PCD_WRITE            0xA0    // 写块
#define RC522_PCD_DECREMENT        0xC0    // 扣款
#define RC522_PCD_INCREMENT        0xC1    // 充值
#define RC522_PCD_RESTORE          0xC2    // 调块数据到缓冲区
#define RC522_PCD_TRANSFER         0xB0    // 保存缓冲区数据
#define RC522_PCD_HALT             0x50    // 休眠

/* RC522寄存器地址 */
// Page 0:命令和状态
#define RC522_REG_COMMAND          0x01    //启动和停止命令执行
#define RC522_REG_COMIEN           0x02    //中断请求使能控制位
#define RC522_REG_DIVIEN           0x03
#define RC522_REG_COMIRQ           0x04    //中断请求位
#define RC522_REG_DIVIRQ           0x05    //包含中断请求标志
#define RC522_REG_ERROR            0x06    //错误标志寄存器
#define RC522_REG_STATUS1          0x07
#define RC522_REG_STATUS2          0x08
#define RC522_REG_FIFO_DATA        0x09    //64字节缓冲区的输入和输出
#define RC522_REG_FIFO_LEVEL       0x0A    //指示缓冲区中保存的字节数
#define RC522_REG_WATER_LEVEL      0x0B
#define RC522_REG_CONTROL          0x0C    //包含不同的控制位,第三位显示最后接收到的有效数据位数
#define RC522_REG_BIT_FRAMING      0x0D    //面向位的帧调节
#define RC522_REG_COLL             0x0E

// Page 1:命令
#define RC522_REG_MODE             0x11
#define RC522_REG_TX_MODE          0x12
#define RC522_REG_RX_MODE          0x13
#define RC522_REG_TX_CONTROL       0x14
#define RC522_REG_TX_AUTO          0x15
#define RC522_REG_TX_SEL           0x16
#define RC522_REG_RX_SEL           0x17
#define RC522_REG_RX_THRESHOLD     0x18
#define RC522_REG_DEMOD            0x19
#define RC522_REG_MF_TX            0x1C
#define RC522_REG_MF_RX            0x1D
#define RC522_REG_SERIAL_SPEED     0x1F

// Page 2:CFG
#define RC522_REG_CRC_RESULT_H     0x21    //显示CRC高字节的值
#define RC522_REG_CRC_RESULT_L     0x22    //显示CRC低字节的值
#define RC522_REG_MOD_WIDTH        0x24
#define RC522_REG_T_MODE           0x2A
#define RC522_REG_T_PRESCALER      0x2B
#define RC522_REG_T_RELOAD_H       0x2C
#define RC522_REG_T_RELOAD_L       0x2D
#define RC522_REG_T_COUNTER_H      0x2E
#define RC522_REG_T_COUNTER_L      0x2F

// Page 3:测试寄存器
#define RC522_REG_TEST_SEL1        0x31
#define RC522_REG_TEST_SEL2        0x32
#define RC522_REG_TEST_PIN_EN      0x33
#define RC522_REG_TEST_PIN_VALUE   0x34
#define RC522_REG_TEST_BUS         0x35
#define RC522_REG_AUTO_TEST        0x36
#define RC522_REG_VERSION          0x37
#define RC522_REG_ANALOG_TEST      0x38
#define RC522_REG_TEST_ADC1        0x39
#define RC522_REG_TEST_ADC2        0x3A
#define RC522_REG_TEST_ADC0        0x3B

/* RC522错误代码 */
#define RC522_OK                   0       // 操作成功
#define RC522_ERROR                1       // 操作失败
#define RC522_COLLISION            2       // 检测到冲突
#define RC522_TIMEOUT              3       // 操作超时
#define RC522_NO_CARD              4       // 没有检测到卡
#define RC522_AUTH_FAIL            5       // 认证失败

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐