需要用户配置 SPI1,或自行改写ReadRawRC和WriteRawRC函数。
说明文档由 AI 生成,代码也差不多。
rc522.h
#ifndef __RC522_H
#define __RC522_H
#include "main.h" // 引入 CubeMX 生成的引脚宏定义 (RC522_CS_Pin 等)
#include "stm32f4xx_hal.h" // 引入 HAL 库
// 和MF522通讯时返回的错误代码
#define MI_OK 0
#define MI_NOTAGERR (-1)
#define MI_ERR (-2)
#define MI_ACCESSERR (-3) // 权限不足或拒绝访问控制块
#define MI_FORMATERR (-4) // 格式错误:控制块的控制位校验不通过 (原码与反码不匹配)
//=============== 配置区 ===============
#define M1_AUTO_HALT_ENABLE
//封装行为宏定义
//封装读写函数(M1开头的)会再读写完毕后自动执行 PcdHalt() 让卡片休眠,防止一直重复读写
//用户可以根据宏定义来使能或禁止自动休眠
#define RF_USE_RTOS // 定义这个宏以启用 FreeRTOS 版本的函数 (osDelay),否则使用 HAL_Delay
//============ 命令字和寄存器定义 ============
// MF522命令字
#define PCD_IDLE 0x00 //取消当前命令
#define PCD_AUTHENT 0x0E //验证密钥
#define PCD_RECEIVE 0x08 //接收数据
#define PCD_TRANSMIT 0x04 //发送数据
#define PCD_TRANSCEIVE 0x0C //发送并接收数据
#define PCD_RESETPHASE 0x0F //复位
#define PCD_CALCCRC 0x03 //CRC计算
// Mifare_One卡片命令字
#define PICC_REQIDL 0x26 //寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 //寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 //防冲撞
#define PICC_ANTICOLL2 0x95 //防冲撞
#define PICC_AUTHENT1A 0x60 //验证A密钥
#define PICC_AUTHENT1B 0x61 //验证B密钥
#define PICC_READ 0x30 //读块
#define PICC_WRITE 0xA0 //写块
#define PICC_DECREMENT 0xC0 //扣款
#define PICC_INCREMENT 0xC1 //充值
#define PICC_RESTORE 0xC2 //调块数据到缓冲区
#define PICC_TRANSFER 0xB0 //保存缓冲区中数据
#define PICC_HALT 0x50 //休眠
// MF522寄存器定义
// PAGE 0
#define RFU00 0x00
#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 RFU0F 0x0F
// PAGE 1
#define RFU10 0x10
#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 RFU1A 0x1A
#define RFU1B 0x1B
#define MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// PAGE 2
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#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
// PAGE 3
#define RFU30 0x30
#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 RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F
//========================================
/* ================= M1卡控制位魔法字典 (Access Bits) ================= */
// 1. 自由开发模式:KeyA/B均可任意读写 (出厂默认)
extern uint8_t CTRL_FREE_DEV[4] ;
// 2. 读写分离模式:KeyA只读,KeyB可读写且可修改密码
extern uint8_t CTRL_MASTER_SLAVE[4] ;
// 3. 防篡改只读模式:KeyA/B均只读,仅KeyB可修改控制位,写时需要先用B修改控制位再写入数据
extern uint8_t CTRL_ANTI_TAMPER[4] ;
// 4. 安全储值卡模式:KeyA仅支持查询/扣款,KeyB支持充值/初始化
extern uint8_t CTRL_SECURE_WALLET[4] ;
//出厂默认密钥
extern uint8_t M1_DefaultKey[6];
// MAD扇区默认密钥 (NDEF卡扇区0的控制块)
extern uint8_t Key_MAD[6];
// NDEF扇区默认密钥 (NDEF卡扇区1~15的控制块)
extern uint8_t Key_NDEF[6];
// 函数原型声明
// RC522 硬件与软件复位及初始化
int8_t PcdReset(void);
// 开启天线发射
void PcdAntennaOn(void);
// 关闭天线发射
void PcdAntennaOff(void);
// 寻卡:寻找天线工作区内的卡片
int8_t PcdRequest(unsigned char req_code,unsigned char *pTagType);
// 防冲突:检测所有卡并获取其中一张的 UID
int8_t PcdAnticoll(unsigned char *pSnr);
// 选卡:将焦点锁定在指定 UID 的卡片上
int8_t PcdSelect(unsigned char *pSnr);
// 验证扇区密码,建立加密通信管道
int8_t PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr);
// 读取卡片块数据 (读取已认证块的16字节)
int8_t PcdRead(unsigned char addr,unsigned char *pData);
// 写入卡片块数据 (向已认证块写入16字节)
int8_t PcdWrite(unsigned char addr,unsigned char *pData);
// 钱包充值/扣款等操作
int8_t PcdValue(unsigned char dd_mode,unsigned char addr,unsigned char *pValue);
// 块数据备份/调配
int8_t PcdBakValue(unsigned char sourceaddr, unsigned char goaladdr);
//主动关闭 RC522 的 Crypto1 硬件加密引擎
void PcdStopCrypto1(void);
// 使目标卡片进入休眠状态
int8_t PcdHalt(void);
// RC522 与卡片通信的核心底层函数(状态机)
int8_t PcdComMF522(uint8_t Command,uint8_t *pInData,uint8_t InLenByte,uint8_t *pOutData,uint16_t *pOutLenBit);
// 利用 RC522 的硬件协处理器计算 CRC 校验码
void CalulateCRC(uint8_t *pIndata,uint8_t len,uint8_t *pOutData);
// 通过 SPI 向 RC522 的寄存器写入数据
void WriteRawRC(uint8_t Address,uint8_t value);
// 通过 SPI 读取 RC522 的寄存器
uint8_t ReadRawRC(uint8_t Address);
// 置位 RC522 寄存器的特定位 (按位或)
void SetBitMask(uint8_t reg,uint8_t mask);
// 清零 RC522 寄存器的特定位 (按位与非)
void ClearBitMask(uint8_t reg,uint8_t mask);
// 【封装】强制唤醒 M1 卡并获取 UID
int8_t M1_ForceWakeUp(uint8_t *uid);
// 【封装】从 M1 卡的指定块写 16 字节数据
int8_t M1_WriteCardBlock(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *writeData);
// 【封装】从 M1 卡的指定块读取 16 字节数据
int8_t M1_ReadCardBlock(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *readData);
// 从 M1 卡读取 16 字节,支持选择验证 Key A 还是 Key B
int8_t M1_ReadCardBlock_Ext(uint8_t *uid, uint8_t blockAddr, uint8_t auth_mode, uint8_t *key, uint8_t *readData);
// 向 M1 卡写入 16 字节,支持选择验证 Key A 还是 Key B
int8_t M1_WriteCardBlock_Ext(uint8_t *uid, uint8_t blockAddr, uint8_t auth_mode, uint8_t *key, uint8_t *writeData);
// [绝对安全写] 向指定绝对块地址写入数据,严格拒绝写控制块
int8_t M1_WriteDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *writeData);
// [绝对读] 从指定绝对块地址读取数据
int8_t M1_ReadDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *readData);
// [相对安全写] 指定扇区号和相对数据块号写入数据,,严格拒绝写控制块
int8_t M1_WriteDataBlock_Rel(uint8_t *uid, uint8_t sector, uint8_t relBlock, uint8_t *key, uint8_t *writeData);
// [相对读] 指定扇区号和相对数据块号读取数据
int8_t M1_ReadDataBlock_Rel(uint8_t *uid, uint8_t sector, uint8_t relBlock, uint8_t *key, uint8_t *readData);
// [高危写] 修改指定扇区的密码和访问控制位,带防砖校验
int8_t M1_WriteSectorControlBlock_Safe(uint8_t *uid, uint8_t sector, uint8_t *key, uint8_t *newControlData);
// 构建 M1 卡控制块数据 (16字节)
void Make_SectorTrailer(uint8_t *newKeyA, uint8_t *newKeyB, uint8_t *ctrlBits, uint8_t *outTrailer);
/*
uint8_t CardType[2];
uint8_t CardUID[4];
uint8_t MyReadBuffer[16];
uint8_t MyWriteData[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
int8_t status;
// --- 写入测试 (写入所有块) ---
for (int i = 0; i < 64; i++)
{
// 1. 唤醒步骤:每次物理操作前,必须重新寻卡和防冲突!
if (PcdRequest(PICC_REQALL, CardType) != MI_OK) {
printf("块 %02d : 寻卡失败,卡片可能不在感应区或已移开\r\n", i);
osDelay(50);
continue; // 唤醒失败,跳过本次循环
}
if (PcdAnticoll(CardUID) != MI_OK) {
printf("块 %02d : 防冲突失败\r\n", i);
osDelay(50);
continue;
}
// 2. 执行安全写入
status = M1_WriteDataBlock_Abs(CardUID, i, M1_DefaultKey, MyWriteData);
// 3. 根据状态码精准打印日志
if (status == MI_OK) {
printf("块 %02d : 成功!数据已写入。\r\n", i);
}
else if (status == MI_ACCESSERR) {
// 遇到块 0 或 3, 7, 11 等控制块,安全机制生效被跳过
printf("块 %02d : 跳过!(厂商只读块 或 密码控制块)\r\n", i);
}
else {
// 底层通信失败或密码验证失败
printf("块 %02d : 失败!密码错误或扇区损坏。\r\n", i);
}
// 给卡片状态机留一点点恢复的缓冲时间
HAL_Delay(10);
PcdHalt();
}
// --- 读取测试 (读取所有 64 块) ---
for (int i = 0; i < 64; i++)
{
// 1. 强力唤醒卡片
if (PcdRequest(PICC_REQALL, CardType) != MI_OK)
{
printf("块 %02d : 寻卡失败\r\n", i);
osDelay(10);
continue;
}
// 2. 防冲突获取 UID
if (PcdAnticoll(CardUID) != MI_OK)
{
continue;
}
// 3. 一招鲜吃遍天:直接用默认的出厂密码 M1_DefaultKey 读取
status = M1_ReadDataBlock_Abs(CardUID, i, M1_DefaultKey, MyReadBuffer);
// 4. 格式化输出结果
if (status == MI_OK)
{
printf("块 %02d [数据] : ", i);
for (int j = 0; j < 16; j++) {
printf("%02X ", MyReadBuffer[j]);
}
printf("\r\n");
}
else if (status == MI_ACCESSERR)
{
// 自动拦截第 3、7、11 等密码控制块,防止读出全 00 的无用数据
printf("块 %02d [控制] : --- 密码控制块 (受保护) ---\r\n", i);
}
else
{
printf("块 %02d [错误] : 验证或读取失败\r\n", i);
}
HAL_Delay(10);
}
*/
#endifrc522.c
#include "rc522.h"
//#include "cmsis_os.h"
extern SPI_HandleTypeDef hspi1;
#define MAXRLEN 18
// 定义 M1 卡的出厂默认密钥 (A密钥,6个 0xFF)
uint8_t M1_DefaultKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// 定义两组 NDEF 标准密钥
uint8_t Key_MAD[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; // 扇区0 (MAD目录) 密钥
uint8_t Key_NDEF[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; // 扇区1~15 (NDEF区) 密钥
/* ================= M1卡控制位魔法字典 (Access Bits) ================= */
// 1. 自由开发模式:KeyA/B均可任意读写 (出厂默认)
uint8_t CTRL_FREE_DEV[4]= {0xFF, 0x07, 0x80, 0x69};
// 2. 读写分离模式:KeyA只读,KeyB可读写且可修改密码
uint8_t CTRL_MASTER_SLAVE[4]= {0x7F, 0x07, 0x88, 0x69};
// 3. 防篡改只读模式:KeyA/B均只读,仅KeyB可修改控制位,写时需要先用B修改控制位再写入数据
uint8_t CTRL_ANTI_TAMPER[4]= {0x5F, 0xC7, 0x8A, 0x69};
// 4. 安全储值卡模式:KeyA仅支持查询/扣款,KeyB支持充值/初始化
uint8_t CTRL_SECURE_WALLET[4]= {0x08, 0x77, 0x8F, 0x69};
/**
* @brief 通过 SPI 读取 RC522 的寄存器
* @param Address: 要读取的寄存器地址
* @return 寄存器中的当前值
*/
uint8_t ReadRawRC(uint8_t Address)
{
uint8_t tx[2];
uint8_t rx[2];
// RC522 SPI 读地址格式:最高位(bit7)为1表示读,bit6~bit1为实际地址,最低位(bit0)为0
tx[0] = ((Address << 1) & 0x7E) | 0x80;
tx[1] = 0; // 发送一个空字节(dummy byte)以产生时钟信号,从而接收数据
HAL_GPIO_WritePin(RC522_CS_GPIO_Port, RC522_CS_Pin, GPIO_PIN_RESET); // 拉低片选,开始通信
HAL_SPI_TransmitReceive(&hspi1, tx, rx, 2, HAL_MAX_DELAY); // 全双工收发
HAL_GPIO_WritePin(RC522_CS_GPIO_Port, RC522_CS_Pin, GPIO_PIN_SET); // 拉高片选,结束通信
return rx[1]; // 返回接收到的第二个字节(第一个字节是状态字,通常丢弃)
}
/**
* @brief 通过 SPI 向 RC522 的寄存器写入数据
* @param Address: 要写入的寄存器地址
* @param value: 要写入的数据值
*/
void WriteRawRC(uint8_t Address, uint8_t value)
{
uint8_t tx[2];
// RC522 SPI 写地址格式:最高位(bit7)为0表示写,bit6~bit1为实际地址,最低位(bit0)为0
tx[0] = ((Address << 1) & 0x7E);
tx[1] = value; // 紧接着发送要写入的数据
HAL_GPIO_WritePin(RC522_CS_GPIO_Port, RC522_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, tx, 2, HAL_MAX_DELAY); // 仅发送数据
HAL_GPIO_WritePin(RC522_CS_GPIO_Port, RC522_CS_Pin, GPIO_PIN_SET);
}
/**
* @brief 置位 RC522 寄存器的特定位 (按位或)
* @param reg: 寄存器地址
* @param mask: 掩码,为 1 的位将被置位
*/
void SetBitMask(uint8_t reg, uint8_t mask)
{
uint8_t tmp = ReadRawRC(reg);
WriteRawRC(reg, tmp | mask);
}
/**
* @brief 清零 RC522 寄存器的特定位 (按位与非)
* @param reg: 寄存器地址
* @param mask: 掩码,为 1 的位将被清零
*/
void ClearBitMask(uint8_t reg, uint8_t mask)
{
uint8_t tmp = ReadRawRC(reg);
WriteRawRC(reg, tmp & (~mask));
}
/**
* @brief RC522 硬件与软件复位及初始化 (使用 HAL_Delay)
* @note 适用于裸机环境或任务调度器启动前
* @return MI_OK 表示成功
*/
int8_t PcdReset(void)
{
// 1. 硬件复位:通过 RST 引脚产生复位脉冲
HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_SET);
#ifndef RF_USE_RTOS
HAL_Delay(2);
#else
osDelay(2);
#endif
HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_RESET);
#ifndef RF_USE_RTOS
HAL_Delay(2);
#else
osDelay(2);
#endif
HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_SET);
#ifndef RF_USE_RTOS
HAL_Delay(2);
#else
osDelay(2);
#endif
// 2. 软件复位:向命令寄存器写入复位命令
WriteRawRC(CommandReg, PCD_RESETPHASE);
// 必须等待 RC522 内部软复位完成!否则后面的配置全被丢弃!
#ifndef RF_USE_RTOS
HAL_Delay(50);
#else
osDelay(50);
#endif
// 3. 配置内部定时器和工作模式 (定义底层通信速率与超时机制)
WriteRawRC(ModeReg, 0x3D); // 定义发送和接收常用模式
WriteRawRC(TReloadRegL, 30); // 定时器重装值低位
WriteRawRC(TReloadRegH, 0); // 定时器重装值高位
WriteRawRC(TModeReg, 0x8D); // 内部定时器设置
WriteRawRC(TPrescalerReg, 0x3E); // 设置定时器分频系数
// 【关键修复2】强制 100% ASK 调制!没有这句 M1 卡绝对不理你
WriteRawRC(TxAutoReg, 0x40);
// 可选增强:将天线接收增益调到最大 (48dB),解决稍微离远一点就读不到的问题
// WriteRawRC(RxSelReg, 0x86);
return MI_OK;
}
/**
* @brief 开启天线发射
* @note 每次与卡片通信前必须确保天线已开启
*/
void PcdAntennaOn(void)
{
uint8_t temp = ReadRawRC(TxControlReg);
// 检查天线引脚 TX1 和 TX2 的控制位是否已开启
if(!(temp & 0x03))
{
SetBitMask(TxControlReg, 0x03);
}
}
/**
* @brief 关闭天线发射
*/
void PcdAntennaOff(void)
{
ClearBitMask(TxControlReg, 0x03);
}
/**
* @brief 利用 RC522 的硬件协处理器计算 CRC 校验码
* @param pIndata: 需要计算 CRC 的数据首地址
* @param len: 数据长度
* @param pOutData: 存放计算结果的 2 字节数组
*/
void CalulateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
uint8_t i, n;
// 清除 CRC 中断标志,停止当前活动,清空 FIFO
ClearBitMask(DivIrqReg, 0x04);
WriteRawRC(CommandReg, PCD_IDLE);
SetBitMask(FIFOLevelReg, 0x80); // 写 1 清空 FIFO
// 将需要计算的数据压入 FIFO
for(i = 0; i < len; i++)
{
WriteRawRC(FIFODataReg, pIndata[i]);
}
// 触发硬件 CRC 计算命令
WriteRawRC(CommandReg, PCD_CALCCRC);
i = 0xFF;
// 轮询等待 CRC 计算完成标志 (位 2)
do
{
n = ReadRawRC(DivIrqReg);
i--;
}
while((i != 0) && !(n & 0x04));
// 读出 2 字节的 CRC 计算结果
pOutData[0] = ReadRawRC(CRCResultRegL);
pOutData[1] = ReadRawRC(CRCResultRegM);
}
/**
* @brief RC522 与 ISO14443 卡片通信的核心底层函数 (状态机)
* @param Command: 发送给 RC522 的命令字 (如发送、认证等)
* @param pInData: 准备发送给卡片的数据
* @param InLenByte: 发送数据的字节长度
* @param pOutData: 用于接收卡片返回数据的缓冲区
* @param pOutLenBit: 接收到的数据长度 (以 bit 为单位)
* @return 状态码 (MI_OK, MI_NOTAGERR, MI_ERR)
*/
int8_t PcdComMF522(uint8_t Command, uint8_t *pInData, uint8_t InLenByte, uint8_t *pOutData, uint16_t *pOutLenBit)
{
int8_t status = MI_ERR;
uint8_t irqEn = 0x00;
uint8_t waitFor = 0x00;
uint8_t lastBits;
uint8_t n;
uint16_t i;
// 1. 根据不同命令配置期望发生的中断类型
switch(Command)
{
case PCD_AUTHENT: // 密钥认证
irqEn = 0x12; // 允许错误中断 | 空闲中断
waitFor = 0x10; // 等待空闲中断 (认证完成)
break;
case PCD_TRANSCEIVE: // 收发数据
irqEn = 0x77; // 允许 Tx/Rx/Idle/Err 等全部中断
waitFor = 0x30; // 等待接收中断 | 空闲中断
break;
}
// 2. 初始化中断及 FIFO 状态
WriteRawRC(ComIEnReg, irqEn | 0x80); // 使能中断请求
ClearBitMask(ComIrqReg, 0x80); // 清除所有中断标志
WriteRawRC(CommandReg, PCD_IDLE); // 停止正在进行的命令
SetBitMask(FIFOLevelReg, 0x80); // 清空 FIFO 指针
// 3. 将发送数据压入 FIFO
for(i = 0; i < InLenByte; i++)
{
WriteRawRC(FIFODataReg, pInData[i]);
}
// 4. 执行命令
WriteRawRC(CommandReg, Command);
// 如果是收发命令,需要设置 BitFramingReg 发送触发位 (启动数据发送)
if(Command == PCD_TRANSCEIVE)
SetBitMask(BitFramingReg, 0x80);
// 5. 等待通信完成或超时 (使用 HAL_GetTick 实现非阻塞延时)
uint32_t start = HAL_GetTick();
do
{
n = ReadRawRC(ComIrqReg);
}
// 循环条件:定时器未归零(n&0x01) && 未收到预期中断(n&waitFor) && 未超时(30ms)
while(!(n & 0x01) && !(n & waitFor) && ((HAL_GetTick() - start) < 30));
ClearBitMask(BitFramingReg, 0x80); // 发送结束,清除触发位
// 6. 检查执行结果
if(!(ReadRawRC(ErrorReg) & 0x1B)) // 检查是否有底层协议错误 (BufferOvfl, CollErr, ParityErr, ProtocolErr)
{
status = MI_OK;
if(n & irqEn & 0x01) // 如果发生了定时器中断,说明没读到卡
status = MI_NOTAGERR;
if(Command == PCD_TRANSCEIVE)
{
// 获取 FIFO 中接收到的字节数
n = ReadRawRC(FIFOLevelReg);
// 检查最后一个字节的有效位数
lastBits = ReadRawRC(ControlReg) & 0x07;
// 计算接收到的总位数 (Bits)
if(lastBits)
*pOutLenBit = (n - 1) * 8 + lastBits;
else
*pOutLenBit = n * 8;
// 异常数据长度保护
if(n == 0) n = 1;
if(n > MAXRLEN) n = MAXRLEN;
// 从 FIFO 中提取接收到的数据
for(i = 0; i < n; i++)
pOutData[i] = ReadRawRC(FIFODataReg);
}
}
SetBitMask(ControlReg, 0x80); // 停止定时器
WriteRawRC(CommandReg, PCD_IDLE); // 将芯片置为空闲
return status;
}
/**
* @brief 寻卡:寻找天线工作区内的卡片
* @param req_code: 寻卡方式 (PICC_REQIDL:寻找休眠外卡, PICC_REQALL:寻找所有卡)
* @param pTagType: 返回 2 字节的卡片类型代码 (如 0x0400 为 M1 S50 卡)
* @return MI_OK 表示寻卡成功
*/
int8_t PcdRequest(uint8_t req_code, uint8_t *pTagType)
{
int8_t status;
uint16_t len;
uint8_t buf[MAXRLEN];
ClearBitMask(Status2Reg, 0x08); // 关闭加密体制 (MIFARE Crypto1)
WriteRawRC(BitFramingReg, 0x07); // 发送寻卡指令时只需发送 7 bit
SetBitMask(TxControlReg, 0x03); // 确保天线开启
buf[0] = req_code;
// 与卡片收发寻卡指令
status = PcdComMF522(PCD_TRANSCEIVE, buf, 1, buf, &len);
// 寻卡成功时,卡片应答 (ATQA) 长度为 16 bits (0x10)
if(status == MI_OK && len == 0x10)
{
pTagType[0] = buf[0];
pTagType[1] = buf[1];
}
else
status = MI_ERR;
return status;
}
/**
* @brief 防冲突:检测工作区内的所有卡,并获取其中一张卡的 UID (序列号)
* @param pSnr: 用于存放返回的 4 字节卡号 (UID)
* @return MI_OK 表示成功获取 UID
*/
int8_t PcdAnticoll(uint8_t *pSnr)
{
int8_t status;
uint8_t i, snr_check = 0;
uint16_t len;
uint8_t buf[MAXRLEN];
ClearBitMask(Status2Reg, 0x08);
WriteRawRC(BitFramingReg, 0x00); // 恢复 8 bit 完整帧发送
ClearBitMask(CollReg, 0x80); // 开启防冲突机制
buf[0] = PICC_ANTICOLL1; // 防冲突命令 0x93
buf[1] = 0x20; // 级联级别 1,发送 2 个字节
status = PcdComMF522(PCD_TRANSCEIVE, buf, 2, buf, &len);
if(status == MI_OK)
{
// 提取 4 字节 UID 并进行异或校验
for(i = 0; i < 4; i++)
{
pSnr[i] = buf[i];
snr_check ^= buf[i]; // 第 5 个字节是前 4 个字节的异或和 (BCC)
}
if(snr_check != buf[4])
status = MI_ERR; // 校验失败说明可能存在多卡冲突
}
SetBitMask(CollReg, 0x80); // 关闭防冲突
return status;
}
/**
* @brief 选卡:将 RC522 的焦点锁定在指定 UID 的卡片上
* @param pSnr: 4 字节的卡号 (由 PcdAnticoll 获取)
* @return MI_OK 表示选卡成功
*/
int8_t PcdSelect(uint8_t *pSnr)
{
int8_t status;
uint8_t i;
uint16_t unLen;
uint8_t buf[MAXRLEN];
// 构造选卡帧:指令 + 长度 + 4字节UID + 1字节BCC校验 + 2字节CRC
buf[0] = PICC_ANTICOLL1;
buf[1] = 0x70; // 0x70 表示发送 7 个字节
buf[6] = 0;
for (i = 0; i < 4; i++)
{
buf[i+2] = pSnr[i];
buf[6] ^= pSnr[i]; // 重新计算 BCC 放入 buf[6]
}
CalulateCRC(buf, 7, &buf[7]); // 追加 CRC 校验码到末尾
ClearBitMask(Status2Reg, 0x08);
status = PcdComMF522(PCD_TRANSCEIVE, buf, 9, buf, &unLen);
// 选卡成功后,卡片会回复 SAK(Select Acknowledge),长度为 24 bit (0x18) 或 8 bit,这里严格校验为 0x18
if ((status == MI_OK) && (unLen == 0x18))
{ status = MI_OK; }
else
{ status = MI_ERR; }
return status;
}
/**
* @brief 验证扇区密码:通过三步相互认证建立加密通信管道
* @param auth_mode: 认证模式 (PICC_AUTHENT1A 或 PICC_AUTHENT1B)
* @param addr: 需要访问的块地址 (0~63)
* @param pKey: 6字节的密钥数组
* @param pSnr: 当前操作卡片的 4字节 UID
* @return MI_OK 表示认证成功
*/
int8_t PcdAuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr)
{
int8_t status;
uint16_t len;
uint8_t i, buf[MAXRLEN];
// 构造认证帧:认证命令 + 块地址 + 6字节密钥 + 4字节UID
buf[0] = auth_mode;
buf[1] = addr;
for(i = 0; i < 6; i++)
buf[i+2] = pKey[i];
for(i = 0; i < 4; i++) // 修复:UID只有4字节
buf[i+8] = pSnr[i];
status = PcdComMF522(PCD_AUTHENT, buf, 12, buf, &len);
// 认证成功后,RC522 内部的 Crypto1 单元会自动置位 Status2Reg 的 Crypto1On 位(bit3)
if(status != MI_OK || !(ReadRawRC(Status2Reg) & 0x08))
status = MI_ERR;
return status;
}
/**
* @brief 读取卡片块数据:从已认证的块读取 16 字节
* @param addr: 要读取的块地址
* @param pData: 用于存放读取结果的缓冲区 (至少16字节)
* @return MI_OK 表示读取成功
*/
int8_t PcdRead(uint8_t addr, uint8_t *pData)
{
int8_t status;
uint16_t unLen;
uint8_t i, buf[MAXRLEN];
// 构造读块帧:读指令 + 块地址 + 2字节CRC
buf[0] = PICC_READ;
buf[1] = addr;
CalulateCRC(buf, 2, &buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, buf, 4, buf, &unLen);
// M1卡读块成功将返回 16 字节数据 + 2 字节 CRC,总计 18 字节 (144 bits = 0x90)
if ((status == MI_OK) && (unLen == 0x90))
{
for (i = 0; i < 16; i++)
{ pData[i] = buf[i]; }
}
else
{ status = MI_ERR; }
return status;
}
/**
* @brief 写入卡片块数据:向已认证的块写入 16 字节
* @param addr: 要写入的块地址
* @param pData: 准备写入的 16 字节数据源
* @return MI_OK 表示写入成功
*/
int8_t PcdWrite(uint8_t addr, uint8_t *pData)
{
int8_t status;
uint16_t unLen;
uint8_t i, buf[MAXRLEN];
// M1卡写操作分为两步:
// Step 1: 发送写块指令
buf[0] = PICC_WRITE;
buf[1] = addr;
CalulateCRC(buf, 2, &buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, buf, 4, buf, &unLen);
// 卡片应答 4 bits,且包含 0x0A 表示 ACK (准备好接收数据)
if ((status != MI_OK) || (unLen != 4) || ((buf[0] & 0x0F) != 0x0A))
{ status = MI_ERR; }
// Step 2: 发送要写入的具体 16 字节数据
if (status == MI_OK)
{
for (i = 0; i < 16; i++)
{ buf[i] = pData[i]; }
CalulateCRC(buf, 16, &buf[16]); // 追加 CRC
status = PcdComMF522(PCD_TRANSCEIVE, buf, 18, buf, &unLen);
// 卡片再次应答 ACK (0x0A) 表示写入物理存储区成功
if ((status != MI_OK) || (unLen != 4) || ((buf[0] & 0x0F) != 0x0A))
{ status = MI_ERR; }
}
return status;
}
/**
* @brief 使目标卡片进入休眠状态
* @note 通信结束后必须调用,释放卡片,否则卡片停留在当前状态不会响应下一次寻卡
* @return 状态码 (通常返回错误是正常的,因为卡片休眠后不再应答)
*/
int8_t PcdHalt(void)
{
int8_t status;
uint16_t len;
uint8_t buf[MAXRLEN];
buf[0] = PICC_HALT;
buf[1] = 0;
CalulateCRC(buf, 2, &buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, buf, 4, buf, &len);
return status;
}
/**
* @brief 【高层封装】向 M1 卡的指定块写入 16 字节数据
* @param uid: 4字节卡号 (由 PcdAnticoll 获取)
* @param blockAddr: 要写入的绝对块号 (0~63)
* @param key: 6字节扇区密钥 (通常是 A 密钥)
* @param writeData: 准备写入的 16 字节数据数组
* @return MI_OK (0) 表示成功,其他值表示失败
*/
int8_t M1_WriteCardBlock(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *writeData)
{
int8_t status;
#ifndef M1_AUTO_HALT_ENABLE
static uint8_t lastUid[4] = {0};
// 1. 选卡:与目标卡片建立专属通信
// 检查是否是新卡
if (memcmp(uid, lastUid, 4) != 0)
{
memcpy(lastUid, uid, 4); // 更新 lastUid
}// 不是新卡,说明之前已经选过了,可以跳过选卡步骤直接认证
else{
#endif
status = PcdSelect(uid);
if (status != MI_OK) return status;
#ifndef M1_AUTO_HALT_ENABLE
}
#endif
// 2. 验证密钥:使用 A 密钥 (PICC_AUTHENT1A) 验证指定块所在的扇区
status = PcdAuthState(PICC_AUTHENT1A, blockAddr, key, uid);
if (status != MI_OK) return status;
// 3. 写入数据:向指定块安全写入 16 字节
status = PcdWrite(blockAddr, writeData);
if (status != MI_OK) return status;
PcdStopCrypto1(); // 断开加密通信管道,防止后续操作受干扰
#ifdef M1_AUTO_HALT_ENABLE
// 4. 休眠卡片:重置状态,等待下次寻卡
PcdHalt();
#endif
return MI_OK;
}
/**
* @brief 【高层封装】从 M1 卡的指定块读取 16 字节数据
* @param uid: 4字节卡号 (由 PcdAnticoll 获取)
* @param blockAddr: 要读取的绝对块号 (0~63)
* @param key: 6字节扇区密钥 (通常是 A 密钥)
* @param readData: 用于存放读取数据的 16 字节数组
* @return MI_OK (0) 表示成功,其他值表示失败
*/
int8_t M1_ReadCardBlock(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *readData)
{
int8_t status;
// 1. 选卡
status = PcdSelect(uid);
if (status != MI_OK) return status;
// 2. 验证扇区密钥
status = PcdAuthState(PICC_AUTHENT1A, blockAddr, key, uid);
if (status != MI_OK) return status;
// 3. 执行读取
status = PcdRead(blockAddr, readData);
if (status != MI_OK) return status;
PcdStopCrypto1(); // 断开加密通信管道,防止后续操作受干扰
#ifdef M1_AUTO_HALT_ENABLE
// 4. 休眠卡片
PcdHalt();
#endif
return MI_OK;
}
/**
* @brief 从 M1 卡读取 16 字节,支持选择验证 Key A 还是 Key B
* @param auth_mode: 验证模式 (填入 PICC_AUTHENT1A 或 PICC_AUTHENT1B)
*/
int8_t M1_ReadCardBlock_Ext(uint8_t *uid, uint8_t blockAddr, uint8_t auth_mode, uint8_t *key, uint8_t *readData)
{
int8_t status;
status = PcdSelect(uid);
if (status != MI_OK) return status;
// 动态传入验证模式,不再写死 Key A!
status = PcdAuthState(auth_mode, blockAddr, key, uid);
if (status != MI_OK) return status;
status = PcdRead(blockAddr, readData);
if (status != MI_OK) return status;
PcdHalt();
return MI_OK;
}
/**
* @brief 向 M1 卡写入 16 字节,支持选择验证 Key A 还是 Key B
*/
int8_t M1_WriteCardBlock_Ext(uint8_t *uid, uint8_t blockAddr, uint8_t auth_mode, uint8_t *key, uint8_t *writeData)
{
int8_t status;
status = PcdSelect(uid);
if (status != MI_OK) return status;
// 动态传入验证模式
status = PcdAuthState(auth_mode, blockAddr, key, uid);
if (status != MI_OK) return status;
status = PcdWrite(blockAddr, writeData);
if (status != MI_OK) return status;
PcdHalt();
return MI_OK;
}
/**
* @brief [安全写] 向指定绝对块地址写入数据,严格拒绝写控制块
* @param uid: 4字节卡号
* @param blockAddr: 绝对块号 (0~63)
* @param key: 扇区密钥
* @param writeData: 16字节数据
* @return MI_OK (成功), MI_ACCESSERR (拒绝访问) 或其他错误码
*/
int8_t M1_WriteDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *writeData)
{
// 拦截:块0是厂商固化只读块,写不进去
if (blockAddr == 0) return MI_ACCESSERR;
// 拦截:如果块号除以4余3,说明它是控制块,拒绝写入业务数据
if ((blockAddr % 4) == 3) return MI_ACCESSERR;
if (blockAddr > 63 ) return MI_ERR; // 越界保护
return M1_WriteCardBlock(uid, blockAddr, key, writeData);
}
/**
* @brief [安全读] 从指定绝对块地址读取数据
*/
int8_t M1_ReadDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *readData)
{
// 拦截:控制块的 Key A 是无法读出的(读出全为0),业务逻辑通常也不需要读出访问控制位
//if ((blockAddr % 4) == 3) return MI_ACCESSERR;
if (blockAddr > 63 ) return MI_ERR; // 越界保护
return M1_ReadCardBlock(uid, blockAddr, key, readData);
}
/**
* @brief [相对安全写] 指定扇区号和相对数据块号写入数据
* @param sector: 扇区号 (0~15)
* @param relBlock: 相对数据块号 (仅限 0, 1, 2)
*/
int8_t M1_WriteDataBlock_Rel(uint8_t *uid, uint8_t sector, uint8_t relBlock, uint8_t *key, uint8_t *writeData)
{
// 拦截:限制只能操作每个扇区的前3个数据块
if (relBlock >= 3) return MI_ACCESSERR;
// 拦截:扇区 0 的相对块 0 是厂商 UID 块,不可写
if (sector == 0 && relBlock == 0) return MI_ACCESSERR;
if (sector > 15) return MI_ERR; // 越界保护
// 换算绝对块号:扇区号 * 4 + 相对块号
uint8_t absBlock = (sector * 4) + relBlock;
return M1_WriteCardBlock(uid, absBlock, key, writeData);
}
/**
* @brief [相对读] 指定扇区号和相对数据块号读取数据
*/
int8_t M1_ReadDataBlock_Rel(uint8_t *uid, uint8_t sector, uint8_t relBlock, uint8_t *key, uint8_t *readData)
{
// 拦截:限制读取范围
//if (relBlock >= 3) return MI_ACCESSERR;
if (sector > 15) return MI_ERR; // 越界保护
// 换算绝对块号:扇区号 * 4 + 相对块号
uint8_t absBlock = (sector * 4) + relBlock;
return M1_ReadCardBlock(uid, absBlock, key, readData);
}
/**
* @brief [高危专属写] 修改指定扇区的密码和访问控制位,带硬核防砖校验
* @param uid: 4字节卡号
* @param sector: 要修改的扇区号 (0~15)
* @param key: 当前验证该扇区所使用的合法密钥
* @param newControlData: 严格构造的16字节控制块数据
* (格式: 6字节新KeyA + 4字节控制位 + 6字节新KeyB)
* @return MI_OK(成功), MI_ACCESSERR(越界), MI_FORMATERR(控制位校验失败杜绝变砖)
*/
int8_t M1_WriteSectorControlBlock_Safe(uint8_t *uid, uint8_t sector, uint8_t *key, uint8_t *newControlData)
{
// 1. 扇区范围保护
if (sector > 15) return MI_ACCESSERR;
// 2. 核心防砖机制:校验访问控制位 (数组的第 6, 7, 8 字节)
uint8_t b6 = newControlData[6];
uint8_t b7 = newControlData[7];
uint8_t b8 = newControlData[8];
// 提取反码 (~C1, ~C2, ~C3)
uint8_t inv_c1 = b6 & 0x0F; // b6 的低 4 位
uint8_t inv_c2 = (b6 >> 4) & 0x0F; // b6 的高 4 位
uint8_t inv_c3 = b7 & 0x0F; // b7 的低 4 位
// 提取原码 (C1, C2, C3)
uint8_t c1 = (b7 >> 4) & 0x0F; // b7 的高 4 位
uint8_t c2 = b8 & 0x0F; // b8 的低 4 位
uint8_t c3 = (b8 >> 4) & 0x0F; // b8 的高 4 位
// 严苛比对:原码与反码必须严格按位取反后相等 (即与 0x0F 异或后相等)
if ( inv_c1 != (c1 ^ 0x0F) ||
inv_c2 != (c2 ^ 0x0F) ||
inv_c3 != (c3 ^ 0x0F) )
{
// 发现控制位格式异常!坚决拦截,返回格式错误
return MI_FORMATERR;
}
// 3. 强制把目标锁定在当前扇区的控制块 (相对块3)
uint8_t targetAbsBlock = (sector * 4) + 3;
// 4. 执行底层物理写入
return M1_WriteCardBlock(uid, targetAbsBlock, key, newControlData);
}
/**
* @brief 构建 M1 卡控制块数据 (16字节)
* @param newKeyA: 6字节的新 Key A
* @param newKeyB: 6字节的新 Key B (如果不打算用 Key B,传全 0xFF 的数组)
* @param ctrlBits: 4字节的控制位与GPB组合
* (例如: CTRL_FREE_DEV、CTRL_MASTER_SLAVE、CTRL_ANTI_TAMPER、CTRL_SECURE_WALLET 等,
* 或直接传入四字节数组)
* @param outTrailer: 输出参数,存入生成好的 16 字节控制块数据
*/
void Make_SectorTrailer(uint8_t *newKeyA, uint8_t *newKeyB, uint8_t *ctrlBits, uint8_t *outTrailer)
{
uint8_t i;
// 1. 填入前 6 字节:Key A
for(i = 0; i < 6; i++) {
outTrailer[i] = newKeyA[i];
}
// 2. 填入中间 4 字节:Access Bits + GPB
for(i = 0; i < 4; i++) {
outTrailer[6 + i] = ctrlBits[i];
}
// 3. 填入后 6 字节:Key B
for(i = 0; i < 6; i++) {
outTrailer[10 + i] = newKeyB[i];
}
}
/**
* @brief 主动关闭 RC522 的 Crypto1 硬件加密引擎
* @note 在任何一次密码验证成功并读写完毕后,强烈建议调用此函数释放状态机
*/
void PcdStopCrypto1(void)
{
// 清除 Status2Reg 的 bit3 (Crypto1On)
ClearBitMask(Status2Reg, 0x08);
}
/**
* @brief [唤醒] 复位卡片与 RC522 状态机,确保回到 Ready 状态
* @param uid: 用于接收唤醒后重新获取的 4 字节卡号
* @return MI_OK (0) 表示卡片已完全就绪,其他值表示感应区确实无卡
* @note 该函数会先尝试温柔唤醒(寻卡),如果失败则通过断电重启卡片来强制复位状态机,最后再次寻卡并获取 UID
*/
int8_t M1_ForceWakeUp(uint8_t *uid)
{
int8_t status;
uint8_t tagType[2];
// 1. 【RC522 侧复位】强制关闭硬件加密机,解除读卡器端的 Crypto1 死锁
ClearBitMask(Status2Reg, 0x08);
// 2. 尝试温柔唤醒 (寻呼所有卡片,包括休眠卡)
status = PcdRequest(PICC_REQALL, tagType);
// 3. 【卡片侧物理复位】如果卡片不理人,断电重启
if (status != MI_OK)
{
PcdAntennaOff(); // 关闭天线
// 延时让卡片释放残余电量并复位
#ifdef RF_USE_RTOS
osDelay(5);
#else
HAL_Delay(5);
#endif
PcdAntennaOn(); // 重新开启天线给卡片供电
// 等待卡片内部系统上电启动完毕
#ifdef RF_USE_RTOS
osDelay(5);
#else
HAL_Delay(5);
#endif
// 再次强制清除一下加密位,防止天线操作带来抖动
ClearBitMask(Status2Reg, 0x08);
// 重新尝试寻卡
status = PcdRequest(PICC_REQALL, tagType);
// 如果物理断电重启后还是寻不到卡,说明感应区真的没卡,或者卡拿远了
if (status != MI_OK)
{
return MI_NOTAGERR;
}
}
// 4. 防冲突:获取卡片唯一的 UID
status = PcdAnticoll(uid);
if (status != MI_OK) return status;
return status;
}📖 RC522 & M1卡 智能驱动库使用说明
本库是专为 STM32 平台(基于 HAL 库)打造的 RC522 RFID 读写器驱动。它针对 Mifare Classic 1K (M1卡) 进行了深度的业务封装,提供了强大的 状态机防死锁(强力唤醒) 和 控制块防砖拦截 功能,适用于裸机与 FreeRTOS 多任务环境。
⚙️ 1. 核心宏配置 (rc522.h)
在 rc522.h 的配置区,您可以通过注释/取消注释以下宏来调整底层行为:
| 宏定义 | 作用说明 |
M1_AUTO_HALT_ENABLE | 自动休眠开关。开启后,封装的高层读写函数(M1_ 开头)在操作完成后会自动执行 PcdHalt() 让卡片休眠,释放射频场,防止重复读写。 |
RF_USE_RTOS | RTOS 兼容开关。开启后,底层的延时函数将使用 FreeRTOS 的 osDelay();关闭则使用裸机的 HAL_Delay(),防止阻塞系统调度。 |
🔑 2. 内置密钥与访问控制位定义
为了简化开发,库中预置了标准的密钥和常用的控制位“魔法字典”。
预设密钥
M1_DefaultKey[6]: 出厂默认密钥 (FF FF FF FF FF FF)。Key_MAD[6]: NDEF 标准下的扇区 0 目录密钥 (A0 A1 A2 A3 A4 A5)。Key_NDEF[6]: NDEF 标准下的数据区密钥 (D3 F7 D3 F7 D3 F7)。
控制位魔法字典 (Access Bits)
您在格式化卡片或修改扇区属性时,可直接使用以下预设数组组合:
CTRL_FREE_DEV: 自由开发模式,KeyA/B 均可任意读写数据区和控制区。CTRL_MASTER_SLAVE: 读写分离模式,KeyA只读,KeyB可读写且可修改密码。CTRL_ANTI_TAMPER: 防篡改只读模式,KeyA/B均只读,仅KeyB可修改控制位。CTRL_SECURE_WALLET: 安全储值卡模式,KeyA仅支持查询/扣款,KeyB支持充值/初始化。
🛠️ 3. 核心 API 介绍与示例
这里重点介绍应用层最常用的高安全级别封装函数。底层的基础寻卡/防冲突函数已整合进 M1_ForceWakeUp。
3.1 强力唤醒与读卡
int8_t M1_ForceWakeUp(uint8_t *uid);
- 功能:无视卡片和读卡器当前的死锁或加密状态,强制复位状态机并获取 4 字节的 UID。
- 用法:替代繁琐的
寻卡 -> 防冲突 -> 选卡流程。
示例代码:
uint8_t uid[4];
if (M1_ForceWakeUp(uid) == MI_OK) {
printf("检测到卡片,UID: %02X %02X %02X %02X\r\n", uid[0], uid[1], uid[2], uid[3]);
}3.2 绝对地址安全读写 (保护控制块)
int8_t M1_ReadDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *readData);
int8_t M1_WriteDataBlock_Abs(uint8_t *uid, uint8_t blockAddr, uint8_t *key, uint8_t *writeData);
- 功能:向 0~63 的绝对块进行读写。
- 安全机制:写入时会自动拦截对块 0 (厂商只读块) 和 块 % 4 == 3 (密码控制块) 的操作,防止误操作导致卡片变砖,返回
MI_ACCESSERR。
示例代码:
uint8_t myData[16] = "Hello RC522!";
uint8_t readBuf[16];
// 尝试写入第 4 块 (扇区1,块0)
if (M1_WriteDataBlock_Abs(uid, 4, M1_DefaultKey, myData) == MI_OK) {
printf("写入成功!\r\n");
}
// 尝试读取第 4 块
if (M1_ReadDataBlock_Abs(uid, 4, M1_DefaultKey, readBuf) == MI_OK) {
printf("读取成功: %s\r\n", readBuf);
}3.3 相对地址读写 (基于扇区)
int8_t M1_WriteDataBlock_Rel(uint8_t *uid, uint8_t sector, uint8_t relBlock, uint8_t *key, uint8_t *writeData);
- 功能:通过指定扇区号 (0~15) 和扇区内的相对块号 (0~2) 进行操作,逻辑更直观。同样具备防止写入控制块的安全拦截。
示例代码:
// 写入 扇区2 的 第1个数据块 (等同于绝对块9)
M1_WriteDataBlock_Rel(uid, 2, 1, M1_DefaultKey, myData);3.4 高危专属写:修改扇区密码与控制位
int8_t M1_WriteSectorControlBlock_Safe(uint8_t *uid, uint8_t sector, uint8_t *key, uint8_t *newControlData);
- 功能:修改指定扇区的密码和权限控制位。
- 防砖极致:内置硬核校验机制,它会提取新控制数据中的控制位原码与反码进行比对,格式错误直接拒绝执行并返回
MI_FORMATERR,彻底杜绝因为组装错控制位导致扇区锁死。
配合组装函数使用示例:
uint8_t new_trailer[16];
// 组装控制块:新KeyA=默认,新KeyB=NDEF密码,权限=只读防篡改
Make_SectorTrailer(M1_DefaultKey, Key_NDEF, CTRL_ANTI_TAMPER, new_trailer);
// 执行高危写入 (假设当前合法密码是 M1_DefaultKey)
int8_t status = M1_WriteSectorControlBlock_Safe(uid, 1, M1_DefaultKey, new_trailer);
if (status == MI_OK) {
printf("扇区 1 的密码及权限已成功修改!\r\n");
}🔄 4. 完整应用场景展示:全盘遍历测试
这是一个遍历读出卡片 64 个块的示例,展示了安全拦截机制的作用:
uint8_t CardUID[4];
uint8_t MyReadBuffer[16];
int8_t status;
for (int i = 0; i < 64; i++)
{
// 1. 强力唤醒卡片并获取 UID
if (M1_ForceWakeUp(CardUID) != MI_OK) {
printf("感应区内无卡片。\r\n");
break;
}
// 2. 尝试用默认密码读取
status = M1_ReadDataBlock_Abs(CardUID, i, M1_DefaultKey, MyReadBuffer);
// 3. 打印分析结果
if (status == MI_OK) {
printf("块 %02d [数据] : ", i);
for (int j = 0; j < 16; j++) printf("%02X ", MyReadBuffer[j]);
printf("\r\n");
}
else if (status == MI_ACCESSERR) {
printf("块 %02d [控制] : --- 密码控制块或厂商块,已被安全保护跳过 ---\r\n", i);
}
else {
printf("块 %02d [错误] : 验证或读取失败 (可能密码错误或数据损坏)\r\n", i);
}
// 建议每次循环留一点点缓冲时间
HAL_Delay(5);
}

发表回复