MFRC522的使用

一、RFID射频识别技术介绍:

       RC522RFID射频模块是一款广泛应用于非接触式RFID系统中的核心组件,由NXP(前身为Philips半导体)公司设计生产。这个模块基于MFRC522芯片,该芯片是一个高度集成的UHFRFID读卡器/写卡器解决方案,能够支持IS014443A标准的卡片和标签。

1.1 RFID模块的组成部分

  1. RFID标签(Tag)

    • 被动标签:无需电池,通过接收读写器发出的射频信号供电。
    • 主动标签:内置电池,可以主动发射信号,通信距离较远。
    • 半主动标签:有电池,但只在接收到外部信号时响应。
  2. RFID读写器(Reader)

    • 通过射频信号与标签进行通信,读取或写入标签中的数据。
    • 一般包括调制解调器、处理器、数据接口和天线。
  3. 天线(Antenna)

    • 用于发射和接收RF信号。天线的设计直接影响射频信号的传输距离和效率。

1.2 RFID模块的工作原理

  1. 发射信号:RFID读写器通过天线发射高频(HF)、超高频(UHF)或微波频段的电磁波。
  2. 接收信号:RFID标签接收到信号后,利用其内置的电路回应特定信息。
  3. 数据交换:读写器接收到标签回应的信号后,对数据进行读取或写入操作。

1.3 常见的RFID模块类型

  1. 低频RFID模块(LF,125kHz):主要用于近距离的身份识别,常见于门禁系统、动物识别等场景。
  2. 高频RFID模块(HF,13.56MHz):常见于电子钱包、门票、身份证、图书馆管理等应用。
  3. 超高频RFID模块(UHF,860-960MHz):适用于较远距离的应用,常用于仓储管理、物流追踪等。

1.4 常见的RFID模块示例

  1. RC522:常用于Arduino和其他单片机的低成本高频RFID读写模块,工作频率为13.56MHz。
  2. MFRC522:也是一种常见的13.56MHz的RFID读写模块,常用于嵌入式系统和简单的RFID应用。
  3. RDM6300:工作频率为125kHz的低频RFID模块,适合近距离应用。

二、MFRC522工作原理:

2.1 MFRC522接口框架:

        MFRC522是高度集成的非接触式(13.56MHz)读写卡芯片。此发送模块利用调制和解调的原理,并将它们完全集成到各种非接触式通信方法和协议中(13.56MHz)。

2.2 MFRC522原理图:

注:

本人代码使用的引脚为

SDA       PA4

SCK       PA5

MISO     PA6

MOSI     PA7

RST       PA1

三、MFRC代码编写:

3.1 一些MFRC522宏定义移植:

3.1.1 RC522_MOSIRC522_MISORC522_SCK

        这些宏定义了用于与 RC522 模块通信的 SPI 引脚。PAout() 用于输出,PAin() 用于输入。通过这些引脚,STM32 可以与 RC522 进行数据交换(MOSI 用于发送数据,MISO 用于接收数据,SCK 提供时钟信号)。

3.1.2 MFRC522_CS(x)

        这是一个宏,用于控制 RC522 的片选信号(CS)。

3.1.3MFRC522_Rst(x):

        这是一个宏,用于控制 RC522 的复位信号(RST)。

// 定义 RC522 的 SPI 引脚连接
// RC522 的 MOSI 引脚连接到 STM32 的 PA5 引脚(主设备输出,从设备输入)
#define  RC522_MOSI   PAout(5)  // SPI 的 MOSI(Master Out Slave In)信号,用于 STM32 向 RC522 发送数据
// RC522 的 MISO 引脚连接到 STM32 的 PA4 引脚(主设备输入,从设备输出)
#define  RC522_MISO   PAin(4)   // SPI 的 MISO(Master In Slave Out)信号,用于 RC522 向 STM32 发送数据
// RC522 的 SCK 引脚连接到 STM32 的 PA6 引脚(时钟信号)
#define  RC522_SCK    PAout(6)  // SPI 时钟信号(SCK),用于同步数据传输

//
// 定义 MFRC522 的片选信号(CS)和复位信号(RST)控制宏
//
// 控制 MFRC522 的片选信号(CS)
// 参数 x = 1 时,CS 引脚设置为高电平(片选信号未激活)
// 参数 x = 0 时,CS 引脚设置为低电平(片选信号激活)
#define MFRC522_CS(x) 	x ? GPIO_SetBits(GPIOA, GPIO_Pin_7) : GPIO_ResetBits(GPIOA, GPIO_Pin_7)
// 当 x 为 1 时,调用 GPIO_SetBits 将 GPIOA 的 7 号引脚设置为高电平,表示禁用 SPI 通信。
// 当 x 为 0 时,调用 GPIO_ResetBits 将 GPIOA 的 7 号引脚设置为低电平,表示启用 SPI 通信。

// 控制 MFRC522 的复位信号(RST)
// 参数 x = 1 时,RST 引脚设置为高电平(复位信号未激活)
// 参数 x = 0 时,RST 引脚设置为低电平(复位信号激活)
#define MFRC522_Rst(x) 	x ? GPIO_SetBits(GPIOA, GPIO_Pin_1) : GPIO_ResetBits(GPIOA, GPIO_Pin_1)
// 当 x 为 1 时,调用 GPIO_SetBits 将 GPIOA 的 1 号引脚设置为高电平,表示不复位。
// 当 x 为 0 时,调用 GPIO_ResetBits 将 GPIOA 的 1 号引脚设置为低电平,表示复位 MFRC522 模块。
/
// MF522 指令定义
/

#define PCD_IDLE              0x00               // 空闲模式指令,表示 MFRC522 处于不活动状态
#define PCD_AUTHENT           0x0E               // 验证指令,用于进行身份验证
#define PCD_RECEIVE           0x08               // 接收数据指令,表示准备接收数据
#define PCD_TRANSMIT          0x04               // 发送数据指令,表示准备发送数据
#define PCD_TRANSCEIVE        0x0C               // 发送并接收数据指令,表示同时进行发送与接收
#define PCD_RESETPHASE        0x0F               // 复位指令,用于复位 MFRC522 模块
#define PCD_CALCCRC           0x03               // CRC 校验指令,用于计算和验证 CRC 校验码

/
// Mifare_One 卡片操作命令定义
/

