一篇文章教会你RFID射频IC卡感应模块(MFRC-522)读取卡号及数据,附STM32代码示例

目录

一、MFRC-522介绍:

(1)NFC与RFID介绍:

(2)特性:

(3)框图:

二、S50非接触式IC卡: 

(1) 主要指标

(2)存储结构:

(3)工作原理:

(4)M1射频卡与读写器的通讯:

(5)S50中文资料下载:

三、代码编写:

(1)第一部分:引脚初始化

(2)第二部分:软件模拟SPI与MFRC522通信

软件模拟SPI通信:

软件模拟SPI接收一个字节数据,高位先行:

使用软件模拟SPI与MFRC522通信:

向MFRC522指定寄存器中写入指定的数据:

置位MFRC522指定寄存器的指定位:

清位RC522指定寄存器的指定位:

(3)第三部分:STM32对MFRC522的基础通信

开启天线:

关闭天线:

复位MFRC522 :

设置MFRC522的工作方式:

(4)第四部分:STM32控制MFRC522与M1卡的通信

通过MFRC522和M1卡通讯(数据的双向传输):

寻卡:

防冲突:

用MFRC522计算CRC16(循环冗余校验):

选定卡片:

 校验卡片密码:

 在M1卡的指定块地址写入指定数据:

读取M1卡的指定块地址的数据:

让卡片进入休眠模式:

四、程序演示:

(1)主函数:

(2)效果演示:

五、代码下载:


一、MFRC-522介绍:

MFRC522是一款高度集成的非接触式(13.56MHz)读写卡芯片,利用调制和解调的原理,支持ISO 14443A/MIFARE标准。它包含发送和接收模块,支持MIFARE Classic加密,最高传输速率可达424kbit/s。MFRC522支持SPI、串行UART和I2C接口,适用于多种主机接口功能。

MFRC522写入并读出数据

(1)NFC与RFID介绍:

近场通信(NFC)是一种短距离高频无线通信技术,允许电子设备在大约十厘米的距离内进行非接触式点对点的数据交换。这项技术由免接触式射频识别(RFID)技术演变而来,并且与RFID兼容。NFC最初由索尼(Sony)和飞利浦(Philips)公司开发,主要用于手机等手持设备,以实现机器对机器(M2M)的通信。

NFC技术因其固有的安全性而在手机支付等应用领域展现出巨大的潜力。与其他无线通信技术相比,NFC提供了更好的安全性,这使得中国物联网校企联盟将其比喻为机器之间的“安全对话”。这种技术的应用前景广阔,尤其是在需要安全数据传输和便捷交互的场景中。

(2)特性:

  • 高度集成的模拟电路,解调和译码响应。

  • 缓冲的输出驱动器与天线连接使用最少的外部元件。

  • 支持ISO 14443A/MIFARE,通信距离高达50mm,取决于天线长度和调谐。

  • 读写器模式下支持MIFARE® Classic加密。

  • 支持ISO 14443 212kbit/s和424kbit/s的更高传输速率通信。

  • 支持的主机接口包括:

    • 10Mbit/s的SPI接口。

    • I2C接口,快速模式速率为400kbit/s,高速模式速率为3400kbit/s。

    • 串行UART,传输速率高达1228.8kbit/s,帧取决于RS232接口,电压电平取决于提供的管脚电压。

  • 64字节的发送和接收FIFO缓冲区。

  • 灵活的中断模式。

  • 低功耗的硬复位功能。

(3)框图:

 

二、S50非接触式IC卡: 

S50非接触式IC卡就是搭配RFID射频IC卡感应模块(MFRC-522)使用的那个钥匙扣和白色卡片。

S50卡是一种非接触式智能卡,通常被称为M1卡。 

(1) 主要指标

  • 容量为8K位(bits)=1K字节(bytes)EEPROM
  • 分为16个扇区,每个扇区为4块,每块16个字节,以块为存取单位
  • 每个扇区有独立的一组密码及访问控制
  • 每张卡有唯一序列号,为32位
  • 具有防冲突机制,支持多卡操作
  • 无电源,自带天线,内含加密控制逻辑和通讯逻辑电路
  • 数据保存期为10年,可改写10万次,读无限次
  • 工作温度:-20℃~50℃(湿度为90%)
  • 工作频率:13.56MHZ
  • 通信速率:106 KBPS
  • 读写距离:10 cm以内(与读写器有关)

(2)存储结构:

  • M1卡分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,(我们也将16个扇区的64个块按绝对地址编号为0~63,存贮结构如下图所示:

  • 第0扇区的块0(即绝对地址0块),它用于存放厂商代码,已经固化,不可更改。
  • 每个扇区的块0、块1、块2为数据块,可用于存贮数据。 
  • 数据块可作两种应用:
    • 用作一般的数据保存,可以进行读、写操作。
    • 用作数据值,可以进行初始化值、加值、减值、读值操作。 
  • 每个扇区的块3为控制块,包括了密码A、存取控制、密码B。具体结构如下:
  • 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位,定义如下:
  •  三个控制位以正和反两种形式存在于存取控制字节中,决定了该块的访问权限(如 
     进行减值操作必须验证KEY A,进行加值操作必须验证KEY B,等等)。三个控制
     位在存取控制字节中的位置,以块0为例:

  •  数据块(块0、块1、块2)的存取控制如下:
  •  例如:当块0的存取控制位C10 C20 C30=1 0 0时,验证密码A或密码B正确后可读;验证密码B正确后可写;不能进行加值、减值操作。
  •  控制块块3的存取控制与数据块(块0、1、2)不同,它的存取控制如下:

  • 例如:当块3的存取控制位C13 C23 C33=1 0 0时,表示:密码A:不可读,验证KEYA或KEYB正确后,可写(更改)。存取控制:验证KEYA或KEYB正确后,可读、可写。密码B:验证KEYA或KEYB正确后,可读、可写。

(3)工作原理:

  • 卡片的电气部分只由一个天线和ASIC组成。
  • 天线:卡片的天线是只有几组绕线的线圈,很适于封装到IS0卡片中。
  • ASIC:卡片的ASIC由一个高速(106KB波特率)的RF接口,一个控制单元和一个8K位EEPROM组成。
  • 工作原理:读写器向M1卡发一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下,LC谐振电路产生共振,从而使电容内有了电荷,在这个电容的另一端,接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到2V时,此电容可做为电源为其它电路提供工作电压,将卡内数据发射出去或接取读写器的数据。

(4)M1射频卡与读写器的通讯:

复位应答(Answer to request):

  • M1射频卡的通讯协议和通讯波特率是定义好的,当有卡片进入读写器的操作范围时,读写器以特定的协议与它通讯,从而确定该卡是否为M1射频卡,即验证卡片的卡型。

复位应答(Answer to request):

  • M1射频卡的通讯协议和通讯波特率是定义好的,当有卡片进入读写器的操作范围时,读写器以特定的协议与它通讯,从而确定该卡是否为M1射频卡,即验证卡片的卡型。

选择卡片(Select Tag):

  • 选择被选中的卡的序列号,并同时返回卡的容量代码。

三次互相确认(3 Pass Authentication):

  • 选定要处理的卡片之后,读写器就确定要访问的扇区号,并对该扇区密码进行密码校

验,在三次相互认证之后就可以通过加密流进行通讯。(在选择另一扇区时,则必须进行
另一扇区密码校验。)

 对数据块的操作:

  • 读 (Read):读一个块;
  • 写 (Write):写一个块;
  • 加(Increment):对数值块进行加值;
  • 减(Decrement):对数值块进行减值;
  • 存储(Restore):将块中的内容存到数据寄存器中;
  • 传输(Transfer):将数据寄存器中的内容写入块中;
  • 中止(Halt):将卡置于暂停工作状态;

(5)S50中文资料下载:

通过网盘分享的文件:S50
链接: https://pan.baidu.com/s/1vVRbD85NpKX6z4oIttrTEQ?pwd=xc27 提取码: xc27

三、代码编写:

(1)第一部分:引脚初始化

  • 初始化输出引脚:将CS、SCK、MOSI和RST引脚初始化为推挽输出模式,并设置为高电平。这些引脚用于控制MFRC522模块的片选、时钟、数据发送和复位。

  • 初始化输入引脚:将MISO引脚初始化为上拉输入模式,用于接收MFRC522模块发送的数据。

  • 确保引脚状态:将所有引脚置为高电平,确保在初始化后这些引脚处于非激活状态,避免意外触发。

// 初始化MFRC522引脚
void MFRC522_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;  					
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  						// 使能GPIOA时钟

    // 初始化输出引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_CS | GPIO_SCK | GPIO_MOSI | GPIO_RST;  	// 选择要初始化的引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  							// 设置为推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  							// 设置GPIO速度为50MHz
    GPIO_Init(MFRC522_PORT, &GPIO_InitStructure);  								// 初始化指定的GPIO端口

    // 初始化输入引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_MISO;  									// 选择MISO引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  								// 设置为上拉输入模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  							// 设置GPIO速度为50MHz
    GPIO_Init(MFRC522_PORT, &GPIO_InitStructure);  								// 初始化指定的GPIO端口

    // 将所有引脚置为高电平
    GPIO_SetBits(MFRC522_PORT, GPIO_CS | GPIO_SCK | GPIO_MOSI | GPIO_RST);  	// 将CS、SCK、MOSI、RST引脚置为高电平
}

