stm32标准库驱动程序之 RC522

rc522.h

/**
 * @file    rc522.h
 * @brief   RC522 RFID模块驱动程序头文件
 * @details 用于STM32F103C8单片机
 * @author  Claude
 * @date    2025-04-10
 */

#ifndef RC522_H
#define RC522_H

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

/* RC522引脚定义 */
#define RC522_SPI                 SPI1
#define RC522_SPI_CLK             RCC_APB2Periph_SPI1

#define RC522_CS_PIN              GPIO_Pin_4
#define RC522_CS_GPIO_PORT        GPIOA
#define RC522_CS_GPIO_CLK         RCC_APB2Periph_GPIOA
#define RC522_CS_LOW()            GPIO_ResetBits(RC522_CS_GPIO_PORT, RC522_CS_PIN)
#define RC522_CS_HIGH()           GPIO_SetBits(RC522_CS_GPIO_PORT, RC522_CS_PIN)

#define RC522_SCK_PIN             GPIO_Pin_5
#define RC522_SCK_GPIO_PORT       GPIOA
#define RC522_SCK_GPIO_CLK        RCC_APB2Periph_GPIOA

#define RC522_MISO_PIN            GPIO_Pin_6
#define RC522_MISO_GPIO_PORT      GPIOA
#define RC522_MISO_GPIO_CLK       RCC_APB2Periph_GPIOA

#define RC522_MOSI_PIN            GPIO_Pin_7
#define RC522_MOSI_GPIO_PORT      GPIOA
#define RC522_MOSI_GPIO_CLK       RCC_APB2Periph_GPIOA

#define RC522_RST_PIN             GPIO_Pin_0
#define RC522_RST_GPIO_PORT       GPIOB
#define RC522_RST_GPIO_CLK        RCC_APB2Periph_GPIOB
#define RC522_RST_LOW()           GPIO_ResetBits(RC522_RST_GPIO_PORT, RC522_RST_PIN)
#define RC522_RST_HIGH()          GPIO_SetBits(RC522_RST_GPIO_PORT, RC522_RST_PIN)

/* RC522寄存器定义 */
#define RC522_REG_COMMAND         0x01
#define RC522_REG_COM_IE_N        0x02
#define RC522_REG_DIV_IE_N        0x03
#define RC522_REG_COM_IRQ         0x04
#define RC522_REG_DIV_IRQ         0x05
#define RC522_REG_ERROR           0x06
#define RC522_REG_STATUS1         0x07
#define RC522_REG_STATUS2         0x08
#define RC522_REG_FIFO_DATA       0x09
#define RC522_REG_FIFO_LEVEL      0x0A
#define RC522_REG_CONTROL         0x0C
#define RC522_REG_BIT_FRAMING     0x0D
#define RC522_REG_COLL            0x0E
#define RC522_REG_MODE            0x11
#define RC522_REG_TX_MODE         0x12
#define RC522_REG_RX_MODE         0x13
#define RC522_REG_TX_CONTROL      0x14
#define RC522_REG_TX_ASK          0x15
#define RC522_REG_CRC_RESULT_H    0x21
#define RC522_REG_CRC_RESULT_L    0x22
#define RC522_REG_MOD_WIDTH       0x24
#define RC522_REG_TYPE_B          0x25
#define RC522_REG_SERIAL_SPEED    0x26
#define RC522_REG_VERSION         0x37

/* RC522命令定义 */
#define RC522_CMD_IDLE            0x00    // 取消当前命令
#define RC522_CMD_MEM             0x01    // 存储数据到FIFO缓冲区
#define RC522_CMD_GENERATE_RANDOM 0x02    // 生成随机数
#define RC522_CMD_CALC_CRC        0x03    // CRC计算
#define RC522_CMD_TRANSMIT        0x04    // 数据传输
#define RC522_CMD_NO_CMD_CHANGE   0x07    // 无命令改变
#define RC522_CMD_RECEIVE         0x08    // 接收数据
#define RC522_CMD_TRANSCEIVE      0x0C    // 发送并接收数据
#define RC522_CMD_MF_AUTHENT      0x0E    // Mifare认证
#define RC522_CMD_SOFT_RESET      0x0F    // 软复位

/* Mifare命令 */
#define PICC_REQIDL               0x26    // 寻找未进入休眠状态的卡
#define PICC_REQALL               0x52    // 寻找所有卡
#define PICC_ANTICOLL             0x93    // 防冲撞
#define PICC_SElECTTAG            0x93    // 选择卡
#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                     0
#define MI_NOTAGERR               1
#define MI_ERR                    2

