一、项目概述

本项目基于 STM32F103C8T6 单片机,采用GPIO 模拟 SPI驱动 MFRC522 射频读卡模块,搭配 I2C OLED 屏幕,实现 13.56M ISO14443A MIFARE 卡片检测、寻卡、防冲突、读取 4 字节 UID、卡型识别并实时显示功能。

演示视频:STM32F103C8T6+RC522射频读卡器+OLED完整教程(软件SPI模拟驱动,读卡显示UID)https://www.bilibili.com/video/BV1tp3E68E9i/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb

整体工作流程

  1. STM32 初始化 RC522、OLED 对应 GPIO 端口
  2. 硬件复位 RC522,配置内部定时器、射频调制参数,开启天线
  3. 读取 RC522 版本寄存器校验 SPI 通信是否正常
  4. RC522 天线输出 13.56MHz 交变射频磁场
  5. 主控发送寻卡指令,卡片上电返回 ATQA 卡片类型标识
  6. 执行防冲突算法,读取 4 字节 UID+BCC 校验码并校验
  7. 选中卡片后发送休眠指令,OLED 刷新卡片信息
  8. 每 100ms 循环扫描,搭配 800ms 移卡消抖,避免屏幕闪烁

三大核心器件分工

器件 核心作用
STM32F103C8T6 运行主逻辑,通过模拟 SPI 下发寄存器指令,处理读卡数据,驱动 OLED 显示
RC522 模块 完成 MCU 与射频卡信号转换,生成 13.56MHz 载波,射频调制 / 解调、协议帧处理
MIFARE 射频卡 无源感应取电,响应寻卡、选卡、休眠指令,返回 UID 与卡片参数

二、硬件接线说明(严格对应工程引脚)

引脚对应表

RC522 模块引脚 STM32 引脚 功能说明
SDA/SS PB12 SPI 片选,低电平有效(非 I2C 数据线)
SCK PB13 SPI 时钟输出
MOSI PB14 STM32 → RC522 数据发送
MISO PB15 RC522 → STM32 数据接收
RST PA8 RC522 硬件复位,高电平正常工作
3.3V 3.3V 模块供电,严禁接 5V
GND GND 共地(必须共地,否则通信失效)
IRQ 悬空 本工程轮询读取,不使用中断
OLED 模块引脚 STM32 引脚 功能
SCL PB8 软件 I2C 时钟
SDA PB9 软件 I2C 数据
VCC 3.3V 供电
GND GND 共地

重点接线避坑说明

  1. RC522 的 SDA 不是 I2C 数据线 RC522 引脚复用:I2C 模式下为 SDA,SPI 模式下是片选 SS。本项目使用模拟 SPI,因此 PB12 只做片选,不能和 OLED 的 PB9 SDA 短接。

  2. 为什么不用硬件 SPI2,改用软件 SPI? STM32F103 SPI2 硬件引脚定义:SCK=PB13、MISO=PB14、MOSI=PB15; 工程要求 MOSI 接 PB14、MISO 接 PB15,和硬件 SPI 数据流向相反,因此将 PB13/14 配置为输出、PB15 配置为输入,纯 GPIO 模拟 SPI 时序。 若想改用硬件 SPI,需要调换 RC522 MOSI/MISO 接线并重写 SPI 驱动。

  3. 供电与布线要求

    • MFRC522 芯片额定 3.3V,5V 供电会烧毁模块;
    • 单片机、RC522、OLED 必须共地,电平无参考基准会出现通信乱码;
    • SCK/MOSI/MISO 信号线尽量短,长线易产生干扰、接触不良。
  4. 天线读卡注意事项 RC522 方形铜线圈为读卡天线:

    • 卡片线圈平面与天线平行,读卡距离最佳;
    • 金属、硬币、大面积 PCB 地平面会吸收射频能量,大幅降低读卡距离;
    • 多张卡片紧贴会互相耦合,读卡不稳定;
    • 普通 RC522 稳定读卡距离仅 1~3cm。

三、RC522 芯片内部架构与通信原理

芯片内部数据流框图

STM32 (模拟 SPI) ↔ SPI 寄存器组 ↔ 64 字节 FIFO 缓存 ↔ 数字控制单元 (CRC / 帧校验 / 防冲突) ↔ 模拟前端 (调制解调) ↔ 13.56MHz 天线 ↔ 射频卡

各模块功能解析

  1. SPI 寄存器组:接收主控配置命令、读写芯片状态;
  2. 64 字节 FIFO:缓存收发数据,隔离 MCU 与射频通信速率差;
  3. 数字单元:自动处理 ISO14443A 帧格式、奇偶校验、CRC 校验、防冲突逻辑;
  4. 模拟前端:载波调制、卡片返回微弱信号放大解调;
  5. 内置定时器:通信超时保护,防止程序卡死;
  6. CRC 协处理器:硬件快速计算 CRC_A 校验码,减轻 MCU 运算压力。

核心关键点:STM32 不产生 13.56MHz 射频载波,射频调制、解调、底层编码全部由 RC522 硬件完成,主控仅操作寄存器即可。

四、13.56MHz RFID 无源卡通信完整原理

基础概念:PCD 与 PICC

  • PCD:读卡器设备(本项目 RC522)
  • PICC:无源射频卡片(MIFARE 卡、钥匙扣、NTAG 标签)

1. 无源卡片无电池如何供电?

RC522 天线持续输出 13.56MHz 交变磁场,卡片内置线圈电磁耦合产生交流感应电压,卡片内部整流稳压后给芯片供电。 读卡距离越远,磁场耦合越弱,极易出现读卡失败、UID 校验错误。

2. 读卡器下发数据(PCD→PICC)

采用 100% ASK 幅度调制,Miller 编码,通过控制 13.56MHz 载波有无传递数据,卡片检测载波幅度变化解析指令。

3. 卡片回传数据(PICC→PCD)

无源卡片无法主动发射射频,采用负载调制: 卡片内部开关改变线圈负载,读卡器天线端感应微弱幅度变化,RC522 模拟前端放大解调,解析 Manchester 编码数据。

五、GPIO 模拟 SPI 底层实现(SPI 模式 0)

SPI 模式 0 时序规则

  • SCK 空闲低电平;高位先行;SCK 高电平时采样数据
  • 全双工通信:发 1 字节同时接收 1 字节

RC522 寄存器读写规则

寄存器地址字节格式:bit7 = 读写标志,bit6~bit1=6 位寄存器地址,bit0 固定 0

  • 写寄存器:(addr << 1) & 0x7E
  • 读寄存器:((addr << 1) & 0x7E) | 0x80