(2)第二部分:软件模拟SPI与MFRC522通信

软件模拟SPI通信:

  • 软件模拟SPI发送一个字节数据,高位先行。

首先将字节的每一位依次发送到MOSI引脚上,同时通过SCK引脚生成时钟脉冲来同步数据传输。检查字节的最高位是否为1,如果是,则将MOSI引脚置为高电平;否则,将MOSI引脚置为低电平。然后,生成一个时钟脉冲,通过将SCK引脚从低电平置为高电平,使从设备在时钟脉冲的上升沿读取MOSI引脚上的数据。每发送一位后,字节左移一位,以便发送下一位。整个过程重复8次,直到字节的所有位都被发送完毕。

void MFRC522_SPI_SendByte(uint8_t byte)
{
    uint8_t n;  	
    for (n = 0; n < 8; n++)  			// 循环8次,因为一个字节有8位
    {
        if (byte & 0x80)  				// 检查当前字节的最高位(第7位)是否为1
            MFRC522_MOSI_1();  			// 如果最高位为1,则将MOSI引脚置为高电平
        else
            MFRC522_MOSI_0();  			// 如果最高位为0,则将MOSI引脚置为低电平
                
        Delay_us(200);  				// 生成200微秒的延迟,确保信号稳定
        MFRC522_SCK_0();  				// 将SCK引脚置为低电平
        Delay_us(200);  				
        MFRC522_SCK_1();  				// 将SCK引脚置为高电平,生成时钟脉冲的上升沿
        Delay_us(200); 					// 生成200微秒的延迟,确保信号稳定
										/*每个时钟脉冲的低电平期间,MOSI引脚被设置为当前位的值,
												而从设备在时钟脉冲的上升沿读取MOSI引脚上的数据。*/
        byte <<= 1;  					// 将字节左移一位,以便在下一次循环中发送下一位
    }
}
  • 软件模拟SPI接收一个字节数据,高位先行:

通过生成8个时钟脉冲,每次在时钟脉冲的上升沿读取MISO引脚上的数据位,并将其依次存储到data变量中。首先将data左移一位,然后将SCK引脚置为低电平,从设备准备发送数据。接着,函数读取MISO引脚的状态,如果MISO为高电平,则将data的最低位设置为1。之后,函数将SCK引脚置为高电平,生成时钟脉冲的上升沿,主设备在这一时刻读取MISO引脚上的数据位。每读取一位后,重复上述过程,直到8位数据全部读取完毕。最后,函数返回读取到的数据。

//软件模拟SPI读取一个字节数据,先读高位 
uint8_t MFRC522_SPI_ReadByte(void)
{
    uint8_t n, data=0;  
    for (n = 0; n < 8; n++)  				// 循环8次
    {
        data <<= 1;  						// 将data左移一位
        MFRC522_SCK_0();  					// 将SCK引脚置为低电平,准备生成时钟脉冲
											// 主设备将SCK引脚置为低电平,从设备准备发送数据
        Delay_us(200);  					// 确保信号稳定
        
        if (MFRC522_MISO_GET()== 1)  		// 读取MISO引脚的状态
            data |= 0x01;  					// 如果MISO为高电平,则将data的最低位设置为1
        
        Delay_us(200);  					
        MFRC522_SCK_1();  					// 将SCK引脚置为高电平,生成时钟脉冲的上升沿
											// 主设备读取MISO引脚上的数据位
        Delay_us(200);  					// 生成200微秒的延迟,确保信号稳定
    }
    return data;  							// 返回读取到的数据
}
  • 使用软件模拟SPI与MFRC522通信:

读取MFRC522指定寄存器的值。

软件模拟SPI协议读取MFRC522模块指定寄存器的值。首先将寄存器地址左移一位,对应要发送的地址字节格式,即位6-位1为寄存器地址,确保地址字节的最高位为1(读取寄存器)和最低位为0。然后,函数使能片选引脚(CS),通过SPI发送构建的命令地址,接着读取寄存器中的值,最后禁用片选引脚,结束通信。最终,函数返回读取到的寄存器值。

uint8_t MFRC522_Read_Register(uint8_t Address)
{
    uint8_t data, Addr; 
 
    // (Address << 1) 将地址左移一位
    // 0x7E 用于确保地址的最高位为0,最低位为0,其余位保持不变
    // 0x80 用于设置读操作标志位(最高位为1表示读操作)
	// 发送的第一个字节(地址字节)定义了操作模式(读或写)和寄存器地址。
	// 在读操作中,MSB位为1表示从MFRC522读出数据。
	// 地址字节的位6-1定义了寄存器地址,最后一位(位0)应设置为0。
    Addr = ((Address << 1) & 0x7E) | 0x80;	// 最高位置1读取数据,最高位置0写入数据,最后一位确保置0

    MFRC522_CS_Enable();					// 使能片选,选中MFRC522
    MFRC522_SPI_SendByte(Addr);				// 发送构建的命令地址

	
    data = MFRC522_SPI_ReadByte();			// 读取寄存器中的值
    MFRC522_CS_Disable();					// 禁用片选,取消选中MFRC522

    
    return data;							// 返回读取到的寄存器值
}
  • 向MFRC522指定寄存器中写入指定的数据:

首先将寄存器地址左移一位,对应要发送的地址字节格式,即位6-位1为寄存器地址,确保地址字节的最高位为0(读取寄存器)和最低位为0。然后,函数使能片选引脚(CS),通过SPI发送构建的命令地址,接着发送要写入的数据,最后禁用片选引脚,结束通信。这样,指定寄存器就被写入了新的数据。

//向MFRC522指定寄存器中写入指定的数据
void MFRC522_Write_Register(uint8_t Address, uint8_t data)
{
    uint8_t Addr;  

    // 构建写操作地址字节
    // (Address << 1) 将地址左移一位,确保位0为0
    // 0x7E 用于确保地址的最高位为0,最低位为0,其余位保持不变
    Addr = (Address << 1) & 0x7E;

    MFRC522_CS_Enable();				// 使能片选,选中MFRC522
    MFRC522_SPI_SendByte(Addr);			// 发送构建的命令地址

	
    MFRC522_SPI_SendByte(data);			// 发送要写入的数据
    MFRC522_CS_Disable();				// 禁用片选,取消选中MFRC522
}
  • 置位MFRC522指定寄存器的指定位:

