MFRC522--RFID读写器

一、RC522说明

        1.RC522 的内部结构仅包含专用功能模块(模拟电路:调制解调、天线驱动;数字电路:FIFO 缓冲区、CRC 协处理器、定时器、中断系统、寄存器组),无独立 CPU,也无程序存储器,无法自主运行用户编写的程序,仅能通过外部主机(如 STM32)发送的 “寄存器配置 + 命令” 被动响应操作(如寻卡、读写)。手册 15 章(命令集)进一步说明:RC522 的所有功能(如复位、发送数据、认证)均需外部主机通过 SPI/I2C/UART 接口发送特定命令(如PCD_RESETPHASE、PCD_TRANSCEIVE)才能触发,自身无法主动发起任何操作。

也就是说RC522也只是一个从机模块,任何流程性操作都是MCU安排的,但是因为他自己又具备一定的自主功能,所以需要在初始化的时候配置一些寄存器。而初始化时的软件复位会导致上一次使用的参数失效,它自身默认的参数又不一定符合工程要求,因此需要在初始化复位后,重新配置他的定时器、中断、适配协议等寄存器,才能使其具有完整的收发控制、通信等功能

        2.在后续操作中,均需要MCU向RC522发送命令。如下文配置流程的那些步骤,都需要MCU发送对应的命令。同时在数据收发校验中,RC522会有标志位存在内部寄存器中,需要MCU读出来判断当前的工作状态(也就是是否成功)。

二、程序配置流程:RC522芯片初始化、相关基础寄存器配置——寻卡——防冲突&获取UID——选择卡片——认证扇区——读写数据——休眠。

(一)RC522芯片初始化:通过SPI等通信协议,配置RC522内部寄存器的一些参数。如设置工作模式、传输速率、定时器、天线驱动、RFID通信协议。

0.总配置

// RC522 初始化入口:配置 GPIO、复位、天线与 ISO 类型
void RC522_Init(void)
{
    RC522_GPIO_Init();    // 初始化 GPIO 引脚
    RC522_Reset();        // 复位并基础初始化寄存器
    RC522_AntennaOff();   // 先关闭天线(清理状态)
    RC522_AntennaOn();    // 再打开天线(确保已开启)
    RC522_ConfigISOType('A'); // 配置为 ISO14443-A 并打开相关设置
}

1.RC522内部基本寄存器配置

// 復位 RC522 芯片并进行基础寄存器初始化
int8_t RC522_Reset(void)
{
    RC522_RST_HIGH();  // 先拉高复位引脚
    Delay_ms(1);       // 短延时
    RC522_RST_LOW();   // 拉低复位
    Delay_ms(1);       // 短延时
    RC522_RST_HIGH();  // 再拉高复位以完成复位序列
    Delay_ms(1);       // 等待芯片稳定
    
    RC522_WriteReg(CommandReg, PCD_RESETPHASE); // 发送软复位命令
    Delay_ms(1); // 等待复位完成
    
    // 基本模式与定时器/发送参数设置(常用初始化值)
    RC522_WriteReg(ModeReg, 0x3D);        // 模式寄存器设置。厂商推荐的默认值
    RC522_WriteReg(TReloadRegL, 30);      // 定时器重载低字节
    RC522_WriteReg(TReloadRegH, 0);       // 定时器重载高字节
    RC522_WriteReg(TModeReg, 0x8D);       // 计时模式寄存器
    RC522_WriteReg(TPrescalerReg, 0x3E);  // 预分频寄存器
    RC522_WriteReg(TxAutoReg, 0x40);      // 自动 Tx 设置(脉冲宽度等)
    
    return MI_OK; // 返回成功
}

2.先关闭天线清理旧状态,再开启天线。


// 打开天线(设置驱动位)
void RC522_AntennaOn(void)
{
    uint8_t i;
    i = RC522_ReadReg(TxControlReg); // 读取当前天线控制寄存器
    if(!(i & 0x03)) // 若低两位未置位(天线未开启)
    {
        RC522_SetBitMask(TxControlReg, 0x03); // 打开天线驱动位(置 0/1 位)
    }
}

// 关闭天线
void RC522_AntennaOff(void)
{
    RC522_ClearBitMask(TxControlReg, 0x03); // 清除天线控制低两位以关闭天线
}

3.配置RC522内部天线相关寄存器(选择读写器和IC卡的通信协议)

// 配置 ISO 类型(当前实现仅支持 ISO14443-A)
// 参数: type - 'A' 表示 ISO14443_A
// 返回: MI_OK / MI_ERR
int8_t RC522_ConfigISOType(uint8_t type)
{
    if(type == 'A') // ISO14443_A
    {
        RC522_ClearBitMask(Status2Reg, 0x08); // 清除 MIFARE 相关状态位
        RC522_WriteReg(ModeReg, 0x3D);        // 设置模式寄存器
        RC522_WriteReg(RxSelReg, 0x86);       // 接收选择寄存器设置
        RC522_WriteReg(RFCfgReg, 0x7F);       // 射频配置
        RC522_WriteReg(TReloadRegL, 30);      // 定时器重载低字节
        RC522_WriteReg(TReloadRegH, 0);       // 定时器重载高字节
        RC522_WriteReg(TModeReg, 0x8D);       // 计时模式
        RC522_WriteReg(TPrescalerReg, 0x3E);  // 预分频
        Delay_ms(1);                          // 短延时
        RC522_AntennaOn();                    // 打开天线
    }
    else
    {
        return MI_ERR; // 不支持其他类型则返回错误
    }
    
    return MI_OK; // 成功返回
}