读写流程:

  1. 拉低 PB12 片选;
  2. 发送地址字节;
  3. 写寄存器:发送数据字节;读寄存器:发送 0x00 时钟,同步读取返回数据;
  4. 拉高片选结束通信。

位操作工具函数

驱动内置SetBitMaskClearBitMask,先读取寄存器原值,仅修改目标位后写回,不破坏其他配置位。

六、RC522 初始化流程与核心寄存器

初始化步骤

  1. 开启 GPIOA、GPIOB 外设时钟;
  2. 配置 PB12/13/14 输出、PB15 浮空输入、PA8 复位输出;
  3. PA8 拉低 2ms 硬件复位,拉高等待 50ms 释放复位;
  4. 发送软件复位指令,配置定时器、调制模式;
  5. 开启 TX1/TX2 射频天线输出;
  6. 读取 VersionReg 版本寄存器,0x00/0xFF 判定通信故障。

关键寄存器配置

寄存器 配置值 功能
CommandReg 0x0F 软件复位 RC522
TxASKReg 0x40 开启 100% ASK 调制
TxControlReg Bit0/1 置 1 打开射频天线
VersionReg 只读 校验 SPI 通信,识别芯片版本

版本寄存器故障判断

  • 0x91:RC522 V1.0;0x92:RC522 V2.0;0x88:兼容芯片
  • 0x00:供电异常、复位未释放、MISO 持续低电平
  • 0xFF:MISO 悬空、片选接线错误、SPI 通信断开

七、ISO14443A 读卡完整协议流程(寻卡→防冲突→选卡→休眠)

整体交互时序

STM32 下发 WUPA 寻卡指令 → RC522 射频发送短帧 → 卡片返回 ATQA 卡型 → 执行防冲突读取 UID+BCC → 校验异或 BCC → 选卡确认卡片 → 发送 HALT 休眠卡片

1. 寻卡 PcdRequest

  • PICC_REQALL(0x52):WUPA,唤醒休眠卡片,全区域扫描;
  • PICC_REQIDL(0x26):REQA,仅扫描未休眠卡片;
  • 发送 7 位短帧,卡片返回 2 字节 ATQA 标识卡片大类。

2. 防冲突 PcdAnticoll

多张卡片同时感应射频场会造成数据冲突,防冲突指令0x93 0x20分离单张卡片,返回 4 字节 UID+1 字节 BCC 校验码; 校验规则:BCC = UID0 ^ UID1 ^ UID2 ^ UID3,异或匹配才判定 UID 有效。

本驱动仅支持 4 字节短 UID 卡片,7/10 字节长 UID 卡需新增 0x95、0x97 级联防冲突逻辑。

3. 选卡 PcdSelect

携带完整 UID+BCC 下发选卡指令,卡片返回 SAK 标识,卡片进入选中状态,才可执行读写扇区操作。

4. 休眠 PcdHalt

下发0x50 0x00 + CRC休眠指令,协议规定卡片不应答即为执行成功,无返回数据代表正常。

八、MIFARE Classic 1K 卡存储与读写拓展(驱动自带接口)

1K 卡存储结构

总容量 1024 字节,16 个扇区,每扇区 4 块,每块 16 字节:

  • 块 0:厂商固化块(包含 UID,禁止写入)
  • 块 1、块 2:用户数据块
  • 块 3:扇区控制块(6 字节 KeyA+3 字节访问控制 + 1 字节用户位 + 6 字节 KeyB)

读写操作流程

  1. 选卡成功后,使用对应扇区密钥执行PcdAuthState认证;
  2. 读块:下发 0x30 读指令,返回 16 字节数据 + CRC;
  3. 写块:分两步,下发写地址指令收到 ACK 后,再发送 16 字节数据;
  4. 操作完成调用PcdStopCrypto1关闭加密。

安全提醒

MIFARE Classic Crypto1 加密算法已被破解,仅适用于教学实验,不可用于门禁、支付等高安全场景;禁止修改真实校园卡、交通卡扇区控制位,极易锁卡报废。

九、OLED 屏幕显示逻辑与消抖处理

三种显示界面

  1. 初始化界面:打印引脚定义、初始化提示;
  2. 故障界面:读取 VersionReg 为 00/FF,提示 SPI / 供电接线错误,每秒重试初始化;
  3. 待机界面:显示芯片版本,提示刷卡;
  4. 读卡界面:打印卡片类型、4 字节 UID,提示移卡切换卡片。

卡型识别规则(通过 ATQA 匹配)

ATQA 屏幕显示
04 00 MIFARE 1K
02 00 MIFARE 4K
44 00 ULTRALIGHT
其他 UNKNOWN

移卡消抖机制

  • 扫描周期:100ms / 次;
  • 移除判定:连续 8 次无卡片应答才判定卡片移开,消抖总时长 800ms;
  • 同卡防抖:缓存上一次 UID,卡片不变时不刷新屏幕,减少闪烁。

十、常见故障排查大全

故障 1:OLED 持续提示 RC522 INIT ERROR

  1. RC522 接 5V 供电,改为 3.3V;
  2. STM32、RC522、OLED 未共地;
  3. MOSI/MISO 引脚接反、片选 PB12 虚接;
  4. RST 引脚 PA8 持续拉低,复位未释放;
  5. 误将 RC522 SDA 接到 OLED PB9;
  6. 排针、杜邦线接触不良。

故障 2:初始化成功,刷卡无任何反应

  1. 使用 125K 低频 ID 卡,RC522 仅支持 13.56M A 类卡;
  2. 卡片与天线垂直、距离超过 5cm;
  3. 天线下方放置金属、铁板;
  4. 多张卡片堆叠,射频耦合干扰;
  5. 3.3V 电源带载能力不足。

故障 3:能读卡,但 UID 乱码、读取不稳定

  1. SPI 信号线过长、接触虚焊;
  2. MOSI/MISO 引脚颠倒;
  3. 周边存在其他 13.56M 读卡器干扰;
  4. 卡片处于读卡临界距离。

故障 4:4 字节 UID 正常,7 字节卡片读取失败

当前驱动仅实现一级防冲突,未支持二级、三级级联,需拓展防冲突函数。

故障 5:UID 正常读取,但扇区认证 / 读块失败

  1. 卡片非 MIFARE Classic,不支持 Classic 读写指令;
  2. 使用了错误扇区密钥;
  3. 扇区访问控制位禁止当前密钥操作;
  4. 未执行 PcdSelect 选卡直接读写;
  5. 上一次加密未关闭 Crypto1。

