【STM32】STM32+RC522-射频IC识别模块

看了很多文章都没有详细讲过RC522和IC卡,这里总结一下,做个笔记

一、RC522

RFID-RC522 是一种常见的射频识别(RFID)模块,用于近距离无线通信。以下是一些基本信息:

主要特点

  • 通信协议: 使用 SPI 通信,与单片机、树莓派等设备轻松集成。
  • 频率: 工作在 13.56 MHz 的高频(HF)范围。
  • 支持协议: 符合 ISO/IEC 14443 标准,支持 MIFARE 卡(如 MIFARE Classic 1K/4K 等)。
  • 供电电压: 通常为 3.3V,但部分模块支持 5V 电平输入。
  • 读写范围: 一般在 2-5 厘米,具体取决于天线设计和环境。
  • 常用功能

    1. 读取卡片 UID: 可以获取 RFID 卡的唯一标识符。
    2. 读写数据块: 支持对 MIFARE 卡片的数据块进行读写操作(需要密钥认证)。
    3. 防碰撞机制: 可识别多个卡片并与目标卡通信。
    4. 功耗低: 支持节能模式。

    硬件接口

    RC522 模块有以下引脚:

  • VCC: 供电(3.3V 或 5V,取决于模块设计)。
  • GND: 地。
  • SCK: SPI 时钟。
  • MOSI: 主机输出从机输入。
  • MISO: 主机输入从机输出。
  • NSS/SDA: SPI 片选/模块选择。
  • RST: 模块复位。
  • 常见应用

  • 门禁系统。
  • 电子支付。
  • 智能考勤。
  • 物流追踪。
  • 二、IC卡

    关于详细的IC卡的介绍可以看下面这个链接,讲的非常详细

    【【教程】手把手教你玩转IC卡(门禁卡、电梯卡、饭卡、水卡、校园卡、一卡通)】https://www.bilibili.com/video/BV11h4y1T7Pm?vd_source=065123a0ee559bc650cb8a6e66c8de94

    我们生活中常见的一般都是cuid卡,它有16个扇区,依次为0扇区-15扇区,每个扇区又分为4个区块,一个区块有16个字节,所以一个扇区有64个字节,卡总共有1024个字节,就是1KB。

    卡的0扇区的第一个区块包含了卡号,校验值,卡类型,卡类型代号,生产厂商信息。第一个区块的前四个字节是卡号,所以我们的卡号是00 00 00 00 – FF FF FF FF的范围内,如果卡号是唯一的,那么就有16^8=4294967296个卡号,42亿多个,一时半会儿也用不完,如果用完了也可以像ipv4->ipv6那样扩展,将六个字节作为卡号,那不就取之不尽用之不竭了吗。我们的身份证的卡号就是6个字节的卡号。当 4 字节 UID 不足以满足需求时,可以使用 7 字节或 10 字节的 UID。这类似于互联网的 IPv4 向 IPv6 的扩展,虽然理论上有限,但可以通过增加长度来大幅度扩展编号空间。

    做一个简易门禁系统可以通过验证uid(卡号)来验证身份,但是问题就是卡号很容易被复制,所以我们就可以用上其他扇区。每个扇区的第四个区块是控制块,在这个区块中,前六个字节是密钥A,后六个字节是密钥B,中间四个字节一般是厂商写好固定的FF 07 80 69。我们想要增加门禁系统的安全性,就可以将01扇区(第2扇区)的第1区块的第一个字节设置为FF,如果你这个字节是FF那你就是我们小区的,如果不是FF就不是我们小区的。在我们读取这个区块的内容的时候,需要验证密钥A或者秘钥B才能访问这个扇区里面的数据,所以我们还可以修改密钥实现简单的加密效果,这里配上后面的代码更好理解。

    三、通信前的准备

    我们使用stm32个四个GPIO模拟SPI与RC522进行通信

    1、SPI协议层

    /*引脚配置*/
    #define RC522_CS(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)(x))
    #define RC522_SCK(x)	GPIO_WriteBit(GPIOB, GPIO_Pin_13, (BitAction)(x))
    #define RC522_DO(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_15, (BitAction)(x))
    #define RC522_DI()		GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)
    
    
    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     通过 RC522 向设备发送一个字节的数据
    // 参数说明     uint8_t data  - 要发送的数据字节
    // 返回参数     void
    // 使用示例     RC522_SendByte(0x3F); // 发送一个字节 0x3F 到 RC522
    // 备注信息     该函数通过控制 SCK 引脚来逐位发送数据,每发送一个位,SCK 会先拉高再拉低。
    //              发送完成后,SCK 会返回低电平,等待下一位数据发送。
    //-------------------------------------------------------------------------------------------------------------------
    void RC522_SendByte(uint8_t data) 
    {
        for (uint8_t i = 0; i < 8; i++) 
    	{
            // 发送高位
            RC522_DO(data & 0x80);
            data <<= 1;
    
            // 拉高 SCK
            RC522_SCK(1);
            RC522_Delay(1);
    
            // 拉低 SCK
            RC522_SCK(0);
            RC522_Delay(1);
        }
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     从 RC522 读取一个字节的数据
    // 参数说明     void
    // 返回参数     uint8_t - 读取到的字节数据
    // 使用示例     uint8_t byte = RC522_ReadByte(); // 从 RC522 读取一个字节的数据
    // 备注信息     该函数通过 SCK 引脚控制 SPI 时钟,读取 MISO 引脚的数据,逐位构建接收到的数据字节。
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_ReadByte(void) 
    {
        uint8_t data = 0;
        for (uint8_t i = 0; i < 8; i++) 
    	{
            // 拉高 SCK
            RC522_SCK(1);
            RC522_Delay(1);
    
            // 读取 MISO 引脚
            data <<= 1;
            if (RC522_DI())
    		{
                data |= 0x01;
            }
    
            // 拉低 SCK
            RC522_SCK(0);
            RC522_Delay(1);
        }
        return data;
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     向 RC522 寄存器写入一个字节的数据
    // 参数说明     uint8_t addr - 寄存器地址
    //              uint8_t value - 要写入的数据
    // 返回参数     void
    // 使用示例     RC522_WriteReg(0x01, 0x0F); // 向 RC522 的地址为 0x01 的寄存器写入值 0x0F
    // 备注信息     该函数通过 SPI 总线向指定寄存器写入数据。发送时会先发送寄存器地址,地址最低位为 0 表示写操作,然后发送要写入的值。          
    //-------------------------------------------------------------------------------------------------------------------
    void RC522_WriteReg(uint8_t addr, uint8_t value) 
    {
        RC522_CS(0); // 片选拉低
        RC522_SendByte((addr << 1) & 0x7E); // 地址左移,最低位 0 表示写
        RC522_SendByte(value); // 写入值
        RC522_CS(1); // 片选拉高
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     从 RC522 寄存器读取一个字节的数据
    // 参数说明     uint8_t addr - 寄存器地址
    // 返回参数     uint8_t - 读取到的寄存器数据
    // 使用示例     uint8_t value = RC522_ReadReg(0x01); // 读取 RC522 地址为 0x01 的寄存器数据
    // 备注信息     该函数通过 SPI 总线读取 RC522 寄存器的数据。
    //              发送时会先发送寄存器地址,地址最低位为 1 表示读操作,然后读取返回值。
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_ReadReg(uint8_t addr) 
    {
        uint8_t value;
        RC522_CS(0); // 片选拉低
        RC522_SendByte(((addr << 1) & 0x7E) | 0x80); // 地址左移,最低位 1 表示读
        value = RC522_ReadByte(); // 读取值
        RC522_CS(1); // 片选拉高
        return value;
    }
    
    void RC522_Init(void)
    {
    	RC522_GPIO_Init();
    	
        // 软件复位
        RC522_WriteReg(0x01, 0x0F);
        RC522_Delay(10);
    
        // 配置寄存器 (根据 RC522 数据手册配置)
        RC522_WriteReg(RC522_ModeReg, 0x3D);		
        RC522_WriteReg(RC522_TReloadRegL, 0x1E);
        RC522_WriteReg(RC522_TReloadRegH, 0x00);	
        RC522_WriteReg(RC522_TModeReg, 0x8D);
        RC522_WriteReg(RC522_TPrescalerReg, 0x3E);
        RC522_WriteReg(RC522_TxASKReg, 0x40);	
    
    	RC522_AntennaOn();
    }
    
    /* 延迟函数 */
    void RC522_Delay(uint16_t time) 
    {
    	Delay_us(time);
    }

    2、寄存器重定义

    芯片手册中有RC522所有寄存器的介绍和地址,在RC522.h文件中添加RC522寄存器的重定义

    #define DEF_FIFO_LENGTH       64                 //FIFO size=64byte
    #define MAXRLEN  18
    
    /* RC522 寄存器定义 */
    // 页面 0:命令和状态
    #define RC522_RFU00                 0x00  // 保留
    #define RC522_CommandReg            0x01  // 命令寄存器
    #define RC522_ComIEnReg             0x02  // 中断使能寄存器
    #define RC522_DivIEnReg             0x03  // 中断使能寄存器(分频器)
    #define RC522_ComIrqReg             0x04  // 中断请求寄存器
    #define RC522_DivIrqReg             0x05  // 中断请求寄存器(分频器)
    #define RC522_ErrorReg              0x06  // 错误标志寄存器
    #define RC522_Status1Reg            0x07  // 通信状态寄存器 1
    #define RC522_Status2Reg            0x08  // 通信状态寄存器 2
    #define RC522_FIFODataReg           0x09  // FIFO 数据寄存器
    #define RC522_FIFOLevelReg          0x0A  // FIFO 水位寄存器
    #define RC522_WaterLevelReg         0x0B  // 水平线设置寄存器
    #define RC522_ControlReg            0x0C  // 控制寄存器
    #define RC522_BitFramingReg         0x0D  // 位控制寄存器
    #define RC522_CollReg               0x0E  // 冲突检测寄存器
    #define RC522_RFU0F                 0x0F  // 保留
    
    // 页面 1:命令配置
    #define RC522_Reserved10            0x10  // 保留
    #define RC522_ModeReg               0x11  // 模式寄存器
    #define RC522_TxModeReg             0x12  // 发射模式寄存器
    #define RC522_RxModeReg             0x13  // 接收模式寄存器
    #define RC522_TxControlReg          0x14  // 发射控制寄存器
    #define RC522_TxASKReg              0x15  // 发射调制寄存器
    #define RC522_TxSelReg              0x16  // 发射选择寄存器
    #define RC522_RxSelReg              0x17  // 接收选择寄存器
    #define RC522_RxThresholdReg        0x18  // 接收阈值寄存器
    #define RC522_DemodReg              0x19  // 解调寄存器
    #define RC522_RFU1A                 0x1A  // 保留
    #define RC522_RFU1B                 0x1B  // 保留
    #define RC522_MfTxReg               0x1C  // MIFARE 发射寄存器
    #define RC522_MfRxReg               0x1D  // MIFARE 接收寄存器
    #define RC522_RFU1E                 0x1E  // 保留
    #define RC522_SerialSpeedReg        0x1F  // 串行通信速度寄存器
    
    // 页面 2:CRC、计时器
    #define RC522_Reserved20            0x20  // 保留
    #define RC522_CRCResultRegH         0x21  // CRC 结果高字节
    #define RC522_CRCResultRegL         0x22  // CRC 结果低字节
    #define RC522_RFU23                 0x23  // 保留
    #define RC522_ModWidthReg           0x24  // 调制宽度寄存器
    #define RC522_RFU25                 0x25  // 保留
    #define RC522_RFCfgReg              0x26  // 接收配置寄存器
    #define RC522_GsNReg                0x27  // 选择寄存器
    #define RC522_CWGsPReg              0x28  // 发射功率寄存器
    #define RC522_ModGsPReg             0x29  // 调制功率寄存器
    #define RC522_TModeReg              0x2A  // 定时模式寄存器
    #define RC522_TPrescalerReg         0x2B  // 定时预分频寄存器
    #define RC522_TReloadRegH           0x2C  // 定时重载高字节
    #define RC522_TReloadRegL           0x2D  // 定时重载低字节
    #define RC522_TCounterValRegH       0x2E  // 定时器计数器高字节
    #define RC522_TCounterValRegL       0x2F  // 定时器计数器低字节
    
    // 页面 3:测试寄存器
    #define RC522_Reserved30            0x30  // 保留
    #define RC522_TestSel1Reg           0x31  // 测试选择 1 寄存器
    #define RC522_TestSel2Reg           0x32  // 测试选择 2 寄存器
    #define RC522_TestPinEnReg          0x33  // 测试针脚使能寄存器
    #define RC522_TestPinValueReg       0x34  // 测试针脚值寄存器
    #define RC522_TestBusReg            0x35  // 测试总线寄存器
    #define RC522_AutoTestReg           0x36  // 自检寄存器
    #define RC522_VersionReg            0x37  // 版本寄存器
    #define RC522_AnalogTestReg         0x38  // 模拟测试寄存器
    #define RC522_TestDAC1Reg           0x39  // 测试 DAC1 寄存器
    #define RC522_TestDAC2Reg           0x3A  // 测试 DAC2 寄存器
    #define RC522_TestADCReg            0x3B  // 测试 ADC 寄存器
    #define RC522_RFU3C                 0x3C  // 保留
    #define RC522_RFU3D                 0x3D  // 保留
    #define RC522_RFU3E                 0x3E  // 保留
    #define RC522_RFU3F                 0x3F  // 保留
    #define RC522_Apezer				0x66  // 防伪标签,作者WX寄存器
    
    /* RC522 命令定义 */
    #define     PCD_IDLE              0x00               // 进入空闲模式
    #define     PCD_AUTHENT           0x0E               // 认证命令
    #define     PCD_RECEIVE           0x08               // 接收数据
    #define     PCD_TRANSMIT          0x04               // 发送数据
    #define     PCD_TRANSCEIVE        0x0C               // 发送并接收数据
    #define     PCD_RESETPHASE        0x0F               // 复位阶段
    #define     PCD_CALCCRC           0x03               // CRC 计算命令
    
    /* Mifare_One 命令寄存器定义 */
    #define PICC_REQIDL           0x26               // 请求处于空闲状态的卡片 (不响应未被选中的卡片)
    #define PICC_REQALL           0x52               // 请求所有卡片(选择多个卡片)
    #define PICC_ANTICOLL1        0x93               // 防碰撞检测命令(第一部分)
    #define PICC_ANTICOLL2        0x95               // 防碰撞检测命令(第二部分)
    #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               // 停止命令(使卡片停止并进入休眠状态)
    
    #define MI_OK           0x00    // 操作成功
    #define MI_ERR          0x01    // 操作失败
    #define MI_NOTAGERR     0x02    // 没有卡片
    #define MI_WRONG        0x03    // 错误的命令
    

    3、RC522_ToCard

    该函数是利用rc522和卡通信的核心函数,该函数的第一个参数command是要执行的命令,第二个参数是发送的命令数组,第三个是发送命令数组的长度,第四个参数用于接收从RC522返回的数据,第五个参数是返回数据数组的长度

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     与 MFRC522 通信,执行特定命令(如认证或数据传输)
    // 参数说明      command  : 要执行的命令(如 PCD_AUTHENT 或 PCD_TRANSCEIVE)
    //               sendData : 发送到 MFRC522 的数据指针
    //               sendLen  : 发送数据的长度
    //               backData : 接收数据的指针(用于存储从 MFRC522 返回的数据)
    //               backLen  : 接收数据的长度指针(用于返回数据位数)
    // 返回参数     uint8_t : 执行状态
    //               MI_OK        - 成功
    //               MI_ERR       - 错误
    //               MI_NOTAGERR  - 未检测到卡片
    // 使用示例      uint8_t status;
    //               uint8_t sendData[] = {0x30, 0x00};
    //               uint8_t backData[16];
    //               uint16_t backLen;
    //               status = RC522_ToCard(PCD_TRANSCEIVE, sendData, 2, backData, &backLen);
    // 备注信息      - PCD_AUTHENT:用于验证密钥
    //               - PCD_TRANSCEIVE:用于发送和接收数据
    //               - 如果返回 MI_OK,backData 和 backLen 会包含从卡片返回的数据
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_ToCard(uint8_t command, uint8_t *sendData, uint8_t sendLen, uint8_t *backData, uint16_t *backLen)
    {
        uint8_t status = MI_ERR;
        uint8_t irqEn = 0x00;
        uint8_t waitIRq = 0x00;
        uint8_t n, lastBits;
        uint16_t i;
    
        // 根据命令设置中断使能位和等待标志位
        if (command == PCD_AUTHENT) {
            irqEn = 0x12; // 允许认证相关中断
            waitIRq = 0x10; // 等待认证完成中断
        } else if (command == PCD_TRANSCEIVE) {
            irqEn = 0x77; // 允许所有与传输相关的中断
            waitIRq = 0x30; // 等待发送完成或接收完成中断
        }
    
        RC522_WriteReg(RC522_ComIEnReg, irqEn | 0x80); // 设置中断使能寄存器
        ClearBitMask(RC522_ComIrqReg, 0x80);           // 清除中断标志位
        RC522_WriteReg(RC522_CommandReg, PCD_IDLE);    // 进入空闲模式
        RC522_WriteReg(RC522_FIFOLevelReg, 0x80);      // 清空 FIFO 数据缓冲区
    
        // 将数据写入 FIFO
        for (i = 0; i < sendLen; i++) {
            RC522_WriteReg(RC522_FIFODataReg, sendData[i]);
        }
    
        // 发起命令
        RC522_WriteReg(RC522_CommandReg, command);
        if (command == PCD_TRANSCEIVE) {
            SetBitMask(RC522_BitFramingReg, 0x80); // 开始发送数据
        }
    
        // 等待命令完成
        i = 1500; // 超时计数器
        do {
            n = RC522_ReadReg(RC522_ComIrqReg); // 读取中断标志寄存器
            i--;
        } while ((i != 0) && !(n & 0x01) && !(n & waitIRq));
    
        ClearBitMask(RC522_BitFramingReg, 0x80); // 停止发送数据
    
        // 检查结果
        if (i != 0) { // 如果未超时
            if (!(RC522_ReadReg(RC522_ErrorReg) & 0x1B)) { // 无传输错误
                status = MI_OK;
                if (n & irqEn & 0x01) {
                    status = MI_NOTAGERR; // 没有检测到卡片
                }
    
                if (command == PCD_TRANSCEIVE) {
                    n = RC522_ReadReg(RC522_FIFOLevelReg); // 读取 FIFO 中接收数据的字节数
                    lastBits = RC522_ReadReg(RC522_ControlReg) & 0x07; // 读取最后几位
                    if (lastBits) {
                        *backLen = (n - 1) * 8 + lastBits; // 数据位数
                    } else {
                        *backLen = n * 8;
                    }
    
                    // 从 FIFO 中读取接收到的数据
                    if (n > 0 && n < 16) {
                        for (i = 0; i < n; i++) {
                            backData[i] = RC522_ReadReg(RC522_FIFODataReg);
                        }
                    }
                }
            } else {
                status = MI_ERR;
            }
        }
    
        return status;
    }

    四、通信

    1、RC522_Request (寻卡)

    第一个参数为命令,第二个参数用于接收返回的数据,当发出寻卡命令时,会返回两个字节的数据,这两个字节对应这卡类型的代码,也就是卡片中第一个区块的第七个和第八个字节         0x4400 – Mifare_UltraLight         0x0400 – Mifare_One(S50)
            0x0200 – Mifare_One(S70)        0x0800 – Mifare_Pro(X)        0x4403 – Mifare_DESFire我们身边的门禁卡一般都是0400,发送命令数组就一个字节,是这个函数的第一个参数

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     寻卡操作,检测 MFRC522 感应区内的卡片
    // 参数说明      reqMode  : 寻卡方式
    //                          0x52 - 寻感应区内所有符合 14443A 标准的卡
    //                          0x26 - 寻未进入休眠状态的卡
    //               backData : 指向返回卡片类型代码的指针
    //                          0x4400 - Mifare_UltraLight
    //                          0x0400 - Mifare_One(S50)
    //                          0x0200 - Mifare_One(S70)
    //                          0x0800 - Mifare_Pro(X)
    //                          0x4403 - Mifare_DESFire
    // 返回参数     uint8_t : 执行状态
    //               MI_OK  - 成功
    //               MI_ERR - 错误
    // 使用示例     uint8_t status;
    //               uint8_t tagType[2];
    //               status = RC522_Request(PICC_REQALL, tagType);
    // 备注信息      - 在调用本函数之前,需要确保 MFRC522 已初始化且天线已打开
    //               - 返回的 backData 包含两字节的卡片类型代码
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_Request(uint8_t reqMode, uint8_t *backData)
    {
        uint8_t status;
        uint8_t command = PCD_TRANSCEIVE;	
    	uint8_t send_command[2]; 
    	uint8_t back_Data[MAXRLEN];	
    	uint16_t recvBits;
    
    	ClearBitMask(RC522_Status2Reg, 0x08);
    	RC522_WriteReg(RC522_BitFramingReg, 0x07);
    	SetBitMask(RC522_TxControlReg, 0x03);
        RC522_WriteReg(RC522_ControlReg, 0x00); // 清除之前的中断
    	
    	send_command[0] = reqMode;
    
        // 发送请求并等待响应
        status = RC522_ToCard(command, send_command, 1, back_Data, &recvBits);
    	
    	if ((status == MI_OK) && (recvBits == 0x10))
    	{    
    		*backData     = back_Data[0];
    		*(backData+1) = back_Data[1];
    	}
    	else
    	{   
    		status = MI_ERR;  
    	}	
        return status;
    }

    2、RC522_Anticoll(防冲突)

    这个函数会返回卡片的4字节uid和1字节的和校验,也就是卡的第一个区块的前五个字节。snr_check ^= back_Data[i];将4个字节的uid进行异或就可以得到第五个字节的和校验,发送命令数组包含两个字节,第一个字节是防冲突命令,第二个字节是NVB参数-0x20,调用RC522_ToCard就可以获得返回的数据

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     防冲突命令,获取卡片 UID
    // 参数说明     uint8_t *backData - 存储返回的数据
    // 返回参数     uint8_t - 返回的状态,MI_OK 表示成功
    // 使用示例     uint8_t status = RC522_Anticoll(backData); 
    // 备注信息     该函数用于从卡片读取 UID,防止冲突并返回 UID。
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_Anticoll(uint8_t *pSnr)
    {
        uint8_t status;
        uint8_t i, snr_check = 0;
        uint8_t command = PCD_TRANSCEIVE;
    	uint8_t send_command[2] = {PICC_ANTICOLL1, 0x20}; // 防冲突命令 + NVB 参数
        uint8_t back_Data[MAXRLEN];
        uint16_t recvBits;
    
        ClearBitMask(RC522_Status2Reg, 0x08);	// 清除 Status2Reg 寄存器的 TempSensclear 位,确保温度传感器不会影响后续操作
        RC522_WriteReg(RC522_BitFramingReg, 0x00);	// 配置 BitFramingReg 寄存器,使帧的最后字节全部传输 8 位,无接收对齐偏移
        ClearBitMask(RC522_CollReg, 0x80);	// 清除 CollReg 寄存器的 ValuesAfterColl 位,确保碰撞后寄存器不保留冲突位数据
    
        // 启动防冲突命令
        status = RC522_ToCard(command, send_command, 2, back_Data, &recvBits);
    	
    	if (status == MI_OK)
    	{
    		for (i = 0; i < 4; i++)
    		{   
    			 *(pSnr+i)  = back_Data[i];
    			 snr_check ^= back_Data[i];
    		}
    		if (snr_check != back_Data[i])
    		{   
    			status = MI_ERR;   
    		}
    		else 
    			*(pSnr + 4) = snr_check;
    	}
        
        SetBitMask(RC522_CollReg, 0x80);
        return status;
    }

    3、RC522_SelectTag(选卡)

    这个函数会返回三个字节,第一个字节卡类别代号,后面两个字节是CRC校验和,发送的命令数组有九个字节,第一个字节是选择命令,第二个字节是NVB参数-0x70,第三个到第七个字节是卡片的uid和异或校验(卡片的前五个字节),第八个和第九个字节是前7个字节的CRC校验(CRC校验由RC522的硬件完成),这样就构成了选卡的9字节的命令

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     RC522_SelectTag - 选择指定的 RFID 标签
    // 参数说明     uint8_t *serNum - 指向 5 字节序列号数组的指针,用于指定要选择的标签
    // 返回参数     uint8_t - 状态码 (MI_OK 表示成功,其他值表示失败)
    // 使用示例     uint8_t status = RC522_SelectTag(serialNumber);
    //             if (status == MI_OK) { /* 选择成功 */ }
    // 备注信息     此函数向 RFID 标签发送选择命令 (Select) 并读取返回的响应数据。
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_SelectTag(uint8_t *serNum)
    {
        uint8_t status;
        uint8_t i;
        uint8_t command = PCD_TRANSCEIVE;
    	uint8_t send_command[9];
        uint8_t back_Data[MAXRLEN];
        uint16_t recvBits;
    
        ClearBitMask(RC522_Status2Reg, 0x08);
        RC522_WriteReg(RC522_BitFramingReg, 0x00);
        ClearBitMask(RC522_CollReg, 0x80);
    
    	send_command[0] = PICC_ANTICOLL1;
    	send_command[1] = 0x70;
    	for (i = 0; i < 5; i++)
    	{
    		send_command[i+2] = *(serNum+i);
    	}
    	CalculateCRC(send_command, 7, &send_command[7]);
    	
        status = RC522_ToCard(command, send_command, 9, back_Data, &recvBits);
    	
    	if ((status == MI_OK) && (recvBits == 0x18))
    	{
    		for (i = 0; i < 3; i++)
    		{
    			*(serNum+i) = back_Data[i];
    		}
    	}
    	
        return status;
    }

    4、RC522_Auth(验证密钥)

    以验证第二扇区的密钥A为例,第一个参数authMode是验证模式,可以是PICC_AUTHENT1A(验证密钥A),也可以是PICC_AUTHENT1B(验证密钥B),第二个参数BlockAddr是区块的地址,比如说我想读取第二扇区的第一区块,那么这个区块的地址就是04,第三个参数Sectorkey就是密钥了,IC卡白卡的密钥出厂都是六个0xFF,当我们想提高安全性时,就可以将数据放在某个扇区里,再修改这个扇区的密钥,实现简易的加密效果。验证完密钥,就可以读取这个扇区里区块的数据了,也可以向这个扇区的区块里写入数据。每个扇区的第四个区块是控制块,密钥就放在这第四个区块里面,利用RC522也是可以修改密钥的,有后门指令,这个后面再说,当然使用手机的NFC软件修改更加简单。

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     验证指定扇区的密钥
    // 参数说明     authMode   - 验证模式,PICC_AUTHENT1A 或 PICC_AUTHENT1B
    //              BlockAddr  - 扇区内的数据块地址(0~63,对于 16 字节的 Mifare 卡)
    //              Sectorkey  - 密钥数组,长度为 6
    //              serNum     - 卡片的 UID(长度为 4 字节)
    // 返回参数     验证状态:MI_OK 表示成功,MI_ERR 表示失败
    // 使用示例     uint8_t status = MFRC522_Auth(PICC_AUTHENT1A, 4, keyA, uid);
    // 备注信息     执行认证后,可以读取或写入对应扇区的块,需确保卡片选中(调用 SelectTag)
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_Auth(uint8_t authMode, uint8_t BlockAddr, uint8_t *Sectorkey, uint8_t *serNum)
    {
        uint8_t sendData[12];
    	uint8_t back_Data[MAXRLEN];
        uint8_t status;
        uint16_t recvBits;
    
        // 构建认证指令
        sendData[0] = authMode;       // 认证模式
        sendData[1] = BlockAddr;      // 数据块地址
        for (uint8_t i = 0; i < 6; i++) 
    	{
            sendData[2 + i] = *(Sectorkey+i); // 密钥
        }
        for (uint8_t i = 0; i < 4; i++) 
    	{
            sendData[8 + i] = *(serNum+i);   // UID 的前 4 字节
        }
    
        // 发送认证指令
        status = RC522_ToCard(PCD_AUTHENT, sendData, 12, back_Data, &recvBits);
    
        // 检查认证状态
        if ((status != MI_OK) || (!(RC522_ReadReg(RC522_Status2Reg) & 0x08))) 
    	{
            status = MI_ERR;  // 验证失败
        }
    
        return status;  // 返回验证状态
    }

    5、RC522_Read(读取区块)

    这个函数是读取区块中16个字节的函数,第一个参数是区块的地址,第二个参数值数组的首地址,用于接收返回的16字节数据

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     从M1卡中读取指定数据块
    // 参数说明     blockAddr:指定读取的块地址 (0~63)
    //              data:存储读取数据的缓冲区指针(长度至少16字节)
    // 返回参数     uint8_t:操作状态,MI_OK 表示成功,其他值表示失败
    // 使用示例     uint8_t buffer[16];
    //              uint8_t status = RC522_Read(10, buffer); // 读取第10块的数据
    // 备注信息     读取前需要完成认证操作 (使用 RC522_Auth 函数认证目标块)
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_Read(uint8_t blockAddr, uint8_t *data)
    {
        uint8_t status;
        uint8_t command = PCD_TRANSCEIVE; // 发送与接收模式
        uint8_t send_command[4];          // 用于发送读块命令的数组
        uint16_t recvBits;                // 接收到的位数
        uint8_t back_Data[MAXRLEN];       // 存储返回的数据
    	
    //	RC522_Reset();
    	RC522_WriteReg(RC522_BitFramingReg, 0x00);
    	ClearBitMask(RC522_CollReg, 0x80);
    
        // 准备读命令(命令+块地址)
        send_command[0] = PICC_READ;      // 读命令(0x30)
        send_command[1] = blockAddr;          // 指定块地址
        CalculateCRC(send_command, 2, &send_command[2]); // 计算读命令的 CRC 并附加
    
        // 发送读命令到卡片
        status = RC522_ToCard(command, send_command, 4, back_Data, &recvBits);
    
        // 检查是否读取成功
        if ((status == MI_OK) && (recvBits == 0x90)) // 数据块长度为 16 字节(128 位)
        {		
            for (uint8_t i = 0; i < 16; i++)
            {
                data[i] = back_Data[i]; // 保存数据到指定缓冲区
            }
        }
        else
        {
            status = MI_ERR; // 返回错误状态
        }
    
        return status;
    }

    6、RC522_Write(写入区块)

    这个函数是向某个区块写入16字节的数据,第一个参数是区块地址,第二个参数是要写入的数据数组的首地址

    //-------------------------------------------------------------------------------------------------------------------
    // 函数简介     写入指定区块的数据
    // 参数说明     uint8_t blockAddr   区块地址(0-63)
    //              uint8_t *writeData  写入的数据指针(长度为16字节)
    // 返回参数     uint8_t             写入状态 (MI_OK 或 MI_ERR)
    // 使用示例     uint8_t data[16] = {0x01, 0x02, ... , 0x10};
    //              RC522_Write(5, data); // 写入第5区块数据
    // 备注信息     确保先认证目标区块所属扇区,再调用本函数写入数据
    //-------------------------------------------------------------------------------------------------------------------
    uint8_t RC522_Write(uint8_t blockAddr, uint8_t *writeData)
    {
        uint8_t status;
        uint8_t send_command[18];
        uint16_t recvBits;
        uint8_t backData[MAXRLEN];
    
        // 构建写入命令
        send_command[0] = PICC_WRITE;  // 写入命令
        send_command[1] = blockAddr;  // 区块地址
        CalculateCRC(send_command, 2, &send_command[2]);
    
        // 发送写命令
        status = RC522_ToCard(PCD_TRANSCEIVE, send_command, 4, backData, &recvBits);
        if ((status != MI_OK) || (recvBits != 4) || ((backData[0] & 0x0F) != 0x0A))
        {
            status = MI_ERR;  // 写命令失败
        }
    
        // 发送16字节数据
        if (status == MI_OK)
        {
            memcpy(send_command, writeData, 16);
            CalculateCRC(send_command, 16, &send_command[16]);
    
            // 发送数据
            status = RC522_ToCard(PCD_TRANSCEIVE, send_command, 18, backData, &recvBits);
            if ((status != MI_OK) || (recvBits != 4) || ((backData[0] & 0x0F) != 0x0A))
            {
                status = MI_ERR;  // 写数据失败
            }
        }
    
        return status;
    }

    五、说明

    根据官方提供的命令流程图,与IC卡建立通信需要经过寻卡—->防冲突—->选卡—->验证密钥,经过以上流程后,就可以读取或者写入区块数据,其中官方的3 Pass Authenticcation spelific sector(三次握手验证),在实际中只要验证密钥A就可以访问扇区中区块的数据了

    作者:A-pezer

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】STM32+RC522-射频IC识别模块

    发表回复