(二)寻卡/卡片探测:RC522向天线辐射范围内发送寻卡指令。处于场中的卡品被激活,并恢复一个ATQA(应答请求)信号

1.寻卡(找到当前相同协议的、空闲状态的卡。)

和读写器协议不同的IC卡的是找不到的。空闲是指读写器发出射频信号后能给出回复的卡。寻卡阶段只是得知有没有空闲卡,没有进行获取IC卡信息的操作。

/* 寻卡(请求卡片类型)
// 参数: req_code - PICC_REQIDL / PICC_REQALL 等
            PICC_REQIDL:用于检测 “空闲状态” 的卡片(未进入休眠的卡片);
            PICC_REQALL:用于检测所有状态的卡片(包括已激活但未休眠的卡片);
//       pTagType - 输出 2 字节,表示卡类型
*/ 返回: MI_OK / MI_ERR
int8_t RC522_Request(uint8_t req_code, uint8_t *pTagType)
{
    int8_t status;            // 返回状态
    uint16_t unLen;           // 输出位长度(位)
    uint8_t ucComMF522Buf[MAXRLEN]; // 通用缓冲区
    
    RC522_ClearBitMask(Status2Reg, 0x08);     // 清除 MIFARE 状态位
    RC522_WriteReg(BitFramingReg, 0x07);      // 设置发送 7 位(请求帧长度)
    RC522_SetBitMask(TxControlReg, 0x03);     // 打开天线驱动(如果未打开)
    
    ucComMF522Buf[0] = req_code;              // 请求代码放入缓冲区
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &unLen); // 发送并接收
    
    if((status == MI_OK) && (unLen == 0x10))  // 期望返回 16 位(类型码)
    {
        pTagType[0] = ucComMF522Buf[0]; // 保存类型码字节0
        pTagType[1] = ucComMF522Buf[1]; // 保存类型码字节1
    }
    else
    {
        status = MI_ERR; // 未按预期返回则视为错误
    }
    
    return status; // 返回结果
}

(三)获取当前响应卡片的卡号,同时防冲突(也就是多个卡片响应时选择出其中一张卡并且识别出它的卡号)(当前防冲突的代码处理不全面,暂不适用多卡识别)

防冲突只是获得IC卡的UID,并没有直接进行读写相关的通信操作,需要在后面的选卡操作中真正选上。

防冲突的原理是

        “当只有一张卡片响应是,所有寄存器收到的bit位数据都是稳定的值;当多张卡片响应时,个别bit位会出现0、1的交替接收,不稳定,就说明收到了不止一张卡片的信号”

发现冲突后的选卡原理是

        “在已有的但不冲突的序列作为开头,之后从冲突位开始用0/1进行枚举尝试选中其中一张卡”

// 防冲突,获取卡序列号(4 字节)
// 参数: pSnr - 输出序列号缓冲(至少 4 字节)
// 返回: MI_OK / MI_ERR
int8_t RC522_Anticoll(uint8_t *pSnr)
{
    int8_t status;
    uint8_t i, snr_check = 0; // snr_check 用于计算校验字节
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    RC522_ClearBitMask(Status2Reg, 0x08); // 清除 MIFARE 状态位
    RC522_WriteReg(BitFramingReg, 0x00);  // 字节对齐(发送整字节)
    RC522_ClearBitMask(CollReg, 0x80);    // 清除碰撞标志位
    
    ucComMF522Buf[0] = PICC_ANTICOLL1;    // 防冲突命令
    ucComMF522Buf[1] = 0x20; // 指定防冲突命令与长度(NVB = 0x20)
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &unLen); // 发送并接收
    
    if(status == MI_OK)
    {
        for(i = 0; i < 4; i++) // 复制 4 字节序列号并计算异或校验
        {
            pSnr[i] = ucComMF522Buf[i]; // 保存序列号字节
            snr_check ^= ucComMF522Buf[i]; // 异或累积
        }
        // 校验位(第5字节)是否匹配
        if(snr_check != ucComMF522Buf[i]) // ucComMF522Buf[4] 为校验字节
        {
            status = MI_ERR; // 校验失败标记错误
        }
    }
    
    RC522_SetBitMask(CollReg, 0x80); // 恢复碰撞寄存器原始位
    return status; // 返回结果
}

(四)选择卡片

根据防冲突阶段获得的卡片UID,选择该卡进行通讯。

// 选择卡片(基于序列号)
// 参数: pSnr - 4 字节序列号
// 返回: MI_OK / MI_ERR
int8_t RC522_Select(uint8_t *pSnr)
{
    int8_t status;
    uint8_t i;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = PICC_ANTICOLL1; // SELECT(第一级命令)标识
    ucComMF522Buf[1] = 0x70; // SELECT 命令 + NVB(指示发送 7 字节含校验)
    ucComMF522Buf[6] = 0;    // 累加校验初始化
    
    for(i = 0; i < 4; i++)
    {
        ucComMF522Buf[i + 2] = pSnr[i]; // 将序列号字节放入请求缓冲
        ucComMF522Buf[6] ^= pSnr[i];    // 计算校验(异或)
    }
    
    RC522_CalculateCRC(ucComMF522Buf, 7, &ucComMF522Buf[7]); // 计算并追加 CRC 到缓冲区尾部
    RC522_ClearBitMask(Status2Reg, 0x08); // 清除状态位
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &unLen); // 发送 SELECT 请求
    
    if((status == MI_OK) && (unLen == 0x18)) // 期望返回 24 位(ACK/SAK 长度)
    {
        status = MI_OK; // 选卡成功
    }
    else
    {
        status = MI_ERR; // 否则选卡失败
    }
    
    return status; // 返回结果
}