/* 定义卡类型结构体 */
typedef struct {
    uint8_t uid[4];               // 卡片UID
    uint8_t sak;                  // 卡片类型标识
} RC522_Card_t;

/**
 * @brief   初始化RC522模块
 * @details 配置SPI接口和GPIO引脚,初始化RC522模块
 * @return  无
 */
void RC522_Init(void);

/**
 * @brief   重置RC522模块
 * @details 通过硬件复位引脚和软件复位命令重置RC522
 * @return  无
 */
void RC522_Reset(void);

/**
 * @brief   向RC522寄存器写入数据
 * @param   addr RC522寄存器地址
 * @param   val  要写入的值
 * @return  无
 */
void RC522_WriteRegister(uint8_t addr, uint8_t val);

/**
 * @brief   从RC522寄存器读取数据
 * @param   addr RC522寄存器地址
 * @return  读取到的寄存器值
 */
uint8_t RC522_ReadRegister(uint8_t addr);

/**
 * @brief   设置RC522寄存器位
 * @param   addr RC522寄存器地址
 * @param   mask 要设置的位掩码
 * @return  无
 */
void RC522_SetRegisterBits(uint8_t addr, uint8_t mask);

/**
 * @brief   清除RC522寄存器位
 * @param   addr RC522寄存器地址
 * @param   mask 要清除的位掩码
 * @return  无
 */
void RC522_ClearRegisterBits(uint8_t addr, uint8_t mask);

/**
 * @brief   计算CRC校验码
 * @param   pInData 输入数据指针
 * @param   len     数据长度
 * @param   pOutData 输出CRC结果指针(2字节)
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_CalculateCRC(uint8_t *pInData, uint8_t len, uint8_t *pOutData);

/**
 * @brief   与卡片通信
 * @details 发送命令并接收卡片响应
 * @param   cmd      命令
 * @param   pInData  要发送的数据
 * @param   inLen    发送数据长度
 * @param   pOutData 接收数据缓冲区
 * @param   pOutLen  接收数据长度指针
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_CommunicateWithCard(uint8_t cmd, uint8_t *pInData, uint8_t inLen, uint8_t *pOutData, uint8_t *pOutLen);

/**
 * @brief   请求卡片
 * @details 寻找卡片并返回卡片类型
 * @param   reqMode  请求模式(PICC_REQIDL或PICC_REQALL)
 * @param   pTagType 卡片类型(2字节)
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_Request(uint8_t reqMode, uint8_t *pTagType);

/**
 * @brief   防冲撞检测
 * @details 读取卡片序列号
 * @param   pCard RC522_Card_t结构体指针,用于存储卡片信息
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_Anticoll(RC522_Card_t *pCard);

/**
 * @brief   选择卡片
 * @details 选择指定UID的卡片进行后续操作
 * @param   pCard RC522_Card_t结构体指针,包含卡片UID信息
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_SelectTag(RC522_Card_t *pCard);

/**
 * @brief   验证卡片密钥
 * @param   authMode 认证模式(PICC_AUTHENT1A或PICC_AUTHENT1B)
 * @param   blockAddr 块地址
 * @param   pKey     密钥(6字节)
 * @param   pCard    卡片信息
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_Auth(uint8_t authMode, uint8_t blockAddr, uint8_t *pKey, RC522_Card_t *pCard);

/**
 * @brief   读取数据块
 * @param   blockAddr 块地址
 * @param   pData    数据缓冲区(16字节)
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_ReadBlock(uint8_t blockAddr, uint8_t *pData);

/**
 * @brief   写入数据块
 * @param   blockAddr 块地址
 * @param   pData    要写入的数据(16字节)
 * @return  执行状态码,MI_OK表示成功
 */
uint8_t RC522_WriteBlock(uint8_t blockAddr, uint8_t *pData);

/**
 * @brief   中止卡片通信
 * @details 使卡片进入休眠状态
 * @return  执行状态码
 */
uint8_t RC522_Halt(void);

/**
 * @brief   关闭RC522天线
 * @return  无
 */
void RC522_AntennaOff(void);

/**
 * @brief   开启RC522天线
 * @return  无
 */
void RC522_AntennaOn(void);

#endif /* RC522_H */

rc522.c

/**
 * @file    rc522.c
 * @brief   RC522 RFID模块驱动程序源文件
 * @details 用于STM32F103C8单片机
 * @author  Claude
 * @date    2025-04-10
 */

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

/**
 * @brief  SPI发送/接收一个字节
 * @param  data 要发送的字节
 * @return 接收到的字节
 */