十一、工程文件结构说明

  1. RC522.c:底层 GPIO 初始化、模拟 SPI、寄存器读写、寻卡 / 防冲突 / 认证 / 读写全功能驱动;
  2. RC522.h:寄存器、命令、返回值宏定义,函数声明;
  3. main.c:主循环,OLED 显示逻辑、读卡扫描、消抖处理;
  4. OLED 驱动文件:软件 I2C 屏幕显示函数。

核心调用流程示例

u8 tag_type[2];
u8 uid[5];
if(RC522_Init() == MI_OK)
{
    // 寻卡
    if(PcdRequest(PICC_REQALL, tag_type) == MI_OK)
    {
        // 防冲突读取UID
        if(PcdAnticoll(uid) == MI_OK)
        {
            // 选中卡片
            if(PcdSelect(uid) == MI_OK)
            {
                // 可拓展:密钥认证、读写数据块
                PcdHalt(); // 卡片休眠
            }
        }
    }
}

main.c

#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "RC522.h"

#define RC522_SCAN_INTERVAL_MS        100U
#define RC522_REMOVE_CONFIRM_COUNT    8U

static void OLED_ShowBoot(void);
static void OLED_ShowReaderReady(u8 version);
static void OLED_ShowReaderError(u8 version);
static void OLED_ShowCard(const u8 *tag_type, const u8 *uid);
static void OLED_ShowHexByte(u8 x, u8 y, u8 value, u8 size);
static const u8 *RC522_GetCardTypeName(const u8 *tag_type);
static u8 RC522_UIDEquals(const u8 *first, const u8 *second);
static void RC522_CopyUID(u8 *destination, const u8 *source);

int main(void)
{
	u8 tag_type[2];
	u8 uid[5];
	u8 last_uid[5];
	u8 version;
	u8 status;
	u8 card_present;
	u8 missed_scans;

	delay_init();
	NVIC_Configuration();
	OLED_Init();
	OLED_ColorTurn(0);
	OLED_DisplayTurn(0);
	OLED_ShowBoot();

	status = RC522_Init();
	version = RC522_GetVersion();
	while(status != MI_OK)
	{
		OLED_ShowReaderError(version);
		delay_ms(1000);
		status = RC522_Init();
		version = RC522_GetVersion();
	}

	card_present = 0U;
	missed_scans = 0U;
	OLED_ShowReaderReady(version);

	while(1)
	{
		status = PcdRequest(PICC_REQALL, tag_type);
		if((status == MI_OK) && (PcdAnticoll(uid) == MI_OK))
		{
			missed_scans = 0U;
			if((card_present == 0U) ||
			   (RC522_UIDEquals(uid, last_uid) == 0U))
			{
				RC522_CopyUID(last_uid, uid);
				OLED_ShowCard(tag_type, uid);
			}
			card_present = 1U;

			/*
			 * Select and halt complete one ISO14443A transaction.  REQALL in
			 * the next scan wakes the card again, so a held card remains
			 * visible while removal can still be detected.
			 */
			if(PcdSelect(uid) == MI_OK)
			{
				PcdHalt();
			}
		}
		else
		{
			if(card_present != 0U)
			{
				if(missed_scans < RC522_REMOVE_CONFIRM_COUNT)
				{
					missed_scans++;
				}
				if(missed_scans >= RC522_REMOVE_CONFIRM_COUNT)
				{
					card_present = 0U;
					OLED_ShowReaderReady(version);
				}
			}
		}
		delay_ms(RC522_SCAN_INTERVAL_MS);
	}
}

static void OLED_ShowBoot(void)
{
	OLED_Clear();
	OLED_ShowString(19, 0, (u8 *)"RC522 + OLED", 16);
	OLED_ShowString(13, 22, (u8 *)"Initializing reader", 12);
	OLED_ShowString(10, 40, (u8 *)"SPI: PB12-PB15", 12);
	OLED_ShowString(28, 52, (u8 *)"RST: PA8", 12);
	OLED_Refresh();
}

static void OLED_ShowReaderReady(u8 version)
{
	OLED_Clear();
	OLED_ShowString(19, 0, (u8 *)"RC522 RFID DEMO", 12);
	OLED_ShowString(25, 16, (u8 *)"Ready VER:", 12);
	OLED_ShowHexByte(85, 16, version, 12);
	OLED_ShowString(12, 33, (u8 *)"Tap a card...", 16);
	OLED_ShowString(13, 52, (u8 *)"CS:PB12 RST:PA8", 12);
	OLED_Refresh();
}

static void OLED_ShowReaderError(u8 version)
{
	OLED_Clear();
	OLED_ShowString(10, 0, (u8 *)"RC522 INIT ERROR", 12);
	OLED_ShowString(31, 16, (u8 *)"VER: 0x", 12);
	OLED_ShowHexByte(73, 16, version, 12);
	OLED_ShowString(13, 34, (u8 *)"Check power / SPI", 12);
	OLED_ShowString(19, 50, (u8 *)"PB12-15, PA8", 12);
	OLED_Refresh();
}

static void OLED_ShowCard(const u8 *tag_type, const u8 *uid)
{
	u8 index;
	u8 x;

	OLED_Clear();
	OLED_ShowString(25, 0, (u8 *)"CARD DETECTED", 12);
	OLED_ShowString(7, 16, (u8 *)"TYPE:", 12);
	OLED_ShowString(37, 16, (u8 *)RC522_GetCardTypeName(tag_type), 12);
	OLED_ShowString(0, 33, (u8 *)"UID:", 16);

	x = 32U;
	for(index = 0U; index < 4U; index++)
	{
		OLED_ShowHexByte(x, 33, uid[index], 16);
		x = (u8)(x + 24U);
	}

	OLED_ShowString(10, 52, (u8 *)"Remove / next card", 12);
	OLED_Refresh();
}

static void OLED_ShowHexByte(u8 x, u8 y, u8 value, u8 size)
{
	static const u8 hex_table[] = "0123456789ABCDEF";
	u8 width;

	width = (u8)(size / 2U);
	OLED_ShowChar(x, y, hex_table[(value >> 4) & 0x0FU], size);
	OLED_ShowChar((u8)(x + width), y, hex_table[value & 0x0FU], size);
}