首先读取指定寄存器的当前值,然后使用按位或操作(|)将指定的位设置为1,最后将修改后的值写回寄存器。

//置位MFRC522指定寄存器的指定位
void MFRC522_SetBit_Register(uint8_t Address, uint8_t mask)
{
    uint8_t temp;  

    //获取寄存器当前值 
    temp = MFRC522_Read_Register(Address);  		// 读取指定寄存器的当前值

    //对指定位进行置位操作后,再将值写入寄存器 
    MFRC522_Write_Register(Address, temp | mask);  	// 使用按位或操作置位,然后写回寄存器
}
  • 清位RC522指定寄存器的指定位:

首先读取指定寄存器的当前值,然后使用按位与操作(&)和按位取反操作(~)将指定的位设置为0,最后将修改后的值写回寄存器。

使用按位取反操作符 ~,将 mask 中为1的位变为0,为0的位变为1。这样,mask 中为1的位在取反后会用于清零 temp 中的对应位。

//清位RC522指定寄存器的指定位
void MFRC522_ClearBit_Register( uint8_t Address, uint8_t mask )
{
        uint8_t temp;
        /* 获取寄存器当前值 */
        temp = MFRC522_Read_Register( Address );            // 读取指定寄存器的当前值
        /* 对指定位进行清位操作后,再将值写入寄存器 */
        MFRC522_Write_Register( Address, temp&(~mask) );	// 使用按位与操作清位,然后写回寄存器
}

(3)第三部分:STM32对MFRC522的基础通信

  • 开启天线:

首先读取 TxControlReg 寄存器的当前值,然后检查天线是否已经开启。如果天线未开启(即 TxControlReg 寄存器的低两位 0x03 为0),则通过 MFRC522_SetBit_Register 函数将这两位设置为1,从而开启天线。

//开启天线
void MFRC522_Antenna_On(void)
{
        uint8_t k;
        k = MFRC522_Read_Register(TxControlReg);
        /* 判断天线是否开启 */
        if(!(k&0x03 ))
                MFRC522_SetBit_Register(TxControlReg,0x03);
}
  • 关闭天线:

通过 MFRC522_ClearBit_Register 函数将 TxControlReg 寄存器的低两位清零,从而关闭天线。

// 关闭天线
void MFRC522_Antenna_Off(void)
{
        /* 直接对相应位清零 */
        MFRC522_ClearBit_Register(TxControlReg,0x03);
}
  • 复位MFRC522 :

// PAGE 0: 命令及状态
#define     CommandReg            0x01    //启动和停止命令的执行

首先通过将RST引脚置高,拉低,置高,确保MFRC522模块正确复位。接着,发送复位命令 0x0FCommandReg 寄存器,并通过 while 循环等待 CommandReg 寄存器的第4位(0x10)为0,表示复位完成。之后,进行寄存器的初始化设置,包括设置发送和接收常用模式(ModeReg 寄存器为 0x3D)、16位定时器低位(TReloadRegL 寄存器为 30)、16位定时器高位(TReloadRegH 寄存器为 0)、内部定时器设置(TModeReg 寄存器为 0x8D)、定时器分频系数(TPrescalerReg 寄存器为 0x3E)以及调制发送信号为100% ASK(TxAutoReg 寄存器为 0x40),确保MFRC522模块在每次复位操作后都能恢复到初始状态,准备好进行后续的通信操作。

// 复位MFRC522
void MFRC522_Rese(void)
{
        
		MFRC522_Reset_Enable();				// 置高RST
        Delay_us (1);						
        MFRC522_Reset_Disable();			// 拉低RST
        Delay_us (1);
		MFRC522_Reset_Enable();				// 置高RST
        Delay_us (1);
        MFRC522_Write_Register(CommandReg, 0x0F);
        while( MFRC522_Read_Register(CommandReg)&0x10);    //开启接收部分电路

        /* 缓冲一下 */
        Delay_us (1);
        MFRC522_Write_Register(ModeReg,0x3D);       //定义发送和接收常用模式
        MFRC522_Write_Register(TReloadRegL,30);     //16位定时器低位
        MFRC522_Write_Register(TReloadRegH,0);      //16位定时器高位
        MFRC522_Write_Register(TModeReg,0x8D);      //内部定时器的设置
        MFRC522_Write_Register(TPrescalerReg,0x3E); //设置定时器分频系数
        MFRC522_Write_Register(TxAutoReg,0x40);     //调制发送信号为100%ASK
}
  • 相关寄存器操作信息:

一篇文章教会你RFID射频IC卡感应模块(MFRC-522)使用文档,附中英文文档下载

  • 设置MFRC522的工作方式:

根据传入的参数 Type 配置MFRC522模块的工作方式。如果 Type 为 'A',则进行寄存器的初始化设置,包括清除接收器和发送器的状态标志、定义发送和接收的常用模式、选择内部接收器设置、配置接收器增益、设置16位定时器重装值、定义内部定时器的设置,并打开天线。

//设置MFRC522的工作方式
void RC522_Config_Type(char Type)
{
        if(Type=='A')
        {
                MFRC522_ClearBit_Register(Status2Reg,0x08);     //接收器和发送器的状态标志
                MFRC522_Write_Register(ModeReg,0x3D);		    //定义发送和接收的常用模式
                MFRC522_Write_Register(RxSelReg,0x86);			//选择内部的接收器设置
                MFRC522_Write_Register(RFCfgReg,0x7F);			//配置接收器增益
                MFRC522_Write_Register(TReloadRegL,30);			//描述16位长的定时器重装值低八位
                MFRC522_Write_Register(TReloadRegH,0);			//描述16位长的定时器重装值高八位
                MFRC522_Write_Register(TModeReg,0x8D);			//定义内部定时器的设置
                MFRC522_Write_Register(TPrescalerReg,0x3E);		//定义内部定时器的设置
                Delay_us(2);
                /* 打开天线 */
                MFRC522_Antenna_On();
        }


        //...可添加其他工作方式
}

(4)第四部分:STM32控制MFRC522与M1卡的通信

  • 通过MFRC522和M1卡通讯(数据的双向传输):