(五)认证

不同的卡片和厂商对扇区和块区的大小分配是不同的。RC522是按 MIFARE Classic内存组织和访问控制规范实现的,其内容如下

扇区:4个块区组成一个扇区

块区:16Byte组成的一个最小读写单位。

在对卡片今昔读写等敏感操作前,必须进行三次相互认证。RC522使用存储在它里面的密钥和卡的UID,与卡片内部存储的密钥进行复杂的加密认证。

// 验证密钥(认证一个扇区块)
// 参数: auth_mode - AUTHENT_A/AUTHENT_B, addr - 块地址, pKey - 6 字节密钥, pSnr - 卡序列号(4 字节)
// 返回: MI_OK / MI_ERR
int8_t RC522_AuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr)
{
    int8_t status;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = auth_mode;        // 认证模式(A/B)
    ucComMF522Buf[1] = addr;             // 块地址
    memcpy(&ucComMF522Buf[2], pKey, 6);  // 复制 6 字节密钥到缓冲
    memcpy(&ucComMF522Buf[8], pSnr, 4);  // 复制卡序列号到缓冲
    
    status = RC522_Command(PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &unLen); // 发送认证命令
    
    // 检查认证后 Status2Reg 的 MIFARE bit(0x08)
    if((status != MI_OK) || (!(RC522_ReadReg(Status2Reg) & 0x08)))
    {
        status = MI_ERR; // 若状态不正确则认证失败
    }
    
    return status; // 返回认证结果
}

/**
  * @brief  尝试认证指定区块
  * @param  snr: 卡片序列号
  * @param  block_addr: 区块地址
  * @retval 认证状态 MI_OK 或 MI_ERR
  */
int8_t TryAuthBlock(uint8_t *snr, uint8_t block_addr)
{
    printf("正在认证区块 %d: ", block_addr);
    int8_t status = RC522_AuthState(PICC_AUTHENT1A, block_addr, default_key, snr);
    
    if(status == MI_OK)
    {
        printf("成功!\r\n");
        
        // 读取当前数据
        uint8_t read_data[16];
        if(RC522_Read(block_addr, read_data) == MI_OK)
        {
            printf("区块 %d 当前数据: ", block_addr);
            for(int i = 0; i < 16; i++)
            {
                printf("%02X ", read_data[i]);
            }
            printf("\r\n");
        }
        return MI_OK;
    }
    else
    {
        printf("失败\r\n");
        return MI_ERR;
    }
}

(六)数据操作:读写卡片

(七)休眠:RC522向卡片发送休眠指令,卡片进入低功耗的休眠状态,不再响应寻卡指令,知道离开后重新进入射频场。

二、RC522代码

RC522.c

#include "rc522.h"
#include "delay.h"
#include <string.h>

// SPI延时(短延时,确保时序)
static void RC522_Delay(void)
{
    volatile uint8_t i;
    for(i = 0; i < 10; i++);
}

// SPI读取一个字节(软件模拟 SPI 模式,从 MOSI/MISO 时序看)
static uint8_t RC522_SPI_ReadByte(void)
{
    uint8_t i;
    uint8_t data = 0;
    
    for(i = 0; i < 8; i++)
    {
        data <<= 1;
        RC522_SCK_LOW();        // 时钟低
        RC522_Delay();
        
        if(RC522_MISO_READ())   // 读 MISO 电平
        {
            data |= 0x01;
        }
        
        RC522_SCK_HIGH();       // 时钟上升采样
        RC522_Delay();
    }
    
    return data;
}

// SPI写入一个字节(软件模拟,将数据按位输出到 MOSI)
static void RC522_SPI_WriteByte(uint8_t data)
{
    uint8_t i;
    
    for(i = 0; i < 8; i++)
    {
        RC522_SCK_LOW();
        RC522_Delay();
        
        if(data & 0x80)
        {
            RC522_MOSI_HIGH();
        }
        else
        {
            RC522_MOSI_LOW();
        }
        
        data <<= 1;
        RC522_Delay();
        RC522_SCK_HIGH();
        RC522_Delay();
    }
}

// GPIO 初始化:配置 CS/SCK/MOSI/MISO/RST 引脚模式与初始电平
void RC522_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能端口时钟(示例为 GPIOA,根据硬件修改)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // CS、SCK、MOSI、RST - 推挽输出
    GPIO_InitStructure.GPIO_Pin = RC522_CS_PIN | RC522_SCK_PIN | RC522_MOSI_PIN | RC522_RST_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(RC522_CS_PORT, &GPIO_InitStructure);
    
    // MISO - 浮空输入
    GPIO_InitStructure.GPIO_Pin = RC522_MISO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(RC522_MISO_PORT, &GPIO_InitStructure);
    
    // 初始电平(片选高,时钟高,MOSI 高,复位高)
    RC522_CS_HIGH();
    RC522_SCK_HIGH();
    RC522_MOSI_HIGH();
    RC522_RST_HIGH();
}

// 读取 RC522 寄存器
// 参数: addr - 寄存器地址
// 返回: 寄存器值
uint8_t RC522_ReadReg(uint8_t addr)
{
    uint8_t value;
    uint8_t addr_byte;
    
    RC522_CS_LOW();
    // 地址字节:7位地址 + 读位 (MSB=1),并左移1位(RC522 通信格式)
    addr_byte = ((addr << 1) & 0x7E) | 0x80;
    RC522_SPI_WriteByte(addr_byte);
    value = RC522_SPI_ReadByte();
    RC522_CS_HIGH();
    
    return value;
}

