STM32F103C8T6 · HAL库 · 软件SPI通信 · ISO/IEC 14443 Type A
MFRC522是NXP半导体公司生产的高度集成非接触式读写芯片,工作于13.56MHz,支持ISO/IEC 14443 Type A标准
电磁感应耦合:MFRC522内部发射器通过天线引脚(TX1/TX2)驱动13.56MHz载波,产生交变磁场。当无源MIFARE卡进入磁场范围(约5cm内),卡片内置天线线圈通过电磁感应获取能量,激活卡片IC。
通信流程:读写器发送REQA(Request Type A)命令寻卡,卡片返回ATQA应答;随后执行ISO 14443A防冲撞序列,获取卡片唯一序列号UID;选卡后进行Crypto1流密码认证(Key A或Key B),认证通过即可读写数据块。
调制方式:读写器到卡片采用100% ASK调制;卡片到读写器采用ASK负载调制,通过13.56MHz副载波传递数据。MFRC522内置64字节FIFO缓冲区和CRC协处理器,自动处理帧格式、校验和编解码。
发送REQA命令检测磁场范围内的ISO 14443A卡片,执行防冲撞算法隔离单张卡并获取4字节UID序列号
支持MIFARE Classic的Crypto1流密码认证,使用6字节Key A或Key B进行挑战-应答式安全验证
认证通过后可对16字节数据块进行读取或写入操作,支持MIFARE 1K(16扇区×4块)和4K卡
| 序号 | 引脚名称 | 类型 | 功能说明 | STM32连接 |
|---|---|---|---|---|
| 1 | SDA / SS | 数字I/O | SPI片选信号(低电平有效) | PA7 |
| 2 | SCK | 数字输入 | SPI时钟线(由主机提供) | PA6 |
| 3 | MOSI | 数字输入 | SPI主出从入(MCU→RC522数据) | PA5 |
| 4 | MISO | 数字输出 | SPI主入从出(RC522→MCU数据) | PA4 |
| 5 | IRQ | 数字输出 | 中断请求(卡片检测/命令完成触发) | 未连接 |
| 6 | GND | 电源 | 接地 | GND |
| 7 | RST | 数字输入 | 复位/掉电控制(低电平复位) | PA0 |
| 8 | 3.3V | 电源 | 供电(3.3V,不可接5V) | 3.3V |
RFID-RC522模块外观实物展示
模块电路原理图与PCB封装设计
基于STM32 HAL库的RC522驱动程序与主程序逻辑分析
使用外部高速晶振(HSE 8MHz),通过PLL倍频9倍获得72MHz系统主频。APB1总线36MHz,APB2总线72MHz。
void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // HSE外部晶振8MHz,PLL倍频9倍 = 72MHz RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz × 9 = 72MHz HAL_RCC_OscConfig(&RCC_OscInitStruct); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 72MHz RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1 = 36MHz RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 = 72MHz HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }
依次完成HAL库初始化、时钟配置、GPIO初始化、OLED显示屏初始化和RC522模块初始化。
int main(void) { HAL_Init(); // HAL库初始化 SystemClock_Config(); // 系统时钟配置 72MHz MX_GPIO_Init(); // GPIO端口时钟使能 OLED_Init(); // OLED显示屏初始化 RC522_Init(); // RFID模块初始化 while (1) { /* 主循环逻辑 */ } }
SDA/SCK/MOSI/RST配置为推挽输出,MISO配置为上拉输入。通过GPIO位操作模拟SPI时序,而非使用硬件SPI外设。
void RC522_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 引脚时钟使能(均在GPIOA) RC522_SDA_GPIO_CLK_ENABLE(); RC522_SCK_GPIO_CLK_ENABLE(); RC522_MOSI_GPIO_CLK_ENABLE(); RC522_MISO_GPIO_CLK_ENABLE(); RC522_RST_GPIO_CLK_ENABLE(); // SDA(PA7)、SCK(PA6)、MOSI(PA5)、RST(PA0) → 推挽输出 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Pin = RC522_SDA_Pin; HAL_GPIO_Init(RC522_SDA_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = RC522_SCK_Pin; HAL_GPIO_Init(RC522_SCK_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = RC522_MOSI_Pin; HAL_GPIO_Init(RC522_MOSI_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = RC522_RST_Pin; HAL_GPIO_Init(RC522_RST_Port, &GPIO_InitStruct); // MISO(PA4) → 上拉输入 GPIO_InitStruct.Pin = RC522_MISO_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(RC522_MISO_Port, &GPIO_InitStruct); }
所有RC522引脚均映射到GPIOA端口,通过宏定义简化操作。RC522_SDA(a)等宏封装了HAL_GPIO_WritePin调用。
#define RC522_SDA_Port GPIOA #define RC522_SCK_Port GPIOA #define RC522_MOSI_Port GPIOA #define RC522_MISO_Port GPIOA #define RC522_RST_Port GPIOA #define RC522_SDA_Pin GPIO_PIN_7 // SPI片选 #define RC522_SCK_Pin GPIO_PIN_6 // SPI时钟 #define RC522_MOSI_Pin GPIO_PIN_5 // 主出从入 #define RC522_MISO_Pin GPIO_PIN_4 // 主入从出 #define RC522_RST_Pin GPIO_PIN_0 // 复位 #define RC522_SDA(a) HAL_GPIO_WritePin(RC522_SDA_Port,RC522_SDA_Pin,(a)?GPIO_PIN_SET:GPIO_PIN_RESET) #define RC522_SCK(a) HAL_GPIO_WritePin(RC522_SCK_Port,RC522_SCK_Pin,(a)?GPIO_PIN_SET:GPIO_PIN_RESET) #define RC522_MOSI(a) HAL_GPIO_WritePin(RC522_MOSI_Port,RC522_MOSI_Pin,(a)?GPIO_PIN_SET:GPIO_PIN_RESET) #define RC522_MISO() HAL_GPIO_ReadPin(RC522_MISO_Port,RC522_MISO_Pin)
通过GPIO位操作模拟SPI MODE 0时序:拉低SDA片选,按MSB优先逐位发送地址字节和数据字节。地址格式:(addr<<1)&0x7E,最高位为0表示写操作。
void WriteRawRC(unsigned char Address, unsigned char value) { unsigned char i, ucAddr; RC522_SCK(0); // SCK拉低 RC522_SDA(0); // 片选拉低(选中RC522) ucAddr = ((Address<<1)&0x7E); // 地址格式:bit7=0(写), bit6=0, addr[6:1] // 发送地址字节(MSB优先) for(i=8;i>0;i--) { if((ucAddr&0x80)==0x80) RC522_MOSI(1); else RC522_MOSI(0); delay_us(1); RC522_SCK(1); // 上升沿锁存数据 delay_us(1); ucAddr <<= 1; RC522_SCK(0); delay_us(1); } // 发送数据字节(MSB优先) for(i=8;i>0;i--) { if((value&0x80)==0x80) RC522_MOSI(1); else RC522_MOSI(0); delay_us(1); RC522_SCK(1); delay_us(1); value <<= 1; RC522_SCK(0); delay_us(1); } RC522_SDA(1); // 释放片选 RC522_SCK(1); }
依次执行寻卡→防冲撞→选卡→密码验证四个步骤,全部成功返回MI_OK。使用默认密钥FFFFFFFFFFFF验证块2(扇区0)的Key A。
signed char RC522_Process(void) { // 1. 寻卡:发送0x26(REQIDL),返回卡片类型到Temp数组 if(PcdRequest(0x26, Temp) == MI_OK) { // 2. 防冲撞:获取4字节卡片序列号到UID数组 if(PcdAnticoll(UID) == MI_OK) { // 3. 选卡:选中该序列号的卡片 if(PcdSelect(UID) == MI_OK) { // 4. 密码验证:Key A(0x60), 块地址2, 默认密钥FFFFFFFFFFFF if(PcdAuthState(0x60, 2, (unsigned char *)DefaultKey, UID) == MI_OK) { return MI_OK; } } } } return MI_ERR; }
PcdRequest成功后,Temp数组存储2字节卡片类型代码ATQA
| 类型代码 | 卡片型号 | 存储容量 |
|---|---|---|
0x4400 | Mifare_UltraLight | 512 字节 |
0x0400 | Mifare_One(S50) | 1024 字节(1K) |
0x0200 | Mifare_One(S70) | 4096 字节(4K) |
0x0800 | Mifare_Pro(X) | 更大容量 |
0x4403 | Mifare_DESFire | DES加密 |
认证通过后,可对指定块地址进行读写。每块16字节,读写命令自动附加CRC校验。MIFARE 1K卡共16扇区×4块=64块,块地址0~63。
// 写数据到指定块(addr为块地址,pData为16字节数据) char PcdWrite(unsigned char addr, unsigned char *pData) { ucComMF522Buf[0] = PICC_WRITE; // 0xA0 写块命令 ucComMF522Buf[1] = addr; CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]); // 第一阶段:发送写命令+块地址,等待卡片ACK status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen); if(status == MI_OK) { // 第二阶段:发送16字节数据+CRC for(i=0;i<16;i++) ucComMF522Buf[i] = *(pData+i); CalulateCRC(ucComMF522Buf, 16, &ucComMF522Buf[16]); status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, &unLen); } return status; } // 读取指定块的16字节数据 char PcdRead(unsigned char addr, unsigned char *pData) { ucComMF522Buf[0] = PICC_READ; // 0x30 读块命令 ucComMF522Buf[1] = addr; CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]); status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen); if(status == MI_OK && unLen == 0x90) // 0x90位 = 144位 = 18字节(16数据+2CRC) { for(i=0;i<16;i++) *(pData+i) = ucComMF522Buf[i]; } return status; }
主循环持续调用RC522_Process()检测卡片。检测成功后在OLED上显示UID和卡片类型,将"Hello RC522!"写入块2并回读显示,最后令卡片休眠并延时500ms防止重复触发。
while (1) { /* RFID逻辑控制 */ if(RC522_Process() == MI_OK) { OLED_Clear(); // 显示卡片UID(4字节十六进制) OLED_CenterPrintf(0, "UID"); OLED_CenterPrintf(16, "%02X %02X %02X %02X", UID[0],UID[1],UID[2],UID[3]); // 显示卡片类型代码 OLED_CenterPrintf(32, "Temp:0X%02X%02X", Temp[0],Temp[1]); // 将数据写入卡片块2 strcpy((char *)RC522_Buf, "Hello RC522!"); PcdWrite(2, RC522_Buf); // 读取块2数据并显示 if(PcdRead(2, RC522_Buf) == MI_OK) { OLED_CenterPrintf(48, "%s", RC522_Buf); } OLED_Update(); // 刷新OLED显示 PcdHalt(); // 卡片进入休眠 HAL_Delay(500); // 延时500ms防止临界点反复触发 } }
本程序操作块2(扇区0的数据块),每扇区最后一个块(块3)为密钥尾块
STM32主程序执行流程,包含while(1)主循环与RC522读卡完整流程
模拟RC522模块读取MIFARE卡的完整过程,观察电磁场激活、卡片检测、数据读写的实时变化
电磁场:RC522天线发射13.56MHz交变磁场,卡片线圈感应取电
寻卡:发送REQA命令,卡片返回ATQA类型代码
防冲撞:获取4字节唯一序列号UID
认证:使用Key A(FFFFFFFFFFFF)验证扇区0
读写:写入"Hello RC522!"到块2,回读并显示