#define PICC_REQIDL           0x26               // 请求 IDLE 状态卡片,表示寻找处于空闲模式的卡片
#define PICC_REQALL           0x52               // 请求 ALL 状态卡片,表示寻找处于任何状态的卡片
#define PICC_ANTICOLL1        0x93               // 防冲突命令1,用于避免多个卡片同时响应
#define PICC_ANTICOLL2        0x95               // 防冲突命令2,用于避免多个卡片同时响应
#define PICC_AUTHENT1A        0x60               // 身份验证命令 A,验证使用 A 密钥
#define PICC_AUTHENT1B        0x61               // 身份验证命令 B,验证使用 B 密钥
#define PICC_READ             0x30               // 读取命令,用于读取卡片的数据块
#define PICC_WRITE            0xA0               // 写入命令,用于写入数据到卡片
#define PICC_DECREMENT        0xC0               // 减法命令,用于减去卡片余额
#define PICC_INCREMENT        0xC1               // 加法命令,用于增加卡片余额
#define PICC_RESTORE          0xC2               // 恢复命令,用于恢复卡片余额到之前的状态
#define PICC_TRANSFER         0xB0               // 转移命令,用于将卡片余额从一个块转移到另一个块
#define PICC_HALT             0x50               // 停止命令,用于将卡片置于停止状态

/
// MF522 FIFO 队列大小定义
/

#define DEF_FIFO_LENGTH       64                 // FIFO 队列的大小,通常用于数据缓存和传输队列

3.2 命令字的定义:

3.2.1 Mifare_One 卡片操作指令

        这些宏定义的是与 RFID 卡片(如 Mifare)进行通信时使用的指令。不同的指令用于不同的操作,例如寻卡、验证身份、读写数据等。

3.2.2 MF522 FIFO 队列大小

   DEF_FIFO_LENGTH 设置了 FIFO(先进先出)队列的大小,即缓冲区的容量,这影响数据的存储和传输。常见的设置为64字节。

3.2.3 MF522 寄存器地址定义

         这些宏定义了 MFRC522 模块中的寄存器地址,每个寄存器有不同的功能,比如控制操作、存储数据、指示错误等。通过这些寄存器可以控制和监测模块的工作状态。

1、PAGE 0 寄存器

        PAGE 0 寄存器包含了 MF522 模块的核心控制寄存器,主要用于配置模块的工作模式、控制数据传输、启用中断、检测错误等。通过这些寄存器,你可以直接与模块进行交互,实现 RFID 系统的功能。

2、PAGE 1 寄存器

  • 这些寄存器主要用于设置无线电模块的工作模式(发送、接收模式)、配置数据传输参数(如发送模式、接收阈值等)。例如:
    • ModeReg:设置模块的操作模式。
    • TxModeReg 和 RxModeReg:分别控制发送和接收的模式。
    • SerialSpeedReg:设置串口通信的速度。

3、 PAGE 2 寄存器

  • 这部分寄存器用于配置和控制无线电通信的各种参数,如 CRC 校验、无线电配置、调制方式等。
    • CRCResultRegH 和 CRCResultRegL:用于存储 CRC 校验的结果。
    • RFCfgReg:配置无线电模块。
    • TModeReg 和 TPrescalerReg:定时器的工作模式和预分频。

4、PAGE 3 寄存器

  • 这一页寄存器主要用于测试和调试 MF522 模块的功能,如版本号、DAC 测试、ADC 测试等。
    • TestSel1Reg 和 TestSel2Reg:选择测试模式。
    • VersionReg:读取模块的版本信息。

5、错误码和常量定义

  • MI_OKMI_NOTAGERR 和 MI_ERR 分别表示操作成功、没有卡片响应和发生了错误。
  • MAX_LEN 表示每次读取或写入的最大数据长度,通常是 18 字节。
/
// Mifare_One 卡片操作指令
/

#define PICC_REQIDL           0x26               // 请求空闲卡片(寻卡),寻找处于空闲状态的卡片
#define PICC_REQALL           0x52               // 请求所有卡片(寻卡),寻找处于任何状态的卡片
#define PICC_ANTICOLL1        0x93               // 防止卡片冲突命令 1,用于在多张卡片靠近时避免同时响应
#define PICC_ANTICOLL2        0x95               // 防止卡片冲突命令 2,进一步防止多张卡片冲突
#define PICC_AUTHENT1A        0x60               // 使用 A 密钥进行认证(身份验证)
#define PICC_AUTHENT1B        0x61               // 使用 B 密钥进行认证(身份验证)
#define PICC_READ             0x30               // 读取数据命令,用于读取卡片中的特定块
#define PICC_WRITE            0xA0               // 写入数据命令,用于向卡片写入数据
#define PICC_DECREMENT        0xC0               // 减少卡片余额命令,用于从卡片余额中减去指定数值
#define PICC_INCREMENT        0xC1               // 增加卡片余额命令,用于向卡片余额中增加指定数值
#define PICC_RESTORE          0xC2               // 恢复卡片余额命令,用于恢复卡片余额到之前的状态
#define PICC_TRANSFER         0xB0               // 转移余额命令,用于将卡片中的余额转移到另一个块
#define PICC_HALT             0x50               // 停止命令,用于将卡片置于停止状态,结束与卡片的通信

/
// MF522 FIFO 队列大小定义
/

#define DEF_FIFO_LENGTH       64                 // FIFO(先进先出)队列的大小,通常设为64字节,缓存临时数据

/
// MF522 寄存器地址定义
/
// PAGE 0
#define RFU00                 0x00               // 保留地址,未使用(Reserved for future use)
#define CommandReg            0x01               // 命令寄存器,用于控制 MFRC522 模块执行操作(如启动、停止、接收、发送等)
#define ComIEnReg             0x02               // 通信中断使能寄存器,用于控制通信中断功能
#define DivlEnReg             0x03               // 分频器使能寄存器,控制分频器的启用
#define ComIrqReg             0x04               // 通信中断标志寄存器,用于标识是否发生通信中断
#define DivIrqReg             0x05               // 分频器中断标志寄存器,用于标识是否发生分频器中断
#define ErrorReg              0x06               // 错误状态寄存器,用于指示 MFRC522 模块的错误状态
#define Status1Reg            0x07               // 状态寄存器 1,表示模块的当前状态
#define Status2Reg            0x08               // 状态寄存器 2,表示模块的当前状态
#define FIFODataReg           0x09               // FIFO 数据寄存器,用于存储数据传输的临时缓冲区
#define FIFOLevelReg          0x0A               // FIFO 队列深度寄存器,表示 FIFO 中存储的字节数
#define WaterLevelReg         0x0B               // 水位寄存器,控制 FIFO 队列的水位值
#define ControlReg            0x0C               // 控制寄存器,用于控制 MFRC522 模块的基本操作
#define BitFramingReg         0x0D               // 位帧寄存器,用于配置数据传输中的帧格式
#define CollReg               0x0E               // 碰撞寄存器,用于检测和处理多卡碰撞情况
#define RFU0F                 0x0F               // 保留地址,未使用(Reserved for future use)
/
// PAGE 1
/