//通过MFRC522和ISO14443卡通讯
//ucCommand:MFRC522命令字
//pInData:通过MFRC522发送到卡片的数据
//ucInLenByte:发送数据的字节长度
//pOutLenBit:返回数据的位长度
//状态值MI_OK(0x26),成功
char PcdComMF522(uint8_t ucCommand, uint8_t *pInData, uint8_t ucInLenByte, uint8_t *pOutData, uint32_t *pOutLenBit)
{
    char cStatus = MI_ERR;  						// 初始化状态为错误
    uint8_t ucIrqEn = 0x00;  						// 初始化允许的中断请求
    uint8_t ucWaitFor = 0x00;  						// 初始化等待的中断标志位
    uint8_t ucLastBits;  							// 用于存储最后接收到的字节的有效位数
    uint8_t ucN;  									// 用于存储FIFO中保存的字节数
    uint32_t ul;  									// 用于存储计数器值

    // 根据命令选择不同的操作模式
    switch (ucCommand)
    {
        case PCD_AUTHENT:  					// Mifare认证
            ucIrqEn = 0x12;  				// 允许错误中断请求ErrIEn 和 空闲中断IdleIEn
            ucWaitFor = 0x10;  				// 认证寻卡等待时候 查询空闲中断标志位
            break;

        case PCD_TRANSCEIVE:  				// 接收发送 发送接收
            ucIrqEn = 0x77;  				// 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
            ucWaitFor = 0x30;  				// 寻卡等待时候 查询接收中断标志位与 空闲中断标志位
            break;

        default:
            break;
    }

    // 设置中断请求寄存器
    MFRC522_Write_Register(ComIEnReg, ucIrqEn | 0x80);  		// IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反
    MFRC522_ClearBit_Register(ComIrqReg, 0x80);  				// Set1该位清零时,CommIRqReg的屏蔽位清零
    MFRC522_Write_Register(CommandReg, PCD_IDLE);  				// 写空闲命令
    MFRC522_SetBit_Register(FIFOLevelReg, 0x80);  				// 置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除

    // 将数据写入FIFO
    for (ul = 0; ul < ucInLenByte; ul++)
        MFRC522_Write_Register(FIFODataReg, pInData[ul]);  		// 写数据进FIFOdata

    // 写命令
    MFRC522_Write_Register(CommandReg, ucCommand);

    // 如果是发送接收命令,启动数据发送
    if (ucCommand == PCD_TRANSCEIVE)
        MFRC522_SetBit_Register(BitFramingReg, 0x80);  			// StartSend置位启动数据发送 该位与收发命令使用时才有效

    // 设置最大等待时间
    ul = 1000;  // 根据时钟频率调整,操作M1卡最大等待时间25ms

    // 等待中断
    do
    {
        ucN = MFRC522_Read_Register(ComIrqReg);  					 // 查询事件中断
        ul--;
    } while ((ul != 0) && (!(ucN & 0x01)) && (!(ucN & ucWaitFor)));  // 退出条件i=0,定时器中断,与写空闲命令

    // 清理StartSend位
    MFRC522_ClearBit_Register(BitFramingReg, 0x80);

    // 检查是否成功
    if (ul != 0)
    {
        if (!(MFRC522_Read_Register(ErrorReg) & 0x1B))  // 读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
        {
            cStatus = MI_OK;  // 设置状态为成功

            if (ucN & ucIrqEn & 0x01)  // 是否发生定时器中断
                cStatus = MI_NOTAGERR;

            if (ucCommand == PCD_TRANSCEIVE)
            {
                ucN = MFRC522_Read_Register(FIFOLevelReg);  // 读FIFO中保存的字节数

                ucLastBits = MFRC522_Read_Register(ControlReg) & 0x07;  // 最后接收到得字节的有效位数

                if (ucLastBits)
                    *pOutLenBit = (ucN - 1) * 8 + ucLastBits;  // N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
                else
                    *pOutLenBit = ucN * 8;  // 最后接收到的字节整个字节有效

                if (ucN == 0)
                    ucN = 1;

                if (ucN > MAXRLEN)
                    ucN = MAXRLEN;

                // 读取FIFO中的数据
                for (ul = 0; ul < ucN; ul++)
                    pOutData[ul] = MFRC522_Read_Register(FIFODataReg);
            }
        }
        else
            cStatus = MI_ERR;  // 设置状态为错误
    }

    // 停止定时器
    MFRC522_SetBit_Register(ControlReg, 0x80);  // stop timer now
    MFRC522_Write_Register(CommandReg, PCD_IDLE);  // 写空闲命令

    return cStatus;  // 返回状态
}

  • 初始化状态和变量:将状态cStatus初始化为错误状态MI_ERR,并初始化允许的中断请求ucIrqEn、等待的中断标志位ucWaitFor、最后接收到的字节的有效位数ucLastBits、FIFO中保存的字节数ucN以及计数器值ul

  • 根据命令选择操作模式:通过switch语句根据传入的ucCommand命令字,设置不同的中断请求允许位ucIrqEn和等待的中断标志位ucWaitFor。例如,当命令为PCD_AUTHENT时,进行Mifare认证,设置允许错误中断请求和空闲中断;当命令为PCD_TRANSCEIVE时,进行接收发送操作,设置允许多种中断请求。

  • 设置中断请求寄存器:通过MFRC522_Write_Register函数将ComIEnReg中断请求寄存器设置为ucIrqEn | 0x80,使IRQ管脚与Status1Reg的IRq位的值相反;通过MFRC522_ClearBit_Register函数将ComIrqReg的屏蔽位清零;写空闲命令到CommandReg;置位FIFOLevelReg的FlushBuffer位,清除内部FIFO的读写指针以及ErrReg的BufferOvfl标志位。

  • 将数据写入FIFO:使用for循环,将pInData中的数据逐字节写入FIFODataReg,写入长度为ucInLenByte

  • 写命令:将ucCommand命令字写入CommandReg

  • 启动数据发送(仅当命令为发送接收时):如果ucCommandPCD_TRANSCEIVE,则置位BitFramingReg的StartSend位,启动数据发送。

  • 设置最大等待时间并等待中断:设置计数器ul为最大等待时间(根据时钟频率调整,操作M1卡最大等待时间25ms),然后通过do...while循环查询ComIrqReg事件中断,直到计数器为0、发生定时器中断或满足写空闲命令的退出条件。

  • 清理StartSend位:通过MFRC522_ClearBit_Register函数清除BitFramingReg的StartSend位。

  • 检查通信是否成功:

    • 如果计数器ul不为0,说明在最大等待时间内有中断发生,接着检查ErrorReg错误标志寄存器,若没有错误标志(BufferOfI、CollErr、ParityErr、ProtocolErr),则将状态cStatus设置为成功MI_OK

    • 如果发生定时器中断,则将状态cStatus设置为MI_NOTAGERR

    • 如果命令为PCD_TRANSCEIVE,读取FIFOLevelReg得到FIFO中保存的字节数ucN,读取ControlReg得到最后接收到的字节的有效位数ucLastBits,根据ucNucLastBits计算读取到的数据总位数*pOutLenBit,并读取FIFO中的数据到pOutData中。

  • 停止定时器并写空闲命令:通过MFRC522_SetBit_Register函数置位ControlReg停止定时器,再写空闲命令到CommandReg

  • 返回状态:最后返回状态cStatus,表示通信是否成功。

  • 寻卡:

  • /*寻卡
    ucReq_code,寻卡方式:
                       = 0x52:寻感应区内所有符合14443A标准的卡
                       = 0x26:寻未进入休眠状态的卡
            pTagType,卡片类型代码:
                       = 0x4400:Mifare_UltraLight
                       = 0x0400:Mifare_One(S50)
                       = 0x0200:Mifare_One(S70)
                       = 0x0800:Mifare_Pro(X))
                       = 0x4403:Mifare_DESFire
    状态值MI_OK,成功*/
    char PcdRequest(uint8_t ucReq_code, uint8_t *pTagType)
    {
        char cStatus;  									// 初始化状态
        uint8_t ucComMF522Buf[MAXRLEN];  				// 用于存储通信数据的缓冲区
        uint32_t ulLen; 								// 用于存储返回数据的位长度
    
        
        MFRC522_ClearBit_Register(Status2Reg, 0x08);	// 清理指示MIFARECryptol单元接通以及所有卡的数据通信被加密的情况
        
        MFRC522_Write_Register(BitFramingReg, 0x07);	// 设置发送的最后一个字节的位数
        
        MFRC522_SetBit_Register(TxControlReg, 0x03);	// 设置TX1,TX2管脚的输出信号传递经发送调制的13.56MHz的能量载波信号
    
        ucComMF522Buf[0] = ucReq_code;  				// 存入寻卡方式
    	// 发送寻卡命令,卡片返回卡的型号代码到ucComMF522Buf中					
        cStatus = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &ulLen);
    
        // 如果寻卡成功,返回卡类型
        if ((cStatus == MI_OK) && (ulLen == 0x10))
        {
            // 接收卡片的型号代码
            *pTagType = ucComMF522Buf[0];
            *(pTagType + 1) = ucComMF522Buf[1];
        }
        else
            cStatus = MI_ERR;  		// 设置状态为错误
    
        return cStatus;  			// 返回状态
    }

定义变量:

  • 定义状态变量cStatus,用于存储寻卡操作的结果状态。
  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN
  • 定义变量ulLen,用于存储返回数据的位长度。

