在ARM Linux应用层下驱动MFRC522

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默认为0
    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};

    // 根据上面对地址字节的要求:
    // bit7为1代表读数据,为0代表写数据
    // bit6-bit1表示寄存器地址(寄存器只用6个bit来表示)
    // bit0默认为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];
}

对上面两个函数再封装一下,得到如下:

/* 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初始值0x6363
    
    mfrc522_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  允许空闲中断IdleIEn
			wait_irq = 0x10;                                    // 认证寻卡等待时候 查询空闲中断标志位
			break;
		}
		case PCD_TRANSCEIVE:                                    // 接收发送 发送接收
        {
			irq_en = 0x77;                                      // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
			wait_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]);            // 写数据进FIFOdata

	mfrc522_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];
    }
    else
        status = 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];            // 读出UID
            snr_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初始值0x6363
    
    mfrc522_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  允许空闲中断IdleIEn
			wait_irq = 0x10;                                    // 认证寻卡等待时候 查询空闲中断标志位
			break;
		}
		case PCD_TRANSCEIVE:                                    // 接收发送 发送接收
        {
			irq_en = 0x77;                                      // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
			wait_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]);            // 写数据进FIFOdata

	mfrc522_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];            // 读出UID
            snr_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];
    }
    else
        status = 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");
        else
            break;

        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初始值0x6363

    mfrc522_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  允许空闲中断IdleIEn
        wait_irq = 0x10; // 认证寻卡等待时候 查询空闲中断标志位
        break;
    }
    case PCD_TRANSCEIVE: // 接收发送 发送接收
    {
        irq_en = 0x77;   // 允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
        wait_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]); // 写数据进FIFOdata

    mfrc522_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;
}

/**
 * @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]; // 读出UID
            snr_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];
    }
    else
        status = 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 = 0
    mfrc522_write_reg(CommandReg, PCD_IDLE);
    mfrc522_set_bit_mask(FIFOLevelReg, 0x80); // Clear the FIFO pointer

    // Writing data to the FIFO
    for (i = 0; i < len; i++)
        mfrc522_write_reg(FIFODataReg, *(in_data + i));
    mfrc522_write_reg(CommandReg, PCD_CALCCRC);

    // Wait CRC calculation is complete
    i = 0xFF;
    do
    {
        n = mfrc522_read_reg(DivIrqReg);
        i--;
    } while ((i != 0) && !(n & 0x04)); // CRCIrq = 1

    // Read CRC calculation result
    out_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;
    else
        status = 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];
    }
    else
        status = 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];
            else
                com_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");
        }

        // 读取数据块里的数据到 data
        if (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驱动的学习,重点在理解各种总线设备驱动模型,各种子系统框架等。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值