static const u8 *RC522_GetCardTypeName(const u8 *tag_type)
{
	if((tag_type[0] == 0x04U) && (tag_type[1] == 0x00U))
	{
		return (const u8 *)"MIFARE 1K";
	}
	if((tag_type[0] == 0x02U) && (tag_type[1] == 0x00U))
	{
		return (const u8 *)"MIFARE 4K";
	}
	if((tag_type[0] == 0x44U) && (tag_type[1] == 0x00U))
	{
		return (const u8 *)"ULTRALIGHT";
	}
	if((tag_type[0] == 0x08U) && (tag_type[1] == 0x00U))
	{
		return (const u8 *)"MIFARE PRO";
	}
	if((tag_type[0] == 0x44U) && (tag_type[1] == 0x03U))
	{
		return (const u8 *)"DESFIRE";
	}
	return (const u8 *)"UNKNOWN";
}

static u8 RC522_UIDEquals(const u8 *first, const u8 *second)
{
	u8 index;

	for(index = 0U; index < 5U; index++)
	{
		if(first[index] != second[index])
		{
			return 0U;
		}
	}
	return 1U;
}

static void RC522_CopyUID(u8 *destination, const u8 *source)
{
	u8 index;

	for(index = 0U; index < 5U; index++)
	{
		destination[index] = source[index];
	}
}

RC522.c

#include "RC522.h"
#include "delay.h"

/* RC522 与 STM32F103C8T6 的 GPIO 引脚定义。 */
#define RC522_CS_PIN              GPIO_Pin_12
#define RC522_SCK_PIN             GPIO_Pin_13
#define RC522_MOSI_PIN            GPIO_Pin_14
#define RC522_MISO_PIN            GPIO_Pin_15
#define RC522_RST_PIN             GPIO_Pin_8

/* 软件 SPI 和复位引脚的电平控制宏。 */
#define RC522_CS_LOW()            GPIO_ResetBits(GPIOB, RC522_CS_PIN)
#define RC522_CS_HIGH()           GPIO_SetBits(GPIOB, RC522_CS_PIN)
#define RC522_SCK_LOW()           GPIO_ResetBits(GPIOB, RC522_SCK_PIN)
#define RC522_SCK_HIGH()          GPIO_SetBits(GPIOB, RC522_SCK_PIN)
#define RC522_MOSI_LOW()          GPIO_ResetBits(GPIOB, RC522_MOSI_PIN)
#define RC522_MOSI_HIGH()         GPIO_SetBits(GPIOB, RC522_MOSI_PIN)
#define RC522_MISO_READ()         GPIO_ReadInputDataBit(GPIOB, RC522_MISO_PIN)
#define RC522_RST_LOW()           GPIO_ResetBits(GPIOA, RC522_RST_PIN)
#define RC522_RST_HIGH()          GPIO_SetBits(GPIOA, RC522_RST_PIN)

/* 轮询超时计数,防止模块未连接时程序永久停在等待循环中。 */
#define RC522_COMM_TIMEOUT        2000U
#define RC522_CRC_TIMEOUT         255U

/* 本文件内部使用的底层函数声明。 */
static void RC522_GPIO_Init(void);
static u8 RC522_SPI_Transfer(u8 data);
static u8 PcdComMF522(u8 command, u8 *in_data, u8 in_length,
                     u8 *out_data, u16 *out_length_bits);
static u8 PcdCalculateCRC(u8 *in_data, u8 length, u8 *out_data);

/*
 * 函数名称:RC522_GPIO_Init
 * 功能说明:初始化 RC522 使用的五根 GPIO 信号线。
 * 参数说明:无。
 * 返回值  :无。
 *
 * SPI 时钟空闲时保持低电平,在 SCK 高电平期间读取数据,对应 SPI 模式 0。
 * PB12、PB13、PB14 配置为推挽输出,PB15 配置为浮空输入,PA8 配置为
 * 推挽输出。初始化结束后释放片选并让 RC522 保持正常工作状态。
 */
static void RC522_GPIO_Init(void)
{
	GPIO_InitTypeDef gpio;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
	                       RCC_APB2Periph_GPIOB, ENABLE);

	gpio.GPIO_Pin = RC522_CS_PIN | RC522_SCK_PIN | RC522_MOSI_PIN;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	gpio.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &gpio);

	gpio.GPIO_Pin = RC522_MISO_PIN;
	gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB, &gpio);

	gpio.GPIO_Pin = RC522_RST_PIN;
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	gpio.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOA, &gpio);

	RC522_CS_HIGH();
	RC522_SCK_LOW();
	RC522_MOSI_LOW();
	RC522_RST_HIGH();
}

/*
 * 函数名称:RC522_SPI_Transfer
 * 功能说明:使用 GPIO 模拟 SPI,同时发送和接收一个字节。
 * 参数说明:data 为需要发送的 8 位数据。
 * 返回值  :在 MISO 引脚上接收到的 8 位数据。
 *
 * 数据从最高位开始传输。由于指定接线中的 PB14/PB15 与 STM32F103
 * 硬件 SPI2 的 MOSI/MISO 方向相反,因此这里使用软件 SPI。
 */
static u8 RC522_SPI_Transfer(u8 data)
{
	u8 bit_mask;
	u8 received;

	received = 0U;
	for(bit_mask = 0x80U; bit_mask != 0U; bit_mask >>= 1)
	{
		if((data & bit_mask) != 0U)
		{
			RC522_MOSI_HIGH();
		}
		else
		{
			RC522_MOSI_LOW();
		}

		RC522_SCK_HIGH();
		if(RC522_MISO_READ() == Bit_SET)
		{
			received |= bit_mask;
		}
		RC522_SCK_LOW();
	}

	return received;
}

/*
 * 函数名称:WriteRawRC
 * 功能说明:通过软件 SPI 向 MFRC522 的一个寄存器写入数据。
 * 参数说明:address 为寄存器地址,value 为需要写入的数据。
 * 返回值  :无。
 *
 * 写操作地址格式为“0AAAAAA0”,其中 AAAAAA 为 6 位寄存器地址。
 */
void WriteRawRC(u8 address, u8 value)
{
	RC522_CS_LOW();
	RC522_SPI_Transfer((u8)((address << 1) & 0x7EU));
	RC522_SPI_Transfer(value);
	RC522_CS_HIGH();
}

/*
 * 函数名称:ReadRawRC
 * 功能说明:通过软件 SPI 读取 MFRC522 的一个寄存器。
 * 参数说明:address 为寄存器地址。
 * 返回值  :寄存器当前的 8 位数据。
 *
 * 读操作需要将地址字节最高位置 1,地址格式为“1AAAAAA0”。
 */