初始化MFRC522模块相关寄存器:

  • 通过MFRC522_ClearBit_Register函数清除Status2Reg寄存器的第3位(0x08),该位指示MIFARECryptol单元是否接通以及所有卡的数据通信是否被加密,清除该位是为了确保在寻卡操作前,相关加密通信状态被重置。
  • 通过MFRC522_Write_Register函数将BitFramingReg寄存器设置为0x07,该寄存器用于控制发送数据的位帧结构,设置为0x07是为了指定发送的最后一个字节的位数,具体含义需结合MFRC522模块的寄存器说明理解。
  • 通过MFRC522_SetBit_Register函数置位TxControlReg寄存器的第0位和第1位(0x03),该寄存器用于控制TX1和TX2管脚的输出信号,置位0x03是为了使这两个管脚输出经发送调制的13.56MHz的能量载波信号,为寻卡操作提供能量场。

准备寻卡命令数据:

  • 将传入的寻卡方式ucReq_code存入缓冲区ucComMF522Buf的第一个位置。寻卡方式有两种,0x52表示寻感应区内所有符合14443A标准的卡,0x26表示寻未进入休眠状态的卡。

执行寻卡操作:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、寻卡命令数据缓冲区ucComMF522Buf、发送数据长度1(因为只发送了一个寻卡方式字节)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送寻卡命令,并接收卡片返回的数据(如果有的话)。

处理寻卡结果:

  • 如果PcdComMF522函数返回的状态cStatusMI_OK(表示操作成功)且返回数据的位长度ulLen为0x10(表示成功接收到卡片返回的型号代码,具体长度值需结合实际卡片返回数据格式理解),则将接收到的卡片型号代码从缓冲区ucComMF522Buf的前两个字节取出,分别存入pTagType指向的地址和pTagType+1指向的地址。卡片类型代码有多种,如0x4400表示Mifare_UltraLight,0x0400表示Mifare_One(S50)等。
  • 如果寻卡操作不成功(状态不是MI_OK或返回数据长度不是预期的0x10),则将状态cStatus设置为MI_ERR(表示错误)。

返回寻卡状态:

  • 最后返回状态cStatus,表示寻卡操作是否成功。如果成功,可以通过pTagType获取到寻到的卡片的类型代码。

防冲突:

  • //防冲突
    //pSnr:卡片序列,4字节,会返回选中卡片的序列
    //状态值MI_OK,成功
    char PcdAnticoll(uint8_t *pSnr)
    {
        char cStatus;  								// 初始化状态变量
        uint8_t uc, ucSnr_check = 0;  				// 用于存储卡片序列号的校验值
        uint8_t ucComMF522Buf[MAXRLEN];  			// 用于存储通信数据的缓冲区
        uint32_t ulLen;  							// 用于存储返回数据的位长度
    
        // 清除MFCryptol On位,只有成功执行MFAuthent命令后,该位才能置位
        MFRC522_ClearBit_Register(Status2Reg, 0x08);
        // 清理BitFramingReg寄存器,停止收发
        MFRC522_Write_Register(BitFramingReg, 0x00);
        // 清除CollReg寄存器的ValuesAfterColl位,所有接收的位在冲突后被清除
        MFRC522_ClearBit_Register(CollReg, 0x80);
    
        // 卡片防冲突命令
        ucComMF522Buf[0] = 0x93;
        ucComMF522Buf[1] = 0x20;
    
        // 将卡片防冲突命令通过RC522传到卡片中,返回的是被选中卡片的序列
        cStatus = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &ulLen);  // 与卡片通信
    
        // 如果通信成功
        if (cStatus == MI_OK)
        {
            // 读取卡片的UID
            for (uc = 0; uc < 4; uc++)
            {
                *(pSnr + uc) = ucComMF522Buf[uc];  // 读出UID
                ucSnr_check ^= ucComMF522Buf[uc];  // 计算校验值
            }
    
            // 检查校验值是否正确
            if (ucSnr_check != ucComMF522Buf[uc])
                cStatus = MI_ERR;  // 如果校验值不匹配,设置状态为错误
        }
    
        // 恢复CollReg寄存器的ValuesAfterColl位
        MFRC522_SetBit_Register(CollReg, 0x80);
    
        return cStatus;  // 返回状态
    }

定义变量:

  • 定义状态变量cStatus,用于存储防冲突操作的结果状态。
  • 定义变量uc用于循环计数,ucSnr_check用于存储卡片序列号的校验值。
  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN
  • 定义变量ulLen,用于存储返回数据的位长度。

初始化MFRC522模块相关寄存器:

  • 通过MFRC522_ClearBit_Register函数清除Status2Reg寄存器的第3位(0x08),该位指示MIFARECryptol单元是否接通,清除该位是为了确保在防冲突操作前,相关加密通信状态被重置。
  • 通过MFRC522_Write_Register函数将BitFramingReg寄存器设置为0x00,该寄存器用于控制发送数据的位帧结构,设置为0x00是为了停止收发操作,为接下来的防冲突命令做准备。
  • 通过MFRC522_ClearBit_Register函数清除CollReg寄存器的第7位(0x80),该位控制所有接收的位在冲突后是否被清除,清除该位是为了在防冲突操作中,接收的位在冲突后会被清除,以便正确处理防冲突逻辑。

准备防冲突命令数据:

  • 将卡片防冲突命令0x93和参数0x20存入缓冲区ucComMF522Buf的前两个位置。这两个字节组成了防冲突命令,用于启动卡片的防冲突机制,让卡片返回其唯一的序列号。
  • 执行防冲突操作:
  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、防冲突命令数据缓冲区ucComMF522Buf、发送数据长度2(因为发送了两个字节的防冲突命令)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送防冲突命令,并接收卡片返回的数据(卡片序列号)。

处理防冲突结果:

  • 如果PcdComMF522函数返回的状态cStatusMI_OK(表示操作成功),则进入处理成功结果的逻辑。
  • 通过for循环,从缓冲区ucComMF522Buf中读取前4个字节,这些字节是卡片返回的序列号,并将它们存入pSnr指向的地址开始的内存区域。同时,在循环中计算序列号的校验值ucSnr_check,通过异或操作累加每个字节的值。
  • 检查计算得到的校验值ucSnr_check是否与缓冲区中第5个字节(即ucComMF522Buf[uc]uc此时为4)的值相等。如果不相等,说明校验失败,将状态cStatus设置为MI_ERR(表示错误)。

恢复寄存器状态:

  • 通过MFRC522_SetBit_Register函数重新置位CollReg寄存器的第7位(0x80),恢复其原始状态,以便后续操作可以正常进行。

返回防冲突状态:

  • 最后返回状态cStatus,表示防冲突操作是否成功。如果成功,可以通过pSnr获取到选中卡片的序列号。

用MFRC522计算CRC16(循环冗余校验):

//用MFRC522计算CRC16(循环冗余校验)
//pIndata:计算CRC16的数组
//ucLen:计算CRC16的数组字节长度
//pOutData:存放计算结果存放的首地址
//状态值MI_OK,成功
void CalculateCRC(uint8_t *pIndata, uint8_t ucLen, uint8_t *pOutData)
{
    uint8_t uc, ucN;  					// 用于循环计数和读取寄存器值

    // 清除DivIrqReg寄存器的CRCIRq位,该位在CRC计算完成后置位
    MFRC522_ClearBit_Register(DivIrqReg, 0x04);

    // 写空闲命令,停止当前操作
    MFRC522_Write_Register(CommandReg, PCD_IDLE);

    // 置位FIFOLevelReg寄存器的FlushBuffer位,清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位
    MFRC522_SetBit_Register(FIFOLevelReg, 0x80);

    // 将要计算CRC的数据写入FIFO
    for (uc = 0; uc < ucLen; uc++)
        MFRC522_Write_Register(FIFODataReg, *(pIndata + uc));

    // 写命令,启动CRC计算
    MFRC522_Write_Register(CommandReg, PCD_CALCCRC);

    // 设置超时计数器
    uc = 0xFF;

    // 等待CRC计算完成
    do
    {
        ucN = MFRC522_Read_Register(DivIrqReg);  		// 读取DivIrqReg寄存器,检查CRC计算是否完成
        uc--;  // 减少超时计数器
    } while ((uc != 0) && !(ucN & 0x04));  				// 退出条件:超时或CRC计算完成

    // 读取CRC计算结果
    pOutData[0] = MFRC522_Read_Register(CRCResultRegL); // 读取CRC结果低字节
    pOutData[1] = MFRC522_Read_Register(CRCResultRegM); // 读取CRC结果高字节
}

