在ARM Linux应用层下驱动MFRC522
文章目录
- 1、前言
- 2、IC卡 和 IC卡读卡器
- 3、MFRC522
- 3.1、寄存器集
- 3.2、命令集
- 3.3、数据操作
- 3.4、基础函数编写
- 3.4.1、MFRC522接线
- 3.4.2、编写SPI操作函数
- 3.4.3、编写MFRC522基础函数
- 3.4.3.1、完整的mfrc522.h
- 3.4.3.2、写寄存器和读寄存器
- 3.4.3.3、复位引脚操作
- 3.4.3.4、天线操作
- 3.4.3.5、初始化操作
- 3.4.3.6、复位操作
- 3.4.3.7、工作模式设置
- 3.4.3.8、和M1卡通信操作
- 3.4.3.9、寻卡操作
- 3.4.3.10、防冲撞操作
- 3.4.3.11、完整的mfrc522.c
- 3.5、测试
- 3.5.1、编译
- 3.5.2、测试
- 4、IC-S50
- 4.1、存储结构
- 4.2、M1卡与读卡器的通信流程
- 4.3、对数据块的操作
- 5、完善剩余的操作函数
- 5.1、mfrc522.h
- 5.2、mfrc522.c
- 6、测试
- 7、总结
1、前言
本文将介绍如何在ARM Linux应用层下,使用SPI驱动MFRC522读写IC-S50。本人也是第一次接触MFRC522,所以本文将记录概念扫盲到最终的驱动实现。
2、IC卡 和 IC卡读卡器
IC卡一般分为接触式IC卡和非接触式IC卡。所以实际上我们将讨论的是非接触式IC卡和非接触式IC卡读卡器。IC卡里是有一个芯片的,IC卡读卡器也是有一个芯片的,很多半导体公司都有生产用于IC卡和IC卡读卡器的芯片。所以再细分下来,我们将讨论的是NXP公司生产的MIFARE Classic系列的S50型号的非接触式卡IC芯片(后文将称为IC-S50)和NXP公司生产的MFRC522非接触式读卡器芯片。它们都工作于13.56MHz。
例如下图是一个使用了MFRC522芯片的读卡器模块,本次实验也是使用这个模块:
下图是使用了IC-S50的非接触式IC卡,右边白色IC卡的物理外观遵循国际标准ISO/IEC 7816,和我们通常使用的银行卡、校园卡的外观是一样的(也可以称M1卡)。其它外观的还有像左边蓝色这种的,常见于门禁等多种场合。
非接触式IC卡通过无线射频(RF)与读卡器通信。它们通常遵循ISO/IEC 14443标准,工作频率为13.56MHz。非接触式IC卡不需要外部电源。它们通过感应天线从读卡器的射频信号中获取能量,这种能量足以驱动卡片内的芯片进行数据处理和通信。如下图为MFRC522和IC卡通信示意框图:
下面我们将进一步了解IC-S50和MFRC522的基本特性。
IC-S50基本特性:
- 8KBit大小的EEPROM存储
- 分为16个扇区,每个扇区为4块,每块16字节,以块为存取单位
- 每个扇区有独立的一组密码及访问控制
- 每张卡有唯一序列号,为 32 位
- 工作频率:13.56MHZ
- 通信速率:106 KBPS
- 读写距离:10 cm 以内(与读写器有关)
MFRC522基本特性:
- 支持的主机接口:SPI、UART、I2C
- 支持ISO 14443A / MIFARE
3、MFRC522
这里实际需要了解两个芯片的操作,分别是MFRC522和IC-S50。MFRC522主要了解的内容有两部分,分别是MFRC522寄存器集 和 MFRC522命令集。
3.1、寄存器集
MFRC522有4组寄存器,分别如下所示。
Page 0(命令和状态寄存器):
Page 1(命令寄存器):
Page 2(配置寄存器):
Page 3(测试寄存器):
3.2、命令集
MFRC522的操作由可执行一系列命令的内部状态机来决定。通过向寄存器集中的命令寄存器写入相应的命令来启动命令。下图是MFRC522的命令集:
3.3、数据操作
这里使用的MFRC522模块是SPI接口。
对于读数据(MSB先行):
对于写数据(MSB先行):
其中对于地址字节有如下要求:地址字节的最高位bit7为1时表示从MFRC522读取数据,为0时表示向MFRC522写入数据。bit6-bit1表示地址。bit0为0。
3.4、基础函数编写
关于MFRC522的使用教程博客有很多。这里推荐直接参考野火基于stm32的MFRC522驱动。我们先以读取到IC卡的ID为阶段性目标,以此测试spi和mfrc522相关的基础函数是正常的。
现在还没介绍IC-S50,又如何知道怎么读ID?没关系,先复制粘贴。因为关于mfrc522的操作函数太多了,先移植一部分。
3.4.1、MFRC522接线
MFRC522 | Linux板卡 |
---|---|
SDA(片选脚) | 任意gpio口 |
MOSI | SPI_MISO |
MISO | SPI_MOSI |
SCK(时钟脚) | SPI_CLK |
RST(复位脚) | 任意gpio口 |
3.3V | 3.3V |
GND | GND |
3.4.2、编写SPI操作函数
下面将直接提供在Linux应用层的spi操作函数,支持多线程下使用。也可以用在其它项目。
spi.h:
/* spi.h */#ifndef _SPI_H
#define _SPI_H#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <gpiod.h>
#include <stdint.h>
#include <linux/spi/spidev.h>
#include <pthread.h>typedef struct spi_handle
{char *dev_name;int fd; pthread_mutex_t mutex;struct gpiod_chip *cs_gpiochip; struct gpiod_line *cs_gpioline;
}spi_handle_t;typedef enum
{SPIMODE0 = SPI_MODE_0,SPIMODE1 = SPI_MODE_1,SPIMODE2 = SPI_MODE_2,SPIMODE3 = SPI_MODE_3,
}SPI_MODE;typedef enum
{S_1M = 1000000,S_6_75M = 6750000,S_8M = 8000000,S_13_5M = 13500000,S_27M = 27000000,
}SPI_SPEED;spi_handle_t *spi_handle_alloc(const char *spi_dev, SPI_MODE spi_mode, SPI_SPEED spi_speed, const char *cs_chip, unsigned int cs_num);
void spi_write_and_read(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf);
void spi_write_then_read(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf, unsigned int recv_buf_len);
void spi_write_byte_data(spi_handle_t *spi, unsigned char data);
void spi_write_nbyte_data(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len);
void spi_handle_free(spi_handle_t *spi);#endif
spi.c:
/* spi.c */#include "spi.h"/******************************** @brief : SPI同时发送数据和接收数据* @param : spi - SPI设备句柄* send_buf - 发送缓冲区* send_buf_len - 发送缓冲区长度* recv_buf - 接收缓冲区* @return: 无*******************************/
void spi_write_and_read(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf)
{struct spi_ioc_transfer xfer[1];int status;if (spi == NULL)return;if (send_buf == NULL || recv_buf == NULL)return;memset(xfer, 0, sizeof(xfer));xfer[0].tx_buf = (unsigned long)send_buf;xfer[0].rx_buf = (unsigned long)recv_buf;xfer[0].len = send_buf_len;pthread_mutex_lock(&(spi->mutex));gpiod_line_set_value(spi->cs_gpioline, 0);status = ioctl(spi->fd, SPI_IOC_MESSAGE(1), xfer);if (status < 0) printf("SPI_IOC_MESSAGE failed!\n");gpiod_line_set_value(spi->cs_gpioline, 1);pthread_mutex_unlock(&(spi->mutex));
}/******************************** @brief : SPI先发送数据后接收数据* @param : spi - SPI设备句柄* send_buf - 发送缓冲区* send_buf_len - 发送缓冲区长度* recv_buf - 接收缓冲区* recv_buf_len - 接收缓冲区长度* @return: 无*******************************/
void spi_write_then_read(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf, unsigned int recv_buf_len)
{struct spi_ioc_transfer xfer[2];int status;if (spi == NULL)return;if (send_buf == NULL || recv_buf == NULL)return;memset(xfer, 0, sizeof(xfer));xfer[0].tx_buf = (unsigned long)send_buf;xfer[0].len = send_buf_len;xfer[1].rx_buf = (unsigned long)recv_buf;xfer[1].len = recv_buf_len;pthread_mutex_lock(&(spi->mutex));gpiod_line_set_value(spi->cs_gpioline, 0);status = ioctl(spi->fd, SPI_IOC_MESSAGE(2), xfer);if (status < 0) printf("SPI_IOC_MESSAGE failed!\n");gpiod_line_set_value(spi->cs_gpioline, 1);pthread_mutex_unlock(&(spi->mutex));
}/******************************** @brief : SPI发送一个字节* @param : spi - SPI设备句柄* data - 待发送的字节数据* @return: 无*******************************/
void spi_write_byte_data(spi_handle_t *spi, unsigned char data)
{unsigned char buff[1] = {data};if (spi == NULL)return;pthread_mutex_lock(&(spi->mutex));gpiod_line_set_value(spi->cs_gpioline, 0);write(spi->fd, &buff, 1);gpiod_line_set_value(spi->cs_gpioline, 1);pthread_mutex_unlock(&(spi->mutex));
}/******************************** @brief : 通过SPI发送多个字节数据* @param : spi - SPI设备句柄* send_buf - 发送缓冲区* send_buf_len - 发送缓冲区长度* @return: 无*******************************/
void spi_write_nbyte_data(spi_handle_t *spi, unsigned char *send_buf, unsigned int send_buf_len)
{struct spi_ioc_transfer xfer[2];unsigned char recv_buf[send_buf_len];int status;if (spi == NULL)return;if (send_buf == NULL)return;memset(xfer, 0, sizeof(xfer));memset(recv_buf, 0, sizeof(send_buf_len));xfer[0].tx_buf = (unsigned long)send_buf;xfer[0].rx_buf = (unsigned long)recv_buf;xfer[0].len = send_buf_len;pthread_mutex_lock(&(spi->mutex));gpiod_line_set_value(spi->cs_gpioline, 0);status = ioctl(spi->fd, SPI_IOC_MESSAGE(1), xfer);if (status < 0) printf("SPI_IOC_MESSAGE failed!\n");gpiod_line_set_value(spi->cs_gpioline, 1);pthread_mutex_unlock(&(spi->mutex));
}/******************************** @brief : 分配并初始化SPI设备句柄* @param : spi_dev - SPI设备文件路径* spi_mode - SPI模式* spi_speed - SPI通信速度* cs_chip - 片选GPIO芯片 * cs_line - 片选GPIO引脚* @return: 成功返回SPI设备句柄,失败返回NULL*******************************/
spi_handle_t *spi_handle_alloc(const char *spi_dev, SPI_MODE spi_mode, SPI_SPEED spi_speed, const char *cs_chip, unsigned int cs_line)
{int ret; int fd;char spi_bits = 8;SPI_SPEED speed = (uint32_t)spi_speed;if (!spi_dev)return NULL;if (!cs_chip)return NULL;fd = open(spi_dev, O_RDWR);if (fd < 0) {printf("open %s failed!\n", spi_dev);return NULL;}/* alloc spi_handle_t */spi_handle_t *spi = (spi_handle_t *)malloc(sizeof(spi_handle_t));if (!spi){printf("spi_handle_t allocation failed!\n");return NULL;}spi->fd = fd;/* spi mode setting */ret = ioctl(spi->fd, SPI_IOC_WR_MODE, &spi_mode); if (ret < 0) {printf("SPI_IOC_WR_MODE failed!\n");free(spi);return NULL;}/* bits per word */ret = ioctl(spi->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits); if (ret < 0) {printf("SPI_IOC_WR_BITS_PER_WORD failed!\n");free(spi);return NULL;}/* spi speed setting */ret = ioctl(spi->fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); if (ret < 0) {printf("SPI_IOC_WR_MAX_SPEED_HZ failed!\n");free(spi);return NULL;}spi->dev_name = (char *)malloc(strlen(spi_dev) + 1);if (!(spi->dev_name)) {printf("dev_name allocation failed!\n");free(spi);return NULL; }strcpy(spi->dev_name, spi_dev);ret = pthread_mutex_init(&spi->mutex, NULL);if (ret != 0) {printf("pthread_mutex_init failed!\n");free(spi->dev_name);free(spi);return NULL;}/* cs pin init */spi->cs_gpiochip = gpiod_chip_open(cs_chip);if (spi->cs_gpiochip == NULL){printf("gpiod_chip_open failed!\n");free(spi->dev_name);free(spi);return NULL;}spi->cs_gpioline = gpiod_chip_get_line(spi->cs_gpiochip, cs_line);if (spi->cs_gpioline == NULL){printf("gpiod_chip_get_line failed!\n");free(spi->dev_name);free(spi);return NULL;}ret = gpiod_line_request_output(spi->cs_gpioline, "cs_gpioline", 1);if (ret < 0){printf("gpiod_line_request_output failed!\n");free(spi->dev_name);free(spi);return NULL;}return spi;
}/******************************** @brief : 释放SPI设备句柄* @param : spi - SPI设备句柄* @return: 无*******************************/
void spi_handle_free(spi_handle_t *spi)
{if (!spi)return;gpiod_line_release(spi->cs_gpioline);gpiod_chip_close(spi->cs_gpiochip);if (spi->dev_name){free(spi->dev_name);}pthread_mutex_destroy(&spi->mutex);free(spi);
}
3.4.3、编写MFRC522基础函数
3.4.3.1、完整的mfrc522.h
本次mfrc522驱动程序涉及两个文件,分别是mfrc522.h和mfrc522.c。如下是mfrc522.h,主要是寄存器、命令集的宏定义。注意了,这里mfrc522寄存器的宏定义命名使用驼峰命名是为了和数据手册保持一致,方便查阅:
/* mfrc522.h */#ifndef MFRC522_H
#define MFRC522_H#include <gpiod.h>
#include "spi.h"#define MI_OK 0
#define MI_NOTAGERR 1
#define MI_ERR 2#define MFRC522_MAX_LEN 18/* MFRC522 REG */
// Page 0 - Command and Status
#define RFU00 0x00
#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivlEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define RFU0F 0x0F
// Page 1 - Command
#define RFU10 0x10
#define ModeReg 0x11
#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 MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// Page 2 - CFG
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsCfgReg 0x28
#define ModGsCfgReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadRegH 0x2C
#define TReloadRegL 0x2D
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F
// Page 3 - TestRegister
#define RFU30 0x30
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B
#define RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F/* MFRC522 CMD */
#define PCD_IDLE 0x00 // 取消当前命令
#define PCD_AUTHENT 0x0E // 验证密钥
#define PCD_RECEIVE 0x08 // 接收数据
#define PCD_TRANSMIT 0x04 // 发送数据
#define PCD_TRANSCEIVE 0x0C // 发送并接收数据
#define PCD_RESETPHASE 0x0F // 复位
#define PCD_CALCCRC 0x03 // CRC计算/* IC-S50 CMD */
#define PICC_REQIDL 0x26 // 寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 // 寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 // 防冲撞
#define PICC_ANTICOLL2 0x95 // 防冲撞
#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 // 休眠int mfrc522_init(const char *spi_dev, const char *cs_chip, unsigned char cs_line,const char *rst_chip, unsigned char rst_line);
void mfrc522_reset(void);
void mfrc522_antenna_on(void);
void mfrc522_antenna_off(void);
void mfrc522_config_iso_type(unsigned char type);
unsigned char mfrc522_to_card(unsigned char command, unsigned char *send_buf, unsigned char send_buf_len, unsigned char *recv_buf, unsigned int *recv_buf_len);
unsigned char mfrc522_anticoll(unsigned char* ser_num);
char mfrc522_pcd_request(unsigned char req_code, unsigned char *tag_type);
void mfrc522_exit(void);#endif
3.4.3.2、写寄存器和读寄存器
关于mfrc522基础操作函数,先实现写寄存器和读寄存器,如下:
/* mfrc522.c */static void mfrc522_write_reg(unsigned char reg, unsigned char value)
{unsigned char send_buf[2];// 根据上面对地址字节的要求:// bit7为1代表读数据,为0代表写数据// bit6-bit1表示寄存器地址(寄存器只用6个bit来表示)// bit0默认为0send_buf[0] = (reg << 1) & 0x7E;send_buf[1] = value;spi_write_nbyte_data(mfrc522_spi, send_buf, sizeof(send_buf));
}static unsigned char mfrc522_read_reg(unsigned char reg)
{unsigned char send_buf[2];unsigned char recv_buf[2] = {0};// 根据上面对地址字节的要求:// bit7为1代表读数据,为0代表写数据// bit6-bit1表示寄存器地址(寄存器只用6个bit来表示)// bit0默认为0send_buf[0] = ((reg << 1) & 0x7E ) | 0x80;send_buf[1] = 0x00;spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);return recv_buf[1];
}
对上面两个函数再封装一下,得到如下:
/* mfrc522.c */// 将指定寄存器中的指定位置1,其它位不变
static void mfrc522_set_bit_mask(unsigned char reg, unsigned char mask)
{unsigned char temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp | mask);
}// 将指定寄存器中的指定位清0,其它位不变
static void mfrc522_clr_bit_mask(unsigned char reg, unsigned char mask)
{unsigned char temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp & (~mask));
}
3.4.3.3、复位引脚操作
下面编写mfrc522复位脚的操作函数。关于复位引脚,数据手册对其的解释如下:
/* mfrc522.c */static void mfrc522_rst_enabled(void)
{gpiod_line_set_value(rst_gpioline, 0);
}static void mfrc522_rst_disabled(void)
{gpiod_line_set_value(rst_gpioline, 1);
}
3.4.3.4、天线操作
下面是mfrc522打开天线与关闭天线的操作函数:
/* mfrc522.c */// mfrc522打开天线
void mfrc522_antenna_on(void)
{unsigned char temp;temp = mfrc522_read_reg(TxControlReg);if (!(temp & 0x03))mfrc522_set_bit_mask(TxControlReg, 0x03);
}// mfrc522关闭天线
void mfrc522_antenna_off(void)
{mfrc522_clr_bit_mask(TxControlReg, 0x03);
}
3.4.3.5、初始化操作
下面是MFRC522的初始化和反初始化函数。分别先初始化spi、复位引脚:
/* mfrc522.c */int mfrc522_init(const char *spi_dev, const char *cs_chip, unsigned char cs_line,const char *rst_chip, unsigned char rst_line)
{int ret;if (!spi_dev)return -1;if (!cs_chip || !rst_chip)return -1;/* spi初始化 */mfrc522_spi = spi_handle_alloc(spi_dev, SPIMODE0, S_1M, cs_chip, cs_line);if (!mfrc522_spi)return -1;/* 复位脚初始化 */rst_gpiochip = gpiod_chip_open(rst_chip);if (rst_gpiochip == NULL){printf("gpiod_chip_open failed!\n");return -1;}rst_gpioline = gpiod_chip_get_line(rst_gpiochip, rst_line);if (rst_gpioline == NULL){printf("gpiod_chip_get_line failed!\n");return -1;}ret = gpiod_line_request_output(rst_gpioline, "rst_gpioline", 1);if (ret < 0){printf("gpiod_line_request_output failed!\n");return -1;}mfrc522_rst_disabled();return 0;
}// mfrc522反初始化
void mfrc522_exit(void)
{if (mfrc522_spi)spi_handle_free(mfrc522_spi);if (rst_gpioline)gpiod_line_release(rst_gpioline);if (rst_gpiochip)gpiod_chip_close(rst_gpiochip);
}
3.4.3.6、复位操作
下面是mfrc522的复位函数:
/* mfrc522.c */void mfrc522_reset(void)
{mfrc522_rst_disabled();usleep(1);mfrc522_rst_enabled(); // 切断内部电流吸收,关闭振荡器,断开输入管脚与外部电路的连接usleep(1);mfrc522_rst_disabled(); // 上升沿启动内部复位阶段usleep(1);mfrc522_write_reg(CommandReg, PCD_RESETPHASE); // 软复位while (mfrc522_read_reg(CommandReg) & 0x10) // 等待mfrc522唤醒结束; usleep(1);mfrc522_write_reg(ModeReg, 0x3D); // 定义发送和接收常用模式:和Mifare卡通讯,CRC初始值0x6363mfrc522_write_reg(TReloadRegL, 30); // 16位定时器低位 mfrc522_write_reg(TReloadRegH, 0); // 16位定时器高位mfrc522_write_reg(TModeReg, 0x8D); // 定义内部定时器的设置mfrc522_write_reg(TPrescalerReg, 0x3E); // 设置定时器分频系数mfrc522_write_reg(TxAutoReg, 0x40); // 调制发送信号为100%ASK
}
3.4.3.7、工作模式设置
下面是mfrc522设置工作模式的操作函数:
/* mfrc522.c */// 设置工作模式
void mfrc522_config_iso_type(unsigned char type)
{if (type == 'A') // ISO14443_A{mfrc522_clr_bit_mask(Status2Reg, 0x08);mfrc522_write_reg(ModeReg, 0x3D); mfrc522_write_reg(RxSelReg, 0x86); mfrc522_write_reg(RFCfgReg, 0x7F); mfrc522_write_reg(TReloadRegL, 30); mfrc522_write_reg(TReloadRegH, 0);mfrc522_write_reg(TModeReg, 0x8D);mfrc522_write_reg(TPrescalerReg, 0x3E);usleep(2);mfrc522_antenna_on(); //开天线}
}
3.4.3.8、和M1卡通信操作
下面是mfrc522和iso14443卡的通信函数:
/* mfrc522.c */unsigned char mfrc522_to_card(unsigned char command, unsigned char *send_buf, unsigned char send_buf_len, unsigned char *recv_buf, unsigned int *recv_buf_len)
{unsigned char status = MI_ERR;unsigned char irq_en = 0x00;unsigned char wait_irq = 0x00;unsigned char last_bits;unsigned char n;unsigned int i = 0;switch (command) {case PCD_AUTHENT: // Mifare认证{irq_en = 0x12; // 允许错误中断请求ErrIEn 允许空闲中断IdleIEnwait_irq = 0x10; // 认证寻卡等待时候 查询空闲中断标志位break;}case PCD_TRANSCEIVE: // 接收发送 发送接收{irq_en = 0x77; // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEnwait_irq = 0x30; // 寻卡等待时候 查询接收中断标志位与 空闲中断标志位break;}default:break;}mfrc522_write_reg(ComIEnReg, irq_en | 0x80); // IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反 mfrc522_clr_bit_mask(ComIrqReg, 0x80); // Set1该位清零时,CommIRqReg的屏蔽位清零mfrc522_write_reg(CommandReg, PCD_IDLE); // 写空闲命令mfrc522_set_bit_mask(FIFOLevelReg, 0x80); // 置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除for (i = 0; i < send_buf_len; i++)mfrc522_write_reg(FIFODataReg, send_buf[i]); // 写数据进FIFOdatamfrc522_write_reg(CommandReg, command); // 写命令if (command == PCD_TRANSCEIVE)mfrc522_set_bit_mask(BitFramingReg, 0x80); // StartSend置位启动数据发送 该位与收发命令使用时才有效do // 认证与寻卡等待时间{n = mfrc522_read_reg(ComIrqReg); // 查询事件中断usleep(1000);i++;} while ((i != 25) && !(n & 0x01) && !(n & wait_irq)); mfrc522_clr_bit_mask(BitFramingReg, 0x80); // 清理允许StartSend位 if (i != 25) {if (!(mfrc522_read_reg(ErrorReg) & 0x1B)) // 读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr{status = MI_OK; if (n & irq_en & 0x01) // 是否发生定时器中断status = MI_NOTAGERR;if (command == PCD_TRANSCEIVE) {n = mfrc522_read_reg(FIFOLevelReg); // 读FIFO中保存的字节数last_bits = mfrc522_read_reg(ControlReg) & 0x07;// 最后接收到得字节的有效位数if (last_bits) *recv_buf_len = (n-1) * 8 + last_bits; // N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数else *recv_buf_len = n*8; // 最后接收到的字节整个字节有效if (n == 0) n = 1;if (n > MFRC522_MAX_LEN) n = MFRC522_MAX_LEN;for (i = 0; i < n; i++) recv_buf[i] = mfrc522_read_reg(FIFODataReg); // 从FIFOdata读数据}} else status = MI_ERR;}mfrc522_set_bit_mask(ControlReg, 0x80); mfrc522_write_reg(CommandReg, PCD_IDLE); return status;
}
3.4.3.9、寻卡操作
下面是mfrc522的寻卡函数:
/* mfrc522.c */char mfrc522_pcd_request(unsigned char req_code, unsigned char *tag_type)
{char status; unsigned char com_buf[MFRC522_MAX_LEN]; unsigned int len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况mfrc522_write_reg(BitFramingReg, 0x07); // 发送的最后一个字节的 七位mfrc522_set_bit_mask(TxControlReg, 0x03); // TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号com_buf[0] = req_code; // 存入卡片命令字status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len); // 寻卡 if ((status == MI_OK) && (len == 0x10)) // 寻卡成功返回卡类型 { *tag_type = com_buf[0];*(tag_type + 1) = com_buf[1];}elsestatus = MI_ERR;return status;
}
3.4.3.10、防冲撞操作
下面是mfrc522的防冲撞函数:
/* mfrc522.c */// 防冲撞
unsigned char mfrc522_anticoll(unsigned char *snr)
{char status;uint8_t i, snr_check = 0;uint8_t com_mfrc522_buf[MFRC522_MAX_LEN]; uint32_t len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位mfrc522_write_reg(BitFramingReg, 0x00); // 清理寄存器 停止收发mfrc522_clr_bit_mask(CollReg, 0x80); // 清ValuesAfterColl所有接收的位在冲突后被清除 com_mfrc522_buf[0] = 0x93; // 卡片防冲突命令com_mfrc522_buf[1] = 0x20;status = mfrc522_to_card(PCD_TRANSCEIVE, com_mfrc522_buf, 2, com_mfrc522_buf, &len); // 与卡片通信if (status == MI_OK) // 通信成功{for (i = 0; i < 4; i++){*(snr + i) = com_mfrc522_buf[i]; // 读出UIDsnr_check ^= com_mfrc522_buf[i];}if (snr_check != com_mfrc522_buf[i])status = MI_ERR; }mfrc522_set_bit_mask(CollReg, 0x80);return status;
}
3.4.3.11、完整的mfrc522.c
好了,到目前为止,终于完成了mfrc522读IC卡 ID所需要的基础操作函数,目前完整的mfrc522.c文件如下:
/* mfrc522.c */#include "mfrc522.h"static spi_handle_t *mfrc522_spi;
static struct gpiod_chip *rst_gpiochip;
static struct gpiod_line *rst_gpioline;static void mfrc522_write_reg(unsigned char reg, unsigned char value)
{unsigned char send_buf[2];send_buf[0] = (reg << 1) & 0x7E;send_buf[1] = value;spi_write_nbyte_data(mfrc522_spi, send_buf, sizeof(send_buf));
}static unsigned char mfrc522_read_reg(unsigned char reg)
{unsigned char send_buf[2];unsigned char recv_buf[2] = {0};send_buf[0] = ((reg << 1) & 0x7E ) | 0x80;send_buf[1] = 0x00;spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);return recv_buf[1];
}// 将寄存器中指定的位置1
static void mfrc522_set_bit_mask(unsigned char reg, unsigned char mask)
{unsigned char temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp | mask);
}// 将寄存器中指定的位清0
static void mfrc522_clr_bit_mask(unsigned char reg, unsigned char mask)
{unsigned char temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp & (~mask));
}//
static void mfrc522_rst_enabled(void)
{gpiod_line_set_value(rst_gpioline, 0);
}static void mfrc522_rst_disabled(void)
{gpiod_line_set_value(rst_gpioline, 1);
}// mfrc522复位
void mfrc522_reset(void)
{mfrc522_rst_disabled();usleep(1);mfrc522_rst_enabled(); // 切断内部电流吸收,关闭振荡器,断开输入管脚与外部电路的连接usleep(1);mfrc522_rst_disabled(); // 上升沿启动内部复位阶段usleep(1);mfrc522_write_reg(CommandReg, PCD_RESETPHASE); // 软复位while (mfrc522_read_reg(CommandReg) & 0x10) // 等待mfrc522唤醒结束; usleep(1);mfrc522_write_reg(ModeReg, 0x3D); // 定义发送和接收常用模式:和Mifare卡通讯,CRC初始值0x6363mfrc522_write_reg(TReloadRegL, 30); // 16位定时器低位 mfrc522_write_reg(TReloadRegH, 0); // 16位定时器高位mfrc522_write_reg(TModeReg, 0x8D); // 定义内部定时器的设置mfrc522_write_reg(TPrescalerReg, 0x3E); // 设置定时器分频系数mfrc522_write_reg(TxAutoReg, 0x40); // 调制发送信号为100%ASK
}// mfrc522打开天线
void mfrc522_antenna_on(void)
{unsigned char temp;temp = mfrc522_read_reg(TxControlReg);if (!(temp & 0x03))mfrc522_set_bit_mask(TxControlReg, 0x03);
}// mfrc522关闭天线
void mfrc522_antenna_off(void)
{mfrc522_clr_bit_mask(TxControlReg, 0x03);
}// 设置工作模式
void mfrc522_config_iso_type(unsigned char type)
{if (type == 'A') // ISO14443_A{mfrc522_clr_bit_mask(Status2Reg, 0x08);mfrc522_write_reg(ModeReg, 0x3D); mfrc522_write_reg(RxSelReg, 0x86); mfrc522_write_reg(RFCfgReg, 0x7F); mfrc522_write_reg(TReloadRegL, 30); mfrc522_write_reg(TReloadRegH, 0);mfrc522_write_reg(TModeReg, 0x8D);mfrc522_write_reg(TPrescalerReg, 0x3E);usleep(2);mfrc522_antenna_on(); //开天线}
}// 通过RC522和ISO14443卡通讯
unsigned char mfrc522_to_card(unsigned char command, unsigned char *send_buf, unsigned char send_buf_len, unsigned char *recv_buf, unsigned int *recv_buf_len)
{unsigned char status = MI_ERR;unsigned char irq_en = 0x00;unsigned char wait_irq = 0x00;unsigned char last_bits;unsigned char n;unsigned int i = 0;switch (command) {case PCD_AUTHENT: // Mifare认证{irq_en = 0x12; // 允许错误中断请求ErrIEn 允许空闲中断IdleIEnwait_irq = 0x10; // 认证寻卡等待时候 查询空闲中断标志位break;}case PCD_TRANSCEIVE: // 接收发送 发送接收{irq_en = 0x77; // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEnwait_irq = 0x30; // 寻卡等待时候 查询接收中断标志位与 空闲中断标志位break;}default:break;}mfrc522_write_reg(ComIEnReg, irq_en | 0x80); // IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反 mfrc522_clr_bit_mask(ComIrqReg, 0x80); // Set1该位清零时,CommIRqReg的屏蔽位清零mfrc522_write_reg(CommandReg, PCD_IDLE); // 写空闲命令mfrc522_set_bit_mask(FIFOLevelReg, 0x80); // 置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除for (i = 0; i < send_buf_len; i++)mfrc522_write_reg(FIFODataReg, send_buf[i]); // 写数据进FIFOdatamfrc522_write_reg(CommandReg, command); // 写命令if (command == PCD_TRANSCEIVE)mfrc522_set_bit_mask(BitFramingReg, 0x80); // StartSend置位启动数据发送 该位与收发命令使用时才有效do // 认证与寻卡等待时间{n = mfrc522_read_reg(ComIrqReg); // 查询事件中断usleep(1000);i++;} while ((i != 50) && !(n & 0x01) && !(n & wait_irq)); mfrc522_clr_bit_mask(BitFramingReg, 0x80); // 清理允许StartSend位 if (i != 50) {if (!(mfrc522_read_reg(ErrorReg) & 0x1B)) // 读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr{status = MI_OK; if (n & irq_en & 0x01) // 是否发生定时器中断status = MI_NOTAGERR;if (command == PCD_TRANSCEIVE) {n = mfrc522_read_reg(FIFOLevelReg); // 读FIFO中保存的字节数last_bits = mfrc522_read_reg(ControlReg) & 0x07;// 最后接收到得字节的有效位数if (last_bits) *recv_buf_len = (n - 1) * 8 + last_bits; // N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数else *recv_buf_len = n * 8; // 最后接收到的字节整个字节有效if (n == 0) n = 1;if (n > MFRC522_MAX_LEN) n = MFRC522_MAX_LEN;for (i = 0; i < n; i++) recv_buf[i] = mfrc522_read_reg(FIFODataReg); // 从FIFOdata读数据}} else status = MI_ERR;}mfrc522_set_bit_mask(ControlReg, 0x80); mfrc522_write_reg(CommandReg, PCD_IDLE); return status;
}// 防冲撞
unsigned char mfrc522_anticoll(unsigned char *snr)
{char status;uint8_t i, snr_check = 0;uint8_t com_mfrc522_buf[MFRC522_MAX_LEN]; uint32_t len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位mfrc522_write_reg(BitFramingReg, 0x00); // 清理寄存器 停止收发mfrc522_clr_bit_mask(CollReg, 0x80); // 清ValuesAfterColl所有接收的位在冲突后被清除 com_mfrc522_buf[0] = 0x93; // 卡片防冲突命令com_mfrc522_buf[1] = 0x20;status = mfrc522_to_card(PCD_TRANSCEIVE, com_mfrc522_buf, 2, com_mfrc522_buf, &len); // 与卡片通信if (status == MI_OK) // 通信成功{for (i = 0; i < 4; i++){*(snr + i) = com_mfrc522_buf[i]; // 读出UIDsnr_check ^= com_mfrc522_buf[i];}if (snr_check != com_mfrc522_buf[i])status = MI_ERR; }mfrc522_set_bit_mask(CollReg, 0x80);return status;
} // 寻卡
char mfrc522_pcd_request(unsigned char req_code, unsigned char *tag_type)
{char status; unsigned char com_buf[MFRC522_MAX_LEN]; unsigned int len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况mfrc522_write_reg(BitFramingReg, 0x07); // 发送的最后一个字节的 七位mfrc522_set_bit_mask(TxControlReg, 0x03); // TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号com_buf[0] = req_code; // 存入卡片命令字status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len); // 寻卡 if ((status == MI_OK) && (len == 0x10)) // 寻卡成功返回卡类型 { *tag_type = com_buf[0];*(tag_type + 1) = com_buf[1];}elsestatus = MI_ERR;return status;
}// mfrc522初始化
int mfrc522_init(const char *spi_dev, const char *cs_chip, unsigned char cs_line,const char *rst_chip, unsigned char rst_line)
{int ret;if (!spi_dev)return -1;if (!cs_chip || !rst_chip)return -1;/* spi初始化 */mfrc522_spi = spi_handle_alloc(spi_dev, SPIMODE0, S_1M, cs_chip, cs_line);if (!mfrc522_spi)return -1;/* 复位脚初始化 */rst_gpiochip = gpiod_chip_open(rst_chip);if (rst_gpiochip == NULL){printf("gpiod_chip_open failed!\n");return -1;}rst_gpioline = gpiod_chip_get_line(rst_gpiochip, rst_line);if (rst_gpioline == NULL){printf("gpiod_chip_get_line failed!\n");return -1;}ret = gpiod_line_request_output(rst_gpioline, "rst_gpioline", 1);if (ret < 0){printf("gpiod_line_request_output failed!\n");return -1;}mfrc522_rst_disabled();return 0;
}// mfrc522反初始化
void mfrc522_exit(void)
{if (mfrc522_spi)spi_handle_free(mfrc522_spi);if (rst_gpioline)gpiod_line_release(rst_gpioline);if (rst_gpiochip)gpiod_chip_close(rst_gpiochip);
}
3.5、测试
下面编写一个main.c用于读取ic卡的id:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pthread.h>
#include <gpiod.h>
#include <math.h>#include "mfrc522.h"int main()
{unsigned char id[4];unsigned char status;int ret;ret = mfrc522_init("/dev/spidev3.0", "/dev/gpiochip6", 11, // cs片选脚"/dev/gpiochip6", 10); // rst复位脚if (ret != 0){printf("mfrc522_init fialed!\n");return -1;}mfrc522_reset(); // mfrc522复位mfrc522_config_iso_type('A'); // 设置模式while(1){status = mfrc522_pcd_request(PICC_REQALL, id);if (status != MI_OK)printf("request card fialed!\n");elsebreak;sleep(0.1); }if (status == MI_OK){printf("request card successfully!\n");if (mfrc522_anticoll(id) == MI_OK)printf("card uid = %02X%02X%02X%02X\n", id[0], id[1], id[2], id[3]);}mfrc522_exit();return 0;
}
3.5.1、编译
如果使用buildroot系统,需要交叉编译。我这使用ubuntu系统,直接gcc编译。执行如下命令进行编译:
gcc -o build main.c spi.c mfrc522.c -lgpiod
3.5.2、测试
执行如下命令测试:
sudo ./build
和手机“NFC工具”APP读取的一致:
4、IC-S50
现在开始介绍S50芯片。
4.1、存储结构
上面介绍过,S50芯片内部有一个eeprom是用来存储数据的。容量为8Kbit,即1024Byte。分为16个扇区,即每个扇区占64Byte。每个扇区又由4个块(块0、块1、块2、块3)组成,即每个块占16Byte。如下图所示:
- 第0扇区的块0,它用于存放厂商代码,已经固化,不可更改。
- 每个扇区的块0、块1、块2为数据块。可以用来存放数据。数据块有两种应用:
- 用作一般的数据保存,可以进行读、写操作。
- 用作数据值,可以进行初始化、加值、减值、读值操作。
- 每个扇区的块3为尾块,存储了该扇区的访问控制信息和密钥,具体结构如下:
尾块的16字节分配如下:
- 字节0-5:存储Key A(密钥A),用于本扇区的访问控制。
- 字节6-9:存储访问控制位(Access Bits),用于定义该扇区中各块的访问权限。
- 字节10-15:存储Key B(密钥B),用于本扇区的访问控制。如果Key B未使用,这些字节可以作为普通数据存储。
这里先小结一下,总的来说就是,M1卡有16个扇区,每个扇区有4个块。我们可以通过mfrc522读卡器来访问M1卡的任意一个扇区中的任意一个块。但每个块的访问是有限制条件的,比如这个块不能读也不能写,这个块可以读但不能写等等。所以我们得清晰的知道每个块有怎样的限制。
假设现在想访问扇区1的尾块(即块3)。上面介绍过,每个扇区的尾块是用来存储KeyA、KeyB和访问控制位的,那现在想访问尾块,说明我们是想要读取或修改密码与访问控制位。所以,得看看访问尾块时,有着什么样的权限:
条件真值表由C1X3、C2X3、C3X3构成(X为某个扇区的编号。X后面的数字为块号,这里是3,代表块3),它们的值可以在尾块的Byte6-Byte9找到:
如C1X3在byte7的bit7,C2X3在byte8的bit3,C3X3在byte8的bit7。假设C1X3、C2X3、C3X3的值分别为100时,会有如下意思:
- Key A:不可读;验证Key B后可写。
- 控制信息:验证Key A或Key B后可读;不可写。
- Key B:不可读;验证Key B后可写。
上面介绍的是尾块的操作权限。现在再看块0~块2的操作权限:
假设现在要访问扇区2的块1。那么就要知道C121、C221、C321的值:
如C121在byte7的bit5,C221在byte8的bit1,C321在byte8的bit5。假设C121、C221、C321的值分别为100时,会有如下意思:
- 验证Key A或Key B后可读。
- 验证Key B后可写。
- 不可进行加值、减值操作。
4.2、M1卡与读卡器的通信流程
下图展示了M1卡与读卡器的通信流程:
- 复位应答:M1射频卡的通讯协议和通讯波特率是定义好的,当有卡片进入读写器的操作范围时读写器以特定的协议与它通讯,从而确定该卡是否为M1射频卡,即验证卡片的卡型。
- 防冲突机制:当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作,未选中的则处于空闲模式等待下一次选卡,该过程会返回被选卡的序列号。
- 选择卡片:选择被选中的卡的序列号,并同时返回卡的容量代码。
- 三次相互验证:选定要处理的卡片之后,读写器就确定要访问的扇区号,并对该扇区密码进行密码校验,在三次相互认证之后就可以通过加密流进行通讯。(在选择另一扇区时,则必须进行另一扇区密码校验。)
其中复位应答和防冲突机制这两部分在上面已经移植完了。后续会继续移植剩下两部分。
4.3、对数据块的操作
我们将继续完成如下操作函数:
- 读(Read):读一个块;
- 写(Write):写一个块;
- 加(Increment):对数值块进行加值;
- 减(Decrement):对数值块进行减值;
- 存储(Restore):将块中的内容存到数据寄存器中;
- 传输(Transfer):将数据寄存器中的内容写入块中;
- 中止(Halt):将卡置于暂停工作状态;
5、完善剩余的操作函数
下面将直接给出完整的mfrc522.h和mfrc522.c。
5.1、mfrc522.h
#ifndef MFRC522_H
#define MFRC522_H#include <gpiod.h>
#include "spi.h"#define MI_OK 0
#define MI_NOTAGERR 1
#define MI_ERR 2#define MFRC522_MAX_LEN 18/* MFRC522 REG */
// Page 0 - Command and Status
#define RFU00 0x00
#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivlEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define RFU0F 0x0F
// Page 1 - Command
#define RFU10 0x10
#define ModeReg 0x11
#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 MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// Page 2 - CFG
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsCfgReg 0x28
#define ModGsCfgReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadRegH 0x2C
#define TReloadRegL 0x2D
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F
// Page 3 - TestRegister
#define RFU30 0x30
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B
#define RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F/* MFRC522 CMD */
#define PCD_IDLE 0x00 // 取消当前命令
#define PCD_AUTHENT 0x0E // 验证密钥
#define PCD_RECEIVE 0x08 // 接收数据
#define PCD_TRANSMIT 0x04 // 发送数据
#define PCD_TRANSCEIVE 0x0C // 发送并接收数据
#define PCD_RESETPHASE 0x0F // 复位
#define PCD_CALCCRC 0x03 // CRC计算/* IC-S50 CMD */
#define PICC_REQIDL 0x26 // 寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 // 寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 // 防冲撞
#define PICC_ANTICOLL2 0x95 // 防冲撞
#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 // 休眠void mfrc522_reset(void);
void mfrc522_config_iso_type(uint8_t type);
uint8_t mfrc522_request(uint8_t req_code, uint8_t *tag_type);
uint8_t mfrc522_anticoll(uint8_t *snr);
uint8_t mfrc522_select(uint8_t *snr);
uint8_t mfrc522_auth_state(uint8_t auth_mode, uint8_t addr, uint8_t *key, uint8_t *snr);
uint8_t mfrc522_read(uint8_t addr, uint8_t *data);
uint8_t mfrc522_write(uint8_t addr, uint8_t *data);
uint8_t mfrc522_write_string(uint8_t addr, uint8_t *data);
uint8_t mfrc522_read_string(uint8_t addr, uint8_t *data);
uint8_t mfrc522_halt(void);
uint8_t mfrc522_change_keya(uint8_t addr, uint8_t *keya);
uint8_t mfrc522_write_data_block(uint8_t addr, uint8_t *data, uint8_t len);
uint8_t mfrc522_read_data_block(uint8_t addr, uint8_t *data);
uint8_t mfrc522_write_amount(uint8_t addr, uint32_t data);
uint8_t mfrc522_read_amount(uint8_t addr, uint32_t *data);
int mfrc522_init(const char *spi_dev, const char *cs_chip, uint8_t cs_line, const char *rst_chip, uint8_t rst_line);
void mfrc522_exit(void);#endif
5.2、mfrc522.c
/* mfrc522.c */#include "mfrc522.h"static spi_handle_t *mfrc522_spi;
static struct gpiod_chip *rst_gpiochip;
static struct gpiod_line *rst_gpioline;/*** @brief 写入MFRC522寄存器* @param reg 寄存器地址* @param value 写入的值*/
static void mfrc522_write_reg(uint8_t reg, uint8_t value)
{uint8_t send_buf[2];send_buf[0] = (reg << 1) & 0x7E;send_buf[1] = value;spi_write_nbyte_data(mfrc522_spi, send_buf, sizeof(send_buf));
}/*** @brief 读取MFRC522寄存器* @param reg 寄存器地址* @return 读取的值*/
static uint8_t mfrc522_read_reg(uint8_t reg)
{uint8_t send_buf[2];uint8_t recv_buf[2] = {0};send_buf[0] = ((reg << 1) & 0x7E) | 0x80;send_buf[1] = 0x00;spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);return recv_buf[1];
}/*** @brief 将寄存器中指定的位置1* @param reg 寄存器地址* @param mask 要设置的位掩码*/
static void mfrc522_set_bit_mask(uint8_t reg, uint8_t mask)
{uint8_t temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp | mask);
}/*** @brief 将寄存器中指定的位清0* @param reg 寄存器地址* @param mask 要清除的位掩码*/
static void mfrc522_clr_bit_mask(uint8_t reg, uint8_t mask)
{uint8_t temp;temp = mfrc522_read_reg(reg);mfrc522_write_reg(reg, temp & (~mask));
}/*** @brief 使能复位引脚*/
static void mfrc522_rst_enabled(void)
{gpiod_line_set_value(rst_gpioline, 0);
}/*** @brief 禁用复位引脚*/
static void mfrc522_rst_disabled(void)
{gpiod_line_set_value(rst_gpioline, 1);
}/*** @brief 复位MFRC522*/
void mfrc522_reset(void)
{mfrc522_rst_disabled();usleep(1);mfrc522_rst_enabled(); // 切断内部电流吸收,关闭振荡器,断开输入管脚与外部电路的连接usleep(1);mfrc522_rst_disabled(); // 上升沿启动内部复位阶段usleep(1);mfrc522_write_reg(CommandReg, PCD_RESETPHASE); // 软复位while (mfrc522_read_reg(CommandReg) & 0x10) // 等待MFRC522唤醒结束;usleep(1);mfrc522_write_reg(ModeReg, 0x3D); // 定义发送和接收常用模式:和Mifare卡通讯,CRC初始值0x6363mfrc522_write_reg(TReloadRegL, 30); // 16位定时器低位mfrc522_write_reg(TReloadRegH, 0); // 16位定时器高位mfrc522_write_reg(TModeReg, 0x8D); // 定义内部定时器的设置mfrc522_write_reg(TPrescalerReg, 0x3E); // 设置定时器分频系数mfrc522_write_reg(TxAutoReg, 0x40); // 调制发送信号为100%ASK
}/*** @brief 打开天线*/
static void mfrc522_antenna_on(void)
{uint8_t temp;temp = mfrc522_read_reg(TxControlReg);if (!(temp & 0x03))mfrc522_set_bit_mask(TxControlReg, 0x03);
}/*** @brief 关闭天线*/
static void mfrc522_antenna_off(void)
{mfrc522_clr_bit_mask(TxControlReg, 0x03);
}/*** @brief 配置ISO类型* @param type ISO类型('A' 或 'B')*/
void mfrc522_config_iso_type(uint8_t type)
{if (type == 'A') // ISO14443_A{mfrc522_clr_bit_mask(Status2Reg, 0x08);mfrc522_write_reg(ModeReg, 0x3D);mfrc522_write_reg(RxSelReg, 0x86);mfrc522_write_reg(RFCfgReg, 0x7F);mfrc522_write_reg(TReloadRegL, 30);mfrc522_write_reg(TReloadRegH, 0);mfrc522_write_reg(TModeReg, 0x8D);mfrc522_write_reg(TPrescalerReg, 0x3E);usleep(2);mfrc522_antenna_on(); // 开天线}
}/*** @brief 通过MFRC522与ISO14443卡通信* @param command 命令* @param send_buf 发送缓冲区* @param send_buf_len 发送缓冲区长度* @param recv_buf 接收缓冲区* @param recv_buf_len 接收缓冲区长度* @return 状态码*/
static uint8_t mfrc522_to_card(uint8_t command, uint8_t *send_buf, uint8_t send_buf_len, uint8_t *recv_buf, uint32_t *recv_buf_len)
{uint8_t status = MI_ERR;uint8_t irq_en = 0x00;uint8_t wait_irq = 0x00;uint8_t last_bits;uint8_t n;uint32_t i = 0;switch (command){case PCD_AUTHENT: // Mifare认证{irq_en = 0x12; // 允许错误中断请求ErrIEn 允许空闲中断IdleIEnwait_irq = 0x10; // 认证寻卡等待时候 查询空闲中断标志位break;}case PCD_TRANSCEIVE: // 接收发送 发送接收{irq_en = 0x77; // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEnwait_irq = 0x30; // 寻卡等待时候 查询接收中断标志位与 空闲中断标志位break;}default:break;}mfrc522_write_reg(ComIEnReg, irq_en | 0x80); // IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反mfrc522_clr_bit_mask(ComIrqReg, 0x80); // Set1该位清零时,CommIRqReg的屏蔽位清零mfrc522_write_reg(CommandReg, PCD_IDLE); // 写空闲命令mfrc522_set_bit_mask(FIFOLevelReg, 0x80); // 置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除for (i = 0; i < send_buf_len; i++)mfrc522_write_reg(FIFODataReg, send_buf[i]); // 写数据进FIFOdatamfrc522_write_reg(CommandReg, command); // 写命令if (command == PCD_TRANSCEIVE)mfrc522_set_bit_mask(BitFramingReg, 0x80); // StartSend置位启动数据发送 该位与收发命令使用时才有效do // 认证与寻卡等待时间{n = mfrc522_read_reg(ComIrqReg); // 查询事件中断usleep(1000);i++;} while ((i != 25) && !(n & 0x01) && !(n & wait_irq));mfrc522_clr_bit_mask(BitFramingReg, 0x80); // 清理允许StartSend位if (i != 25){if (!(mfrc522_read_reg(ErrorReg) & 0x1B)) // 读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr{status = MI_OK;if (n & irq_en & 0x01) // 是否发生定时器中断status = MI_NOTAGERR;if (command == PCD_TRANSCEIVE){n = mfrc522_read_reg(FIFOLevelReg); // 读FIFO中保存的字节数last_bits = mfrc522_read_reg(ControlReg) & 0x07; // 最后接收到得字节的有效位数if (last_bits)*recv_buf_len = (n - 1) * 8 + last_bits; // N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数else*recv_buf_len = n * 8; // 最后接收到的字节整个字节有效if (n == 0)n = 1;if (n > MFRC522_MAX_LEN)n = MFRC522_MAX_LEN;for (i = 0; i < n; i++)recv_buf[i] = mfrc522_read_reg(FIFODataReg); // 从FIFOdata读数据}}elsestatus = MI_ERR;}mfrc522_set_bit_mask(ControlReg, 0x80);mfrc522_write_reg(CommandReg, PCD_IDLE);return status;
}/*** @brief 防冲撞* @param snr 卡片序列号* @return 状态码*/
uint8_t mfrc522_anticoll(uint8_t *snr)
{uint8_t status;uint8_t i, snr_check = 0;uint8_t com_buf[MFRC522_MAX_LEN];uint32_t len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位mfrc522_write_reg(BitFramingReg, 0x00); // 清理寄存器 停止收发mfrc522_clr_bit_mask(CollReg, 0x80); // 清ValuesAfterColl所有接收的位在冲突后被清除com_buf[0] = 0x93; // 卡片防冲突命令com_buf[1] = 0x20;status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 2, com_buf, &len); // 与卡片通信if (status == MI_OK) // 通信成功{for (i = 0; i < 4; i++){*(snr + i) = com_buf[i]; // 读出UIDsnr_check ^= com_buf[i];}if (snr_check != com_buf[i])status = MI_ERR;}mfrc522_set_bit_mask(CollReg, 0x80);return status;
}/*** @brief 寻卡* @param req_code 寻卡命令* @param tag_type 卡片类型* @return 状态码*/
uint8_t mfrc522_request(uint8_t req_code, uint8_t *tag_type)
{uint8_t status;uint8_t com_buf[MFRC522_MAX_LEN];uint32_t len;mfrc522_clr_bit_mask(Status2Reg, 0x08); // 清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况mfrc522_write_reg(BitFramingReg, 0x07); // 发送的最后一个字节的 七位mfrc522_set_bit_mask(TxControlReg, 0x03); // TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号com_buf[0] = req_code; // 存入卡片命令字status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len); // 寻卡if ((status == MI_OK) && (len == 0x10)) // 寻卡成功返回卡类型{*tag_type = com_buf[0];*(tag_type + 1) = com_buf[1];}elsestatus = MI_ERR;return status;
}/*** @brief CRC校验* @param in_data 输入数据* @param len 数据长度* @param out_data 输出数据*/
static void mfrc522_calculate_crc(uint8_t *in_data, uint8_t len, uint8_t *out_data)
{uint8_t i, n;mfrc522_clr_bit_mask(DivIrqReg, 0x04); // CRCIrq = 0mfrc522_write_reg(CommandReg, PCD_IDLE);mfrc522_set_bit_mask(FIFOLevelReg, 0x80); // Clear the FIFO pointer// Writing data to the FIFOfor (i = 0; i < len; i++)mfrc522_write_reg(FIFODataReg, *(in_data + i));mfrc522_write_reg(CommandReg, PCD_CALCCRC);// Wait CRC calculation is completei = 0xFF;do{n = mfrc522_read_reg(DivIrqReg);i--;} while ((i != 0) && !(n & 0x04)); // CRCIrq = 1// Read CRC calculation resultout_data[0] = mfrc522_read_reg(CRCResultRegL);out_data[1] = mfrc522_read_reg(CRCResultRegM);
}/*** @brief 选定卡片* @param snr 卡片序列号* @return 状态码*/
uint8_t mfrc522_select(uint8_t *snr)
{uint8_t status;uint8_t i;uint8_t com_buf[MFRC522_MAX_LEN];uint32_t len;com_buf[0] = PICC_ANTICOLL1;com_buf[1] = 0x70;com_buf[6] = 0;for (i = 0; i < 4; i++){com_buf[i + 2] = *(snr + i);com_buf[6] ^= *(snr + i);}mfrc522_calculate_crc(com_buf, 7, &com_buf[7]);mfrc522_clr_bit_mask(Status2Reg, 0x08);status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 9, com_buf, &len);if ((status == MI_OK) && (len == 0x18))status = MI_OK;elsestatus = MI_ERR;return status;
}/*** @brief 验证卡片密码* @param auth_mode 认证模式* @param addr 块地址* @param key 密钥* @param snr 卡片序列号* @return 状态码*/
uint8_t mfrc522_auth_state(uint8_t auth_mode, uint8_t addr, uint8_t *key, uint8_t *snr)
{uint8_t status;uint8_t i, com_buf[MFRC522_MAX_LEN];uint32_t len;com_buf[0] = auth_mode;com_buf[1] = addr;for (i = 0; i < 6; i++)com_buf[i + 2] = *(key + i);for (i = 0; i < 6; i++)com_buf[i + 8] = *(snr + i);status = mfrc522_to_card(PCD_AUTHENT, com_buf, 12, com_buf, &len);if ((status != MI_OK) || (!(mfrc522_read_reg(Status2Reg) & 0x08)))status = MI_ERR;return status;
}/*** @brief 写数据到M1卡一块* @param addr 块地址* @param data 写入的数据* @return 状态码*/
uint8_t mfrc522_write(uint8_t addr, uint8_t *data)
{uint8_t status;uint8_t i, com_buf[MFRC522_MAX_LEN];uint32_t len;com_buf[0] = PICC_WRITE;com_buf[1] = addr;mfrc522_calculate_crc(com_buf, 2, &com_buf[2]);status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 4, com_buf, &len);if ((status != MI_OK) || (len != 4) || ((com_buf[0] & 0x0F) != 0x0A))status = MI_ERR;if (status == MI_OK){for (i = 0; i < 16; i++)com_buf[i] = *(data + i);mfrc522_calculate_crc(com_buf, 16, &com_buf[16]);status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 18, com_buf, &len);if ((status != MI_OK) || (len != 4) || ((com_buf[0] & 0x0F) != 0x0A))status = MI_ERR;}return status;
}/*** @brief 读取M1卡一块数据* @param addr 块地址* @param data 读出的数据* @return 状态码*/
uint8_t mfrc522_read(uint8_t addr, uint8_t *data)
{uint8_t status;uint8_t i, com_buf[MFRC522_MAX_LEN];uint32_t len;com_buf[0] = PICC_READ;com_buf[1] = addr;mfrc522_calculate_crc(com_buf, 2, &com_buf[2]);status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 4, com_buf, &len);if ((status == MI_OK) && (len == 0x90)){for (i = 0; i < 16; i++)*(data + i) = com_buf[i];}elsestatus = MI_ERR;return status;
}/*** @brief 判断 addr 是否数据块* @param addr 块地址* @return 返回值 1:是数据块;0:不是数据块*/
static uint8_t mfrc522_is_data_block(uint8_t addr)
{if (addr == 0){printf("第0扇区的块0不可更改,不应对其进行操作\r\n");return 0;}/* 如果是数据块(不包含数据块0) */if ((addr < 64) && (((addr + 1) % 4) != 0)){return 1;}printf("块地址不是指向数据块\r\n");return 0;
}/*** @brief 写 data 字符串到M1卡中的数据块* @param addr 数据块地址* @param data 写入的数据* @return 状态码*/
uint8_t mfrc522_write_string(uint8_t addr, uint8_t *data)
{/* 如果是数据块(不包含数据块0),则写入 */if (mfrc522_is_data_block(addr)){return mfrc522_write(addr, data);}return MI_ERR;
}/*** @brief 读取M1卡中的一块数据到 data* @param addr 数据块地址* @param data 读出的数据* @return 状态码*/
uint8_t mfrc522_read_string(uint8_t addr, uint8_t *data)
{/* 如果是数据块(不包含数据块0),则读取 */if (mfrc522_is_data_block(addr)){return mfrc522_read(addr, data);}return MI_ERR;
}/*** @brief 命令卡片进入休眠状态* @return 状态码*/
uint8_t mfrc522_halt(void)
{uint8_t com_buf[MFRC522_MAX_LEN];uint32_t len;com_buf[0] = PICC_HALT;com_buf[1] = 0;mfrc522_calculate_crc(com_buf, 2, &com_buf[2]);mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 4, com_buf, &len);return MI_OK;
}/*** @brief 写入钱包金额* @param addr 块地址* @param data 写入的金额* @return 状态码*/
uint8_t mfrc522_write_amount(uint8_t addr, uint32_t data)
{uint8_t status;uint8_t com_buf[16];com_buf[0] = (data & ((uint32_t)0x000000ff));com_buf[1] = (data & ((uint32_t)0x0000ff00)) >> 8;com_buf[2] = (data & ((uint32_t)0x00ff0000)) >> 16;com_buf[3] = (data & ((uint32_t)0xff000000)) >> 24;com_buf[4] = ~(data & ((uint32_t)0x000000ff));com_buf[5] = ~(data & ((uint32_t)0x0000ff00)) >> 8;com_buf[6] = ~(data & ((uint32_t)0x00ff0000)) >> 16;com_buf[7] = ~(data & ((uint32_t)0xff000000)) >> 24;com_buf[8] = (data & ((uint32_t)0x000000ff));com_buf[9] = (data & ((uint32_t)0x0000ff00)) >> 8;com_buf[10] = (data & ((uint32_t)0x00ff0000)) >> 16;com_buf[11] = (data & ((uint32_t)0xff000000)) >> 24;com_buf[12] = addr;com_buf[13] = ~addr;com_buf[14] = addr;com_buf[15] = ~addr;status = mfrc522_write(addr, com_buf);return status;
}/*** @brief 读取钱包金额* @param addr 块地址* @param data 读出的金额* @return 状态码*/
uint8_t mfrc522_read_amount(uint8_t addr, uint32_t *data)
{uint8_t status = MI_ERR;uint8_t j;uint8_t com_buf[16];status = mfrc522_read(addr, com_buf);if (status != MI_OK)return status;for (j = 0; j < 4; j++){if ((com_buf[j] != com_buf[j + 8]) && (com_buf[j] != ~com_buf[j + 4])) // 验证一下是不是钱包的数据break;}if (j == 4){status = MI_OK;*data = com_buf[0] + (com_buf[1] << 8) + (com_buf[2] << 16) + (com_buf[3] << 24);}else{status = MI_ERR;*data = 0;}return status;
}/*** @brief 修改控制块 addr 的密码A。注意 addr 指的是控制块的地址。* 必须要校验密码B,密码B默认为6个0xFF,如果密码B也忘记了,那就改不了密码A了* @note 注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题* @param addr 控制块地址* @param keya 新的密码A,六个字符,比如 "123456"* @return 状态码*/
uint8_t mfrc522_change_keya(uint8_t addr, uint8_t *keya)
{uint8_t keyb_value[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥uint8_t array_id[4]; /*先后存放IC卡的类型和UID(IC卡序列号)*/uint8_t com_buf[16];uint8_t j;/*寻卡*/while (mfrc522_request(PICC_REQALL, array_id) != MI_OK){printf("寻卡失败\r\n");usleep(1000000);}printf("寻卡成功\n");/* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/if (mfrc522_anticoll(array_id) == MI_OK){/* 选中卡 */mfrc522_select(array_id);/* 校验 B 密码 */if (mfrc522_auth_state(PICC_AUTHENT1B, addr, keyb_value, array_id) != MI_OK){printf("检验密码B失败\r\n");}// 读取控制块里原本的数据(只要修改密码A,其他数据不改)if (mfrc522_read(addr, com_buf) != MI_OK){printf("读取控制块数据失败\r\n");return MI_ERR;}/* 修改密码A */for (j = 0; j < 6; j++)com_buf[j] = keya[j];if (mfrc522_write(addr, com_buf) != MI_OK){printf("写入数据到控制块失败\r\n");return MI_ERR;}printf("密码A修改成功!\r\n");mfrc522_halt();return MI_OK;}return MI_ERR;
}
/*** @brief 按照RC522操作流程写入16字节数据到块 addr* 函数里校验的是密码B,密码B默认为6个0xFF,也可以校验密码A* mfrc522_write_data_block(1, "123456789\n", 10); //字符串不够16个字节的后面补零写入* @note 注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题* 注意:使用该函数要注意 addr 是块0、数据块还是控制块,该函数内部不对此做判断* @param addr 块地址* @param data 写入的数据* @param len 数据长度* @return 状态码*/
uint8_t mfrc522_write_data_block(uint8_t addr, uint8_t *data, uint8_t len)
{uint8_t keyb_value[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥uint8_t array_id[4]; /*先后存放IC卡的类型和UID(IC卡序列号)*/uint8_t com_buf[16];uint8_t j;/*寻卡*/while (mfrc522_request(PICC_REQALL, array_id) != MI_OK){printf("寻卡失败\r\n");usleep(1000000);}printf("寻卡成功\n");/* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/if (mfrc522_anticoll(array_id) == MI_OK){/* 选中卡 */mfrc522_select(array_id);/* 校验 B 密码 */if (mfrc522_auth_state(PICC_AUTHENT1B, addr, keyb_value, array_id) != MI_OK){printf("检验密码B失败\r\n");}/* 拷贝 data 里的 len 个字符到 com_buf */for (j = 0; j < 16; j++){if (j < len)com_buf[j] = data[j];elsecom_buf[j] = 0; // 16个字节若是未填满的字节置0}/* 写入字符串 */if (mfrc522_write(addr, com_buf) != MI_OK){printf("写入数据到数据块失败\r\n");return MI_ERR;}printf("写入数据成功!\r\n");mfrc522_halt();return MI_OK;}return MI_ERR;
}/*** @brief 读取M1卡数据* @note 注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题* 注意:使用该函数要注意 addr 是块0、数据块还是控制块,该函数内部不对此做判断* @param addr 块地址* @param data 读出的数据* @return 状态码*/
uint8_t mfrc522_read_data_block(uint8_t addr, uint8_t *data)
{uint8_t keyb_value[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥uint8_t array_id[4]; /*先后存放IC卡的类型和UID(IC卡序列号)*//*寻卡*/while (mfrc522_request(PICC_REQALL, array_id) != MI_OK){printf("寻卡失败\r\n");usleep(1000000);}printf("寻卡成功\n");/* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/if (mfrc522_anticoll(array_id) == MI_OK){/* 选中卡 */mfrc522_select(array_id);/* 校验 B 密码 */if (mfrc522_auth_state(PICC_AUTHENT1B, addr, keyb_value, array_id) != MI_OK){printf("检验密码B失败\r\n");}// 读取数据块里的数据到 dataif (mfrc522_read(addr, data) != MI_OK){printf("读取数据块失败\r\n");return MI_ERR;}printf("读取数据成功!\r\n");mfrc522_halt();return MI_OK;}return MI_ERR;
}/*** @brief 初始化MFRC522* @param spi_dev SPI设备文件路径* @param cs_chip CS芯片名称* @param cs_line CS引脚编号* @param rst_chip 复位芯片名称* @param rst_line 复位引脚编号* @return 初始化结果*/
int mfrc522_init(const char *spi_dev,const char *cs_chip, uint8_t cs_line,const char *rst_chip, uint8_t rst_line)
{int ret;if (!spi_dev)return -1;if (!cs_chip || !rst_chip)return -1;/* SPI初始化 */mfrc522_spi = spi_handle_alloc(spi_dev, SPIMODE0, S_1M, cs_chip, cs_line);if (!mfrc522_spi)return -1;/* 复位脚初始化 */rst_gpiochip = gpiod_chip_open(rst_chip);if (rst_gpiochip == NULL){printf("gpiod_chip_open failed!\n");return -1;}rst_gpioline = gpiod_chip_get_line(rst_gpiochip, rst_line);if (rst_gpioline == NULL){printf("gpiod_chip_get_line failed!\n");return -1;}ret = gpiod_line_request_output(rst_gpioline, "rst_gpioline", 1);if (ret < 0){printf("gpiod_line_request_output failed!\n");return -1;}mfrc522_rst_disabled();return 0;
}/*** @brief 反初始化MFRC522*/
void mfrc522_exit(void)
{if (mfrc522_spi)spi_handle_free(mfrc522_spi);if (rst_gpioline)gpiod_line_release(rst_gpioline);if (rst_gpiochip)gpiod_chip_close(rst_gpiochip);
}
6、测试
下面编写一个测试程序main.c。完成写入金额和读取金额的操作。
/* main.c */#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pthread.h>
#include <gpiod.h>
#include <math.h>#include "mfrc522.h"// 通常新卡的密码A和密码B都是0xff。可以通过访问每个扇区的尾块来修改密码。
// 修改完密码后,要自行记录。因为密码A和密码B都是不可读的。
uint8_t key_value[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 卡A密钥int main()
{unsigned char id[4];unsigned char status;int ret;uint32_t write_value = 100;uint32_t read_value;/** 块地址的计算:* 块地址 是从0开始的连续编号,范围是 0x00 到 0x3F(总共64个块)* 扇区地址 是从0开始的连续编号,范围是 0x00 到 0x0F(总共16个扇区)* * 具体计算公式:* 扇区号 = 块地址 / 4* 块号 = 块地址 % 4* * 假设块地址为0x11* 扇区号 = 0x11 / 4 = 0x04(第5个扇区)* 块号 = 0x11 % 4 = 0x01(第2个块,即块1)* 因此,0x11 对应 扇区4的块1。*/unsigned char addr = 0x11;ret = mfrc522_init("/dev/spidev3.0","/dev/gpiochip6", 11,"/dev/gpiochip6", 10);if (ret != 0){printf("mfrc522_init fialed!\n");return -1;}mfrc522_reset();mfrc522_config_iso_type('A');while (1){status = mfrc522_request(PICC_REQALL, id);if (status != MI_OK)printf("request card fialed!\n");if (status == MI_OK){printf("request card successfully!\n");if (mfrc522_anticoll(id) != MI_OK){printf("anticoll failed, continue!\n");continue;}status = mfrc522_select(id); // 选定卡片if (status != MI_OK){printf("select card failed, continue!\n");continue;}status = mfrc522_auth_state(PICC_AUTHENT1A, addr, key_value, id); // 校验密码if (status != MI_OK){printf("autu failed, continue!\n");continue;}status = mfrc522_write_amount(addr, write_value); // 写入金额if (status != MI_OK){printf("write amount failed, continue!\n");continue;}status = mfrc522_read_amount(addr, &read_value);if (status != MI_OK){printf("read amount failed, continue!\n");continue;}printf("card uid = %02X%02X%02X%02X\n", id[0], id[1], id[2], id[3]);printf("read value = %d\r\n", read_value);mfrc522_halt();break;}sleep(0.1);}mfrc522_exit();return 0;
}
目前总共涉及的文件有:
执行如下命令编译程序:
gcc -o build main.c mfrc522.c spi.c -lgpiod
执行如下命令运行程序:
sudo ./build
使用手机nfc工具查看,扇区4的块1被修改。0x64转成十进制就是100。
7、总结
参考文章:基于STM32的RC522门禁系统程序解读_mfrc522-CSDN博客
依稀记得小时候拿家里的门禁卡去外面找人复制一个新的,那已是10几年前的事。在查阅MFRC522手册时,发现这款芯片诞生于2007年。其实用的产品功能我们不谈。只是如今2025年,在嵌入式教育的视野中还有它的存在。当然还有很多外设都经典流传至今。那对于嵌入式入门学习来说,到底什么是核心?我想更多是培养学者独立阅读数据手册的技能,熟悉常见的外设通讯协议,加强程序编码或阅读能力等等。但初学时的这种习惯容易遗留到往后的工作中,一字一句的扣代码只会降低开发效率,也不会让你大富大贵。我只想表达,不必花太多心思在类似于这种寄存器或外设驱动的研究上,更应该侧重于上层应用或框架性的东西。包括Linux驱动的学习,重点在理解设备驱动框架,各种子系统框架。
以上也只是我入行一年左右带给我的眼界而作出的讨论。不必理会。