u8 ReadRawRC(u8 address)
{
	u8 value;

	RC522_CS_LOW();
	RC522_SPI_Transfer((u8)(((address << 1) & 0x7EU) | 0x80U));
	value = RC522_SPI_Transfer(0x00U);
	RC522_CS_HIGH();

	return value;
}

/*
 * 函数名称:SetBitMask
 * 功能说明:将指定寄存器中掩码对应的位设置为 1,其余位保持不变。
 * 参数说明:reg 为寄存器地址,mask 为需要置位的位掩码。
 * 返回值  :无。
 */
void SetBitMask(u8 reg, u8 mask)
{
	WriteRawRC(reg, (u8)(ReadRawRC(reg) | mask));
}

/*
 * 函数名称:ClearBitMask
 * 功能说明:将指定寄存器中掩码对应的位清零,其余位保持不变。
 * 参数说明:reg 为寄存器地址,mask 为需要清零的位掩码。
 * 返回值  :无。
 */
void ClearBitMask(u8 reg, u8 mask)
{
	WriteRawRC(reg, (u8)(ReadRawRC(reg) & (u8)(~mask)));
}

/*
 * 函数名称:PcdReset
 * 功能说明:先对 RC522 进行硬件复位,再执行软件复位和基础参数配置。
 * 参数说明:无。
 * 返回值  :无。
 */
void PcdReset(void)
{
	u16 timeout;

	/* 硬件复位可以让刚上电或中途重新连接的模块从确定状态开始工作。 */
	RC522_RST_LOW();
	delay_ms(2);
	RC522_RST_HIGH();
	delay_ms(50);

	WriteRawRC(CommandReg, PCD_RESETPHASE);
	timeout = 1000U;
	while(((ReadRawRC(CommandReg) & 0x10U) != 0U) && (timeout != 0U))
	{
		timeout--;
	}

	/*
	 * 设置 MFRC522 内部定时器,通信等待周期约为 15 ms。
	 * 同时配置 100% ASK 调制以及 ISO14443A 通信所需的 CRC 初始值。
	 */
	WriteRawRC(TModeReg, 0x8DU);
	WriteRawRC(TPrescalerReg, 0x3EU);
	WriteRawRC(TReloadRegL, 30U);
	WriteRawRC(TReloadRegH, 0U);
	WriteRawRC(TxASKReg, 0x40U);
	WriteRawRC(ModeReg, 0x3DU);
}

/*
 * 函数名称:PcdAntennaOn
 * 功能说明:打开 TX1、TX2 射频输出,启动 RC522 天线载波。
 * 参数说明:无。
 * 返回值  :无。
 */
void PcdAntennaOn(void)
{
	u8 value;

	value = ReadRawRC(TxControlReg);
	if((value & 0x03U) != 0x03U)
	{
		SetBitMask(TxControlReg, 0x03U);
	}
}

/*
 * 函数名称:PcdAntennaOff
 * 功能说明:关闭 TX1、TX2 射频输出,停止 RC522 天线载波。
 * 参数说明:无。
 * 返回值  :无。
 */
void PcdAntennaOff(void)
{
	ClearBitMask(TxControlReg, 0x03U);
}

/*
 * 函数名称:RC522_GetVersion
 * 功能说明:读取 VersionReg,检查软件 SPI 是否能够与 RC522 正常通信。
 * 参数说明:无。
 * 返回值  :RC522 芯片版本号。
 */
u8 RC522_GetVersion(void)
{
	return ReadRawRC(VersionReg);
}

/*
 * 函数名称:RC522_Init
 * 功能说明:初始化 GPIO、复位 RC522、打开天线并检查芯片版本号。
 * 参数说明:无。
 * 返回值  :MI_OK 表示初始化成功,MI_ERR 表示通信异常。
 */
u8 RC522_Init(void)
{
	u8 version;

	RC522_GPIO_Init();
	PcdReset();
	PcdAntennaOn();
	version = RC522_GetVersion();

	/*
	 * 常见原厂芯片返回 0x91 或 0x92,部分兼容芯片返回 0x88。
	 * 这里仅把总线悬空时常见的 0x00、0xFF 判定为错误,以便其他版本的
	 * 兼容教学模块也能正常使用。
	 */
	if((version == 0x00U) || (version == 0xFFU))
	{
		return MI_ERR;
	}

	return MI_OK;
}

/*
 * 函数名称:PcdComMF522
 * 功能说明:执行 MFRC522 的认证或收发命令,是寻卡、选卡、读写的公共接口。
 * 参数说明:
 *   command         MFRC522 内部命令,仅支持 PCD_AUTHENT 或 PCD_TRANSCEIVE。
 *   in_data         待发送数据缓冲区。
 *   in_length       待发送字节数。
 *   out_data        接收数据缓冲区。
 *   out_length_bits 实际接收到的数据位数。
 * 返回值  :MI_OK、MI_NOTAGERR 或 MI_ERR。
 */