定义计数变量:

  • 定义两个8位无符号整型变量ucucNuc用于循环计数,ucN用于读取寄存器值。

准备CRC计算:

  • 通过MFRC522_ClearBit_Register函数清除DivIrqReg寄存器的第2位(0x04),该位是CRC计算完成中断标志位,清除它是为了在启动新的CRC计算前,确保该位为未置位状态。
  • 通过MFRC522_Write_Register函数写空闲命令PCD_IDLECommandReg寄存器,停止MFRC522模块的当前操作,为启动CRC计算做准备。
  • 通过MFRC522_SetBit_Register函数置位FIFOLevelReg寄存器的第7位(0x80),该位是FlushBuffer位,置位它会清除内部FIFO的读写指针以及ErrReg寄存器的BufferOvfl标志位,清空FIFO,以便存放待计算CRC的数据。

写入待计算CRC的数据:

  • 使用for循环,将待计算CRC的数据从数组pIndata中逐字节写入FIFODataReg寄存器,写入长度为ucLen。这些数据将被存储在FIFO中,用于后续的CRC计算。

启动CRC计算:

  • 通过MFRC522_Write_Register函数写命令PCD_CALCCRCCommandReg寄存器,启动CRC计算。MFRC522模块接收到该命令后,会开始对FIFO中的数据进行CRC16计算。

等待CRC计算完成:

  • 设置超时计数器uc为0xFF(255),用于防止无限等待CRC计算完成。
  • 使用do...while循环,通过MFRC522_Read_Register函数读取DivIrqReg寄存器的值到ucN。循环的退出条件是超时计数器uc为0或ucN的第2位(0x04,即CRC计算完成中断标志位)被置位。在循环中,每次读取后都会减少超时计数器uc的值。

读取CRC计算结果:

  • 当CRC计算完成后(或超时退出循环),通过MFRC522_Read_Register函数分别读取CRCResultRegLCRCResultRegM寄存器的值到数组pOutData的前两个位置。CRCResultRegL寄存器存储CRC结果的低字节,CRCResultRegM寄存器存储CRC结果的高字节。

函数结束:

  • 函数不返回状态值,而是通过指针pOutData将计算得到的CRC16结果(两个字节)返回给调用者。可以通过pOutData获取到CRC计算的结果,用于后续的数据校验等操作。

选定卡片:

// 选定卡片
// pSnr:卡片序列号,4字节
// 状态值MI_OK,成功
char PcdSelect(uint8_t *pSnr)
{
    char ucN;  							// 初始化状态变量
    uint8_t uc;  						// 用于循环计数
    uint8_t ucComMF522Buf[MAXRLEN];  	// 用于存储通信数据的缓冲区
    uint32_t ulLen;  					// 用于存储返回数据的位长度

    // PICC_ANTICOLL1:防冲突命令
    ucComMF522Buf[0] = PICC_ANTICOLL1;
    ucComMF522Buf[1] = 0x70;
    ucComMF522Buf[6] = 0;

    // 将卡片序列号和校验值写入缓冲区
    for (uc = 0; uc < 4; uc++)
    {
        ucComMF522Buf[uc + 2] = *(pSnr + uc);  		// 读出UID
        ucComMF522Buf[6] ^= *(pSnr + uc);  			// 计算校验值
    }

    // 计算CRC16校验值
    CalculateCRC(ucComMF522Buf, 7, &ucComMF522Buf[7]);

    // 清除Status2Reg寄存器的MFCryptol On位
    MFRC522_ClearBit_Register(Status2Reg, 0x08);

    // 通过PcdComMF522函数发送选择卡片命令
    ucN = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &ulLen);

    // 检查通信是否成功
    if ((ucN == MI_OK) && (ulLen == 0x18))
        ucN = MI_OK;  	// 如果成功,返回MI_OK
    else
        ucN = MI_ERR;   // 如果失败,返回MI_ERR

    return ucN;  // 返回状态
}

定义变量:

  • 定义状态变量ucN,用于存储选定卡片操作的结果状态。

  • 定义循环计数变量uc

  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN

  • 定义变量ulLen,用于存储返回数据的位长度。

准备选择卡片命令数据:

  • 将防冲突命令PICC_ANTICOLL1(通常为0x93)和参数0x70存入缓冲区ucComMF522Buf的前两个位置。这两个字节组成了选择卡片命令的起始部分。

  • 初始化缓冲区的第7个位置(索引为6)为0,用于后续计算校验值。

写入卡片序列号和计算校验值:

  • 使用for循环,从传入的卡片序列号数组pSnr中读取4个字节的序列号,将它们依次存入缓冲区ucComMF522Buf的第3到第6个位置(索引为2到5)。同时,在循环中通过异或操作累加每个序列号字节的值到缓冲区的第7个位置(索引为6),计算校验值。

计算CRC16校验值:

  • 调用CalculateCRC函数,传入缓冲区ucComMF522Buf、数据长度7(从索引0到6的数据,包括命令、参数、序列号和校验值)以及缓冲区的第8个位置(索引为7)的地址作为输出。该函数会计算CRC16校验值,并将结果的两个字节存入缓冲区的第8和第9个位置(索引为7和8)。

初始化MFRC522模块相关寄存器:

  • 通过MFRC522_ClearBit_Register函数清除Status2Reg寄存器的第3位(0x08),该位指示MIFARECryptol单元是否接通,清除该位是为了确保在选定卡片操作前,相关加密通信状态被重置。

执行选定卡片操作:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、准备好的命令数据缓冲区ucComMF522Buf、发送数据长度9(包括命令、参数、序列号、校验值和CRC16校验值)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送选择卡片命令,并接收卡片返回的数据。

处理选定卡片结果:

  • 检查PcdComMF522函数返回的状态ucN是否为MI_OK(表示操作成功)以及返回数据的位长度ulLen是否为0x18(表示成功接收到卡片返回的确认数据,具体长度值需结合实际卡片返回数据格式理解)。如果两个条件都满足,说明选定卡片操作成功,将状态ucN设置为MI_OK;否则,将状态ucN设置为MI_ERR(表示错误)。

返回选定卡片状态:

  • 最后返回状态ucN,表示选定卡片操作是否成功。如果成功,可以继续进行后续的卡片操作,如读写数据等。

 校验卡片密码:

// ucAuth_mode:密码验证模式:
//              = 0x60,验证A密钥
//              = 0x61,验证B密钥
// ucAddr:块地址
// pKey:密码
// pSnr:卡片序列号,4字节
// 状态值MI_OK,成功
char PcdAuthState(uint8_t ucAuth_mode, uint8_t ucAddr, uint8_t *pKey, uint8_t *pSnr)
{
    char cStatus;  						// 初始化状态变量
    uint8_t uc;  						// 用于循环计数
    uint8_t ucComMF522Buf[MAXRLEN];  	// 用于存储通信数据的缓冲区
    uint32_t ulLen;  					// 用于存储返回数据的位长度

    // 前两个字节存储验证模式和块地址
    ucComMF522Buf[0] = ucAuth_mode;  		// 验证模式
    ucComMF522Buf[1] = ucAddr;  			// 块地址

    // 2~7字节存储密码(6个字节)
    for (uc = 0; uc < 6; uc++)
        ucComMF522Buf[uc + 2] = *(pKey + uc);

    // 8~13字节存储序列号(4个字节)
    for (uc = 0; uc < 4; uc++)
        ucComMF522Buf[uc + 8] = *(pSnr + uc);

    // 进行冗余校验,14~15两个字节存储校验结果
    cStatus = PcdComMF522(PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &ulLen);

    // 判断验证是否成功
    if ((cStatus != MI_OK) || (!(MFRC522_Read_Register(Status2Reg) & 0x08)))
        cStatus = MI_ERR;  	// 如果验证失败,设置状态为错误

    return cStatus;  		// 返回状态
}