// 写 RC522 寄存器
// 参数: addr - 寄存器地址, value - 要写入的值
void RC522_WriteReg(uint8_t addr, uint8_t value)
{
    uint8_t addr_byte;
    
    RC522_CS_LOW();
    // 写操作:MSB=0
    addr_byte = ((addr << 1) & 0x7E);
    RC522_SPI_WriteByte(addr_byte);
    RC522_SPI_WriteByte(value);
    RC522_CS_HIGH();
}

// 设置寄存器位掩码(将 mask 指定的位置1)
void RC522_SetBitMask(uint8_t reg, uint8_t mask)
{
    uint8_t tmp;
    tmp = RC522_ReadReg(reg);
    RC522_WriteReg(reg, tmp | mask);
}

// 清除寄存器位掩码(将 mask 指定的位置0)
void RC522_ClearBitMask(uint8_t reg, uint8_t mask)
{
    uint8_t tmp;
    tmp = RC522_ReadReg(reg);
    RC522_WriteReg(reg, tmp & ~mask);
}

// 使用内部 CRC 计算数据的 CRC 值
// 参数: pIndata - 输入数据指针, len - 数据长度, pOutData - 输出 2 字节 CRC(低位, 高位)
void RC522_CalculateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
    uint8_t i, n;
    
    RC522_ClearBitMask(DivIrqReg, 0x04);       // 清除 CRC 中断标志
    RC522_WriteReg(CommandReg, PCD_IDLE);      // 取消当前命令
    RC522_SetBitMask(FIFOLevelReg, 0x80);      // 清空 FIFO
    
    for(i = 0; i < len; i++)
    {
        RC522_WriteReg(FIFODataReg, pIndata[i]); // 写入数据到 FIFO
    }
    
    RC522_WriteReg(CommandReg, PCD_CALCCRC);   // 启动 CRC 计算
    
    i = 0xFF;
    do
    {
        n = RC522_ReadReg(DivIrqReg);
        i--;
    } while((i != 0) && !(n & 0x04));          // 等待 CRC 完成中断
    
    pOutData[0] = RC522_ReadReg(CRCResultRegL); // CRC 低位
    pOutData[1] = RC522_ReadReg(CRCResultRegM); // CRC 高位
}

// 向 RC522 发送命令并处理返回(通用命令函数)
// 参数:
//  command - PCD_* 命令
//  pInData  - 输入数据缓冲区
//  inLen    - 输入数据长度(字节)
//  pOutData - 输出数据缓冲区(用于接收数据)
//  pOutLen  - 输出比特长度(以位为单位)
// 返回: MI_OK / MI_ERR / MI_NOTAGERR 等
int8_t RC522_Command(uint8_t command, uint8_t *pInData, uint8_t inLen, 
                    uint8_t *pOutData, uint16_t *pOutLen)
{
    int8_t status = MI_ERR;
    uint8_t irqEn = 0x00;
    uint8_t waitFor = 0x00;
    uint8_t lastBits;
    uint8_t n;
    uint16_t i;
    
    switch(command)
    {
        case PCD_AUTHENT:
            irqEn = 0x12;   // 认证中断
            waitFor = 0x10;
            break;
        case PCD_TRANSCEIVE:
            irqEn = 0x77;   // 发送接收相关中断使能
            waitFor = 0x30; // 等待接收完成标志
            break;
        default:
            break;
    }
    
    RC522_WriteReg(ComIEnReg, irqEn | 0x80); // 允许中断并打开最高位
    RC522_ClearBitMask(ComIrqReg, 0x80);     // 清空中断标志
    RC522_WriteReg(CommandReg, PCD_IDLE);    // 取消命令
    RC522_SetBitMask(FIFOLevelReg, 0x80);    // 清空 FIFO
    
    for(i = 0; i < inLen; i++)
    {
        RC522_WriteReg(FIFODataReg, pInData[i]); // 写入发送数据
    }
    
    RC522_WriteReg(CommandReg, command);     // 执行命令
    
    if(command == PCD_TRANSCEIVE)
    {
        RC522_SetBitMask(BitFramingReg, 0x80); // 启动发送
    }
    
    i = 2000; // 超时计数(循环等待)
    do
    {
        n = RC522_ReadReg(ComIrqReg);
        i--;
    } while((i != 0) && !(n & 0x01) && !(n & waitFor));
    
    RC522_ClearBitMask(BitFramingReg, 0x80); // 清除发送启动标志
    
    if(i != 0)
    {
        if(!(RC522_ReadReg(ErrorReg) & 0x1B)) // 检查错误寄存器(BufferOvfl, ParityErr, ProtocolErr 等)
        {
            status = MI_OK;
            if(n & irqEn & 0x01)
            {
                status = MI_NOTAGERR; // 没有标签
            }
            if(command == PCD_TRANSCEIVE)
            {
                // 读取接收数据长度与最后的有效位数
                n = RC522_ReadReg(FIFOLevelReg);
                lastBits = RC522_ReadReg(ControlReg) & 0x07;
                if(lastBits)
                {
                    *pOutLen = (n - 1) * 8 + lastBits; // 有效位数
                }
                else
                {
                    *pOutLen = n * 8;
                }
                if(n == 0)
                {
                    n = 1;
                }
                if(n > MAXRLEN)
                {
                    n = MAXRLEN;
                }
                for(i = 0; i < n; i++)
                {
                    pOutData[i] = RC522_ReadReg(FIFODataReg); // 读取 FIFO 数据
                }
            }
        }
        else
        {
            status = MI_ERR;
        }
    }
    
    RC522_SetBitMask(ControlReg, 0x80); // 设置照明/控制位(根据库原意保留)
    RC522_WriteReg(CommandReg, PCD_IDLE);
    return status;
}

