一、RFID射频识别技术介绍:
RC522RFID射频模块是一款广泛应用于非接触式RFID系统中的核心组件,由NXP(前身为Philips半导体)公司设计生产。这个模块基于MFRC522芯片,该芯片是一个高度集成的UHFRFID读卡器/写卡器解决方案,能够支持IS014443A标准的卡片和标签。
1.1 RFID模块的组成部分
-
RFID标签(Tag):
- 被动标签:无需电池,通过接收读写器发出的射频信号供电。
- 主动标签:内置电池,可以主动发射信号,通信距离较远。
- 半主动标签:有电池,但只在接收到外部信号时响应。
-
RFID读写器(Reader):
- 通过射频信号与标签进行通信,读取或写入标签中的数据。
- 一般包括调制解调器、处理器、数据接口和天线。
-
天线(Antenna):
- 用于发射和接收RF信号。天线的设计直接影响射频信号的传输距离和效率。
1.2 RFID模块的工作原理
- 发射信号:RFID读写器通过天线发射高频(HF)、超高频(UHF)或微波频段的电磁波。
- 接收信号:RFID标签接收到信号后,利用其内置的电路回应特定信息。
- 数据交换:读写器接收到标签回应的信号后,对数据进行读取或写入操作。
1.3 常见的RFID模块类型
- 低频RFID模块(LF,125kHz):主要用于近距离的身份识别,常见于门禁系统、动物识别等场景。
- 高频RFID模块(HF,13.56MHz):常见于电子钱包、门票、身份证、图书馆管理等应用。
- 超高频RFID模块(UHF,860-960MHz):适用于较远距离的应用,常用于仓储管理、物流追踪等。
1.4 常见的RFID模块示例
- RC522:常用于Arduino和其他单片机的低成本高频RFID读写模块,工作频率为13.56MHz。
- MFRC522:也是一种常见的13.56MHz的RFID读写模块,常用于嵌入式系统和简单的RFID应用。
- RDM6300:工作频率为125kHz的低频RFID模块,适合近距离应用。
二、MFRC522工作原理:
2.1 MFRC522接口框架:
MFRC522是高度集成的非接触式(13.56MHz)读写卡芯片。此发送模块利用调制和解调的原理,并将它们完全集成到各种非接触式通信方法和协议中(13.56MHz)。
2.2 MFRC522原理图:
注:
本人代码使用的引脚为
SDA PA4
SCK PA5
MISO PA6
MOSI PA7
RST PA1
三、MFRC代码编写:
3.1 一些MFRC522宏定义移植:
3.1.1 RC522_MOSI、RC522_MISO、RC522_SCK:
这些宏定义了用于与 RC522 模块通信的 SPI 引脚。PAout()
用于输出,PAin()
用于输入。通过这些引脚,STM32 可以与 RC522 进行数据交换(MOSI 用于发送数据,MISO 用于接收数据,SCK 提供时钟信号)。
3.1.2 MFRC522_CS(x):
这是一个宏,用于控制 RC522 的片选信号(CS)。
3.1.3MFRC522_Rst(x):
这是一个宏,用于控制 RC522 的复位信号(RST)。
// 定义 RC522 的 SPI 引脚连接
// RC522 的 MOSI 引脚连接到 STM32 的 PA5 引脚(主设备输出,从设备输入)
#define RC522_MOSI PAout(5) // SPI 的 MOSI(Master Out Slave In)信号,用于 STM32 向 RC522 发送数据
// RC522 的 MISO 引脚连接到 STM32 的 PA4 引脚(主设备输入,从设备输出)
#define RC522_MISO PAin(4) // SPI 的 MISO(Master In Slave Out)信号,用于 RC522 向 STM32 发送数据
// RC522 的 SCK 引脚连接到 STM32 的 PA6 引脚(时钟信号)
#define RC522_SCK PAout(6) // SPI 时钟信号(SCK),用于同步数据传输
//
// 定义 MFRC522 的片选信号(CS)和复位信号(RST)控制宏
//
// 控制 MFRC522 的片选信号(CS)
// 参数 x = 1 时,CS 引脚设置为高电平(片选信号未激活)
// 参数 x = 0 时,CS 引脚设置为低电平(片选信号激活)
#define MFRC522_CS(x) x ? GPIO_SetBits(GPIOA, GPIO_Pin_7) : GPIO_ResetBits(GPIOA, GPIO_Pin_7)
// 当 x 为 1 时,调用 GPIO_SetBits 将 GPIOA 的 7 号引脚设置为高电平,表示禁用 SPI 通信。
// 当 x 为 0 时,调用 GPIO_ResetBits 将 GPIOA 的 7 号引脚设置为低电平,表示启用 SPI 通信。
// 控制 MFRC522 的复位信号(RST)
// 参数 x = 1 时,RST 引脚设置为高电平(复位信号未激活)
// 参数 x = 0 时,RST 引脚设置为低电平(复位信号激活)
#define MFRC522_Rst(x) x ? GPIO_SetBits(GPIOA, GPIO_Pin_1) : GPIO_ResetBits(GPIOA, GPIO_Pin_1)
// 当 x 为 1 时,调用 GPIO_SetBits 将 GPIOA 的 1 号引脚设置为高电平,表示不复位。
// 当 x 为 0 时,调用 GPIO_ResetBits 将 GPIOA 的 1 号引脚设置为低电平,表示复位 MFRC522 模块。
/
// MF522 指令定义
/
#define PCD_IDLE 0x00 // 空闲模式指令,表示 MFRC522 处于不活动状态
#define PCD_AUTHENT 0x0E // 验证指令,用于进行身份验证
#define PCD_RECEIVE 0x08 // 接收数据指令,表示准备接收数据
#define PCD_TRANSMIT 0x04 // 发送数据指令,表示准备发送数据
#define PCD_TRANSCEIVE 0x0C // 发送并接收数据指令,表示同时进行发送与接收
#define PCD_RESETPHASE 0x0F // 复位指令,用于复位 MFRC522 模块
#define PCD_CALCCRC 0x03 // CRC 校验指令,用于计算和验证 CRC 校验码
/
// Mifare_One 卡片操作命令定义
/
#define PICC_REQIDL 0x26 // 请求 IDLE 状态卡片,表示寻找处于空闲模式的卡片
#define PICC_REQALL 0x52 // 请求 ALL 状态卡片,表示寻找处于任何状态的卡片
#define PICC_ANTICOLL1 0x93 // 防冲突命令1,用于避免多个卡片同时响应
#define PICC_ANTICOLL2 0x95 // 防冲突命令2,用于避免多个卡片同时响应
#define PICC_AUTHENT1A 0x60 // 身份验证命令 A,验证使用 A 密钥
#define PICC_AUTHENT1B 0x61 // 身份验证命令 B,验证使用 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 FIFO 队列大小定义
/
#define DEF_FIFO_LENGTH 64 // FIFO 队列的大小,通常用于数据缓存和传输队列
3.2 命令字的定义:
3.2.1 Mifare_One 卡片操作指令:
这些宏定义的是与 RFID 卡片(如 Mifare)进行通信时使用的指令。不同的指令用于不同的操作,例如寻卡、验证身份、读写数据等。
3.2.2 MF522 FIFO 队列大小:
DEF_FIFO_LENGTH
设置了 FIFO(先进先出)队列的大小,即缓冲区的容量,这影响数据的存储和传输。常见的设置为64字节。
3.2.3 MF522 寄存器地址定义:
这些宏定义了 MFRC522 模块中的寄存器地址,每个寄存器有不同的功能,比如控制操作、存储数据、指示错误等。通过这些寄存器可以控制和监测模块的工作状态。
1、PAGE 0 寄存器
PAGE 0 寄存器包含了 MF522 模块的核心控制寄存器,主要用于配置模块的工作模式、控制数据传输、启用中断、检测错误等。通过这些寄存器,你可以直接与模块进行交互,实现 RFID 系统的功能。
2、PAGE 1 寄存器
- 这些寄存器主要用于设置无线电模块的工作模式(发送、接收模式)、配置数据传输参数(如发送模式、接收阈值等)。例如:
ModeReg
:设置模块的操作模式。TxModeReg
和RxModeReg
:分别控制发送和接收的模式。SerialSpeedReg
:设置串口通信的速度。
3、 PAGE 2 寄存器
- 这部分寄存器用于配置和控制无线电通信的各种参数,如 CRC 校验、无线电配置、调制方式等。
CRCResultRegH
和CRCResultRegL
:用于存储 CRC 校验的结果。RFCfgReg
:配置无线电模块。TModeReg
和TPrescalerReg
:定时器的工作模式和预分频。
4、PAGE 3 寄存器
- 这一页寄存器主要用于测试和调试 MF522 模块的功能,如版本号、DAC 测试、ADC 测试等。
TestSel1Reg
和TestSel2Reg
:选择测试模式。VersionReg
:读取模块的版本信息。
5、错误码和常量定义
MI_OK
、MI_NOTAGERR
和MI_ERR
分别表示操作成功、没有卡片响应和发生了错误。MAX_LEN
表示每次读取或写入的最大数据长度,通常是 18 字节。
/
// Mifare_One 卡片操作指令
/
#define PICC_REQIDL 0x26 // 请求空闲卡片(寻卡),寻找处于空闲状态的卡片
#define PICC_REQALL 0x52 // 请求所有卡片(寻卡),寻找处于任何状态的卡片
#define PICC_ANTICOLL1 0x93 // 防止卡片冲突命令 1,用于在多张卡片靠近时避免同时响应
#define PICC_ANTICOLL2 0x95 // 防止卡片冲突命令 2,进一步防止多张卡片冲突
#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 FIFO 队列大小定义
/
#define DEF_FIFO_LENGTH 64 // FIFO(先进先出)队列的大小,通常设为64字节,缓存临时数据
/
// MF522 寄存器地址定义
/
// PAGE 0
#define RFU00 0x00 // 保留地址,未使用(Reserved for future use)
#define CommandReg 0x01 // 命令寄存器,用于控制 MFRC522 模块执行操作(如启动、停止、接收、发送等)
#define ComIEnReg 0x02 // 通信中断使能寄存器,用于控制通信中断功能
#define DivlEnReg 0x03 // 分频器使能寄存器,控制分频器的启用
#define ComIrqReg 0x04 // 通信中断标志寄存器,用于标识是否发生通信中断
#define DivIrqReg 0x05 // 分频器中断标志寄存器,用于标识是否发生分频器中断
#define ErrorReg 0x06 // 错误状态寄存器,用于指示 MFRC522 模块的错误状态
#define Status1Reg 0x07 // 状态寄存器 1,表示模块的当前状态
#define Status2Reg 0x08 // 状态寄存器 2,表示模块的当前状态
#define FIFODataReg 0x09 // FIFO 数据寄存器,用于存储数据传输的临时缓冲区
#define FIFOLevelReg 0x0A // FIFO 队列深度寄存器,表示 FIFO 中存储的字节数
#define WaterLevelReg 0x0B // 水位寄存器,控制 FIFO 队列的水位值
#define ControlReg 0x0C // 控制寄存器,用于控制 MFRC522 模块的基本操作
#define BitFramingReg 0x0D // 位帧寄存器,用于配置数据传输中的帧格式
#define CollReg 0x0E // 碰撞寄存器,用于检测和处理多卡碰撞情况
#define RFU0F 0x0F // 保留地址,未使用(Reserved for future use)
/
// PAGE 1
/
#define RFU10 0x10 // 保留地址,未使用(Reserved for future use)
#define ModeReg 0x11 // 工作模式寄存器,用于设置 MF522 模块的操作模式,如接收、发送等
#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 RFU1C 0x1C // 保留地址,未使用
#define RFU1D 0x1D // 保留地址,未使用
#define RFU1E 0x1E // 保留地址,未使用
#define SerialSpeedReg 0x1F // 串口速度寄存器,用于配置串口通信速度
/
// PAGE 2
/
#define RFU20 0x20 // 保留地址,未使用
#define CRCResultRegH 0x21 // CRC 校验结果寄存器(高位),用于存储 CRC 校验值的高字节
#define CRCResultRegL 0x22 // CRC 校验结果寄存器(低位),用于存储 CRC 校验值的低字节
#define RFU23 0x23 // 保留地址,未使用
#define ModWidthReg 0x24 // 调制宽度寄存器,用于设置调制方式的宽度
#define RFU25 0x25 // 保留地址,未使用
#define RFCfgReg 0x26 // 无线电配置寄存器,用于设置无线电模块的配置
#define GsNReg 0x27 // GsN 寄存器,用于调整无线电频率或参数
#define CWGsCfgReg 0x28 // CW(Carrier Wave)配置寄存器,用于配置载波信号
#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 // 测试选择寄存器 1,用于选择测试模式
#define TestSel2Reg 0x32 // 测试选择寄存器 2,用于选择测试模式
#define TestPinEnReg 0x33 // 测试引脚使能寄存器,控制测试引脚是否启用
#define TestPinValueReg 0x34 // 测试引脚值寄存器,用于设置或读取测试引脚的值
#define TestBusReg 0x35 // 测试总线寄存器,用于控制测试总线的操作
#define AutoTestReg 0x36 // 自动测试寄存器,用于启用自动测试功能
#define VersionReg 0x37 // 版本寄存器,用于读取 MF522 模块的版本信息
#define AnalogTestReg 0x38 // 模拟测试寄存器,用于进行模拟信号的测试
#define TestDAC1Reg 0x39 // 测试 DAC1 寄存器,用于测试数字到模拟转换器 1
#define TestDAC2Reg 0x3A // 测试 DAC2 寄存器,用于测试数字到模拟转换器 2
#define TestADCReg 0x3B // 测试 ADC 寄存器,用于测试模拟到数字转换器
#define RFU3C 0x3C // 保留地址,未使用
#define RFU3D 0x3D // 保留地址,未使用
#define RFU3E 0x3E // 保留地址,未使用
#define RFU3F 0x3F // 保留地址,未使用
/
// MF522 错误码与常量定义
/
#define MI_OK 0 // 操作成功
#define MI_NOTAGERR 1 // 没有发现卡片
#define MI_ERR 2 // 错误发生
#define MAX_LEN 18 // 最大数据长度,表示每次读取或写入的数据字节数(最大为 18 字节)
3.2 引脚初始化:
void STM32_SPI3_Init(void)
{
// 定义 GPIO 初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置 GPIOA 的引脚 1, 5, 6, 7 为推挽输出(用于 SPI 信号)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置 GPIO 速度为 50MHz
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; // 配置引脚 1, 5, 6, 7
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的这些引脚
// 配置 GPIOA 的引脚 4 为下拉输入(用于读取 SPI 信号)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 设置为下拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 配置引脚 4
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的引脚 4
// 使能 MFRC522 片选信号为高电平(禁用 SPI 通信)
MFRC522_CS(1);
}
3.3 SPI通信封装
// SPI3_Send:发送一个字节数据通过SPI接口
void SPI3_Send(u8 val)
{
u8 recv_data = 0, i = 0;
RC522_SCK = 0; // 初始化时将时钟线(SCK)设置为低电平
for(i = 0; i < 8; i++) // 循环发送每一位数据(共8位)
{
// 检查当前要发送的位值
if(val & (1 << (7 - i))) // 如果当前位是1
{
RC522_MOSI = 1; // 设置MOSI为高电平,表示发送1
}
else
{
RC522_MOSI = 0; // 否则,设置MOSI为低电平,表示发送0
}
delay_us(5); // 延时,保证SPI时序正确
RC522_SCK = 1; // 拉高时钟线(SCK)开始传输数据
delay_us(5); // 再次延时,确保数据传输稳定
}
}
// SPI3_Receive:接收一个字节数据通过SPI接口
u8 SPI3_Receive(void)
{
u8 recv_data = 0, i = 0;
RC522_SCK = 0; // 初始化时将时钟线(SCK)设置为低电平
for(i = 0; i < 8; i++) // 循环接收每一位数据(共8位)
{
RC522_MOSI = 0; // 先将MOSI设置为低电平(不影响数据线的电平)
delay_us(5); // 延时,确保时序正确
RC522_SCK = 1; // 拉高时钟线(SCK)开始接收数据
delay_us(5); // 延时,确保数据稳定
// 从MISO线读取数据
if(RC522_MISO == 1) // 如果MISO线为高电平,表示当前位是1
{
recv_data |= (1 << (7 - i)); // 将接收到的1存储到对应的位上
}
RC522_SCK = 0; // 将时钟线拉低,为下一次时钟脉冲做准备
}
return recv_data; // 返回接收到的字节数据
}
3.4 MFRC522通信函数封装
// 通过SPI接口与MFRC522模块进行数据交换
// 函数说明:写入MFRC522模块的寄存器
// 输入参数:addr--寄存器地址;val--要写入的值
void Write_MFRC522(u8 addr, u8 val)
{
// 拉低CS引脚,选择MFRC522设备
MFRC522_CS(0);
// 发送寄存器地址,左移1位并掩码,选择寄存器
SPI3_Send((addr << 1) & 0x7E);
// 发送要写入的值
SPI3_Send(val);
// 拉高CS引脚,结束数据传输
MFRC522_CS(1);
}
// 通过SPI接口与MFRC522模块进行数据交换
// 函数说明:读取MFRC522模块的寄存器
// 输入参数:addr--寄存器地址
// 返回值:返回寄存器的值
u8 Read_MFRC522(u8 addr)
{
u8 val;
// 拉低CS引脚,选择MFRC522设备
MFRC522_CS(0);
// 发送读取命令,地址左移并设置高位为1,表示读取操作
SPI3_Send(((addr << 1) & 0x7E) | 0x80);
// 接收返回的寄存器值
val = SPI3_Receive();
// 拉高CS引脚,结束数据传输
MFRC522_CS(1);
// 返回读取的值
return val;
}
// 设置指定寄存器的某些位
// 函数说明:设置MFRC522寄存器的指定位
// 输入参数:reg--寄存器地址;mask--要设置的掩码位
void SetBitMask(u8 reg, u8 mask)
{
u8 tmp = 0;
// 读取寄存器当前值
tmp = Read_MFRC522(reg);
// 使用OR操作设置掩码位
Write_MFRC522(reg, tmp | mask); // 设置寄存器中的掩码位
}
// 清除指定寄存器的某些位
// 函数说明:清除MFRC522寄存器的指定位
// 输入参数:reg--寄存器地址;mask--要清除的掩码位
void ClearBitMask(u8 reg, u8 mask)
{
u8 tmp = 0;
// 读取寄存器当前值
tmp = Read_MFRC522(reg);
// 使用AND操作清除掩码位
Write_MFRC522(reg, tmp & (~mask)); // 清除寄存器中的掩码位
}
// 打开天线
// 函数说明:打开MFRC522的天线
void AntennaOn(void)
{
u8 temp;
// 读取TxControl寄存器的当前状态
temp = Read_MFRC522(TxControlReg);
// 如果TxControl寄存器的低2位是0,表示天线关闭,打开天线
if ((temp & 0x03) == 0)
{
SetBitMask(TxControlReg, 0x03); // 设置TxControl寄存器的低2位为1,打开天线
}
}
// 关闭天线
// 函数说明:关闭MFRC522的天线
void AntennaOff(void)
{
// 清除TxControl寄存器的低2位,关闭天线
ClearBitMask(TxControlReg, 0x03);
}
// 复位MFRC522模块
// 函数说明:执行硬件和软件复位
void MFRC522_Reset(void)
{
// 硬件复位信号通过MFRC522_Rst函数控制
// 首先拉高RST引脚,然后拉低,再拉高完成复位
MFRC522_Rst(1); // 拉高复位引脚
Delay1_us(1); // 延时1微秒
MFRC522_Rst(0); // 拉低复位引脚
Delay1_us(1); // 延时1微秒
MFRC522_Rst(1); // 拉高复位引脚
Delay1_us(1); // 延时1微秒
// 软件复位
Write_MFRC522(CommandReg, PCD_RESETPHASE); // 通过写入寄存器复位MFRC522
}
3.5 MFRC522模块功能设置初始化
void MFRC522_Initializtion(void)
{
// 初始化SPI3接口,用于与MFRC522模块进行通信
STM32_SPI3_Init();
// 对MFRC522进行复位,确保模块在初始化时处于已知状态
MFRC522_Reset();
// 下面的配置是对MFRC522的一些寄存器进行初始化,设置工作模式、定时器等
// Timer配置: TPrescaler*TreloadVal/6.78MHz = 0xD3E*0x32/6.78=25ms
// TModeReg寄存器:配置MFRC522的定时器工作模式
// 设置TAuto=1,表示定时器的溢出后自动重载(即自动重复计时),同时启用调制模式
Write_MFRC522(TModeReg, 0x8D);
// 另外一种配置(已注释掉),用于自动重启计时器
// Write_MFRC522(TModeReg, 0x1D); // TAutoRestart=1,表示计时器到期后自动重启,适合某些测试应用
// TPrescalerReg寄存器:设置定时器的预分频器值
// 这里设置为0x3E,表示定时器频率经过预分频后达到一个适合的值
Write_MFRC522(TPrescalerReg, 0x3E);
// TReloadRegL寄存器:设置定时器重载值的低字节
// TReloadRegH寄存器:设置定时器重载值的高字节
// 组合起来,定时器溢出周期是0xD3E * 0x32 / 6.78MHz = 25ms
Write_MFRC522(TReloadRegL, 0x32); // 设置定时器重载值的低字节
Write_MFRC522(TReloadRegH, 0x00); // 设置定时器重载值的高字节
// TxAutoReg寄存器:配置发射部分的自动调制参数
// 这里设置为0x40,表示启用100% ASK调制
Write_MFRC522(TxAutoReg, 0x40);
// ModeReg寄存器:配置MFRC522的工作模式
// 这里设置为0x3D,表示启用CRC校验(0x6363)以确保数据传输的完整性
Write_MFRC522(ModeReg, 0x3D);
// CommandReg寄存器:启动MFRC522模块
// 0x00表示取消所有挂起的命令,启动模块准备好接收指令
Write_MFRC522(CommandReg, 0x00);
// 这里可以配置接收增益,但此行被注释掉了
// RFCfgReg寄存器:配置接收增益,0x7F表示48dB增益,适合远距离的接收
// Write_MFRC522(RFCfgReg, 0x7F); // RxGain = 48dB,增强接收灵敏度
// 启用MFRC522的天线,确保设备能正常发射和接收信号
AntennaOn(); // 打开天线,使得MFRC522能够工作并接收RFID卡片信号
}
3.6 MFRC读写以及设置休眠
// 读取指定块的数据
// 该函数用于读取MFRC522模块中指定块地址的数据。
// 参数说明:
// blockAddr -- 要读取的块地址。
// recvData -- 接收数据的缓冲区,用于存放读取到的数据。
// 返回值:
// 如果读取成功,返回MI_OK;如果失败,返回MI_ERR。
u8 MFRC522_Read(u8 blockAddr, u8 *recvData)
{
u8 status; // 用于存储操作状态
u16 unLen; // 用于存储接收数据的长度
// 设置要读取的命令和块地址
recvData[0] = PICC_READ;
recvData[1] = blockAddr;
// 计算CRC校验码
CalulateCRC(recvData, 2, &recvData[2]);
// 向卡片发送读取命令,并接收返回的数据
status = MFRC522_ToCard(PCD_TRANSCEIVE, recvData, 4, recvData, &unLen);
// 检查返回的状态和长度是否符合要求
if ((status != MI_OK) || (unLen != 0x90)) // 0x90 是成功读取的返回长度
status = MI_ERR; // 如果不符合要求,返回错误状态
// 返回状态
return status;
}
// 向指定块写入数据
// 该函数用于向MFRC522模块中指定块地址写入数据。
// 参数说明:
// blockAddr -- 要写入的块地址。
// writeData -- 要写入的数据,16字节。
// 返回值:
// 如果写入成功,返回MI_OK;如果失败,返回MI_ERR。
u8 MFRC522_Write(u8 blockAddr, u8 *writeData)
{
u8 status; // 用于存储操作状态
u16 recvBits; // 用于存储接收的数据位数
u8 i;
u8 buff[18]; // 用于存放发送和接收数据的缓冲区
// 设置要写入的命令和块地址
buff[0] = PICC_WRITE;
buff[1] = blockAddr;
// 计算CRC校验码
CalulateCRC(buff, 2, &buff[2]);
// 向卡片发送写命令,并接收返回的状态
status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &recvBits);
// 检查返回的状态是否符合要求
if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
status = MI_ERR; // 如果不符合要求,返回错误状态
// 如果第一步成功,开始写入16字节数据
if (status == MI_OK)
{
// 将要写入的数据填充到缓冲区
for (i = 0; i < 16; i++) // 向FIFO写入16字节数据
buff[i] = *(writeData + i);
// 计算16字节数据的CRC校验码
CalulateCRC(buff, 16, &buff[16]);
// 向卡片发送数据并接收返回的状态
status = MFRC522_ToCard(PCD_TRANSCEIVE, buff, 18, buff, &recvBits);
// 检查返回的状态是否符合要求
if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
status = MI_ERR; // 如果不符合要求,返回错误状态
}
// 返回状态
return status;
}
// 向卡片发送Halt命令以停止通信
// 该函数用于向MFRC522模块发送Halt命令,以终止通信。
// 终止后,卡片进入休眠状态,释放资源。
// 参数说明:
// 无参数
// 返回值:
// 无返回值。
void MFRC522_Halt(void)
{
u16 unLen; // 用于存储接收数据的长度
u8 buff[4]; // 用于存放发送和接收数据的缓冲区
// 设置Halt命令和参数
buff[0] = PICC_HALT;
buff[1] = 0;
// 计算CRC校验码
CalulateCRC(buff, 2, &buff[2]);
// 向卡片发送Halt命令
MFRC522_ToCard(PCD_TRANSCEIVE, buff, 4, buff, &unLen);
}
3.7 请求卡片、抗冲突、计算 CRC、选择卡片函数
// MFRC522 请求卡片类型
// 此函数用于向 RFID 卡发送请求命令,检测并返回卡片类型。
// 输入参数:
// reqMode -- 请求模式,指定请求类型(例如查找卡片)
// TagType -- 返回的卡片类型
// 返回值:
// MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Request(u8 reqMode, u8 *TagType)
{
u8 status;
u16 backBits; // 接收的数据位长度
Write_MFRC522(BitFramingReg, 0x07); // TxLastBists = BitFramingReg[2..0]
TagType[0] = reqMode;
status = MFRC522_ToCard(PCD_TRANSCEIVE, TagType, 1, TagType, &backBits);
// 如果没有成功返回,或者接收到的数据位数不正确,返回错误
if ((status != MI_OK) || (backBits != 0x10))
{
status = MI_ERR;
}
return status;
}
// MFRC522 防止卡片冲突
// 此函数用于防止多个卡片之间发生冲突,并返回找到的卡片序列号。
// 输入参数:
// serNum -- 存储卡片的 4 字节序列号(如果成功找到卡片)
// 返回值:
// MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Anticoll(u8 *serNum)
{
u8 status;
u8 i;
u8 serNumCheck = 0;
u16 unLen;
ClearBitMask(Status2Reg, 0x08); // 清除 TempSens
ClearBitMask(CollReg, 0x80); // 清除 ValuesAfterColl
Write_MFRC522(BitFramingReg, 0x00); // TxLastBists = BitFramingReg[2..0]
serNum[0] = PICC_ANTICOLL1;
serNum[1] = 0x20;
status = MFRC522_ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);
if (status == MI_OK)
{
// 计算并验证卡片序列号校验位
for(i = 0; i < 4; i++)
serNumCheck ^= serNum[i];
if(serNumCheck != serNum[i])
status = MI_ERR;
}
SetBitMask(CollReg, 0x80); // 设置 ValuesAfterColl = 1
return status;
}
// MFRC522 计算 CRC
// 此函数用于计算 CRC 校验码,用于数据传输时的错误检测。
// 输入参数:
// pIndata -- 需要计算 CRC 的数据
// len -- 数据长度
// 输出参数:
// pOutData -- 返回计算出的 CRC 结果
void CalulateCRC(u8 *pIndata, u8 len, u8 *pOutData)
{
u16 i;
u8 n;
ClearBitMask(DivIrqReg, 0x04); // CRCIRQ = 0
SetBitMask(FIFOLevelReg, 0x80); // 启动 FIFO
Write_MFRC522(CommandReg, PCD_IDLE);
// 将数据写入 FIFO 缓存
for (i = 0; i < len; i++)
Write_MFRC522(FIFODataReg, *(pIndata + i));
// 启动 CRC 计算
Write_MFRC522(CommandReg, PCD_CALCCRC);
// 等待 CRC 计算完成
i = 1000;
do
{
n = Read_MFRC522(DivIrqReg);
i--;
} while ((i != 0) && !(n & 0x04)); // CRCIRQ = 1
// 读取计算结果并返回
pOutData[0] = Read_MFRC522(CRCResultRegL);
pOutData[1] = Read_MFRC522(CRCResultRegH);
Write_MFRC522(CommandReg, PCD_IDLE);
}
// MFRC522 选择卡片
// 此函数用于选择特定的卡片,并返回卡片的大小。
// 输入参数:
// serNum -- 卡片的 4 字节序列号
// 返回值:
// 卡片大小,成功则返回卡片类型大小,否则返回 0
u8 MFRC522_SelectTag(u8 *serNum)
{
u8 i;
u8 status;
u8 size;
u16 recvBits;
u8 buffer[9];
buffer[0] = PICC_ANTICOLL1; // 发送 ANTICOLL1 命令
buffer[1] = 0x70;
buffer[6] = 0x00;
// 将卡片的序列号存入缓冲区
for (i = 0; i < 4; i++)
{
buffer[i + 2] = *(serNum + i); // buffer[2] - buffer[5] 为卡片序列号
buffer[6] ^= *(serNum + i); // 校验
}
// 计算 CRC 校验码并添加到缓冲区
CalulateCRC(buffer, 7, &buffer[7]); // buffer[7] - buffer[8] 为 CRC 校验码
ClearBitMask(Status2Reg, 0x08);
// 向卡片发送选择命令
status = MFRC522_ToCard(PCD_TRANSCEIVE, buffer, 9, buffer, &recvBits);
// 判断是否成功并返回卡片大小
if ((status == MI_OK) && (recvBits == 0x18))
size = buffer[0];
else
size = 0;
return size;
}
// MFRC522 卡片认证
// 此函数用于进行卡片的认证操作,例如读取卡片中的数据块。
// 输入参数:
// authMode -- 认证模式(0x60 为认证模式 A,0x61 为认证模式 B)
// BlockAddr -- 数据块地址
// Sectorkey -- 存储认证密钥
// serNum -- 卡片的序列号
// 返回值:
// MI_OK -- 成功,其他值 -- 失败
u8 MFRC522_Auth(u8 authMode, u8 BlockAddr, u8 *Sectorkey, u8 *serNum)
{
u8 status;
u16 recvBits;
u8 i;
u8 buff[12];
// 认证模式 + 块地址 + 密钥 + 卡片序列号
buff[0] = authMode; // 认证模式
buff[1] = BlockAddr; // 数据块地址
for (i = 0; i < 6; i++)
buff[i + 2] = *(Sectorkey + i); // 密钥
for (i = 0; i < 4; i++)
buff[i + 8] = *(serNum + i); // 卡片序列号
// 向卡片发送认证命令
status = MFRC522_ToCard(PCD_AUTHENT, buff, 12, buff, &recvBits);
// 判断认证是否成功
if ((status != MI_OK) || (!(Read_MFRC522(Status2Reg) & 0x08)))
status = MI_ERR;
return status;
}
四、IC卡芯片原理分析
4.1 介绍
基本信息
1.每张卡有唯一32位ID
2.卡的容量是1Kbit的EEPROM
3.分为16个扇区,每个扇区分为4块,每块16个字节,以块为存取单位(16个扇区*4个块*16字节数据=1024字节(1K内存))
4.每个扇区都有独立的一组密码和访问控制
内部信息
1.扇区0的块0用来固化厂商代码;
2.每个扇区的块0、块1、块2为数据块,可用于存贮数据。
数据块可作两种应用:
★ 用作一般的数据保存,可以进行读、写操作。
★ 用作数据值,可以进行初始化值、加值、减值、读值操作。
3.每个扇区的块3作为控制块,存放:密码A(6字节)、存取控制(4字节)、密码B(6字节)
在存取控制中每个块都有相应的三个控 制位,定义如下:
4.2 存储结构
M1卡分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,(我们也 将16个扇区的64个块按绝对地址编号为0~63,存贮结构如下图所示:
4.3 工作原理
结构:
4.4 M1射频卡与读写器的通讯:
4.5 MFRC522与IC卡之间的通讯相应代码
u8 MFRC522_ToCard(u8 command, u8 *sendData, u8 sendLen, u8 *backData, u16 *backLen)
{
u8 status = MI_ERR; // 初始化状态为错误状态
u8 irqEn = 0x00; // 中断使能寄存器的值
u8 waitIRq = 0x00; // 等待中断标志
u8 lastBits; // 用于保存最后的位数
u8 n; // 用于存储FIFO级别寄存器的值
u16 i; // 循环变量
// 根据不同命令选择对应的中断使能和等待中断
switch (command)
{
case PCD_AUTHENT: // 鉴权命令
irqEn = 0x12; // 启用鉴权中断
waitIRq = 0x10; // 等待鉴权完成的中断
break;
case PCD_TRANSCEIVE: // 发送/接收命令
irqEn = 0x77; // 启用发送和接收的中断
waitIRq = 0x30; // 等待发送/接收完成的中断
break;
default:
break;
}
// 设置中断使能寄存器,允许对应的中断
Write_MFRC522(ComIEnReg, irqEn | 0x80);
// 清除中断标志寄存器中的过时中断标志
ClearBitMask(ComIrqReg, 0x80);
// 设置FIFO级别寄存器,清空FIFO缓冲区
SetBitMask(FIFOLevelReg, 0x80);
// 将MFRC522设置为IDLE状态,停止之前的命令
Write_MFRC522(CommandReg, PCD_IDLE);
// 将发送数据写入FIFO数据寄存器
for (i = 0; i < sendLen; i++)
{
Write_MFRC522(FIFODataReg, sendData[i]);
}
// 发起命令
Write_MFRC522(CommandReg, command);
// 如果是接收命令,启动数据发送
if (command == PCD_TRANSCEIVE)
{
SetBitMask(BitFramingReg, 0x80); // 启动发送
}
// 等待接收数据(最多2000个循环,即50ms)
i = 2000;
do
{
n = Read_MFRC522(ComIrqReg); // 读取中断寄存器
i--; // 循环计数减1
} while ((i != 0) && !(n & 0x01) && !(n & waitIRq)); // 判断是否接收到数据或出现错误
// 停止发送
ClearBitMask(BitFramingReg, 0x80);
// 如果没有超时,且没有出现错误
if (i != 0)
{
if (!(Read_MFRC522(ErrorReg) & 0x1B)) // 检查是否有错误
{
if (n & irqEn & 0x01) // 检查是否成功接收到数据
status = MI_NOTAGERR; // 没有标签错误
// 如果是发送接收命令,处理接收到的数据
if (command == PCD_TRANSCEIVE)
{
n = Read_MFRC522(FIFOLevelReg); // 获取FIFO缓冲区的字节数
lastBits = Read_MFRC522(ControlReg) & 0x07; // 获取最后的位数
if (lastBits != 0)
*backLen = (n - 1) * 8 + lastBits; // 根据最后的位数计算接收到的数据长度
else
*backLen = n * 8; // 数据长度为n个字节
// 如果FIFO为空,设置n为1
if (n == 0)
n = 1;
// 如果FIFO数据超过最大长度,限制为最大长度
if (n > MAX_LEN)
n = MAX_LEN;
// 读取FIFO数据寄存器,将接收到的数据存储到backData数组中
for (i = 0; i < n; i++)
{
backData[i] = Read_MFRC522(FIFODataReg);
}
}
status = MI_OK; // 成功接收到数据
}
else
status = MI_ERR; // 发生错误
}
// 停止计时器并将MFRC522设置为IDLE状态
Write_MFRC522(ControlReg, 0x80);
Write_MFRC522(CommandReg, PCD_IDLE);
return status; // 返回操作状态
}
// 根据不同命令选择对应的中断使能和等待中断
switch (command)
{
case PCD_AUTHENT: // 鉴权命令
irqEn = 0x12; // 启用鉴权中断
waitIRq = 0x10; // 等待鉴权完成的中断
break;
case PCD_TRANSCEIVE: // 发送/接收命令
irqEn = 0x77; // 启用发送和接收的中断
waitIRq = 0x30; // 等待发送/接收完成的中断
break;
default:
break;
}
鉴权命令:
利用 Write_MFRC522(ComIEnReg, irqEn | 0x80);将CommIEnReg设置启动
IdleIEn
中断使能位
IdleIEn
代表的是 空闲中断使能,通常用于处理设备在空闲状态时的一些操作。具体来说,IdleIEn
用于控制当系统或设备处于空闲状态时,是否触发中断。
ErrIEn错误中断使能
ErrIEn
的作用是控制错误发生时是否触发中断。开启(使能)ErrIEn
后,如果设备或系统发生了预定义的错误情况,将会触发中断请求,通知系统进行处理。这通常有助于系统在出现故障或异常时,迅速采取措施进行处理或修复。
非上0X80是为了启动最高位IRqInv,启动中断使能
设置waitIRq = 0x10; // 等待鉴权完成的中断
通过这句while ((i != 0) && !(n & 0x01) && !(n & waitIRq)); 判断相应的中断有没用产生,例如此设置,判断空闲中断标志位有没用置一。
发送/接收命令同上设置。
五 、在main函数中使用
5.1 录入新卡
u8 Add_Rfid(void)
{
u8 ID; // 定义一个变量来存储输入的ID
u16 time6=0; // 用于计时的变量
u8 i, key_num, status=1, card_size; // 初始化多个变量
OLED_Clear(); // 清屏
Show_Str(0,0,128,16,"=== 添加RFID卡 ===",16,0); // 在OLED显示屏上显示标题
Show_Str(0,20,128,12,"请按下添加RFID卡:",12,0); // 提示用户按下按钮以添加RFID卡
Show_Str(0,52,128,12,"等待中",12,0); // 显示“等待中”提示
OLED_Refresh_Gram(); // 更新OLED显示内容
MFRC522_Initializtion(); // 初始化MFRC522模块(RFID读写器)
while(1)
{
AntennaOn(); // 打开天线,准备接收卡片信号
status = MFRC522_Request(0x52, card_pydebuf); // 请求卡片,如果有卡片,返回状态
if(status == 0) // 如果成功检测到卡片
{
printf("rc522 ok\r\n"); // 输出调试信息
Show_Str(0,38,128,12,"检测到卡片!",12,0); // 在OLED显示屏上显示“检测到卡片”
OLED_Refresh_Gram(); // 更新OLED显示内容
status = MFRC522_Anticoll(card_numberbuf); // 防碰撞,获取卡片ID
card_size = MFRC522_SelectTag(card_numberbuf); // 选择卡片
status = MFRC522_Auth(0x60, 4, card_key0Abuf, card_numberbuf); // 认证卡片(使用密钥)
status = MFRC522_Write(4, card_writebuf); // 向卡片写入数据
status = MFRC522_Read(4, card_readbuf); // 从卡片读取数据
printf("卡片的ID是:");
for(i=0; i<5; i++)
{
printf("%#x ", card_numberbuf[i]); // 输出卡片ID
}
printf("\r\n");
AntennaOff(); // 关闭天线
OLED_Clear_NOupdate(); // 清除显示但不更新OLED
Show_Str(0,12,128,12,"请输入ID(0-9): ",12,0); // 提示用户输入ID
Show_Str(122,52,128,12," ",12,0); // 清除显示的内容
Show_Str(0,52,128,12,"确认 输入 完成 退出",12,0); // 提示用户输入完成后退出
OLED_Refresh_Gram(); // 更新OLED显示内容
do
ID = GET_NUM(); // 获取用户输入的ID
while(!(ID < 10)); // 确保ID是0到9之间的数字
printf("输入的ID是:%d\r\n", ID); // 输出用户输入的ID
OLED_Clear_NOupdate(); // 清除显示但不更新OLED
Show_Str(0,38,128,12,"保存ID信息.",12,0); // 显示“保存ID信息”
OLED_Refresh_Gram(); // 更新OLED显示内容
for(i=0; i<5; i++)
{
sys.cardid[ID][i] = card_numberbuf[i]; // 将卡片ID存入系统中的指定位置
}
STMFLASH_Write(SYS_SAVEADDR, (u16*)&sys, sizeof(sys)); // 将数据写入FLASH存储
for(i=0; i<10; i++)
printf("cardid={%X,%X,%X,%X}\r\n", sys.cardid[i][0], sys.cardid[i][1], sys.cardid[i][2], sys.cardid[i][3]); // 输出系统中所有卡片的ID
Show_Str(0,38,128,12,"添加成功!",12,0); // 在OLED显示屏上显示“添加成功”
OLED_Refresh_Gram(); // 更新OLED显示内容
delay_ms(1000); // 延迟1秒
OLED_Clear(); // 清除显示
return 0; // 退出函数
}
key_num = Button4_4_Scan(); // 扫描按键输入
time6++; // 增加计时器
if(time6 % 50 == 0 || key_num == 13) // 如果经过50次循环或者按下某个特定按键
{
OLED_Clear(); // 清除显示
return 1; // 退出函数
}
}
}
5.2 rfid锁验证
u8 MFRC522_lock(void)
{
u8 i, j, status = 1, card_size;
u8 count;
u8 prtfbuf[64];
// 开启RFID天线
AntennaOn();
// 请求检测是否有RFID卡片
status = MFRC522_Request(0x52, card_pydebuf);
if(status == 0) // 如果成功检测到卡片
{
// 如果超过最大错误次数,退出函数
if(DisErrCnt()) return -1; // 超过错误次数,返回错误
// 检测到卡片,开始进行防碰撞操作,获取卡片ID
status = MFRC522_Anticoll(card_numberbuf);
// 选择卡片
card_size = MFRC522_SelectTag(card_numberbuf);
// 使用密钥认证卡片
status = MFRC522_Auth(0x60, 4, card_key0Abuf, card_numberbuf);
// 向卡片写入数据
status = MFRC522_Write(4, card_writebuf);
// 从卡片读取数据
status = MFRC522_Read(4, card_readbuf);
count = 0; // 初始化计数器
// 遍历系统中存储的卡片ID,比较当前卡片与系统中存储的卡片ID
for(j = 0; j < 10; j++)
{
printf("\r\n卡片 %d 的对比结果:", j);
for(i = 0; i < 5; i++)
{
printf("%x=%x ", card_numberbuf[i], sys.cardid[j][i]);
// 比较当前卡片ID和系统中保存的ID
if(card_numberbuf[i] == sys.cardid[j][i]) count++; // 如果匹配则计数
}
printf("\r\n");
// 如果有4个字节匹配,认为是有效的卡片
if(count >= 4)
{
sys.errCnt = 0; // 重置错误计数
OLED_Clear_NOupdate(); // 清除OLED显示,但不更新
sprintf(prtfbuf, "RFID:%d解锁成功", j); // 显示解锁成功信息
Show_Str(12, 13, 128, 20, prtfbuf, 12, 0); // 在OLED显示上输出卡片号
OLED_Refresh_Gram(); // 更新OLED显示
delay_ms(500); // 延迟500毫秒
DisUnLock(); // 调用解锁操作
return 0; // 解锁成功,返回0
}
else
count = 0; // 如果没有匹配,重置计数器
}
// 如果没有找到匹配的卡片,增加错误计数
sys.errCnt++;
// 如果错误计数超过最大次数,设定锁定时间
if(sys.errCnt > MAXERRTIMES)
sys.errTime = 30; // 锁定30秒,无法解锁
// 显示错误信息
OLED_Clear(); // 清除显示
Show_Str(12, 13, 128, 20, "卡片验证失败", 12, 0); // 显示“卡片验证失败”
OLED_Refresh_Gram(); // 更新OLED显示
beep_on_mode1(); // 发出警告音
OLED_Clear(); // 清空显示
OLED_Show_Font(56, 48, 0); // 显示锁定图标
DisFlag = 1; // 设置标志位表示锁定状态
}
// 关闭RFID天线
AntennaOff();
return 1; // 返回1表示锁定操作完成
}
更详细的代码解析可以参考我的新文章,下面是链接:
参考连接:第49集:RFID-RC522射频卡门禁卡_哔哩哔哩_bilibili
参考链接:RFID射频模块(MFRC522 STM32)_哔哩哔哩_bilibili
参考文件资料(百度网盘): https://pan.baidu.com/s/1B-FoQOjUbdM8Pw2X-sAm4A
提取码: skb3