定义变量:

  • 定义状态变量cStatus,用于存储密码验证操作的结果状态。

  • 定义循环计数变量uc

  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN

  • 定义变量ulLen,用于存储返回数据的位长度。

准备密码验证命令数据:

  • 将密码验证模式ucAuth_mode0x60表示验证A密钥,0x61表示验证B密钥)存入缓冲区ucComMF522Buf的第一个位置。

  • 将块地址ucAddr存入缓冲区的第二个位置。块地址指定了要进行密码验证的卡片数据块。

写入密码:

  • 使用for循环,从传入的密码数组pKey中读取6个字节的密码,将它们依次存入缓冲区ucComMF522Buf的第3到第8个位置(索引为2到7)。

写入卡片序列号:

  • 使用for循环,从传入的卡片序列号数组pSnr中读取4个字节的序列号,将它们依次存入缓冲区ucComMF522Buf的第9到第12个位置(索引为8到11)。

执行密码验证操作:

  • 调用PcdComMF522函数,传入命令PCD_AUTHENT(表示密码验证操作)、准备好的命令数据缓冲区ucComMF522Buf、发送数据长度12(包括验证模式、块地址、密码和序列号)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送密码验证命令,并接收卡片返回的数据。

判断验证是否成功:

  • 检查PcdComMF522函数返回的状态cStatus是否为MI_OK(表示操作成功)。同时,检查Status2Reg寄存器的第3位(0x08)是否被置位,该位指示MIFARECryptol单元是否接通,即密码验证是否成功。如果两个条件都满足,说明密码验证成功;否则,将状态cStatus设置为MI_ERR(表示错误)。

返回验证状态:

  • 最后返回状态cStatus,表示密码验证操作是否成功。如果成功,可以继续进行后续的卡片操作,如读写数据等;如果失败,需要重新进行密码验证或检查密码和序列号是否正确。

 在M1卡的指定块地址写入指定数据:

//在M1卡的指定块地址写入指定数据
//ucAddr:块地址
//pData:写入的数据,16字节
//状态值MI_OK,成功
char PcdWrite ( uint8_t ucAddr, uint8_t * pData )
{
    char cStatus;
          uint8_t uc,ucComMF522Buf[MAXRLEN];
    uint32_t ulLen;
   
    ucComMF522Buf[0] = PICC_WRITE;			//写块命令
    ucComMF522Buf[1] = ucAddr;				//写块地址
        
    //进行循环冗余校验,将结果存储在&ucComMF522Buf[2] 
    CalculateCRC (ucComMF522Buf,2,&ucComMF522Buf[2]);
    
        /* PCD_TRANSCEIVE:发送并接收数据命令,通过MFRC522向卡片发送写块命令 */
    cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf,4,ucComMF522Buf, &ulLen);

                /* 通过卡片返回的信息判断,MFRC522是否与卡片正常通信 */
    if ((cStatus!=MI_OK)||(ulLen!= 4)||((ucComMF522Buf[0]&0x0F)!=0x0A))
      cStatus = MI_ERR;   
        
    if (cStatus==MI_OK)
    {
                        //memcpy(ucComMF522Buf, pData, 16);
                        /* 将要写入的16字节的数据,传入ucComMF522Buf数组中 */
      for (uc=0;uc<16;uc++)
                          ucComMF522Buf [uc]=*(pData+uc);  
                        /* 冗余校验 */
      CalculateCRC(ucComMF522Buf,16,&ucComMF522Buf[16]);
      /* 通过MFRC522,将16字节数据包括2字节校验结果写入卡片中 */
      cStatus=PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,18,ucComMF522Buf,&ulLen);
                        /* 判断写地址是否成功 */
                        if((cStatus!=MI_OK)||(ulLen!=4)||((ucComMF522Buf[0]&0x0F)!=0x0A))
        cStatus=MI_ERR;                           
    } 
    return cStatus;        
}

定义变量:

  • 定义状态变量cStatus,用于存储写入操作的结果状态。

  • 定义循环计数变量uc

  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN

  • 定义变量ulLen,用于存储返回数据的位长度。

准备写块命令数据:

  • 将写块命令PICC_WRITE(通常为0xA0)存入缓冲区ucComMF522Buf的第一个位置。

  • 将块地址ucAddr存入缓冲区的第二个位置。块地址指定了要写入数据的卡片数据块。

计算CRC16校验值:

  • 调用CalculateCRC函数,传入缓冲区ucComMF522Buf、数据长度2(命令和块地址)以及缓冲区的第3个位置的地址作为输出。该函数会计算CRC16校验值,并将结果的两个字节存入缓冲区的第3和第4个位置。

发送写块命令:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、准备好的命令数据缓冲区ucComMF522Buf、发送数据长度4(包括命令、块地址和CRC16校验值)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送写块命令,并接收卡片返回的数据。

判断写块命令是否成功:

  • 检查PcdComMF522函数返回的状态cStatus是否为MI_OK(表示操作成功)、返回数据的位长度ulLen是否为4以及返回数据的第一个字节的低四位是否为0x0A。如果这些条件都满足,说明写块命令发送成功;否则,将状态cStatus设置为MI_ERR(表示错误)。

准备写入数据:

  • 如果写块命令发送成功,将要写入的16字节数据从数组pData中逐字节复制到缓冲区ucComMF522Buf的前16个位置。

计算写入数据的CRC16校验值:

  • 调用CalculateCRC函数,传入缓冲区ucComMF522Buf、数据长度16(要写入的数据)以及缓冲区的第17个位置的地址作为输出。该函数会计算CRC16校验值,并将结果的两个字节存入缓冲区的第17和第18个位置。

发送写入数据:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE、包含要写入的数据和CRC16校验值的缓冲区ucComMF522Buf、发送数据长度18(16字节数据加上2字节CRC16校验值)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送写入数据,并接收卡片返回的数据。

判断写入数据是否成功:

  • 再次检查PcdComMF522函数返回的状态cStatus是否为MI_OK、返回数据的位长度ulLen是否为4以及返回数据的第一个字节的低四位是否为0x0A。如果这些条件都满足,说明写入数据成功;否则,将状态cStatus设置为MI_ERR

返回写入状态:

  • 最后返回状态cStatus,表示写入操作是否成功。如果成功,可以确认数据已正确写入卡片;如果失败,需要检查命令发送、数据写入等步骤是否存在问题。

读取M1卡的指定块地址的数据:

// 读取M1卡的指定块地址的数据
// ucAddr:块地址
// pData:读出的数据,16字节
// 状态值MI_OK,成功
char PcdRead(uint8_t ucAddr, uint8_t *pData)
{
    char cStatus;  					 // 初始化状态变量
    uint8_t uc;  					 // 用于循环计数
    uint8_t ucComMF522Buf[MAXRLEN];  // 用于存储通信数据的缓冲区
    uint32_t ulLen;  				 // 用于存储返回数据的位长度

    // 设置读取命令和块地址
    ucComMF522Buf[0] = PICC_READ;   // 读取命令
    ucComMF522Buf[1] = ucAddr;  	// 块地址
    // 计算CRC16校验值
    CalculateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
    // 通过MFRC522将命令传给卡片
    cStatus = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &ulLen);

    // 如果传输正常,将读取到的数据传入pData中
    if ((cStatus == MI_OK) && (ulLen == 0x90))
    {
        for (uc = 0; uc < 16; uc++)
            *(pData + uc) = ucComMF522Buf[uc];
    }
    else
        cStatus = MI_ERR;  // 如果传输失败,设置状态为错误
    return cStatus;  // 返回状态
}