// 寻卡(请求卡片类型)
// 参数: req_code - PICC_REQIDL / PICC_REQALL 等
//       pTagType - 输出 2 字节,表示卡类型
// 返回: MI_OK / MI_ERR
int8_t RC522_Request(uint8_t req_code, uint8_t *pTagType)
{
    int8_t status;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    RC522_ClearBitMask(Status2Reg, 0x08);     // 清除 MIFARE 相关状态
    RC522_WriteReg(BitFramingReg, 0x07);      // 设置位帧(发送 7 位)
    RC522_SetBitMask(TxControlReg, 0x03);     // 打开天线驱动
    
    ucComMF522Buf[0] = req_code;
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &unLen);
    
    if((status == MI_OK) && (unLen == 0x10))  // 返回 16 位
    {
        pTagType[0] = ucComMF522Buf[0];
        pTagType[1] = ucComMF522Buf[1];
    }
    else
    {
        status = MI_ERR;
    }
    
    return status;
}

// 防冲突,获取卡序列号(4 字节)
// 参数: pSnr - 输出序列号缓冲(至少 4 字节)
// 返回: MI_OK / MI_ERR
int8_t RC522_Anticoll(uint8_t *pSnr)
{
    int8_t status;
    uint8_t i, snr_check = 0;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    RC522_ClearBitMask(Status2Reg, 0x08);
    RC522_WriteReg(BitFramingReg, 0x00);  // 以字节对齐
    RC522_ClearBitMask(CollReg, 0x80);
    
    ucComMF522Buf[0] = PICC_ANTICOLL1;
    ucComMF522Buf[1] = 0x20; // 指定防冲突命令与长度
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &unLen);
    
    if(status == MI_OK)
    {
        for(i = 0; i < 4; i++)
        {
            pSnr[i] = ucComMF522Buf[i];
            snr_check ^= ucComMF522Buf[i];
        }
        // 校验位(第5字节)是否匹配
        if(snr_check != ucComMF522Buf[i])
        {
            status = MI_ERR;
        }
    }
    
    RC522_SetBitMask(CollReg, 0x80);
    return status;
}

// 选择卡片(基于序列号)
// 参数: pSnr - 4 字节序列号
// 返回: MI_OK / MI_ERR
int8_t RC522_Select(uint8_t *pSnr)
{
    int8_t status;
    uint8_t i;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = PICC_ANTICOLL1;
    ucComMF522Buf[1] = 0x70; // SELECT 命令 + NVB
    ucComMF522Buf[6] = 0;
    
    for(i = 0; i < 4; i++)
    {
        ucComMF522Buf[i + 2] = pSnr[i];
        ucComMF522Buf[6] ^= pSnr[i]; // 计算校验
    }
    
    RC522_CalculateCRC(ucComMF522Buf, 7, &ucComMF522Buf[7]); // 追加 CRC
    RC522_ClearBitMask(Status2Reg, 0x08);
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &unLen);
    
    if((status == MI_OK) && (unLen == 0x18)) // 应返回 24 位(应答位长度)
    {
        status = MI_OK;
    }
    else
    {
        status = MI_ERR;
    }
    
    return status;
}

// 验证密钥(认证一个扇区块)
// 参数: auth_mode - AUTHENT_A/AUTHENT_B, addr - 块地址, pKey - 6 字节密钥, pSnr - 卡序列号(4 字节)
// 返回: MI_OK / MI_ERR
int8_t RC522_AuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr)
{
    int8_t status;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = auth_mode;
    ucComMF522Buf[1] = addr;
    memcpy(&ucComMF522Buf[2], pKey, 6);
    memcpy(&ucComMF522Buf[8], pSnr, 4);
    
    status = RC522_Command(PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &unLen);
    
    // 检查认证后 Status2Reg 的 MIFARE bit(0x08)
    if((status != MI_OK) || (!(RC522_ReadReg(Status2Reg) & 0x08)))
    {
        status = MI_ERR;
    }
    
    return status;
}

// 读块(读取 16 字节数据)
// 参数: addr - 块地址, pData - 输出缓冲长度至少 16 字节
// 返回: MI_OK / MI_ERR
int8_t RC522_Read(uint8_t addr, uint8_t *pData)
{
    int8_t status;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = PICC_READ;
    ucComMF522Buf[1] = addr;
    RC522_CalculateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]); // 追加 CRC
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
    
    if((status == MI_OK) && (unLen == 0x90)) // 16 字节 * 8 = 0x80 (加上控制位为 0x90)
    {
        memcpy(pData, ucComMF522Buf, 16);
    }
    else
    {
        status = MI_ERR;
    }
    
    return status;
}