static uint8_t RC522_SPITransfer(uint8_t data)
{
    // 等待发送缓冲区为空
    while (SPI_I2S_GetFlagStatus(RC522_SPI, SPI_I2S_FLAG_TXE) == RESET);
    
    // 发送数据
    SPI_I2S_SendData(RC522_SPI, data);
    
    // 等待接收完成
    while (SPI_I2S_GetFlagStatus(RC522_SPI, SPI_I2S_FLAG_RXNE) == RESET);
    
    // 返回接收到的数据
    return SPI_I2S_ReceiveData(RC522_SPI);
}

/**
 * @brief  初始化RC522模块
 */
void RC522_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef SPI_InitStructure;
    
    // 使能SPI和GPIO时钟
    RCC_APB2PeriphClockCmd(RC522_SPI_CLK | RC522_CS_GPIO_CLK | RC522_SCK_GPIO_CLK | 
                           RC522_MISO_GPIO_CLK | RC522_MOSI_GPIO_CLK | RC522_RST_GPIO_CLK, ENABLE);
    
    // 配置SPI引脚
    GPIO_InitStructure.GPIO_Pin = RC522_SCK_PIN | RC522_MOSI_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(RC522_SCK_GPIO_PORT, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = RC522_MISO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // 浮空输入
    GPIO_Init(RC522_MISO_GPIO_PORT, &GPIO_InitStructure);
    
    // 配置CS引脚
    GPIO_InitStructure.GPIO_Pin = RC522_CS_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(RC522_CS_GPIO_PORT, &GPIO_InitStructure);
    
    // 配置RST引脚
    GPIO_InitStructure.GPIO_Pin = RC522_RST_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(RC522_RST_GPIO_PORT, &GPIO_InitStructure);
    
    // 初始电平
    RC522_CS_HIGH();    // 片选拉高,不选中
    RC522_RST_HIGH();   // 复位引脚拉高
    
    // SPI配置
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 双线全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                        // 主模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                    // 8位数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                           // 时钟极性,空闲低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                         // 第一个边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                            // 软件控制片选
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;   // 波特率预分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                   // 高位先行
    SPI_InitStructure.SPI_CRCPolynomial = 7;                             // CRC多项式
    SPI_Init(RC522_SPI, &SPI_InitStructure);
    
    // 使能SPI
    SPI_Cmd(RC522_SPI, ENABLE);
    
    // 复位RC522
    RC522_Reset();
    
    // 配置RC522模式
    RC522_WriteRegister(RC522_REG_TX_MODE, 0x00);       // 发送模式:CRC初始值0x6363
    RC522_WriteRegister(RC522_REG_RX_MODE, 0x00);       // 接收模式:CRC使能,接收数据后CRC校验
    RC522_WriteRegister(RC522_REG_MOD_WIDTH, 0x26);     // ModWidth设置
    
    // 配置定时器和其他设置
    RC522_WriteRegister(RC522_REG_CONTROL, 0x10);       // 设置发射强度
    RC522_WriteRegister(RC522_REG_TX_ASK, 0x40);        // 设置为100%ASK调制
    RC522_WriteRegister(RC522_REG_MODE, 0x3D);          // CRC初始值为0x6363
    
    // 开启天线
    RC522_AntennaOn();
}

/**
 * @brief  重置RC522模块
 */
void RC522_Reset(void)
{
    // 硬复位
    RC522_RST_HIGH();
    delay_us(1);
    RC522_RST_LOW();
    delay_us(1);
    RC522_RST_HIGH();
    delay_us(1);
    
    // 软复位
    RC522_WriteRegister(RC522_REG_COMMAND, RC522_CMD_SOFT_RESET);
    
    // 等待复位完成
    delay_ms(50);
    
    // 写入模式配置
    RC522_WriteRegister(RC522_REG_MODE, 0x3D);
    
    // 写入定时器配置
    RC522_WriteRegister(RC522_REG_CONTROL, 0x10);  // T0Control
    RC522_WriteRegister(RC522_REG_COM_IRQ, 0x00);  // 清除所有中断
    RC522_WriteRegister(RC522_REG_DIV_IRQ, 0x00);  // 清除所有中断标志
}

/**
 * @brief  向RC522寄存器写入数据
 * @param  addr RC522寄存器地址
 * @param  val  要写入的值
 */
void RC522_WriteRegister(uint8_t addr, uint8_t val)
{
    // 地址格式:0XXXXXX0,写操作最低位为0
    addr = (addr << 1) & 0x7E;
    
    RC522_CS_LOW();                // 选中RC522
    RC522_SPITransfer(addr);       // 发送地址
    RC522_SPITransfer(val);        // 发送数据
    RC522_CS_HIGH();               // 取消选中
}

/**
 * @brief  从RC522寄存器读取数据
 * @param  addr RC522寄存器地址
 * @return 读取到的寄存器值
 */
