当前位置: 首页 > news >正文

在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接线

MFRC522Linux板卡
SDA(片选脚)任意gpio口
MOSISPI_MISO
MISOSPI_MOSI
SCK(时钟脚)SPI_CLK
RST(复位脚)任意gpio口
3.3V3.3V
GNDGND

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驱动的学习,重点在理解设备驱动框架,各种子系统框架。

以上也只是我入行一年左右带给我的眼界而作出的讨论。不必理会。

相关文章:

  • vue项目中使用antvX6(可拖拽,vue3)
  • 【Vue】组件基础
  • 浙江大学 DeepSeek 公开课 第三季 第1期讲座 - 唐谈 研究员 (附PPT下载) | 突破信息差
  • 【Linux网络】构建UDP服务器与字典翻译系统
  • 基于LangChain与Neo4j构建企业关系图谱的金融风控实施方案,结合工商数据、供应链记录及舆情数据,实现隐性关联识别与动态风险评估
  • java 使用Caffeine实现本地缓存
  • 归一化对C4.5决策树无效的数学原理与实证分析
  • ios17 音频加载失败问题
  • 基础服务系列-Mac Ngrok 内网穿透
  • 如何在腾讯云Ubuntu服务器上部署Node.js项目
  • Novartis诺华制药社招入职综合能力测评真题SHL题库考什么?
  • 在kali中安装AntSword(蚁剑)
  • 【 Git 全局忽略文件完全指南:配置、规则与最佳实践】
  • 强化学习系统学习路径与实践方法
  • 微软Edge浏览器字体设置
  • 在线查看【免费】avi,mov,rm,webm,ts,rm,mkv,mpeg,ogg,mpg,rmvb,wmv,3gp,ts,swf文件格式网站
  • 部署Kimi-VL-A3B-Instruct视频推理
  • GPU软硬件架构协同设计解析
  • EtherCAT 模型(Reference Model)
  • 使用 inobounce 解决 iOS 皮筋效果导致的无法下拉刷新
  • 习近平致电祝贺诺沃亚当选连任厄瓜多尔总统
  • 广西出现今年首场超警洪水
  • 黄仁勋结束年内第二次中国行:关键时刻,重申对中国市场承诺
  • 福特预期6月美国市场涨价,机构称加税让每辆汽车成本至少增加数千美元
  • 轻流科技薄智元:AI时代,打造“工业智造”需要“共生式进化”
  • 一代油画家的“色彩之诗”:周碧初捐赠艺术展上海举行