#define     RFU10                 0x10               // 保留地址,未使用(Reserved for future use)
#define     ModeReg               0x11               // 工作模式寄存器,用于设置 MF522 模块的操作模式,如接收、发送等
#define     TxModeReg             0x12               // 发送模式寄存器,控制发送模式的设置
#define     RxModeReg             0x13               // 接收模式寄存器,控制接收模式的设置
#define     TxControlReg          0x14               // 发送控制寄存器,用于配置数据发送的参数
#define     TxAutoReg             0x15               // 自动发送寄存器,控制自动发送功能
#define     TxSelReg              0x16               // 发送选择寄存器,用于选择发送模式
#define     RxSelReg              0x17               // 接收选择寄存器,用于选择接收模式
#define     RxThresholdReg        0x18               // 接收阈值寄存器,用于配置接收时的信号强度阈值
#define     DemodReg              0x19               // 解调寄存器,用于设置接收信号的解调方式
#define     RFU1A                 0x1A               // 保留地址,未使用
#define     RFU1B                 0x1B               // 保留地址,未使用
#define     RFU1C                 0x1C               // 保留地址,未使用
#define     RFU1D                 0x1D               // 保留地址,未使用
#define     RFU1E                 0x1E               // 保留地址,未使用
#define     SerialSpeedReg        0x1F               // 串口速度寄存器,用于配置串口通信速度

/
// PAGE 2
/

#define     RFU20                 0x20               // 保留地址,未使用
#define     CRCResultRegH         0x21               // CRC 校验结果寄存器(高位),用于存储 CRC 校验值的高字节
#define     CRCResultRegL         0x22               // CRC 校验结果寄存器(低位),用于存储 CRC 校验值的低字节
#define     RFU23                 0x23               // 保留地址,未使用
#define     ModWidthReg           0x24               // 调制宽度寄存器,用于设置调制方式的宽度
#define     RFU25                 0x25               // 保留地址,未使用
#define     RFCfgReg              0x26               // 无线电配置寄存器,用于设置无线电模块的配置
#define     GsNReg                0x27               // GsN 寄存器,用于调整无线电频率或参数
#define     CWGsCfgReg            0x28               // CW(Carrier Wave)配置寄存器,用于配置载波信号
#define     ModGsCfgReg           0x29               // 调制和载波配置寄存器,用于设置调制和载波方式
#define     TModeReg              0x2A               // 定时器模式寄存器,用于设置定时器的工作模式
#define     TPrescalerReg         0x2B               // 定时器预分频器寄存器,用于配置定时器的分频值
#define     TReloadRegH           0x2C               // 定时器重装载值寄存器(高位)
#define     TReloadRegL           0x2D               // 定时器重装载值寄存器(低位)
#define     TCounterValueRegH     0x2E               // 定时器计数器值寄存器(高位)
#define     TCounterValueRegL     0x2F               // 定时器计数器值寄存器(低位)

/
// PAGE 3
/

#define     RFU30                 0x30               // 保留地址,未使用
#define     TestSel1Reg           0x31               // 测试选择寄存器 1,用于选择测试模式
#define     TestSel2Reg           0x32               // 测试选择寄存器 2,用于选择测试模式
#define     TestPinEnReg          0x33               // 测试引脚使能寄存器,控制测试引脚是否启用
#define     TestPinValueReg       0x34               // 测试引脚值寄存器,用于设置或读取测试引脚的值
#define     TestBusReg            0x35               // 测试总线寄存器,用于控制测试总线的操作
#define     AutoTestReg           0x36               // 自动测试寄存器,用于启用自动测试功能
#define     VersionReg            0x37               // 版本寄存器,用于读取 MF522 模块的版本信息
#define     AnalogTestReg         0x38               // 模拟测试寄存器,用于进行模拟信号的测试
#define     TestDAC1Reg           0x39               // 测试 DAC1 寄存器,用于测试数字到模拟转换器 1
#define     TestDAC2Reg           0x3A               // 测试 DAC2 寄存器,用于测试数字到模拟转换器 2
#define     TestADCReg            0x3B               // 测试 ADC 寄存器,用于测试模拟到数字转换器
#define     RFU3C                 0x3C               // 保留地址,未使用
#define     RFU3D                 0x3D               // 保留地址,未使用
#define     RFU3E                 0x3E               // 保留地址,未使用
#define     RFU3F                 0x3F               // 保留地址,未使用

/
// MF522 错误码与常量定义
/

#define MI_OK                     0                // 操作成功
#define MI_NOTAGERR               1                // 没有发现卡片
#define MI_ERR                    2                // 错误发生

#define MAX_LEN                   18               // 最大数据长度,表示每次读取或写入的数据字节数(最大为 18 字节)

3.2 引脚初始化:

void STM32_SPI3_Init(void) 
{ 
    // 定义 GPIO 初始化结构体
    GPIO_InitTypeDef GPIO_InitStructure;

    // 使能 GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 配置 GPIOA 的引脚 1, 5, 6, 7 为推挽输出(用于 SPI 信号)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        // 设置为推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 设置 GPIO 速度为 50MHz
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;  // 配置引脚 1, 5, 6, 7
    GPIO_Init(GPIOA, &GPIO_InitStructure);                   // 初始化 GPIOA 的这些引脚

    // 配置 GPIOA 的引脚 4 为下拉输入(用于读取 SPI 信号)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;           // 设置为下拉输入模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;               // 配置引脚 4
    GPIO_Init(GPIOA, &GPIO_InitStructure);                   // 初始化 GPIOA 的引脚 4


    // 使能 MFRC522 片选信号为高电平(禁用 SPI 通信)
    MFRC522_CS(1);
}

3.3 SPI通信封装

// SPI3_Send:发送一个字节数据通过SPI接口
void SPI3_Send(u8 val)  
{ 
    u8 recv_data = 0, i = 0;
    RC522_SCK = 0;  // 初始化时将时钟线(SCK)设置为低电平
    for(i = 0; i < 8; i++)  // 循环发送每一位数据(共8位)
    {
        // 检查当前要发送的位值
        if(val & (1 << (7 - i)))  // 如果当前位是1
        {
            RC522_MOSI = 1;  // 设置MOSI为高电平,表示发送1
        }
        else
        {
            RC522_MOSI = 0;  // 否则,设置MOSI为低电平,表示发送0
        }
        delay_us(5);  // 延时,保证SPI时序正确
        RC522_SCK = 1;  // 拉高时钟线(SCK)开始传输数据
        delay_us(5);  // 再次延时,确保数据传输稳定
    }
}