uint8_t RC522_ReadRegister(uint8_t addr)
{
    uint8_t val;
    
    // 地址格式:1XXXXXX0,读操作最高位为1
    addr = ((addr << 1) & 0x7E) | 0x80;
    
    RC522_CS_LOW();                // 选中RC522
    RC522_SPITransfer(addr);       // 发送地址
    val = RC522_SPITransfer(0x00); // 读取数据
    RC522_CS_HIGH();               // 取消选中
    
    return val;
}

/**
 * @brief  设置RC522寄存器位
 * @param  addr RC522寄存器地址
 * @param  mask 要设置的位掩码
 */
void RC522_SetRegisterBits(uint8_t addr, uint8_t mask)
{
    uint8_t tmp;
    tmp = RC522_ReadRegister(addr);
    RC522_WriteRegister(addr, tmp | mask);
}

/**
 * @brief  清除RC522寄存器位
 * @param  addr RC522寄存器地址
 * @param  mask 要清除的位掩码
 */
void RC522_ClearRegisterBits(uint8_t addr, uint8_t mask)
{
    uint8_t tmp;
    tmp = RC522_ReadRegister(addr);
    RC522_WriteRegister(addr, tmp & (~mask));
}

/**
 * @brief  计算CRC校验码
 * @param  pInData 输入数据指针
 * @param  len     数据长度
 * @param  pOutData 输出CRC结果指针(2字节)
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_CalculateCRC(uint8_t *pInData, uint8_t len, uint8_t *pOutData)
{
    uint8_t status, i, n;
    
    // 清除CRC_IRQ中断标志
    RC522_ClearRegisterBits(RC522_REG_DIV_IRQ, 0x04);
    
    // 清除FIFO缓冲区
    RC522_SetRegisterBits(RC522_REG_FIFO_LEVEL, 0x80);
    
    // 将数据写入FIFO
    for (i = 0; i < len; i++) {
        RC522_WriteRegister(RC522_REG_FIFO_DATA, *(pInData + i));
    }
    
    // 发送CRC计算命令
    RC522_WriteRegister(RC522_REG_COMMAND, RC522_CMD_CALC_CRC);
    
    // 等待CRC计算完成
    i = 0xFF;
    do {
        n = RC522_ReadRegister(RC522_REG_DIV_IRQ);
        i--;
    } while ((i != 0) && !(n & 0x04));  // CRCIrq = 1表示计算完成
    
    // 读取CRC计算结果
    if (i != 0) {
        pOutData[0] = RC522_ReadRegister(RC522_REG_CRC_RESULT_L);
        pOutData[1] = RC522_ReadRegister(RC522_REG_CRC_RESULT_H);
        status = MI_OK;
    } else {
        status = MI_ERR;
    }
    
    return status;
}

/**
 * @brief  与卡片通信
 * @param  cmd      命令
 * @param  pInData  要发送的数据
 * @param  inLen    发送数据长度
 * @param  pOutData 接收数据缓冲区
 * @param  pOutLen  接收数据长度指针
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_CommunicateWithCard(uint8_t cmd, uint8_t *pInData, uint8_t inLen, uint8_t *pOutData, uint8_t *pOutLen)
{
    uint8_t status = MI_ERR;
    uint8_t irqEn = 0x00;
    uint8_t waitIRq = 0x00;
    uint8_t lastBits;
    uint8_t n;
    uint8_t i;
    
    switch (cmd) {
        case RC522_CMD_MF_AUTHENT:   // Mifare认证
            irqEn = 0x12;            // 允许错误中断和空闲中断
            waitIRq = 0x10;          // 等待空闲中断
            break;
        case RC522_CMD_TRANSCEIVE:   // 发送并接收数据
            irqEn = 0x77;            // 允许发送、接收、空闲、计时器结束中断
            waitIRq = 0x30;          // 等待接收中断和空闲中断
            break;
        default:
            break;
    }
    
    // 设置中断开关
    RC522_WriteRegister(RC522_REG_COM_IE_N, irqEn | 0x80); // 允许中断请求
    
    // 清除中断标志
    RC522_ClearRegisterBits(RC522_REG_COM_IRQ, 0x80);      // 清除所有中断标志
    RC522_SetRegisterBits(RC522_REG_FIFO_LEVEL, 0x80);     // 清空FIFO
    
    // 发送命令
    RC522_WriteRegister(RC522_REG_COMMAND, RC522_CMD_IDLE);  // 取消当前命令
    
    // 写入数据到FIFO
    for (i = 0; i < inLen; i++) {
        RC522_WriteRegister(RC522_REG_FIFO_DATA, pInData[i]);
    }
    
    // 执行命令
    RC522_WriteRegister(RC522_REG_COMMAND, cmd);
    
    // 如果是发送并接收数据命令,需要启动发送
    if (cmd == RC522_CMD_TRANSCEIVE) {
        RC522_SetRegisterBits(RC522_REG_BIT_FRAMING, 0x80); // 启动数据发送
    }
    
    // 等待命令完成
    i = 2000; // 根据时钟频率调整,等待最长时间约25ms
    do {
        n = RC522_ReadRegister(RC522_REG_COM_IRQ);
        i--;
    } while ((i != 0) && !(n & 0x01) && !(n & waitIRq)); // 等待命令完成
    
    // 清除启动位
    RC522_ClearRegisterBits(RC522_REG_BIT_FRAMING, 0x80);
    
    // 检查命令执行结果
    if (i != 0) {
        // 读取错误标志寄存器
        if (!(RC522_ReadRegister(RC522_REG_ERROR) & 0x1B)) {
            status = MI_OK;
            
            // 如果碰撞错误
            if (n & 0x08) {
                status = MI_ERR;
            }
            
            // 读取接收数据长度
            if (cmd == RC522_CMD_TRANSCEIVE) {
                n = RC522_ReadRegister(RC522_REG_FIFO_LEVEL);
                lastBits = RC522_ReadRegister(RC522_REG_CONTROL) & 0x07;
                
                if (lastBits) {
                    *pOutLen = (n - 1) * 8 + lastBits;
                } else {
                    *pOutLen = n * 8;
                }
                
                if (n == 0) {
                    n = 1;
                }
                
                if (n > 16) {
                    n = 16;
                }
                
                // 从FIFO读取接收数据
                for (i = 0; i < n; i++) {
                    pOutData[i] = RC522_ReadRegister(RC522_REG_FIFO_DATA);
                }
                
                // 如果没有数据,返回错误
                if (n == 0) {
                    status = MI_ERR;
                }
            }
        } else {
            status = MI_ERR;
        }
    }
    
    return status;
}

/**
 * @brief  请求卡片
 * @param  reqMode  请求模式(PICC_REQIDL或PICC_REQALL)
 * @param  pTagType 卡片类型(2字节)
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_Request(uint8_t reqMode, uint8_t *pTagType)
{
    uint8_t status;
    uint8_t data_to_send[1];
    uint8_t recv_data[16];
    uint8_t recv_bits;
    
    // 清除MIFARECrypto1On位
    RC522_ClearRegisterBits(RC522_REG_STATUS2, 0x08);
    
    // 设置发送数据位长度
    RC522_WriteRegister(RC522_REG_BIT_FRAMING, 0x07);  // 7位
    
    // 准备发送数据
    data_to_send[0] = reqMode;
    
    // 发送请求命令
    status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, data_to_send, 1, recv_data, &recv_bits);
    
    // 如果成功接收到数据,返回卡片类型
    if ((status == MI_OK) && (recv_bits == 0x10)) {
        *pTagType = recv_data[0];
        *(pTagType + 1) = recv_data[1];
    } else {
        status = MI_ERR;
    }
    
    return status;
}

/**
 * @brief  防冲撞检测
 * @param  pCard RC522_Card_t结构体指针,用于存储卡片信息
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_Anticoll(RC522_Card_t *pCard)
{
    uint8_t status;
    uint8_t i, check_sum = 0;
    uint8_t serial_in[2];
    uint8_t serial_out[16];
    uint8_t recv_bits;
    
    // 清除MIFARECrypto1On位
    RC522_ClearRegisterBits(RC522_REG_STATUS2, 0x08);
    
    // 设置BitFraming:不使用发送CRC,整字节发送
    RC522_WriteRegister(RC522_REG_BIT_FRAMING, 0x00);
    
    // 准备防冲撞命令
    serial_in[0] = PICC_ANTICOLL;  // 防冲撞命令
    serial_in[1] = 0x20;           // NVB - 0x20表示没有传输的有效字节
    
    // 发送防冲撞命令
    status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, serial_in, 2, serial_out, &recv_bits);
    
    // 如果接收成功
    if (status == MI_OK) {
        // 检查是否接收到完整的UID
        if (recv_bits == 0x40) {
            // 计算校验和
            for (i = 0; i < 4; i++) {
                pCard->uid[i] = serial_out[i];
                check_sum ^= serial_out[i];  // 异或校验
            }
            
            // 检查校验和是否正确
            if (check_sum != serial_out[4]) {
                status = MI_ERR;
            }
        } else {
            status = MI_ERR;
        }
    }
    
    return status;
}

/**
 * @brief  选择卡片
 * @param  pCard RC522_Card_t结构体指针,包含卡片UID信息
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_SelectTag(RC522_Card_t *pCard)
{
    uint8_t status;
    uint8_t i;
    uint8_t buffer[9];
    uint8_t recv_data[16];
    uint8_t recv_bits;
    
    // 准备选择卡片命令
    buffer[0] = PICC_SElECTTAG;  // 选择卡片命令
    buffer[1] = 0x70;            // NVB - 0x70表示7个有效字节
    
    // 复制UID到发送缓冲区
    for (i = 0; i < 4; i++) {
        buffer[i+2] = pCard->uid[i];
    }
    
    // 计算并添加BCC
    buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; // BCC校验
    
    // 计算CRC
    status = RC522_CalculateCRC(buffer, 7, &buffer[7]);
    
    if (status == MI_OK) {
        // 发送选择卡片命令
        status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, buffer, 9, recv_data, &recv_bits);
        
        // 如果接收成功并且接收到SAK
        if ((status == MI_OK) && (recv_bits == 0x08)) {
            pCard->sak = recv_data[0];  // 保存SAK值
            return recv_data[0];
        } else {
            return 0;
        }
    }
    
    return 0;
}

/**
 * @brief  验证卡片密钥
 * @param  authMode 认证模式(PICC_AUTHENT1A或PICC_AUTHENT1B)
 * @param  blockAddr 块地址
 * @param  pKey     密钥(6字节)
 * @param  pCard    卡片信息
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_Auth(uint8_t authMode, uint8_t blockAddr, uint8_t *pKey, RC522_Card_t *pCard)
{
    uint8_t status;
    uint8_t buffer[12];
    uint8_t recv_data[16];
    uint8_t recv_bits;
    
    // 准备认证命令
    buffer[0] = authMode;      // 认证命令(A或B)
    buffer[1] = blockAddr;     // 块地址
    
    // 复制密钥到发送缓冲区
    for (uint8_t i = 0; i < 6; i++) {
        buffer[i+2] = *(pKey+i);
    }
    
    // 复制卡片UID到发送缓冲区
    for (uint8_t i = 0; i < 4; i++) {
        buffer[i+8] = pCard->uid[i];
    }
    
    // 发送认证命令
    status = RC522_CommunicateWithCard(RC522_CMD_MF_AUTHENT, buffer, 12, recv_data, &recv_bits);
    
    // 检查MIFARECrypto1On位是否置位
    if ((status != MI_OK) || !(RC522_ReadRegister(RC522_REG_STATUS2) & 0x08)) {
        status = MI_ERR;
    }
    
    return status;
}

/**
 * @brief  读取数据块
 * @param  blockAddr 块地址
 * @param  pData    数据缓冲区(16字节)
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_ReadBlock(uint8_t blockAddr, uint8_t *pData)
{
    uint8_t status;
    uint8_t buffer[4];
    uint8_t recv_bits;
    
    // 准备读块命令
    buffer[0] = PICC_READ;      // 读块命令
    buffer[1] = blockAddr;      // 块地址
    
    // 计算CRC
    status = RC522_CalculateCRC(buffer, 2, &buffer[2]);
    
    if (status == MI_OK) {
        // 发送读块命令
        status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, buffer, 4, pData, &recv_bits);
        
        // 检查接收到的数据长度
        if ((status != MI_OK) || (recv_bits != 0x90)) {
            status = MI_ERR;
        }
    }
    
    return status;
}

/**
 * @brief  写入数据块
 * @param  blockAddr 块地址
 * @param  pData    要写入的数据(16字节)
 * @return 执行状态码,MI_OK表示成功
 */
