MFRC522--RFID读写器
1.RC522内部基本寄存器配置2.先关闭天线清理旧状态,再开启天线。3.配置RC522内部天线相关寄存器(选择读写器和IC卡的通信协议)(二)寻卡/卡片探测RC522向天线辐射范围内发送寻卡指令。处于场中的卡品被激活,并恢复一个ATQA(应答请求)信号读写器通过发射特定协议的“敲门信号”,来探测其可对话的卡片。这个过程在物理层和协议层进行了双重过滤。即找到当前相同协议的、空闲状态的卡。 只能唤醒
一、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.核心:
读写器通过发射特定协议的“敲门信号”,来探测其可对话的卡片。这个过程在物理层和协议层进行了双重过滤。即找到当前相同协议的、空闲状态的卡。
2.完整过程:
物理层过滤(硬件层面,自动发生):
频率匹配:RC522工作在 13.56MHz。只有同样谐振在13.56MHz的高频(HF)卡的线圈才能从磁场中获得能量并激活。低频(125kHz)卡因频率完全不同,根本不会“醒来”。
调制与解调匹配:RC522以 ISO/IEC 14443 Type A 协议规定的 ASK 100%(改进米勒编码) 方式调制信号。只有内置了能解调此特定波形的电路的卡片,才能正确理解读写器发出的“0”和“1”。
→ 此步过滤掉了所有低频卡和不支持Type A解调方式的卡。
协议层交互(软件指令层面):
当卡片通过物理层考验后,真正的“对话”才开始。
读写器“敲门”:RC522发送协议规定的
REQA(请求,代码0x26) 或WUPA(唤醒,代码0x52) 指令帧。卡片“应答”:能听懂Type A协议的卡片,在正确的时序内,以协议规定的编码方式回复一个
ATQA(对请求的应答) 信号。这个应答是一个简短的2字节数据,主要告诉读写器:“我是Type A卡,我的大致卡类(如MIFARE Classic)是什么”。→ 此步过滤掉了同为13.56MHz但使用其他协议(如Type B, 15693)的卡,它们听不懂
REQA命令,所以不会回复ATQA。
3.状态说明:
-
REQA只能唤醒并探测处于 “IDLE(空闲)/ READY(就绪)”状态的卡片。 -
如果卡片因之前操作而进入 “HALT(休眠)”状态,则会对
REQA保持沉默。此时必须使用WUPA指令才能将其唤醒并应答。
/* 寻卡(请求卡片类型)
// 参数: 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; // 返回结果
}
(三)获取当前响应卡片的卡号,同时防冲突
也就是多个卡片响应时选择出其中一张卡并且识别出它的卡号(当前防冲突的代码处理不全面,暂不适用多卡识别)
1.核心:
当多张同类型卡片同时进入场区时,通过特定算法选取其中一张进行后续操作。
防冲突只是获得IC卡的UID,并没有直接进行读写相关的通信操作,需要在后面的选卡操作中真正选上。
2.防冲突:
当只有一张卡片响应时,所有寄存器收到的bit位数据都是稳定的值;当多张卡片响应时,个别bit位会出现0、1的交替接收,不稳定,就说明收到了不止一张卡片的信号;发现冲突后,在已有的但不冲突的序列作为开头,之后从冲突位开始用0/1进行枚举尝试选中其中一张卡。
下面是举例:
假设场内有3张MIFARE Classic卡,它们的UID分别是:
卡A:
0x12 0x34 0x56 0x78卡B:
0x12 0x34 0x55 0xAA卡C:
0x12 0x35 0xAA 0xBB第1步:发送防冲突命令,接收第一次响应
读写器发送ANTICOLLISION命令(代码0x930x20),要求所有卡片回复其UID的前面部分。
卡A 回复:
1 2 3 4(为简化,用10进制表示)卡B 回复:
1 2 3 5卡C 回复:
1 2 3 5信号在空中叠加,读写器接收到的是:在第一个字节(
12)和第二个字节(34)位置,所有卡回复一致,信号稳定。但在第三个字节,卡A回复56(二进制0101 0110),卡B和卡C回复55(0101 0101)。读写器会在比特位级别检测到冲突:例如,56和55的最后一个比特位分别是0和1,导致该位电平异常,被标记为“冲突位”。第2步:定位并解决冲突(“二叉树点名法”)
读写器发现第3个字节有冲突。它会记录下目前无冲突的部分,即UID前两个字节 = 0x12 0x34。然后,它会在冲突发生的那个比特位(以及其后的位)上,通过发送不同的“选择条件”来筛选卡片。这个过程就像老师在一群同时报学号的学生中,从冲突的那一位开始,挨个问“下一位是0的请举手?”、“是1的请举手?”。
第一次筛选:读写器发送一个新的命令,内容是“寻找UID前两个字节为
0x12 0x34,且第三个字节的冲突位(假设为最低位)为0的卡”。
卡A (
56的末位是0):符合条件,响应。卡B和卡C (
55的末位是1):不符合,保持沉默。结果:此时只有卡A响应,冲突消失。读写器顺利接收到卡A完整的第3、4字节
0x56 0x78,并计算出校验位,从而获得了卡A的完整UID:0x12 0x34 0x56 0x78。完成第一张卡的选择:读写器立即对卡A的完整UID执行
SELECT(选择) 命令。卡A被选中,进入活跃状态。其他卡片(B和C)虽然也听到了这个广播,但因为UID不匹配,所以不会响应,继续处于等待状态。处理剩余卡片:读写器完成与卡A的通信并发送
HALT命令让其休眠后,可以从头开始新一轮的寻卡和防冲突流程。
这次,卡A已休眠,不会响应
ANTICOLLISION命令。当读写器再次发送命令“寻找UID前两个字节为
0x12 0x34...”时,卡B和卡C会再次响应,并在第三个字节(55)上达成一致,没有冲突。读写器可以顺利获得卡B的完整UID(0x12 0x34 0x55 0xAA)并选中它。如此循环,直到场内所有卡都被处理完毕。
===================================================================
开始防冲突
│
↓
发送ANTICOLLISION命令,接收卡片回复的UID部分
│
↓
检测回复信号 ----> 发现冲突位?
│ │
│无冲突 │有冲突
│ │
↓ ↓
获得完整UID 记录已确定的无冲突前缀
│ │
↓ ↓
进入选卡流程 从冲突位开始,发送带“0”或“1”选择条件的
新ANTICOLLISION命令,筛选出一组卡
│
└───→ 返回“检测回复信号”步骤,直到无冲突
// 防冲突,获取卡序列号(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; // 返回结果
}
(五)认证
下面的连接中,介绍了认证相关的内容。
// 验证密钥(认证一个扇区块)
// 参数: 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;
}
}
(六)数据操作:
读写卡片
(七)休眠:
休眠(HALT)是一个“硬重启”信号,而非“待机”。
-
状态:卡片休眠后,仅能响应唯一的
WUPA唤醒指令,对所有其他命令(包括REQA和数据指令)无响应。 -
后果:休眠会强制终止当前的整个通信会话,包括加密通道和逻辑连接。
-
唤醒后:卡片被唤醒后,其状态等同于一张刚进入射频场的新卡,必须从头开始完整的“寻卡→防冲突→选卡→认证”流程,才能再次进行数据操作。
二、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);
}
}
更多推荐



所有评论(0)