// SPI3_Receive:接收一个字节数据通过SPI接口
u8 SPI3_Receive(void)  
{ 
    u8 recv_data = 0, i = 0;
    RC522_SCK = 0;  // 初始化时将时钟线(SCK)设置为低电平
    for(i = 0; i < 8; i++)  // 循环接收每一位数据(共8位)
    {
        RC522_MOSI = 0;  // 先将MOSI设置为低电平(不影响数据线的电平)
        delay_us(5);  // 延时,确保时序正确
        RC522_SCK = 1;  // 拉高时钟线(SCK)开始接收数据
        delay_us(5);  // 延时,确保数据稳定
        // 从MISO线读取数据
        if(RC522_MISO == 1)  // 如果MISO线为高电平,表示当前位是1
        {
            recv_data |= (1 << (7 - i));  // 将接收到的1存储到对应的位上
        }
        RC522_SCK = 0;  // 将时钟线拉低,为下一次时钟脉冲做准备
    }
    return recv_data;  // 返回接收到的字节数据
}

3.4 MFRC522通信函数封装

// 通过SPI接口与MFRC522模块进行数据交换
// 函数说明:写入MFRC522模块的寄存器
// 输入参数:addr--寄存器地址;val--要写入的值
void Write_MFRC522(u8 addr, u8 val) 
{
	// 拉低CS引脚,选择MFRC522设备
	MFRC522_CS(0);  
	// 发送寄存器地址,左移1位并掩码,选择寄存器
	SPI3_Send((addr << 1) & 0x7E);  
	// 发送要写入的值
	SPI3_Send(val);  
	// 拉高CS引脚,结束数据传输
	MFRC522_CS(1);  
}

// 通过SPI接口与MFRC522模块进行数据交换
// 函数说明:读取MFRC522模块的寄存器
// 输入参数:addr--寄存器地址
// 返回值:返回寄存器的值
u8 Read_MFRC522(u8 addr) 
{  
	u8 val;
	// 拉低CS引脚,选择MFRC522设备
	MFRC522_CS(0);     
	// 发送读取命令,地址左移并设置高位为1,表示读取操作
	SPI3_Send(((addr << 1) & 0x7E) | 0x80);   
	// 接收返回的寄存器值
	val = SPI3_Receive();    
	// 拉高CS引脚,结束数据传输
	MFRC522_CS(1);  
	// 返回读取的值
	return val;  
}

// 设置指定寄存器的某些位
// 函数说明:设置MFRC522寄存器的指定位
// 输入参数:reg--寄存器地址;mask--要设置的掩码位
void SetBitMask(u8 reg, u8 mask)   
{     
	u8 tmp = 0;
	// 读取寄存器当前值
	tmp = Read_MFRC522(reg);     
	// 使用OR操作设置掩码位
	Write_MFRC522(reg, tmp | mask);  // 设置寄存器中的掩码位
}

// 清除指定寄存器的某些位
// 函数说明:清除MFRC522寄存器的指定位
// 输入参数:reg--寄存器地址;mask--要清除的掩码位
void ClearBitMask(u8 reg, u8 mask)   
{     
	u8 tmp = 0;
	// 读取寄存器当前值
	tmp = Read_MFRC522(reg);     
	// 使用AND操作清除掩码位
	Write_MFRC522(reg, tmp & (~mask));  // 清除寄存器中的掩码位
}

// 打开天线
// 函数说明:打开MFRC522的天线
void AntennaOn(void) 
{  
	u8 temp;
	// 读取TxControl寄存器的当前状态
	temp = Read_MFRC522(TxControlReg);  
	// 如果TxControl寄存器的低2位是0,表示天线关闭,打开天线
	if ((temp & 0x03) == 0)  
	{   
		SetBitMask(TxControlReg, 0x03);  // 设置TxControl寄存器的低2位为1,打开天线
	}
}

// 关闭天线
// 函数说明:关闭MFRC522的天线
void AntennaOff(void) 
{  
	// 清除TxControl寄存器的低2位,关闭天线
	ClearBitMask(TxControlReg, 0x03);
}

// 复位MFRC522模块
// 函数说明:执行硬件和软件复位
void MFRC522_Reset(void) 
{ 
	// 硬件复位信号通过MFRC522_Rst函数控制
	// 首先拉高RST引脚,然后拉低,再拉高完成复位
	MFRC522_Rst(1);  // 拉高复位引脚
	Delay1_us(1);    // 延时1微秒
	MFRC522_Rst(0);  // 拉低复位引脚
	Delay1_us(1);    // 延时1微秒
	MFRC522_Rst(1);  // 拉高复位引脚
	Delay1_us(1);    // 延时1微秒

	// 软件复位
	Write_MFRC522(CommandReg, PCD_RESETPHASE);  // 通过写入寄存器复位MFRC522
}

3.5 MFRC522模块功能设置初始化

void MFRC522_Initializtion(void) 
{
	// 初始化SPI3接口,用于与MFRC522模块进行通信
	STM32_SPI3_Init();  
	
	// 对MFRC522进行复位,确保模块在初始化时处于已知状态
	MFRC522_Reset();         
	
	// 下面的配置是对MFRC522的一些寄存器进行初始化,设置工作模式、定时器等

	// Timer配置: TPrescaler*TreloadVal/6.78MHz = 0xD3E*0x32/6.78=25ms
	// TModeReg寄存器:配置MFRC522的定时器工作模式
	// 设置TAuto=1,表示定时器的溢出后自动重载(即自动重复计时),同时启用调制模式
	Write_MFRC522(TModeReg, 0x8D); 
	// 另外一种配置(已注释掉),用于自动重启计时器
	// Write_MFRC522(TModeReg, 0x1D); // TAutoRestart=1,表示计时器到期后自动重启,适合某些测试应用
	
	// TPrescalerReg寄存器:设置定时器的预分频器值
	// 这里设置为0x3E,表示定时器频率经过预分频后达到一个适合的值
	Write_MFRC522(TPrescalerReg, 0x3E); 
	
	// TReloadRegL寄存器:设置定时器重载值的低字节
	// TReloadRegH寄存器:设置定时器重载值的高字节
	// 组合起来,定时器溢出周期是0xD3E * 0x32 / 6.78MHz = 25ms
	Write_MFRC522(TReloadRegL, 0x32); 	// 设置定时器重载值的低字节
	Write_MFRC522(TReloadRegH, 0x00); 	// 设置定时器重载值的高字节
	
	// TxAutoReg寄存器:配置发射部分的自动调制参数
	// 这里设置为0x40,表示启用100% ASK调制
	Write_MFRC522(TxAutoReg, 0x40); 
	
	// ModeReg寄存器:配置MFRC522的工作模式
	// 这里设置为0x3D,表示启用CRC校验(0x6363)以确保数据传输的完整性
	Write_MFRC522(ModeReg, 0x3D); 
	
	// CommandReg寄存器:启动MFRC522模块
	// 0x00表示取消所有挂起的命令,启动模块准备好接收指令
	Write_MFRC522(CommandReg, 0x00); 
	
	// 这里可以配置接收增益,但此行被注释掉了
	// RFCfgReg寄存器:配置接收增益,0x7F表示48dB增益,适合远距离的接收
	// Write_MFRC522(RFCfgReg, 0x7F);   // RxGain = 48dB,增强接收灵敏度
	
	// 启用MFRC522的天线,确保设备能正常发射和接收信号
	AntennaOn();  // 打开天线,使得MFRC522能够工作并接收RFID卡片信号
}

 3.6 MFRC读写以及设置休眠