static u8 PcdComMF522(u8 command, u8 *in_data, u8 in_length,
                     u8 *out_data, u16 *out_length_bits)
{
	u8 irq_enable;
	u8 wait_irq;
	u8 irq_value;
	u8 error_value;
	u8 fifo_length;
	u8 last_bits;
	u8 index;
	u16 timeout;
	u8 status;

	if((in_data == 0) || (out_data == 0) || (out_length_bits == 0))
	{
		return MI_ERR;
	}

	irq_enable = 0U;
	wait_irq = 0U;
	if(command == PCD_AUTHENT)
	{
		irq_enable = 0x12U;
		wait_irq = 0x10U;
	}
	else if(command == PCD_TRANSCEIVE)
	{
		irq_enable = 0x77U;
		wait_irq = 0x30U;
	}
	else
	{
		return MI_ERR;
	}

	WriteRawRC(ComIEnReg, (u8)(irq_enable | 0x80U));
	ClearBitMask(ComIrqReg, 0x80U);
	SetBitMask(FIFOLevelReg, 0x80U);
	WriteRawRC(CommandReg, PCD_IDLE);

	/* 清空 FIFO 后,将本次待发送的数据逐字节写入 FIFO。 */
	for(index = 0U; index < in_length; index++)
	{
		WriteRawRC(FIFODataReg, in_data[index]);
	}

	WriteRawRC(CommandReg, command);
	if(command == PCD_TRANSCEIVE)
	{
		/* StartSend 位置 1 后,MFRC522 开始发送 FIFO 中的数据。 */
		SetBitMask(BitFramingReg, 0x80U);
	}

	/* 轮询中断标志,同时用软件计数限制最长等待时间。 */
	timeout = RC522_COMM_TIMEOUT;
	do
	{
		irq_value = ReadRawRC(ComIrqReg);
		timeout--;
	}
	while((timeout != 0U) &&
	      ((irq_value & 0x01U) == 0U) &&
	      ((irq_value & wait_irq) == 0U));

	if(command == PCD_TRANSCEIVE)
	{
		ClearBitMask(BitFramingReg, 0x80U);
	}

	if(timeout == 0U)
	{
		return MI_ERR;
	}

	error_value = ReadRawRC(ErrorReg);
	if((error_value & 0x1BU) != 0U)
	{
		return MI_ERR;
	}

	status = MI_OK;
	if((irq_value & irq_enable & 0x01U) != 0U)
	{
		status = MI_NOTAGERR;
	}

	if((status == MI_OK) && (command == PCD_TRANSCEIVE))
	{
		fifo_length = ReadRawRC(FIFOLevelReg);
		last_bits = (u8)(ReadRawRC(ControlReg) & 0x07U);

		/* 根据最后一个字节的有效位数计算本次应答的总位数。 */
		if(last_bits != 0U)
		{
			*out_length_bits =
			    (u16)(((u16)(fifo_length - 1U) * 8U) + last_bits);
		}
		else
		{
			*out_length_bits = (u16)((u16)fifo_length * 8U);
		}

		if(fifo_length == 0U)
		{
			fifo_length = 1U;
		}
		if(fifo_length > RC522_MAX_LEN)
		{
			fifo_length = RC522_MAX_LEN;
		}

		for(index = 0U; index < fifo_length; index++)
		{
			out_data[index] = ReadRawRC(FIFODataReg);
		}
	}

	return status;
}

/*
 * 函数名称:PcdCalculateCRC
 * 功能说明:调用 MFRC522 内部 CRC 协处理器计算 ISO14443A CRC_A。
 * 参数说明:
 *   in_data  参与 CRC 计算的数据。
 *   length   输入数据字节数。
 *   out_data 保存 2 字节 CRC,低字节在前、高字节在后。
 * 返回值  :MI_OK 表示计算完成,MI_ERR 表示参数错误或计算超时。
 */
static u8 PcdCalculateCRC(u8 *in_data, u8 length, u8 *out_data)
{
	u8 index;
	u8 irq_value;
	u16 timeout;

	if((in_data == 0) || (out_data == 0))
	{
		return MI_ERR;
	}

	ClearBitMask(DivIrqReg, 0x04U);
	SetBitMask(FIFOLevelReg, 0x80U);

	for(index = 0U; index < length; index++)
	{
		WriteRawRC(FIFODataReg, in_data[index]);
	}

	WriteRawRC(CommandReg, PCD_CALCCRC);
	timeout = RC522_CRC_TIMEOUT;
	do
	{
		irq_value = ReadRawRC(DivIrqReg);
		timeout--;
	}
	while((timeout != 0U) && ((irq_value & 0x04U) == 0U));

	if(timeout == 0U)
	{
		return MI_ERR;
	}

	out_data[0] = ReadRawRC(CRCResultRegL);
	out_data[1] = ReadRawRC(CRCResultRegH);
	return MI_OK;
}

/*
 * 函数名称:PcdRequest
 * 功能说明:发送 REQA 或 WUPA 寻卡命令,检测天线范围内是否存在卡片。
 * 参数说明:
 *   req_code PICC_REQIDL 表示只寻找未休眠卡,PICC_REQALL 表示寻找全部卡。
 *   tag_type 用于保存卡片返回的 2 字节 ATQA 类型信息。
 * 返回值  :MI_OK 表示寻卡成功,MI_NOTAGERR 表示未发现卡片。
 */
u8 PcdRequest(u8 req_code, u8 *tag_type)
{
	u8 buffer[RC522_MAX_LEN];
	u16 received_bits;
	u8 status;

	if(tag_type == 0)
	{
		return MI_ERR;
	}

	/* ISO14443A 的 REQA/WUPA 命令使用 7 位短帧发送。 */
	ClearBitMask(Status2Reg, 0x08U);
	WriteRawRC(BitFramingReg, 0x07U);
	SetBitMask(TxControlReg, 0x03U);
	buffer[0] = req_code;

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 1U,
	                     buffer, &received_bits);
	if((status != MI_OK) || (received_bits != 16U))
	{
		return (status == MI_NOTAGERR) ? MI_NOTAGERR : MI_ERR;
	}

	tag_type[0] = buffer[0];
	tag_type[1] = buffer[1];
	return MI_OK;
}

/*
 * 函数名称:PcdAnticoll
 * 功能说明:执行第一级防冲突,取得卡片的 4 字节 UID。
 * 参数说明:uid 至少提供 5 字节空间,前 4 字节为 UID,第 5 字节为 BCC。
 * 返回值  :MI_OK 表示 UID 及异或校验正确,否则返回 MI_ERR。
 */
u8 PcdAnticoll(u8 *uid)
{
	u8 buffer[RC522_MAX_LEN];
	u8 checksum;
	u8 index;
	u16 received_bits;
	u8 status;

	if(uid == 0)
	{
		return MI_ERR;
	}

	WriteRawRC(BitFramingReg, 0x00U);
	ClearBitMask(CollReg, 0x80U);
	buffer[0] = PICC_ANTICOLL1;
	buffer[1] = 0x20U;

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 2U,
	                     buffer, &received_bits);
	if((status != MI_OK) || (received_bits != 40U))
	{
		return MI_ERR;
	}

	checksum = 0U;
	for(index = 0U; index < 4U; index++)
	{
		uid[index] = buffer[index];
		checksum ^= buffer[index];
	}
	uid[4] = buffer[4];

	if(checksum != uid[4])
	{
		return MI_ERR;
	}

	return MI_OK;
}

/*
 * 函数名称:PcdSelect
 * 功能说明:使用防冲突阶段取得的 UID 选择目标卡片。
 * 参数说明:uid 为 4 字节 UID 加 1 字节 BCC 校验值。
 * 返回值  :收到 24 位 SAK 应答时返回 MI_OK,否则返回 MI_ERR。
 */