uint8_t RC522_WriteBlock(uint8_t blockAddr, uint8_t *pData)
{
    uint8_t status;
    uint8_t buffer[18];
    uint8_t recv_data[16];
    uint8_t recv_bits;
    
    // 准备写块命令
    buffer[0] = PICC_WRITE;     // 写块命令
    buffer[1] = blockAddr;      // 块地址
    
    // 计算CRC
    status = RC522_CalculateCRC(buffer, 2, &buffer[2]);
    
    if (status == MI_OK) {
        // 发送写块命令
        status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, buffer, 4, recv_data, &recv_bits);
        
        // 检查卡片是否已准备好接收数据(ACK)
        if ((status == MI_OK) && (recv_bits == 4) && ((recv_data[0] & 0x0F) == 0x0A)) {
            // 准备待写入的16字节数据加CRC
            for (uint8_t i = 0; i < 16; i++) {
                buffer[i] = *(pData+i);
            }
            
            // 计算CRC
            status = RC522_CalculateCRC(buffer, 16, &buffer[16]);
            
            if (status == MI_OK) {
                // 发送数据
                status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, buffer, 18, recv_data, &recv_bits);
                
                // 检查写入是否成功(ACK)
                if ((status != MI_OK) || (recv_bits != 4) || ((recv_data[0] & 0x0F) != 0x0A)) {
                    status = MI_ERR;
                }
            }
        } else {
            status = MI_ERR;
        }
    }
    
    return status;
}