// 读取指定块的数据
// 该函数用于读取MFRC522模块中指定块地址的数据。
// 参数说明:
// blockAddr -- 要读取的块地址。
// recvData -- 接收数据的缓冲区,用于存放读取到的数据。
// 返回值:
// 如果读取成功,返回MI_OK;如果失败,返回MI_ERR。
u8 MFRC522_Read(u8 blockAddr, u8 *recvData) 
{     
    u8  status;     // 用于存储操作状态
    u16 unLen;      // 用于存储接收数据的长度
    // 设置要读取的命令和块地址
    recvData[0] = PICC_READ;     
    recvData[1] = blockAddr;     
    // 计算CRC校验码
    CalulateCRC(recvData, 2, &recvData[2]);     
    // 向卡片发送读取命令,并接收返回的数据
    status = MFRC522_ToCard(PCD_TRANSCEIVE, recvData, 4, recvData, &unLen);
    // 检查返回的状态和长度是否符合要求
    if ((status != MI_OK) || (unLen != 0x90))  // 0x90 是成功读取的返回长度
        status = MI_ERR; // 如果不符合要求,返回错误状态
    // 返回状态
    return status;
}

// 向指定块写入数据
// 该函数用于向MFRC522模块中指定块地址写入数据。
// 参数说明:
// blockAddr -- 要写入的块地址。
// writeData -- 要写入的数据,16字节。
// 返回值:
// 如果写入成功,返回MI_OK;如果失败,返回MI_ERR。
u8 MFRC522_Write(u8 blockAddr, u8 *writeData) 
{     
    u8  status;     // 用于存储操作状态
    u16 recvBits;   // 用于存储接收的数据位数
    u8  i;  
    u8  buff[18];   // 用于存放发送和接收数据的缓冲区
    // 设置要写入的命令和块地址
    buff[0] = PICC_WRITE;     
    buff[1] = blockAddr;     
    // 计算CRC校验码
    CalulateCRC(buff, 2, &buff[2]);     
    // 向卡片发送写命令,并接收返回的状态
    status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &recvBits);
    // 检查返回的状态是否符合要求
    if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
        status = MI_ERR; // 如果不符合要求,返回错误状态
    // 如果第一步成功,开始写入16字节数据
    if (status == MI_OK)     
    {         
        // 将要写入的数据填充到缓冲区
        for (i = 0; i < 16; i++)  // 向FIFO写入16字节数据
            buff[i] = *(writeData + i);
        // 计算16字节数据的CRC校验码
        CalulateCRC(buff, 16, &buff[16]);         
        // 向卡片发送数据并接收返回的状态
        status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 18, buff, &recvBits);           
        // 检查返回的状态是否符合要求
        if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))               
            status = MI_ERR; // 如果不符合要求,返回错误状态
    }          
    // 返回状态
    return status;
}

// 向卡片发送Halt命令以停止通信
// 该函数用于向MFRC522模块发送Halt命令,以终止通信。
// 终止后,卡片进入休眠状态,释放资源。
// 参数说明:
// 无参数
// 返回值:
// 无返回值。
void MFRC522_Halt(void) 
{    
    u16 unLen;     // 用于存储接收数据的长度
    u8  buff[4];   // 用于存放发送和接收数据的缓冲区
    // 设置Halt命令和参数
    buff[0] = PICC_HALT;     
    buff[1] = 0;     
    // 计算CRC校验码
    CalulateCRC(buff, 2, &buff[2]);       
    // 向卡片发送Halt命令
    MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &unLen);
}

3.7   请求卡片、抗冲突、计算 CRC、选择卡片函数

// MFRC522 请求卡片类型
// 此函数用于向 RFID 卡发送请求命令,检测并返回卡片类型。
// 输入参数:
//   reqMode -- 请求模式,指定请求类型(例如查找卡片)
//   TagType -- 返回的卡片类型
// 返回值:
//   MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Request(u8 reqMode, u8 *TagType)
{
    u8 status;
    u16 backBits;   // 接收的数据位长度

    Write_MFRC522(BitFramingReg, 0x07);  // TxLastBists = BitFramingReg[2..0]   
    TagType[0] = reqMode;  
    status = MFRC522_ToCard(PCD_TRANSCEIVE, TagType, 1, TagType, &backBits); 

    // 如果没有成功返回,或者接收到的数据位数不正确,返回错误
    if ((status != MI_OK) || (backBits != 0x10))  
    {       
        status = MI_ERR;
    }

    return status;
}

// MFRC522 防止卡片冲突
// 此函数用于防止多个卡片之间发生冲突,并返回找到的卡片序列号。
// 输入参数:
//   serNum -- 存储卡片的 4 字节序列号(如果成功找到卡片)
// 返回值:
//   MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Anticoll(u8 *serNum) 
{     
    u8  status;     
    u8  i;     
    u8  serNumCheck = 0;     
    u16 unLen;

    ClearBitMask(Status2Reg, 0x08);  			// 清除 TempSens
    ClearBitMask(CollReg, 0x80);   				// 清除 ValuesAfterColl
    Write_MFRC522(BitFramingReg, 0x00);  		// TxLastBists = BitFramingReg[2..0]
    serNum[0] = PICC_ANTICOLL1;     
    serNum[1] = 0x20;     
    status = MFRC522_ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);

    if (status == MI_OK)
    {   
        // 计算并验证卡片序列号校验位
        for(i = 0; i < 4; i++)   
            serNumCheck ^= serNum[i];

        if(serNumCheck != serNum[i])        
            status = MI_ERR;
    }

    SetBitMask(CollReg, 0x80);  // 设置 ValuesAfterColl = 1

    return status;
}

