看了很多文章都没有详细讲过RC522和IC卡,这里总结一下,做个笔记
一、RC522
RFID-RC522 是一种常见的射频识别(RFID)模块,用于近距离无线通信。以下是一些基本信息:
主要特点
- 通信协议: 使用 SPI 通信,与单片机、树莓派等设备轻松集成。
- 频率: 工作在 13.56 MHz 的高频(HF)范围。
- 支持协议: 符合 ISO/IEC 14443 标准,支持 MIFARE 卡(如 MIFARE Classic 1K/4K 等)。
- 供电电压: 通常为 3.3V,但部分模块支持 5V 电平输入。
- 读写范围: 一般在 2-5 厘米,具体取决于天线设计和环境。
常用功能
- 读取卡片 UID: 可以获取 RFID 卡的唯一标识符。
- 读写数据块: 支持对 MIFARE 卡片的数据块进行读写操作(需要密钥认证)。
- 防碰撞机制: 可识别多个卡片并与目标卡通信。
- 功耗低: 支持节能模式。
硬件接口
RC522 模块有以下引脚:
- VCC: 供电(3.3V 或 5V,取决于模块设计)。
- GND: 地。
- SCK: SPI 时钟。
- MOSI: 主机输出从机输入。
- MISO: 主机输入从机输出。
- NSS/SDA: SPI 片选/模块选择。
- RST: 模块复位。
常见应用
- 门禁系统。
- 电子支付。
- 智能考勤。
- 物流追踪。
二、IC卡
关于详细的IC卡的介绍可以看下面这个链接,讲的非常详细
【【教程】手把手教你玩转IC卡(门禁卡、电梯卡、饭卡、水卡、校园卡、一卡通)】https://www.bilibili.com/video/BV11h4y1T7Pm?vd_source=065123a0ee559bc650cb8a6e66c8de94
我们生活中常见的一般都是cuid卡,它有16个扇区,依次为0扇区-15扇区,每个扇区又分为4个区块,一个区块有16个字节,所以一个扇区有64个字节,卡总共有1024个字节,就是1KB。
卡的0扇区的第一个区块包含了卡号,校验值,卡类型,卡类型代号,生产厂商信息。第一个区块的前四个字节是卡号,所以我们的卡号是00 00 00 00 - FF FF FF FF的范围内,如果卡号是唯一的,那么就有16^8=4294967296个卡号,42亿多个,一时半会儿也用不完,如果用完了也可以像ipv4->ipv6那样扩展,将六个字节作为卡号,那不就取之不尽用之不竭了吗。我们的身份证的卡号就是6个字节的卡号。当 4 字节 UID 不足以满足需求时,可以使用 7 字节或 10 字节的 UID。这类似于互联网的 IPv4 向 IPv6 的扩展,虽然理论上有限,但可以通过增加长度来大幅度扩展编号空间。
做一个简易门禁系统可以通过验证uid(卡号)来验证身份,但是问题就是卡号很容易被复制,所以我们就可以用上其他扇区。每个扇区的第四个区块是控制块,在这个区块中,前六个字节是密钥A,后六个字节是密钥B,中间四个字节一般是厂商写好固定的FF 07 80 69。我们想要增加门禁系统的安全性,就可以将01扇区(第2扇区)的第1区块的第一个字节设置为FF,如果你这个字节是FF那你就是我们小区的,如果不是FF就不是我们小区的。在我们读取这个区块的内容的时候,需要验证密钥A或者秘钥B才能访问这个扇区里面的数据,所以我们还可以修改密钥实现简单的加密效果,这里配上后面的代码更好理解。
三、通信前的准备
我们使用stm32个四个GPIO模拟SPI与RC522进行通信
1、SPI协议层
/*引脚配置*/
#define RC522_CS(x) GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)(x))
#define RC522_SCK(x) GPIO_WriteBit(GPIOB, GPIO_Pin_13, (BitAction)(x))
#define RC522_DO(x) GPIO_WriteBit(GPIOB, GPIO_Pin_15, (BitAction)(x))
#define RC522_DI() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14)
//-------------------------------------------------------------------------------------------------------------------
// 函数简介 通过 RC522 向设备发送一个字节的数据
// 参数说明 uint8_t data - 要发送的数据字节
// 返回参数 void
// 使用示例 RC522_SendByte(0x3F); // 发送一个字节 0x3F 到 RC522
// 备注信息 该函数通过控制 SCK 引脚来逐位发送数据,每发送一个位,SCK 会先拉高再拉低。
// 发送完成后,SCK 会返回低电平,等待下一位数据发送。
//-------------------------------------------------------------------------------------------------------------------
void RC522_SendByte(uint8_t data)
{
for (uint8_t i = 0; i < 8; i++)
{
// 发送高位
RC522_DO(data & 0x80);
data <<= 1;
// 拉高 SCK
RC522_SCK(1);
RC522_Delay(1);
// 拉低 SCK
RC522_SCK(0);
RC522_Delay(1);
}
}
//-------------------------------------------------------------------------------------------------------------------
// 函数简介 从 RC522 读取一个字节的数据
// 参数说明 void
// 返回参数 uint8_t - 读取到的字节数据
// 使用示例 uint8_t byte = RC522_ReadByte(); // 从 RC522 读取一个字节的数据
// 备注信息 该函数通过 SCK 引脚控制 SPI 时钟,读取 MISO 引脚的数据,逐位构建接收到的数据字节。
//-------------------------------------------------------------------------------------------------------------------
uint8_t RC522_ReadByte(void)
{
uint8_t data = 0;
for (uint8_t i = 0; i < 8; i++)
{
// 拉高 SCK
RC522_SCK(1);
RC522_Delay(1);
// 读取 MISO 引脚
data <<= 1;
if (RC522_DI())
{
data |= 0x01;
}
// 拉低 SCK
RC522_SCK(0);
RC522_Delay(1);
}
return data;
}
//-------------------------------------------------------------------------------------------------------------------
// 函数简介 向 RC522 寄存器写入一个字节的数据
// 参数说明 uint8_t addr - 寄存器地址
// uint8_t value - 要写入的数据
// 返回参数 void
// 使用示例 RC522_WriteReg(0x01, 0x0F); // 向 RC522 的地址为 0x01 的寄存器写入值 0x0F
// 备注信息 该函数通过 SPI 总线向指定寄存器写入数据。发送时会先发送寄存器地址,地址最低位为 0 表示写操作,然后发送要写入的值。
//-------------------------------------------------------------------------------------------------------------------
void RC522_WriteReg(uint8_t addr, uint8_t value)
{
RC522_CS(0); // 片选拉