/**
 * @brief  中止卡片通信
 * @return 执行状态码
 */
uint8_t RC522_Halt(void)
{
    uint8_t status;
    uint8_t buffer[4];
    uint8_t recv_data[16];
    uint8_t recv_bits;
    
    // 准备休眠命令
    buffer[0] = PICC_HALT;      // 休眠命令
    buffer[1] = 0;              // 必须为0
    
    // 计算CRC
    status = RC522_CalculateCRC(buffer, 2, &buffer[2]);
    
    if (status == MI_OK) {
        // 发送休眠命令
        status = RC522_CommunicateWithCard(RC522_CMD_TRANSCEIVE, buffer, 4, recv_data, &recv_bits);
    }
    
    // 注意:在休眠命令中,卡片不会回应,所以这里不检查接收的数据
    
    return status;
}

/**
 * @brief  关闭RC522天线
 */
void RC522_AntennaOff(void)
{
    // 清除寄存器中的天线驱动位
    RC522_ClearRegisterBits(RC522_REG_TX_CONTROL, 0x03);
}

/**
 * @brief  开启RC522天线
 */
void RC522_AntennaOn(void)
{
    uint8_t value;
    
    // 读取当前值
    value = RC522_ReadRegister(RC522_REG_TX_CONTROL);
    
    // 如果天线未开启,则开启
    if (!(value & 0x03)) {
        RC522_SetRegisterBits(RC522_REG_TX_CONTROL, 0x03);
    }
}

