上一节主要是ESP8266连接ONENET,传送门如下
这一节主要是实现stm32对RFID-RC522的驱动实现身份识别
RC522-RFID驱动
这个小节的驱动主要是来做RC522,我们使用的是硬件spi接口,RC522这个芯片用的还是比较多的,RC522可以使用I2C或者SPI接口,某宝上能买到的大多是SPI接口,这里我们从接口配置开始到读取卡片来走一遍整个过程。
1、接口配置
我这里使用的是SPI1,速度设置到1.125左右,然后生成代码就可以
2、封装接口
uint8_t SPI1SendByte(uint8_t data) {
unsigned char writeCommand[1];
unsigned char readValue[1];
writeCommand[0] = data;
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&writeCommand, (uint8_t*)&readValue, 1, 10);
return readValue[0];
}
void SPI1_WriteReg(uint8_t address, uint8_t value) {
cs_reset();
SPI1SendByte(address);
SPI1SendByte(value);
cs_set();
}
uint8_t SPI1_ReadReg(uint8_t address) {
uint8_t val;
cs_reset();
SPI1SendByte(address);
val = SPI1SendByte(0x00);
cs_set();
return val;
}
3、驱动代码
下面这个三个代码比较关键,主要是:
1、执行 RFID 卡片的防冲突操作,读取并验证卡片的序列号(UID)。
2、读取 MIFARE 卡片的指定数据块
3、向 MIFARE 卡片写入指定数据块
/**
* @brief 执行 RFID 卡片的防冲突操作,读取并验证卡片的序列号(UID)。
*
* 该函数通过 MFRC522 读卡器与 RFID 卡片进行通信,执行防冲突(Anti-collision)操作,获取卡片的唯一标识符(UID)。此操作用于在多个卡片靠近读卡器时,确保可以正确地识别并读取每张卡片的 UID。
*
* 防冲突操作的目的是避免多个卡片同时响应读取器请求时造成的冲突,确保每次操作只处理一个卡片。
*
* @param serNum 指向 `uint8_t` 数组的指针,函数执行成功后,该数组将包含卡片的 4 字节序列号(UID)。
* 该数组长度至少为 5 字节(包含一个校验字节)。
*
* @return 返回操作状态:
* - `MI_OK`:成功获取并验证卡片的序列号(UID)。
* - `MI_ERR`:操作失败,可能是由于通信错误或 UID 校验失败。
*
* @note
* - 在执行防冲突操作时,`serNum[0]` 和 `serNum[1]` 是用来发送给卡片的指令和参数,
* 卡片将返回其 UID 数据。
* - 函数会根据返回的 UID 校验结果进行验证,确保数据的完整性和正确性。
*
* @see MFRC522_WriteRegister, MFRC522_ToCard
*/
uint8_t MFRC522_Anticoll(uint8_t* serNum) {
uint8_t status;
uint8_t i;
uint8_t serNumCheck = 0;
uint16_t unLen;
MFRC522_WriteRegister(MFRC522_REG_BIT_FRAMING, 0x00); // TxLastBists = BitFramingReg[2..0]
serNum[0] = PICC_ANTICOLL;
serNum[1] = 0x20;
status = MFRC522_ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);
if (status == MI_OK) {
// Check card serial number
for (i = 0; i < 4; i++) serNumCheck ^= serNum[i];
if (serNumCheck != serNum[i]) status = MI_ERR;
}
return status;
}
/**
* @brief 读取 MIFARE 卡片的指定数据块
*
* 该函数向 MIFARE 卡发送读取命令并从卡片读取指定的块的数据。读取的块数据通过 `recvData` 参数返回。
* 该函数会计算并附加命令数据的 CRC 校验,发送给卡片,并等待卡片响应。读取操作成功时,返回卡片的响应数据和相关状态信息。
*
* @param[in] blockAddr 要读取的数据块地址 (0x00 ~ 0x0F),指定读取的块。
* @param[out] recvData 指向接收缓冲区的指针,用于存放读取到的数据和 CRC 校验码。接收缓冲区至少应为 18 字节。
*
* @retval MI_OK 读取成功,数据块已正确读取,返回的数据长度为 0x90。
* @retval MI_ERR 读取失败,可能的原因包括:
* - 卡片未响应或响应格式不正确。
* - 读取的数据块地址无效或数据读取错误。
*
* @note
* - 读取的块大小为 16 字节,返回的数据包括卡片响应和读取的数据。
* - 在调用此函数之前,请确保已经正确初始化 MFRC522 模块和相关硬件。
* - 返回的 `recvData` 缓冲区中前 2 字节为读取命令和块地址,后续字节为卡片返回的数据。
*/
uint8_t MFRC522_Read(uint8_t blockAddr, uint8_t* recvData) {
uint8_t status;
uint16_t unLen;
recvData[0] = PICC_READ;
recvData[1] = blockAddr;
MFRC522_CalculateCRC(recvData,2, &recvData[2]);
status = MFRC522_ToCard(PCD_TRANSCEIVE, recvData, 4, recvData, &unLen);
if ((status != MI_OK) || (unLen != 0x90)) status = MI_ERR;
return status;
}
/**
* @brief 向 MIFARE 卡片写入指定数据块
*
* 该函数向 MIFARE 卡片发送写入命令,并将提供的 16 字节数据写入指定的块。函数先发送写入命令并检查卡片响应,若响应正确,则将数据写入 FIFO,并计算 CRC 校验码,最后再次发送数据到卡片。
* 如果操作成功,数据会被写入卡片的指定块中。
*
* @param[in] blockAddr 要写入的 MIFARE 数据块地址 (0x00 ~ 0x0F),指定写入的目标块。
* @param[in] writeData 指向要写入数据的指针,数据长度应为 16 字节。
*
* @retval MI_OK 写入成功,数据已正确写入指定的块。
* @retval MI_ERR 写入失败,可能的原因包括:
* - 卡片未响应或响应格式不正确。
* - 写入操作发生错误或数据块地址无效。
*
* @note
* - 该函数会先向卡片发送写入命令,并验证卡片是否响应正确。
* - 如果卡片响应正确,会将 16 字节数据发送到卡片进行写入。
* - 数据块的大小为 16 字节,传入的 `writeData` 缓冲区必须包含 16 字节有效数据。
* - 在调用此函数之前,请确保已经正确初始化 MFRC522 模块和相关硬件。
*/
uint8_t MFRC522_Write(uint8_t blockAddr, uint8_t* writeData) {
uint8_t status;
uint16_t recvBits;
uint8_t i;
uint8_t buff[18];
buff[0] = PICC_WRITE;
buff[1] = blockAddr;
MFRC522_CalculateCRC(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;
if (status == MI_OK) {
// Data to the FIFO write 16Byte
for (i = 0; i < 16; i++) buff[i] = *(writeData+i);
MFRC522_CalculateCRC(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;
}
4、初始化函数
这个函数是初始化函数,函数注释已经详细表明函数过程
/**
* @brief 初始化 MFRC522 模块
*
* 该函数完成 MFRC522 模块的初始化工作,包括复位模块、配置寄存器、设置通信参数以及开启天线等。初始化完成后,模块将准备好与 RFID 卡片进行通信。
*
* - 首先通过 `MFRC522_Reset` 进行模块复位,确保模块处于已知的初始状态。
* - 然后设置 T_MODE、T_PRESCALER 和 T_RELOAD 等寄存器,配置定时器及其重载值,以确保时钟稳定。
* - 配置射频部分的增益值和阈值,确保 RF 部分的信号强度和接收灵敏度符合预期。
* - 启用天线,并设置通信模式,使模块处于待命状态,等待与卡片的通信。
*
* @note 在调用此函数前,请确保与 MFRC522 模块的 SPI 或 I2C 接口已正确初始化。
*
* @retval 无
*/
void MFRC522_Init(void) {
MFRC522_Reset();
MFRC522_WriteRegister(MFRC522_REG_T_MODE, 0x8D);
MFRC522_WriteRegister(MFRC522_REG_T_PRESCALER, 0x3E);
MFRC522_WriteRegister(MFRC522_REG_T_RELOAD_L, 30);
MFRC522_WriteRegister(MFRC522_REG_T_RELOAD_H, 0);
MFRC522_WriteRegister(MFRC522_REG_RF_CFG, 0x7F); // 48dB gain
MFRC522_WriteRegister(MFRC522_REG_RX_THRESHOLD, 0x84);
MFRC522_WriteRegister(MFRC522_REG_TX_AUTO, 0x40);
MFRC522_WriteRegister(MFRC522_REG_MODE, 0x3D);
MFRC522_AntennaOn(); // Open the antenna
}
完成上述操作之后通过调用基本可以实现卡号识别
5、用户使用代码
这两个是卡号,我们可以先指定两个卡号,当时别到卡号之后会与我们保存的卡号进行对比,这样就可以实现对不同身份的认证了
{0x44, 0x33, 0x43, 0x38, 0x42, 0x35, 0x30, 0x42}, // 用户 1
{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, // 用户 2
// 用户 UID 表,最多存储 10 个用户,每个用户的 UID 长度为 8 字节
static uint8_t userUIDTable[MAX_USERS][UID_LENGTH] = {
{0x44, 0x33, 0x43, 0x38, 0x42, 0x35, 0x30, 0x42}, // 用户 1
{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, // 用户 2
// 其他用户的 UID 可以按需添加
};
// 比较两个 UID 是否相同
bool compareUID(uint8_t* uid1, uint8_t* uid2)
{
return (memcmp(uid1, uid2, UID_LENGTH) == 0);
}
// 查找给定的 UID 是否匹配表中的某个用户 UID
int findUserByUID(uint8_t* readUID)
{
for (int i = 0; i < MAX_USERS; i++)
{
if (compareUID(readUID, userUIDTable[i])) {
return i; // 返回找到的用户的索引
}
}
return -1; // 没有找到匹配的 UID
}
//只在rfid里面使用
static void char_to_hex(uint8_t data, uint8_t* retstr) {
static const uint8_t digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
retstr[0] = digits[(data >> 4) & 0x0F]; // 高4位
retstr[1] = digits[data & 0x0F]; // 低4位
}
/**
* @brief 获取RC522读卡器的UID,并将其存储到调用者提供的缓冲区中。
*
* @param[out] txBuffer 用于存储转换后的UID的缓冲区。
* @param[in,out] bufferSize 输入时为缓冲区大小,输出时为实际写入的数据字节数。
*
* @return 返回操作状态:
* - `MI_OK`:成功获取并转换UID。
* - `MI_ERR`:操作失败,可能是由于没有找到卡片或其它错误。
*/
uint8_t getCardUID(uint8_t* txBuffer, uint8_t* bufferSize)
{
uint8_t b = 0;
uint8_t str[16]; // 用来存储卡片UID
uint8_t retstr[2]; // 只需要2个字符
// 寻卡
if (!MFRC522_Request(PICC_REQIDL, str)) // 请求卡片
{
// 获得卡序列号
if (!MFRC522_Anticoll(str)) // 防冲突,获取卡的UID
{
// 将序列号转换为十六进制并存入 txBuffer
for (uint8_t i = 0; i < 4; i++) {
char_to_hex(str[i], retstr);
txBuffer[b++] = retstr[0]; // 将转换后的十六进制字符存入缓冲区
txBuffer[b++] = retstr[1]; // 将转换后的十六进制字符存入缓冲区
}
*bufferSize = b; // 更新实际写入的字节数
return MI_OK; // 成功
}
}
*bufferSize = 0; // 如果失败,设置字节数为0
return MI_ERR; // 失败
}