u8 PcdSelect(u8 *uid)
{
	u8 buffer[9];
	u8 index;
	u16 received_bits;
	u8 status;

	if(uid == 0)
	{
		return MI_ERR;
	}

	buffer[0] = PICC_ANTICOLL1;
	buffer[1] = 0x70U;
	for(index = 0U; index < 5U; index++)
	{
		buffer[index + 2U] = uid[index];
	}

	if(PcdCalculateCRC(buffer, 7U, &buffer[7]) != MI_OK)
	{
		return MI_ERR;
	}

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 9U,
	                     buffer, &received_bits);
	if((status == MI_OK) && (received_bits == 24U))
	{
		return MI_OK;
	}

	return MI_ERR;
}

/*
 * 函数名称:PcdAuthState
 * 功能说明:对 MIFARE Classic 指定数据块执行 Crypto1 密钥认证。
 * 参数说明:
 *   auth_mode  PICC_AUTHENT1A 使用 Key A,PICC_AUTHENT1B 使用 Key B。
 *   block_addr 需要认证的数据块地址。
 *   key        6 字节扇区密钥。
 *   uid        当前卡片 UID,认证过程使用前 4 字节。
 * 返回值  :认证成功返回 MI_OK,否则返回 MI_ERR。
 */
u8 PcdAuthState(u8 auth_mode, u8 block_addr, u8 *key, u8 *uid)
{
	u8 buffer[12];
	u8 index;
	u16 received_bits;
	u8 status;

	if((key == 0) || (uid == 0))
	{
		return MI_ERR;
	}

	buffer[0] = auth_mode;
	buffer[1] = block_addr;
	for(index = 0U; index < 6U; index++)
	{
		buffer[index + 2U] = key[index];
	}
	for(index = 0U; index < 4U; index++)
	{
		buffer[index + 8U] = uid[index];
	}

	status = PcdComMF522(PCD_AUTHENT, buffer, 12U,
	                     buffer, &received_bits);
	if((status != MI_OK) || ((ReadRawRC(Status2Reg) & 0x08U) == 0U))
	{
		return MI_ERR;
	}

	return MI_OK;
}

/*
 * 函数名称:PcdRead
 * 功能说明:读取 MIFARE Classic 的一个数据块。
 * 参数说明:
 *   block_addr 数据块地址,调用前需要先完成选卡和密码认证。
 *   data       保存读取结果的缓冲区,长度不得小于 16 字节。
 * 返回值  :成功返回 MI_OK,通信或帧长度异常返回 MI_ERR。
 */
u8 PcdRead(u8 block_addr, u8 *data)
{
	u8 buffer[RC522_MAX_LEN];
	u8 index;
	u16 received_bits;
	u8 status;

	if(data == 0)
	{
		return MI_ERR;
	}

	buffer[0] = PICC_READ;
	buffer[1] = block_addr;
	if(PcdCalculateCRC(buffer, 2U, &buffer[2]) != MI_OK)
	{
		return MI_ERR;
	}

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 4U,
	                     buffer, &received_bits);
	if((status != MI_OK) || (received_bits != 144U))
	{
		return MI_ERR;
	}

	for(index = 0U; index < 16U; index++)
	{
		data[index] = buffer[index];
	}
	return MI_OK;
}

/*
 * 函数名称:PcdWrite
 * 功能说明:向 MIFARE Classic 的一个数据块写入 16 字节数据。
 * 参数说明:
 *   block_addr 数据块地址,调用前需要先完成选卡和密码认证。
 *   data       待写入的 16 字节数据。
 * 返回值  :两个写入阶段均收到 4 位 ACK 时返回 MI_OK。
 *
 * 注意:不要随意写入扇区尾块,否则可能修改密钥和访问控制位。
 */
u8 PcdWrite(u8 block_addr, u8 *data)
{
	u8 buffer[RC522_MAX_LEN];
	u8 index;
	u16 received_bits;
	u8 status;

	if(data == 0)
	{
		return MI_ERR;
	}

	buffer[0] = PICC_WRITE;
	buffer[1] = block_addr;
	if(PcdCalculateCRC(buffer, 2U, &buffer[2]) != MI_OK)
	{
		return MI_ERR;
	}

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 4U,
	                     buffer, &received_bits);
	if((status != MI_OK) || (received_bits != 4U) ||
	   ((buffer[0] & 0x0FU) != 0x0AU))
	{
		return MI_ERR;
	}

	for(index = 0U; index < 16U; index++)
	{
		buffer[index] = data[index];
	}
	if(PcdCalculateCRC(buffer, 16U, &buffer[16]) != MI_OK)
	{
		return MI_ERR;
	}

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 18U,
	                     buffer, &received_bits);
	if((status != MI_OK) || (received_bits != 4U) ||
	   ((buffer[0] & 0x0FU) != 0x0AU))
	{
		return MI_ERR;
	}

	return MI_OK;
}

/*
 * 函数名称:PcdStopCrypto1
 * 功能说明:清除 MFCrypto1On 位,结束当前卡片的 Crypto1 加密通信。
 * 参数说明:无。
 * 返回值  :无。
 */
void PcdStopCrypto1(void)
{
	ClearBitMask(Status2Reg, 0x08U);
}

/*
 * 函数名称:PcdHalt
 * 功能说明:向当前卡片发送 HALT 命令,使其进入休眠状态。
 * 参数说明:无。
 * 返回值  :卡片不返回数据即表示 HALT 成功,此时返回 MI_OK。
 */
u8 PcdHalt(void)
{
	u8 buffer[4];
	u16 received_bits;
	u8 status;

	buffer[0] = PICC_HALT;
	buffer[1] = 0x00U;
	if(PcdCalculateCRC(buffer, 2U, &buffer[2]) != MI_OK)
	{
		return MI_ERR;
	}

	status = PcdComMF522(PCD_TRANSCEIVE, buffer, 4U,
	                     buffer, &received_bits);

	/* ISO14443A 规定卡片通过“不应答”的方式确认 HALT 命令。 */
	if(status == MI_NOTAGERR)
	{
		return MI_OK;
	}
	return status;
}

RC522.h

#ifndef __RC522_H
#define __RC522_H

#include "sys.h"