// MFRC522 计算 CRC
// 此函数用于计算 CRC 校验码,用于数据传输时的错误检测。
// 输入参数:
//   pIndata -- 需要计算 CRC 的数据
//   len -- 数据长度
// 输出参数:
//   pOutData -- 返回计算出的 CRC 结果
void CalulateCRC(u8 *pIndata, u8 len, u8 *pOutData) 
{     
    u16 i;
    u8  n;

    ClearBitMask(DivIrqReg, 0x04);   			// CRCIRQ = 0
    SetBitMask(FIFOLevelReg, 0x80);   		// 启动 FIFO
    Write_MFRC522(CommandReg, PCD_IDLE);   

    // 将数据写入 FIFO 缓存
    for (i = 0; i < len; i++)
        Write_MFRC522(FIFODataReg, *(pIndata + i));

    // 启动 CRC 计算
    Write_MFRC522(CommandReg, PCD_CALCCRC);

    // 等待 CRC 计算完成
    i = 1000;     
    do      
    {         
        n = Read_MFRC522(DivIrqReg);         
        i--;     
    } while ((i != 0) && !(n & 0x04));   // CRCIRQ = 1

    // 读取计算结果并返回
    pOutData[0] = Read_MFRC522(CRCResultRegL);     
    pOutData[1] = Read_MFRC522(CRCResultRegH);
    Write_MFRC522(CommandReg, PCD_IDLE);
}

// MFRC522 选择卡片
// 此函数用于选择特定的卡片,并返回卡片的大小。
// 输入参数:
//   serNum -- 卡片的 4 字节序列号
// 返回值:
//   卡片大小,成功则返回卡片类型大小,否则返回 0
u8 MFRC522_SelectTag(u8 *serNum) 
{     
    u8  i;     
    u8  status;     
    u8  size;     
    u16 recvBits;     
    u8  buffer[9];

    buffer[0] = PICC_ANTICOLL1;	// 发送 ANTICOLL1 命令
    buffer[1] = 0x70;
    buffer[6] = 0x00;						     
    
    // 将卡片的序列号存入缓冲区
    for (i = 0; i < 4; i++)					
    {
        buffer[i + 2] = *(serNum + i);	// buffer[2] - buffer[5] 为卡片序列号
        buffer[6]  ^= *(serNum + i);		// 校验
    }

    // 计算 CRC 校验码并添加到缓冲区
    CalulateCRC(buffer, 7, &buffer[7]);	// buffer[7] - buffer[8] 为 CRC 校验码
    ClearBitMask(Status2Reg, 0x08);
    
    // 向卡片发送选择命令
    status = MFRC522_ToCard(PCD_TRANSCEIVE, buffer, 9, buffer, &recvBits);

    // 判断是否成功并返回卡片大小
    if ((status == MI_OK) && (recvBits == 0x18))    
        size = buffer[0];     
    else    
        size = 0;

    return size; 
}

// MFRC522 卡片认证
// 此函数用于进行卡片的认证操作,例如读取卡片中的数据块。
// 输入参数:
//   authMode -- 认证模式(0x60 为认证模式 A,0x61 为认证模式 B)
//   BlockAddr -- 数据块地址
//   Sectorkey -- 存储认证密钥
//   serNum -- 卡片的序列号
// 返回值:
//   MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Auth(u8 authMode, u8 BlockAddr, u8 *Sectorkey, u8 *serNum) 
{     
    u8  status;     
    u16 recvBits;     
    u8  i;  
    u8  buff[12];    

    // 认证模式 + 块地址 + 密钥 + 卡片序列号
    buff[0] = authMode;		// 认证模式
    buff[1] = BlockAddr;	// 数据块地址
    for (i = 0; i < 6; i++)
        buff[i + 2] = *(Sectorkey + i);	// 密钥
    
    for (i = 0; i < 4; i++)
        buff[i + 8] = *(serNum + i);		// 卡片序列号
    
    // 向卡片发送认证命令
    status = MFRC522_ToCard(PCD_AUTHENT, buff, 12, buff, &recvBits);

    // 判断认证是否成功
    if ((status != MI_OK) || (!(Read_MFRC522(Status2Reg) & 0x08)))
        status = MI_ERR;

    return status;
}

四、IC卡芯片原理分析
 

4.1 介绍

基本信息
        1.每张卡有唯一32位ID
        2.卡的容量是1Kbit的EEPROM
        3.分为16个扇区,每个扇区分为4块,每块16个字节,以块为存取单位(16个扇区*4个块*16字节数据=1024字节(1K内存))
        4.每个扇区都有独立的一组密码和访问控制
内部信息
        1.扇区0的块0用来固化厂商代码;

        2.每个扇区的块0、块1、块2为数据块,可用于存贮数据。

         数据块可作两种应用:

         ★ 用作一般的数据保存,可以进行读、写操作。

         ★ 用作数据值,可以进行初始化值、加值、减值、读值操作。
        3.每个扇区的块3作为控制块,存放:密码A(6字节)、存取控制(4字节)、密码B(6字节)

        

在存取控制中每个块都有相应的三个控 制位,定义如下:

4.2  存储结构

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

 4.3 工作原理

结构:

4.4 M1射频卡与读写器的通讯:

4.5  MFRC522与IC卡之间的通讯相应代码