/**
 * @brief  延时微秒函数
 * @param  us 微秒数
 */
static void delay_us(uint32_t us)
{
    uint32_t i;
    for (i = 0; i < us * 8; i++) {
        __NOP();  // 空操作,根据实际时钟频率调整循环次数
    }
}

/**
 * @brief  延时毫秒函数
 * @param  ms 毫秒数
 */
static void delay_ms(uint32_t ms)
{
    uint32_t i;
    for (i = 0; i < ms; i++) {
        delay_us(1000);  // 调用微秒延时1000次
    }
}

使用示例代码

/**
 * @file    rc522_example.c
 * @brief   RC522 RFID模块应用实例
 * @details 实现卡片检测、读取和写入功能
 * @author  Claude
 * @date    2025-04-10
 */

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

// 根据您的项目配置串口打印函数
void USART_Printf(const char* str);
void PrintHex(uint8_t* data, uint8_t length);

// 默认密钥A(出厂默认)
uint8_t DefaultKeyA[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// 测试数据(16字节,用于写入扇区)
uint8_t TestData[16] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};

int main(void)
{
    // 系统初始化(时钟配置等)
    SystemInit();
    
    // 初始化串口(用于调试输出)
    // USART_Config(); // 根据您的项目需要实现
    
    // 初始化RC522模块
    RC522_Init();
    
    // 打印初始化信息
    USART_Printf("RC522 RFID Module Initialized\r\n");
    USART_Printf("Firmware Version: ");
    uint8_t version = RC522_ReadRegister(RC522_REG_VERSION);
    printf("0x%02X\r\n", version);
    
    // 卡片信息结构体
    RC522_Card_t card;
    uint8_t cardType[2];
    uint8_t blockData[16];
    uint8_t status;
    
    // 主循环
    while (1) {
        // 清除先前的卡片数据
        memset(&card, 0, sizeof(RC522_Card_t));
        
        // 检查是否有新卡片
        status = RC522_Request(PICC_REQIDL, cardType);
        
        if (status == MI_OK) {
            USART_Printf("\r\n");
            USART_Printf("Card detected!\r\n");
            USART_Printf("Card type: ");
            printf("0x%02X%02X\r\n", cardType[0], cardType[1]);
            
            // 执行防冲撞检测,获取卡片UID
            status = RC522_Anticoll(&card);
            
            if (status == MI_OK) {
                USART_Printf("Card UID: ");
                PrintHex(card.uid, 4);
                USART_Printf("\r\n");
                
                // 选择检测到的卡片
                uint8_t sak = RC522_SelectTag(&card);
                USART_Printf("Card SAK: 0x%02X\r\n", sak);
                
                // 演示读取数据块(块1,通常是用户数据区)
                USART_Printf("Reading block 1...\r\n");
                
                // 首先需要验证密钥
                status = RC522_Auth(PICC_AUTHENT1A, 1, DefaultKeyA, &card);
                
                if (status == MI_OK) {
                    USART_Printf("Authentication successful\r\n");
                    
                    // 读取数据块1
                    status = RC522_ReadBlock(1, blockData);
                    
                    if (status == MI_OK) {
                        USART_Printf("Block 1 data: ");
                        PrintHex(blockData, 16);
                        USART_Printf("\r\n");
                        
                        // 写入测试数据到块1(如果需要)
                        USART_Printf("Writing test data to block 1...\r\n");
                        status = RC522_WriteBlock(1, TestData);
                        
                        if (status == MI_OK) {
                            USART_Printf("Write successful\r\n");
                            
                            // 再次读取确认写入成功
                            status = RC522_ReadBlock(1, blockData);
                            
                            if (status == MI_OK) {
                                USART_Printf("Block 1 data (after write): ");
                                PrintHex(blockData, 16);
                                USART_Printf("\r\n");
                            }
                        } else {
                            USART_Printf("Write failed\r\n");
                        }
                    } else {
                        USART_Printf("Read failed\r\n");
                    }
                } else {
                    USART_Printf("Authentication failed\r\n");
                }
                
                // 使卡片进入休眠状态
                RC522_Halt();
            }
        }
        
        // 延时等待(避免过快循环扫描)
        for (uint32_t i = 0; i < 1000000; i++) {
            __NOP();
        }
    }
}