// 写块(写入 16 字节数据)
// 参数: addr - 块地址, pData - 16 字节写入数据
// 返回: MI_OK / MI_ERR
int8_t RC522_Write(uint8_t addr, uint8_t *pData)
{
    int8_t status;
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = PICC_WRITE;
    ucComMF522Buf[1] = addr;
    RC522_CalculateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]); // 写命令带 CRC
    
    status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
    
    // 首次应答检查(ACK)
    if((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
    {
        status = MI_ERR;
    }
    
    if(status == MI_OK)
    {
        memcpy(ucComMF522Buf, pData, 16);
        RC522_CalculateCRC(ucComMF522Buf, 16, &ucComMF522Buf[16]); // 数据后追加 CRC
        
        status = RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, &unLen);
        
        // 写结束应答检查
        if((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
        {
            status = MI_ERR;
        }
    }
    
    return status;
}

// 休眠卡片(发送 HALT 命令)
// 返回: MI_OK (不检查应答)
int8_t RC522_Halt(void)
{
    uint16_t unLen;
    uint8_t ucComMF522Buf[MAXRLEN];
    
    ucComMF522Buf[0] = PICC_HALT;
    ucComMF522Buf[1] = 0;
    RC522_CalculateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
    
    // 发送 HALT 命令,忽略返回结果
    RC522_Command(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
    
    return MI_OK;
}

// 復位 RC522 芯片并进行基础寄存器初始化
int8_t RC522_Reset(void)
{
    RC522_RST_HIGH();
    Delay_ms(1);
    RC522_RST_LOW();
    Delay_ms(1);
    RC522_RST_HIGH();
    Delay_ms(1);
    
    RC522_WriteReg(CommandReg, PCD_RESETPHASE);
    Delay_ms(1);
    
    // 基本模式与定时器/发送参数设置(常用初始化值)
    RC522_WriteReg(ModeReg, 0x3D);
    RC522_WriteReg(TReloadRegL, 30);
    RC522_WriteReg(TReloadRegH, 0);
    RC522_WriteReg(TModeReg, 0x8D);
    RC522_WriteReg(TPrescalerReg, 0x3E);
    RC522_WriteReg(TxAutoReg, 0x40);
    
    return MI_OK;
}

// 打开天线(设置驱动位)
void RC522_AntennaOn(void)
{
    uint8_t i;
    i = RC522_ReadReg(TxControlReg);
    if(!(i & 0x03))
    {
        RC522_SetBitMask(TxControlReg, 0x03); // 打开天线驱动位
    }
}

// 关闭天线
void RC522_AntennaOff(void)
{
    RC522_ClearBitMask(TxControlReg, 0x03);
}

// 配置 ISO 类型(当前实现仅支持 ISO14443-A)
// 参数: type - 'A' 表示 ISO14443_A
// 返回: MI_OK / MI_ERR
int8_t RC522_ConfigISOType(uint8_t type)
{
    if(type == 'A') // ISO14443_A
    {
        RC522_ClearBitMask(Status2Reg, 0x08);
        RC522_WriteReg(ModeReg, 0x3D);
        RC522_WriteReg(RxSelReg, 0x86);
        RC522_WriteReg(RFCfgReg, 0x7F);
        RC522_WriteReg(TReloadRegL, 30);
        RC522_WriteReg(TReloadRegH, 0);
        RC522_WriteReg(TModeReg, 0x8D);
        RC522_WriteReg(TPrescalerReg, 0x3E);
        Delay_ms(1);
        RC522_AntennaOn();
    }
    else
    {
        return MI_ERR;
    }
    
    return MI_OK;
}

// RC522 初始化入口:配置 GPIO、复位、天线与 ISO 类型
void RC522_Init(void)
{
    RC522_GPIO_Init();
    RC522_Reset();
    RC522_AntennaOff();
    RC522_AntennaOn();
    RC522_ConfigISOType('A');
}

RC522.h

#ifndef __RC522_H
#define __RC522_H

#include "stm32f10x.h"
#include <stdint.h>

// 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

// Mifare卡片命令
#define PICC_REQIDL           0x26
#define PICC_REQALL           0x52
#define PICC_ANTICOLL1        0x93
#define PICC_ANTICOLL2        0x95
#define PICC_AUTHENT1A        0x60
#define PICC_AUTHENT1B        0x61
#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

// FIFO长度
#define DEF_FIFO_LENGTH       64
#define MAXRLEN               18

// RC522寄存器定义
#define CommandReg            0x01
#define ComIEnReg             0x02
#define DivlEnReg             0x03
#define ComIrqReg             0x04
#define DivIrqReg             0x05
#define ErrorReg              0x06
#define Status1Reg            0x07
#define Status2Reg            0x08
#define FIFODataReg           0x09
#define FIFOLevelReg          0x0A
#define WaterLevelReg         0x0B
#define ControlReg            0x0C
#define BitFramingReg         0x0D
#define CollReg               0x0E
#define ModeReg               0x11
#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 MifareReg             0x1C
#define SerialSpeedReg        0x1F
#define CRCResultRegM         0x21
#define CRCResultRegL         0x22
#define ModWidthReg           0x24
#define RFCfgReg              0x26
#define GsNReg                0x27
#define CWGsCfgReg            0x28
#define ModGsCfgReg           0x29
#define TModeReg              0x2A
#define TPrescalerReg         0x2B
#define TReloadRegH           0x2C
#define TReloadRegL           0x2D
#define TCounterValueRegH     0x2E
#define TCounterValueRegL     0x2F
#define TestSel1Reg           0x31
#define TestSel2Reg           0x32
#define TestPinEnReg          0x33
#define TestPinValueReg       0x34
#define TestBusReg            0x35
#define AutoTestReg           0x36
#define VersionReg            0x37
#define AnalogTestReg         0x38
#define TestDAC1Reg           0x39
#define TestDAC2Reg           0x3A
#define TestADCReg            0x3B

// 返回值定义
#define MI_OK                 0
#define MI_NOTAGERR           (-1)
#define MI_ERR                (-2)

// GPIO引脚定义 - 根据实际连接修改
#define RC522_CS_PORT         GPIOA
#define RC522_CS_PIN          GPIO_Pin_4

#define RC522_SCK_PORT        GPIOA
#define RC522_SCK_PIN         GPIO_Pin_5

#define RC522_MOSI_PORT       GPIOA
#define RC522_MOSI_PIN        GPIO_Pin_7

#define RC522_MISO_PORT       GPIOA
#define RC522_MISO_PIN        GPIO_Pin_6

#define RC522_RST_PORT        GPIOA
#define RC522_RST_PIN         GPIO_Pin_3

// 控制宏定义
#define RC522_CS_LOW()        GPIO_ResetBits(RC522_CS_PORT, RC522_CS_PIN)
#define RC522_CS_HIGH()       GPIO_SetBits(RC522_CS_PORT, RC522_CS_PIN)

#define RC522_SCK_LOW()       GPIO_ResetBits(RC522_SCK_PORT, RC522_SCK_PIN)
#define RC522_SCK_HIGH()      GPIO_SetBits(RC522_SCK_PORT, RC522_SCK_PIN)

#define RC522_MOSI_LOW()      GPIO_ResetBits(RC522_MOSI_PORT, RC522_MOSI_PIN)
#define RC522_MOSI_HIGH()     GPIO_SetBits(RC522_MOSI_PORT, RC522_MOSI_PIN)

#define RC522_RST_LOW()       GPIO_ResetBits(RC522_RST_PORT, RC522_RST_PIN)
#define RC522_RST_HIGH()      GPIO_SetBits(RC522_RST_PORT, RC522_RST_PIN)

#define RC522_MISO_READ()     GPIO_ReadInputDataBit(RC522_MISO_PORT, RC522_MISO_PIN)

// 函数声明
void RC522_GPIO_Init(void);
uint8_t RC522_ReadReg(uint8_t addr);
void RC522_WriteReg(uint8_t addr, uint8_t value);
void RC522_SetBitMask(uint8_t reg, uint8_t mask);
void RC522_ClearBitMask(uint8_t reg, uint8_t mask);
void RC522_CalculateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData);
int8_t RC522_Command(uint8_t command, uint8_t *pInData, uint8_t inLen, 
                    uint8_t *pOutData, uint16_t *pOutLen);
int8_t RC522_Request(uint8_t req_code, uint8_t *pTagType);
int8_t RC522_Anticoll(uint8_t *pSnr);
int8_t RC522_Select(uint8_t *pSnr);
int8_t RC522_AuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr);
int8_t RC522_Read(uint8_t addr, uint8_t *pData);
int8_t RC522_Write(uint8_t addr, uint8_t *pData);
int8_t RC522_Halt(void);
int8_t RC522_Reset(void);
void RC522_AntennaOn(void);
void RC522_AntennaOff(void);
void RC522_Init(void);
int8_t RC522_ConfigISOType(uint8_t type);

#endif

main.c

#include "stm32f10x.h"
#include "rc522.h"
#include "delay.h"
#include "usart.h"
#include <stdio.h>
#include <string.h>

// 默认密钥
uint8_t default_key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// 员工信息结构体
typedef struct {
    uint8_t id[4];           // 职工ID (4字节)
    uint8_t name[8];         // 姓名 (8字节)
    uint8_t department[4];   // 部门 (4字节)
} EmployeeInfo;

// 创建自定义员工数据
void CreateCustomEmployee(EmployeeInfo *employee)
{
    // === 在这里修改员工数据 ===
    
    // 职工ID (4字节) - 修改这里的数字
    uint32_t emp_id = 0001;  // 改为您想要的职工ID
    
    // 姓名 (最多8个字符) - 修改这里的名字
    char *emp_name = "QL.ql";  // 改为您想要的姓名
    
    // 部门 (最多4个字符) - 修改这里的部门
    char *emp_dept = "IT";   // 改为您想要的部门
    
    // === 不要修改下面的代码 ===
    
    // 设置职工ID
    employee->id[0] = (emp_id >> 24) & 0xFF;
    employee->id[1] = (emp_id >> 16) & 0xFF;
    employee->id[2] = (emp_id >> 8) & 0xFF;
    employee->id[3] = emp_id & 0xFF;
    
    // 设置姓名,不足补空格
    memset(employee->name, ' ', 8);
    int name_len = strlen(emp_name);
    if(name_len > 8) name_len = 8;
    memcpy(employee->name, emp_name, name_len);
    
    // 设置部门,不足补空格
    memset(employee->department, ' ', 4);
    int dept_len = strlen(emp_dept);
    if(dept_len > 4) dept_len = 4;
    memcpy(employee->department, emp_dept, dept_len);
}

// 尝试认证块
int8_t TryAuthBlock(uint8_t *snr, uint8_t block_addr)
{
    printf("尝试认证块 %d: ", block_addr);
    int8_t status = RC522_AuthState(PICC_AUTHENT1A, block_addr, default_key, snr);
    
    if(status == MI_OK)
    {
        printf("成功!\r\n");
        
        // 读取当前数据
        uint8_t read_data[16];
        if(RC522_Read(block_addr, read_data) == MI_OK)
        {
            printf("块 %d 当前数据: ", block_addr);
            for(int i = 0; i < 16; i++)
            {
                printf("%02X ", read_data[i]);
            }
            printf("\r\n");
        }
        return MI_OK;
    }
    else
    {
        printf("失败\r\n");
        return MI_ERR;
    }
}

// 写入员工信息到卡片
int8_t WriteEmployeeInfo(uint8_t *snr, uint8_t block_addr, EmployeeInfo *employee)
{
    int8_t status;
    uint8_t write_data[16];
    
    // 清空数据缓冲区
    memset(write_data, 0, sizeof(write_data));
    
    // 将员工信息复制到写入缓冲区
    memcpy(&write_data[0], employee->id, 4);
    memcpy(&write_data[4], employee->name, 8);
    memcpy(&write_data[12], employee->department, 4);
    
    printf("准备写入员工信息到块 %d:\r\n", block_addr);
    printf("职工ID: %02X %02X %02X %02X (十进制: %u)\r\n", 
           employee->id[0], employee->id[1], employee->id[2], employee->id[3],
           (employee->id[0] << 24) | (employee->id[1] << 16) | 
           (employee->id[2] << 8) | employee->id[3]);
    printf("姓名: ");
    for(int i = 0; i < 8; i++)
    {
        if(employee->name[i] >= 32 && employee->name[i] <= 126)
            printf("%c", employee->name[i]);
        else
            printf(" ");
    }
    printf("\r\n");
    printf("部门: ");
    for(int i = 0; i < 4; i++)
    {
        if(employee->department[i] >= 32 && employee->department[i] <= 126)
            printf("%c", employee->department[i]);
        else
            printf(" ");
    }
    printf("\r\n");
    
    // 写入数据
    status = RC522_Write(block_addr, write_data);
    if(status == MI_OK)
    {
        printf("员工信息写入成功!\r\n");
        
        // 验证写入
        uint8_t read_data[16];
        if(RC522_Read(block_addr, read_data) == MI_OK)
        {
            // 检查数据是否一致
            int match = 1;
            for(int i = 0; i < 16; i++)
            {
                if(read_data[i] != write_data[i])
                {
                    match = 0;
                    break;
                }
            }
            if(match)
            {
                printf("数据验证成功! 员工信息已正确写入\r\n");
            }
            else
            {
                printf("数据验证失败!\r\n");
                status = MI_ERR;
            }
        }
    }
    else
    {
        printf("写入失败! 状态: %d\r\n", status);
    }
    
    return status;
}

// 读取并显示员工信息
int8_t ReadEmployeeInfo(uint8_t *snr, uint8_t block_addr)
{
    int8_t status;
    uint8_t read_data[16];
    EmployeeInfo employee;
    
    status = RC522_Read(block_addr, read_data);
    if(status == MI_OK)
    {
        // 解析员工信息
        memcpy(employee.id, &read_data[0], 4);
        memcpy(employee.name, &read_data[4], 8);
        memcpy(employee.department, &read_data[12], 4);
        
        printf("=== 读取到的员工信息 ===\r\n");
        printf("块地址: %d\r\n", block_addr);
        printf("职工ID: %02X %02X %02X %02X", 
               employee.id[0], employee.id[1], employee.id[2], employee.id[3]);
        printf(" (十进制: %u)\r\n", 
               (employee.id[0] << 24) | (employee.id[1] << 16) | 
               (employee.id[2] << 8) | employee.id[3]);
        
        printf("姓名: ");
        for(int i = 0; i < 8; i++)
        {
            if(employee.name[i] >= 32 && employee.name[i] <= 126)
                printf("%c", employee.name[i]);
            else
                printf(" ");
        }
        printf("\r\n");
        
        printf("部门: ");
        for(int i = 0; i < 4; i++)
        {
            if(employee.department[i] >= 32 && employee.department[i] <= 126)
                printf("%c", employee.department[i]);
            else
                printf(" ");
        }
        printf("\r\n");
        printf("========================\r\n");
    }
    else
    {
        printf("读取员工信息失败!\r\n");
    }
    
    return status;
}

int main(void)
{
    uint8_t status;
    uint8_t snr[4];
    uint8_t card_type[2];
    
    USART1_Init(115200);
    printf("=== RFID员工信息写入系统 ===\r\n");
    
    RC522_Init();
    printf("RC522 初始化成功\r\n");
    printf("等待卡片...\r\n");
    
    while(1)
    {
        // 寻卡
        status = RC522_Request(PICC_REQIDL, card_type);
        
        if(status == MI_OK)
        {
            printf("\r\n=== 检测到卡片 ===\r\n");
            
            // 防冲突获取序列号
            status = RC522_Anticoll(snr);
            
            if(status == MI_OK)
            {
                printf("卡片序列号: %02X %02X %02X %02X\r\n", 
                       snr[0], snr[1], snr[2], snr[3]);
                
                // 选择卡片
                status = RC522_Select(snr);
                
                if(status == MI_OK)
                {
                    printf("卡片已选中\r\n");
                    
                    // 尝试认证块1
                    uint8_t target_block = 1;
                    status = TryAuthBlock(snr, target_block);
                    
                    if(status == MI_OK)
                    {
                        // 创建自定义员工数据
                        EmployeeInfo employee;
                        CreateCustomEmployee(&employee);
                        
                        // 写入员工信息
                        printf("\r\n--- 写入员工信息 ---\r\n");
                        status = WriteEmployeeInfo(snr, target_block, &employee);
                        
                        if(status == MI_OK)
                        {
                            Delay_ms(100);
                            
                            // 重新读取验证
                            printf("\r\n--- 重新读取验证 ---\r\n");
                            ReadEmployeeInfo(snr, target_block);
                        }
                    }
                    else
                    {
                        printf("认证失败,无法写入数据\r\n");
                    }
                    
                    // 停止卡片
                    RC522_Halt();
                    printf("卡片已停止\r\n");
                    printf("=== 操作完成 ===\r\n\r\n");
                }
                else
                {
                    printf("卡片选择失败\r\n");
                }
            }
            else
            {
                printf("防冲突失败\r\n");
            }
        }
        
        Delay_ms(500);
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值