定义变量:

  • 定义状态变量cStatus,用于存储读取操作的结果状态。

  • 定义循环计数变量uc

  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN

  • 定义变量ulLen,用于存储返回数据的位长度。

准备读取命令数据:

  • 将读取命令PICC_READ(通常为0x30)存入缓冲区ucComMF522Buf的第一个位置。

  • 将块地址ucAddr存入缓冲区的第二个位置。块地址指定了要读取数据的卡片数据块。

计算CRC16校验值:

  • 调用CalculateCRC函数,传入缓冲区ucComMF522Buf、数据长度2(命令和块地址)以及缓冲区的第3个位置的地址作为输出。该函数会计算CRC16校验值,并将结果的两个字节存入缓冲区的第3和第4个位置。

发送读取命令:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、准备好的命令数据缓冲区ucComMF522Buf、发送数据长度4(包括命令、块地址和CRC16校验值)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送读取命令,并接收卡片返回的数据。

判断读取命令是否成功:

  • 检查PcdComMF522函数返回的状态cStatus是否为MI_OK(表示操作成功)以及返回数据的位长度ulLen是否为0x90(表示成功接收到16字节的数据加上一些额外的通信开销,具体长度值需结合实际卡片返回数据格式理解)。如果这些条件都满足,说明读取命令发送成功且数据已正确接收;否则,将状态cStatus设置为MI_ERR(表示错误)。

读取数据:

  • 如果读取命令发送成功且数据已正确接收,使用for循环,将缓冲区ucComMF522Buf中的前16个字节(即读取到的数据)逐字节复制到数组pData中。

返回读取状态:

  • 最后返回状态cStatus,表示读取操作是否成功。如果成功,可以通过pData获取到读取的数据;如果失败,需要检查命令发送、数据接收等步骤是否存在问题。

让卡片进入休眠模式:

// 让卡片进入休眠模式
// 状态值MI_OK,成功
char PcdHalt(void)
{
    uint8_t ucComMF522Buf[MAXRLEN];  // 用于存储通信数据的缓冲区
    uint32_t ulLen;  // 用于存储返回数据的位长度

    // 设置休眠命令
    ucComMF522Buf[0] = PICC_HALT;  		// 休眠命令
    ucComMF522Buf[1] = 0;  				// 休眠命令的参数
    // 计算CRC16校验值
    CalculateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
    // 通过MFRC522将命令传给卡片
    PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &ulLen);

    return MI_OK;  // 返回成功状态
}

定义变量:

  • 定义缓冲区数组ucComMF522Buf,用于存储与MFRC522模块通信的数据,数组长度为MAXRLEN

  • 定义变量ulLen,用于存储返回数据的位长度。

准备休眠命令数据:

  • 将休眠命令PICC_HALT(通常为0x50)存入缓冲区ucComMF522Buf的第一个位置。

  • 将休眠命令的参数0存入缓冲区的第二个位置。这个参数通常用于特定的命令格式,但对于休眠命令,通常不需要额外的参数。

计算CRC16校验值:

  • 调用CalculateCRC函数,传入缓冲区ucComMF522Buf、数据长度2(命令和参数)以及缓冲区的第3个位置的地址作为输出。该函数会计算CRC16校验值,并将结果的两个字节存入缓冲区的第3和第4个位置。

发送休眠命令:

  • 调用PcdComMF522函数,传入命令PCD_TRANSCEIVE(表示接收发送操作)、准备好的命令数据缓冲区ucComMF522Buf、发送数据长度4(包括命令、参数和CRC16校验值)、接收数据缓冲区ucComMF522Buf以及返回数据位长度指针&ulLen。该函数会通过MFRC522模块发送休眠命令,并接收卡片返回的数据。

返回成功状态:

  • 由于休眠命令的发送通常不需要检查返回数据(卡片进入休眠模式后不会返回数据),因此直接返回成功状态MI_OK

四、程序演示:

(1)主函数:

  • 所有密码默认为16个字节的0xff。
  • 初始化硬件设备(OLED显示屏、串口通信、MFRC522模块),然后进入一个无限循环,不断寻卡、防冲突、选卡、验证卡片密码、写入数据到指定块、读取数据串口打印并显示ID在OLED屏幕上。
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MFRC522.h"
#include "uart.h"
#include "stdio.h"
#include "string.h"

/* 卡的ID存储,32位,4字节 */
u8 ucArray_ID [4]; 
//返回状态 
uint8_t ucStatusReturn;     

int main(void)
{
    int i = 0;
    
    uint8_t read_write_data[16]={0};					 //读写数据缓存
    uint8_t card_KEY[6] ={0xff,0xff,0xff,0xff,0xff,0xff};//默认密码
	
    OLED_Init();  
    Uart_Init(115200);

    printf ("MFRC522_Init....\r\n");
    MFRC522_Init( );//IC卡IO口初始化 
    MFRC522_Rese( );//复位MFRC522    
    printf ("MFRC522_Start\r\n");
        
    while(1) 
    {

        /* 寻卡(方式:范围内全部),第一次寻卡失败后再进行一次,寻卡成功时卡片序列传入数组ucArray_ID中 */
        if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
        {  
                        ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID );
        }

        if (ucStatusReturn==MI_OK)
        {
                /* 防冲突操作,被选中的卡片序列传入数组ucArray_ID中 */
                if (PcdAnticoll (ucArray_ID) == MI_OK)
                {
                        //输出卡ID                
					printf("ID: %X %X %X %X\r\n", ucArray_ID[0], ucArray_ID [1], ucArray_ID [2], ucArray_ID [3]);
					OLED_ShowString(0, 0,"ID:",OLED_8X16);
					OLED_ShowHexNum(0, 16,ucArray_ID[0],2,OLED_8X16);
					OLED_ShowHexNum(32, 16,ucArray_ID[1],2,OLED_8X16);
					OLED_ShowHexNum(64, 16,ucArray_ID[2],2,OLED_8X16);
					OLED_ShowHexNum(96, 16,ucArray_ID[3],2,OLED_8X16);
                         //选卡
                        if( PcdSelect(ucArray_ID) != MI_OK )
                        {    printf("PcdSelect failure\r\n");         }

                        //校验卡片密码
                        //数据块6的密码A进行校验(所有密码默认为16个字节的0xff)
                        if( PcdAuthState(PICC_AUTHENT1B, 6, card_KEY,  ucArray_ID) != MI_OK )
                        {    printf("PcdAuthState failure\r\n");      }

                        //往数据块4写入数据read_write_data
                        read_write_data[0] = 0x66;	
						read_write_data[1] = 0x66;
						read_write_data[2] = 0x66;
						read_write_data[3] = 0x66;
						read_write_data[4] = 0x66;
						read_write_data[5] = 0x66;
						read_write_data[6] = 0x66;
						read_write_data[7] = 0x66;
						//...其余均为零
                        if( PcdWrite(4,read_write_data) != MI_OK )
                        {    printf("PcdWrite failure\r\n");          }

                        //将read_write_data的16位数据,填充为0(清除数据的意思)
                        memset(read_write_data,0,16);
                        Delay_us(8);

                        //读取数据块4的数据
                        if( PcdRead(4,read_write_data) != MI_OK )
                        {    printf("PcdRead failure\r\n");           }

                        //输出读出的数据
                        for( i = 0; i < 16; i++ )
                        {
                        printf("%x ",read_write_data[i]);
                        }
                        printf("\r\n");
                }
        } 
		OLED_Update();
    }       
}

(2)效果演示:

MFRC522写入并读出数据

五、代码下载:

通过网盘分享的文件:33-MFRC522读取写入数据
链接: https://pan.baidu.com/s/1uJcqS-3TWzLas46ewTc-CQ?pwd=xjyw 提取码: xjyw

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The_xzs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值