u8 MFRC522_ToCard(u8 command, u8 *sendData, u8 sendLen, u8 *backData, u16 *backLen)
{
    u8 status = MI_ERR;               // 初始化状态为错误状态
    u8 irqEn = 0x00;                  // 中断使能寄存器的值
    u8 waitIRq = 0x00;                // 等待中断标志
    u8 lastBits;                      // 用于保存最后的位数
    u8 n;                             // 用于存储FIFO级别寄存器的值
    u16 i;                            // 循环变量
    // 根据不同命令选择对应的中断使能和等待中断
    switch (command)
    {
        case PCD_AUTHENT:             // 鉴权命令
            irqEn = 0x12;             // 启用鉴权中断
            waitIRq = 0x10;           // 等待鉴权完成的中断
            break;
        case PCD_TRANSCEIVE:          // 发送/接收命令
            irqEn = 0x77;             // 启用发送和接收的中断
            waitIRq = 0x30;           // 等待发送/接收完成的中断
            break;
        default:
            break;
    }

    // 设置中断使能寄存器,允许对应的中断
    Write_MFRC522(ComIEnReg, irqEn | 0x80);
    // 清除中断标志寄存器中的过时中断标志
    ClearBitMask(ComIrqReg, 0x80);
    // 设置FIFO级别寄存器,清空FIFO缓冲区
    SetBitMask(FIFOLevelReg, 0x80);
    // 将MFRC522设置为IDLE状态,停止之前的命令
    Write_MFRC522(CommandReg, PCD_IDLE);

    // 将发送数据写入FIFO数据寄存器
    for (i = 0; i < sendLen; i++)
    {
        Write_MFRC522(FIFODataReg, sendData[i]);
    }

    // 发起命令
    Write_MFRC522(CommandReg, command);

    // 如果是接收命令,启动数据发送
    if (command == PCD_TRANSCEIVE)
    {
        SetBitMask(BitFramingReg, 0x80); // 启动发送
    }

    // 等待接收数据(最多2000个循环,即50ms)
    i = 2000;
    do
    {
        n = Read_MFRC522(ComIrqReg);    // 读取中断寄存器
        i--;                            // 循环计数减1
    } while ((i != 0) && !(n & 0x01) && !(n & waitIRq)); // 判断是否接收到数据或出现错误

    // 停止发送
    ClearBitMask(BitFramingReg, 0x80);

    // 如果没有超时,且没有出现错误
    if (i != 0)
    {
        if (!(Read_MFRC522(ErrorReg) & 0x1B)) // 检查是否有错误
        {
            if (n & irqEn & 0x01)  // 检查是否成功接收到数据
                status = MI_NOTAGERR; // 没有标签错误

            // 如果是发送接收命令,处理接收到的数据
            if (command == PCD_TRANSCEIVE)
            {
                n = Read_MFRC522(FIFOLevelReg); // 获取FIFO缓冲区的字节数
                lastBits = Read_MFRC522(ControlReg) & 0x07; // 获取最后的位数
                if (lastBits != 0)
                    *backLen = (n - 1) * 8 + lastBits;  // 根据最后的位数计算接收到的数据长度
                else
                    *backLen = n * 8;  // 数据长度为n个字节

                // 如果FIFO为空,设置n为1
                if (n == 0)
                    n = 1;
                // 如果FIFO数据超过最大长度,限制为最大长度
                if (n > MAX_LEN)
                    n = MAX_LEN;

                // 读取FIFO数据寄存器,将接收到的数据存储到backData数组中
                for (i = 0; i < n; i++)
                {
                    backData[i] = Read_MFRC522(FIFODataReg);
                }
            }

            status = MI_OK;  // 成功接收到数据
        }
        else
            status = MI_ERR;  // 发生错误
    }

    // 停止计时器并将MFRC522设置为IDLE状态
    Write_MFRC522(ControlReg, 0x80);
    Write_MFRC522(CommandReg, PCD_IDLE);

    return status;  // 返回操作状态
}


    // 根据不同命令选择对应的中断使能和等待中断
    switch (command)
    {
        case PCD_AUTHENT:             // 鉴权命令
            irqEn = 0x12;             // 启用鉴权中断
            waitIRq = 0x10;           // 等待鉴权完成的中断
            break;
        case PCD_TRANSCEIVE:          // 发送/接收命令
            irqEn = 0x77;             // 启用发送和接收的中断
            waitIRq = 0x30;           // 等待发送/接收完成的中断
            break;
        default:
            break;
    }

鉴权命令:

利用 Write_MFRC522(ComIEnReg, irqEn | 0x80);将CommIEnReg设置启动

IdleIEn 中断使能位

  IdleIEn 代表的是 空闲中断使能,通常用于处理设备在空闲状态时的一些操作。具体来说,IdleIEn 用于控制当系统或设备处于空闲状态时,是否触发中断。

ErrIEn错误中断使能

  ErrIEn 的作用是控制错误发生时是否触发中断。开启(使能)ErrIEn 后,如果设备或系统发生了预定义的错误情况,将会触发中断请求,通知系统进行处理。这通常有助于系统在出现故障或异常时,迅速采取措施进行处理或修复。

非上0X80是为了启动最高位IRqInv,启动中断使能

设置waitIRq = 0x10;           // 等待鉴权完成的中断

      通过这句while ((i != 0) && !(n & 0x01) && !(n & waitIRq)); 判断相应的中断有没用产生,例如此设置,判断空闲中断标志位有没用置一。

发送/接收命令同上设置。

五 、在main函数中使用

5.1  录入新卡