/*
 * 文件名称:RC522.h
 * 功能说明:MFRC522 射频读卡模块驱动头文件
 * 适用芯片:STM32F103C8T6
 * 通信方式:GPIO 模拟 SPI(SPI 模式 0,数据高位先发送)
 *
 * 硬件连接:
 *   RC522 SDA/SS -> STM32 PB12(片选信号,低电平有效)
 *   RC522 SCK    -> STM32 PB13(SPI 时钟)
 *   RC522 MOSI   -> STM32 PB14(STM32 向 RC522 发送数据)
 *   RC522 MISO   -> STM32 PB15(STM32 接收 RC522 数据)
 *   RC522 RST    -> STM32 PA8 (硬件复位,高电平正常工作)
 *
 * 注意:
 *   本工程严格按照上述接线使用 PB14 作为 MOSI、PB15 作为 MISO。
 *   该方向与 STM32F103 的硬件 SPI2 复用功能相反,因此不能直接使用
 *   SPI2 外设,驱动中采用 GPIO 模拟 SPI 完成通信。
 */

#define RC522_MAX_LEN             18U

/* 驱动函数返回值,与参考例程中的定义保持兼容。 */
#define MI_OK                     0x00U
#define MI_NOTAGERR               0xFFU
#define MI_ERR                    0xFEU

/* MFRC522 内部命令字。 */
#define PCD_IDLE                  0x00U  /* 取消当前命令,进入空闲状态 */
#define PCD_AUTHENT               0x0EU  /* 执行 MIFARE 密钥认证 */
#define PCD_RECEIVE               0x08U  /* 启动接收电路 */
#define PCD_TRANSMIT              0x04U  /* 启动发送电路 */
#define PCD_TRANSCEIVE            0x0CU  /* 发送数据并自动接收应答 */
#define PCD_RESETPHASE            0x0FU  /* 软件复位 MFRC522 */
#define PCD_CALCCRC               0x03U  /* 使用内部协处理器计算 CRC */

/* ISO14443A / MIFARE 卡片命令字。 */
#define PICC_REQIDL               0x26U  /* 寻找未进入休眠状态的卡 */
#define PICC_REQALL               0x52U  /* 寻找天线范围内的全部卡 */
#define PICC_ANTICOLL1            0x93U  /* UID 第一级防冲突/选卡命令 */
#define PICC_AUTHENT1A            0x60U  /* 使用 Key A 验证扇区密码 */
#define PICC_AUTHENT1B            0x61U  /* 使用 Key B 验证扇区密码 */
#define PICC_READ                 0x30U  /* 读取 MIFARE 数据块 */
#define PICC_WRITE                0xA0U  /* 写入 MIFARE 数据块 */
#define PICC_HALT                 0x50U  /* 让卡片进入休眠状态 */

/* MFRC522 寄存器地址:第 0 页,命令与通信状态寄存器。 */
#define CommandReg                0x01U
#define ComIEnReg                 0x02U
#define DivIEnReg                 0x03U
#define ComIrqReg                 0x04U
#define DivIrqReg                 0x05U
#define ErrorReg                  0x06U
#define Status1Reg                0x07U
#define Status2Reg                0x08U
#define FIFODataReg               0x09U
#define FIFOLevelReg              0x0AU
#define WaterLevelReg             0x0BU
#define ControlReg                0x0CU
#define BitFramingReg             0x0DU
#define CollReg                   0x0EU

/* MFRC522 寄存器地址:第 1 页,发送、接收和调制配置寄存器。 */
#define ModeReg                   0x11U
#define TxModeReg                 0x12U
#define RxModeReg                 0x13U
#define TxControlReg              0x14U
#define TxASKReg                  0x15U
#define TxSelReg                  0x16U
#define RxSelReg                  0x17U
#define RxThresholdReg            0x18U
#define DemodReg                  0x19U
#define MifareReg                 0x1CU
#define SerialSpeedReg            0x1FU

/* MFRC522 寄存器地址:第 2 页,CRC、射频增益和定时器寄存器。 */
#define CRCResultRegH             0x21U
#define CRCResultRegL             0x22U
#define ModWidthReg               0x24U
#define RFCfgReg                  0x26U
#define GsNReg                    0x27U
#define CWGsCfgReg                0x28U
#define ModGsCfgReg               0x29U
#define TModeReg                  0x2AU
#define TPrescalerReg             0x2BU
#define TReloadRegH               0x2CU
#define TReloadRegL               0x2DU

/* MFRC522 芯片版本寄存器。 */
#define VersionReg                0x37U

/*
 * 初始化与诊断接口:
 * RC522_Init       初始化 GPIO、复位芯片、配置射频参数并开启天线。
 * RC522_GetVersion 读取芯片版本号,常见值为 0x91、0x92 或 0x88。
 */
u8 RC522_Init(void);
u8 RC522_GetVersion(void);
void PcdReset(void);
void PcdAntennaOn(void);
void PcdAntennaOff(void);

/*
 * 卡片操作接口:
 * PcdRequest     寻卡并读取 2 字节 ATQA 卡片类型。
 * PcdAnticoll    执行防冲突,读取 4 字节 UID 和 1 字节校验值。
 * PcdSelect      根据 UID 选择一张卡片。
 * PcdAuthState   使用 Key A 或 Key B 对指定数据块进行认证。
 * PcdRead        从指定块读取 16 字节数据。
 * PcdWrite       向指定块写入 16 字节数据。
 * PcdHalt        命令当前卡片进入休眠状态。
 * PcdStopCrypto1 停止芯片内部 Crypto1 加密单元。
 */
u8 PcdRequest(u8 req_code, u8 *tag_type);
u8 PcdAnticoll(u8 *uid);
u8 PcdSelect(u8 *uid);
u8 PcdAuthState(u8 auth_mode, u8 block_addr, u8 *key, u8 *uid);
u8 PcdRead(u8 block_addr, u8 *data);
u8 PcdWrite(u8 block_addr, u8 *data);
u8 PcdHalt(void);
void PcdStopCrypto1(void);

/*
 * 底层寄存器接口:
 * 对教学实验开放,可用于观察 MFRC522 寄存器的读、写和位操作过程。
 */
void WriteRawRC(u8 address, u8 value);
u8 ReadRawRC(u8 address);
void SetBitMask(u8 reg, u8 mask);
void ClearBitMask(u8 reg, u8 mask);

#endif

十二、总结

本项目使用 STM32 模拟 SPI 驱动 RC522,完整复现 ISO14443A 射频卡通信全流程,从硬件接线、芯片底层原理、射频无源卡取电通信、协议分层、MIFARE 存储结构到故障排查全覆盖,适合单片机 RFID 入门教学。 RC522 作为射频前端,隔离复杂射频信号处理,主控仅通过简单寄存器指令即可完成读卡,搭配 OLED 直观展示 UID 与卡片类型,代码模块化、易拓展,可二次开发门禁、刷卡记录、身份识别等小型项目。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