/**
 * @brief  以十六进制形式打印数据
 * @param  data   数据指针
 * @param  length 数据长度
 */
void PrintHex(uint8_t* data, uint8_t length)
{
    char buffer[5];
    for (uint8_t i = 0; i < length; i++) {
        sprintf(buffer, "%02X ", data[i]);
        USART_Printf(buffer);
    }
}

/**
 * @brief  通过串口发送字符串
 * @param  str 字符串指针
 * @note   这是一个简化版实现,您需要根据实际项目配置来实现此函数
 */
void USART_Printf(const char* str)
{
    // 示例实现,根据您的项目配置修改
    while (*str) {
        // 等待发送缓冲区为空
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        
        // 发送字符
        USART_SendData(USART1, *str++);
    }
}

/**
 * @brief  初始化USART1用于调试输出
 * @note   这是一个简化版实现,您需要根据实际项目配置来实现此函数
 */
void USART_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    // 使能GPIO和USART时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置USART1 Tx引脚(PA9)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置USART1 Rx引脚(PA10)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置USART1参数
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

### RFID-RC522标准库下载与示例代码 对于RFID-RC522模块的支持,在不同的平台上可以找到相应的库来简化开发过程。 #### Arduino平台上的MFRC522库 在Arduino环境中,可以通过官方IDE安装`MFRC522`库。这一步骤简单明了,只需打开Arduino IDE,访问“工具”下的“库管理”,搜索关键词`MFRC522`并完成安装即可[^2]。此库不仅包含了基本的操作函数,还附带了一些实用的例子帮助开发者快速上手。 ```cpp #include <SPI.h> #include <MFRC522.h> #define RST_PIN 9 // Configurable, see typical pin layout above #define SS_PIN 10 // Configurable, see typical pin layout above MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance. void setup() { Serial.begin(9600); SPI.begin(); // Init SPI bus mfrc522.PCD_Init(); // Init MFRC522 card } void loop() { // Look for new cards if (mfrc522.PICC_IsNewCardPresent()) { // Select one of the cards if (mfrc522.PICC_ReadCardSerial()) { // Show UID on serial monitor Serial.print(F("UID tag :")); String content= ""; byte letter; for (byte i = 0; i < mfrc522.uid.size; i++) { Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); Serial.print(mfrc522.uid.uidByte[i], HEX); content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ")); content.concat(String(mfrc522.uid.uidByte[i], HEX)); } Serial.println(); mfrc522.PICC_HaltA(); // Stop reading } } } ``` 这段代码展示了如何初始化设备以及读取卡片唯一标识符(UID),这对于大多数应用场景来说已经足够入门使用。 #### Raspberry Pi环境中的Pi-RC522 Python库 针对树莓派用户,则有专门为此平台定制的Python库——`pi-rc522`可供选用。这个由Ondřej Surý维护的开源项目允许使用者方便地操作连接至GPIO接口的MFRC522芯片,并且能够处理ISO 14443A协议下的一系列命令,比如验证密钥、读扇区数据等[^3]。 为了获取上述任一版本的标准库及其配套资源文件夹,请前往对应的GitHub页面或其他可信源码托管站点进行克隆或下载压缩包形式发布的最新稳定版发布物。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值