u8 Add_Rfid(void)
{
    u8 ID;               // 定义一个变量来存储输入的ID
    u16 time6=0;         // 用于计时的变量
    u8 i, key_num, status=1, card_size;  // 初始化多个变量
    OLED_Clear();        // 清屏
    Show_Str(0,0,128,16,"=== 添加RFID卡 ===",16,0);  // 在OLED显示屏上显示标题
    Show_Str(0,20,128,12,"请按下添加RFID卡:",12,0);  // 提示用户按下按钮以添加RFID卡
    Show_Str(0,52,128,12,"等待中",12,0);  // 显示“等待中”提示
    OLED_Refresh_Gram(); // 更新OLED显示内容

    MFRC522_Initializtion();  // 初始化MFRC522模块(RFID读写器)

    while(1)
    {
        AntennaOn();  // 打开天线,准备接收卡片信号
        status = MFRC522_Request(0x52, card_pydebuf);  // 请求卡片,如果有卡片,返回状态
        if(status == 0)  // 如果成功检测到卡片
        {
            printf("rc522 ok\r\n");  // 输出调试信息
            Show_Str(0,38,128,12,"检测到卡片!",12,0);  // 在OLED显示屏上显示“检测到卡片”
            OLED_Refresh_Gram();  // 更新OLED显示内容
            status = MFRC522_Anticoll(card_numberbuf);  // 防碰撞,获取卡片ID
            card_size = MFRC522_SelectTag(card_numberbuf);  // 选择卡片
            status = MFRC522_Auth(0x60, 4, card_key0Abuf, card_numberbuf);  // 认证卡片(使用密钥)
            status = MFRC522_Write(4, card_writebuf);  // 向卡片写入数据
            status = MFRC522_Read(4, card_readbuf);  // 从卡片读取数据

            printf("卡片的ID是:");
            for(i=0; i<5; i++)
            {
                printf("%#x ", card_numberbuf[i]);  // 输出卡片ID
            }
            printf("\r\n");

            AntennaOff();  // 关闭天线

            OLED_Clear_NOupdate();  // 清除显示但不更新OLED
            Show_Str(0,12,128,12,"请输入ID(0-9):  ",12,0);  // 提示用户输入ID
            Show_Str(122,52,128,12," ",12,0);  // 清除显示的内容
            Show_Str(0,52,128,12,"确认 输入 完成 退出",12,0);  // 提示用户输入完成后退出
            OLED_Refresh_Gram();  // 更新OLED显示内容

            do
                ID = GET_NUM();  // 获取用户输入的ID
            while(!(ID < 10));  // 确保ID是0到9之间的数字

            printf("输入的ID是:%d\r\n", ID);  // 输出用户输入的ID
            OLED_Clear_NOupdate();  // 清除显示但不更新OLED
            Show_Str(0,38,128,12,"保存ID信息.",12,0);  // 显示“保存ID信息”
            OLED_Refresh_Gram();  // 更新OLED显示内容

            for(i=0; i<5; i++)
            {
                sys.cardid[ID][i] = card_numberbuf[i];  // 将卡片ID存入系统中的指定位置
            }

            STMFLASH_Write(SYS_SAVEADDR, (u16*)&sys, sizeof(sys));  // 将数据写入FLASH存储

            for(i=0; i<10; i++)
                printf("cardid={%X,%X,%X,%X}\r\n", sys.cardid[i][0], sys.cardid[i][1], sys.cardid[i][2], sys.cardid[i][3]);  // 输出系统中所有卡片的ID

            Show_Str(0,38,128,12,"添加成功!",12,0);  // 在OLED显示屏上显示“添加成功”
            OLED_Refresh_Gram();  // 更新OLED显示内容
            delay_ms(1000);  // 延迟1秒
            OLED_Clear();  // 清除显示
            return 0;  // 退出函数
        }

        key_num = Button4_4_Scan();  // 扫描按键输入
        time6++;  // 增加计时器
        if(time6 % 50 == 0 || key_num == 13)  // 如果经过50次循环或者按下某个特定按键
        {
            OLED_Clear();  // 清除显示
            return 1;  // 退出函数
        }
    }
}

5.2  rfid锁验证 

u8 MFRC522_lock(void)
{
    u8 i, j, status = 1, card_size;
    u8 count;
    u8 prtfbuf[64];
    
    // 开启RFID天线
    AntennaOn();

    // 请求检测是否有RFID卡片
    status = MFRC522_Request(0x52, card_pydebuf);
    if(status == 0)  // 如果成功检测到卡片
    {
        // 如果超过最大错误次数,退出函数
        if(DisErrCnt()) return -1;  // 超过错误次数,返回错误

        // 检测到卡片,开始进行防碰撞操作,获取卡片ID
        status = MFRC522_Anticoll(card_numberbuf);
        // 选择卡片
        card_size = MFRC522_SelectTag(card_numberbuf);
        // 使用密钥认证卡片
        status = MFRC522_Auth(0x60, 4, card_key0Abuf, card_numberbuf);
        // 向卡片写入数据
        status = MFRC522_Write(4, card_writebuf);
        // 从卡片读取数据
        status = MFRC522_Read(4, card_readbuf);
        
        count = 0;  // 初始化计数器
        
        // 遍历系统中存储的卡片ID,比较当前卡片与系统中存储的卡片ID
        for(j = 0; j < 10; j++)
        {
            printf("\r\n卡片 %d 的对比结果:", j);
            for(i = 0; i < 5; i++)
            {
                printf("%x=%x    ", card_numberbuf[i], sys.cardid[j][i]);
                // 比较当前卡片ID和系统中保存的ID
                if(card_numberbuf[i] == sys.cardid[j][i]) count++;  // 如果匹配则计数
            }
            printf("\r\n");

            // 如果有4个字节匹配,认为是有效的卡片
            if(count >= 4)
            {
                sys.errCnt = 0;  // 重置错误计数
                OLED_Clear_NOupdate();  // 清除OLED显示,但不更新
                sprintf(prtfbuf, "RFID:%d解锁成功", j);  // 显示解锁成功信息
                Show_Str(12, 13, 128, 20, prtfbuf, 12, 0);  // 在OLED显示上输出卡片号
                OLED_Refresh_Gram();  // 更新OLED显示
                delay_ms(500);  // 延迟500毫秒
                DisUnLock();  // 调用解锁操作
                return 0;  // 解锁成功,返回0
            }
            else
                count = 0;  // 如果没有匹配,重置计数器
        }
        
        // 如果没有找到匹配的卡片,增加错误计数
        sys.errCnt++;

        // 如果错误计数超过最大次数,设定锁定时间
        if(sys.errCnt > MAXERRTIMES)
            sys.errTime = 30;  // 锁定30秒,无法解锁
        
        // 显示错误信息
        OLED_Clear();  // 清除显示
        Show_Str(12, 13, 128, 20, "卡片验证失败", 12, 0);  // 显示“卡片验证失败”
        OLED_Refresh_Gram();  // 更新OLED显示
        beep_on_mode1();  // 发出警告音
        OLED_Clear();  // 清空显示
        OLED_Show_Font(56, 48, 0);  // 显示锁定图标
        DisFlag = 1;  // 设置标志位表示锁定状态
    }
    
    // 关闭RFID天线
    AntennaOff();

    return 1;  // 返回1表示锁定操作完成
}

更详细的代码解析可以参考我的新文章,下面是链接:

解析MFRC522封装代码-CSDN博客

 参考连接:第49集:RFID-RC522射频卡门禁卡_哔哩哔哩_bilibili

参考链接:RFID射频模块(MFRC522 STM32)_哔哩哔哩_bilibili

参考文件资料(百度网盘): https://pan.baidu.com/s/1B-FoQOjUbdM8Pw2X-sAm4A

提取码: skb3

### 使用软件SPI驱动MFRC522 RFID模块 为了实现通过软件SPI接口与MFRC522通信,在STM32平台上可以采取如下方法: #### 初始化GPIO引脚配置 首先,需要定义用于模拟SPI总线的四个主要信号线——SCK(串行时钟),MOSI(主输出从输入),MISO(主输入从输出)以及NSS(片选)[^1]。 ```c void SPI_Software_Init(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // 假设使用PA端口 /* SCK */ GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* MOSI */ GPIO_InitStruct.Pin = GPIO_PIN_7; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* MISO */ GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* NSS */ GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOA, &GPIO📐⚗⚗ ⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗📐⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗📐⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值