十念 发表于 5 天前

在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芯片的读卡器模块,本次实验也是利用这个模块:
https://i-blog.csdnimg.cn/img_convert/bd62604423a6ef21b694959e6139f6d5.png
下图是利用了IC-S50的非接触式IC卡,右边白色IC卡的物理外观遵循国际标准ISO/IEC 7816,和我们通常利用的银行卡、校园卡的外观是一样的(也可以称M1卡)。其它外观的另有像左边蓝色这种的,常见于门禁等多种场合。
https://i-blog.csdnimg.cn/img_convert/5e6f4927cb3340ba63b60f5e2e72c5e1.png
非接触式IC卡通过无线射频(RF)与读卡器通信。它们通常遵循ISO/IEC 14443标准,工作频率为13.56MHz。非接触式IC卡不必要外部电源。它们通过感应天线从读卡器的射频信号中获取能量,这种能量足以驱动卡片内的芯片举行数据处置惩罚和通信。如下图为MFRC522和IC卡通信示意框图:
https://i-blog.csdnimg.cn/img_convert/a85d2a69922fc6b69ff6289bfdbc331e.png
下面我们将进一步相识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(命令和状态寄存器):
https://i-blog.csdnimg.cn/img_convert/701e5a50b5d0730a49c99c3c01ad8e87.png
Page 1(命令寄存器):
https://i-blog.csdnimg.cn/img_convert/2566124446d08ae81320bc62afdb903a.png
Page 2(配置寄存器):
https://i-blog.csdnimg.cn/img_convert/7e2d0d83680aefd34b362baca3cd54e3.png
Page 3(测试寄存器):
https://i-blog.csdnimg.cn/img_convert/b8743bf3e945a07c8cfe9b80b78bc703.png
3.2、命令集

MFRC522的利用由可执行一系列命令的内部状态机来决定。通过向寄存器集中的命令寄存器写入相应的命令来启动命令。下图是MFRC522的命令集:
https://i-blog.csdnimg.cn/img_convert/0440941fd36150f4bf17e0530bac1e34.png
3.3、数据利用

这里利用的MFRC522模块是SPI接口。
对于读数据(MSB先行):
https://i-blog.csdnimg.cn/img_convert/f6411b0566c92ce39085f80431c76f7a.png
对于写数据(MSB先行):
https://i-blog.csdnimg.cn/img_convert/861c07cddee2b3b4dc3302950c2951f2.png
其中对于地址字节有如下要求:地址字节的最高位bit7为1时表示从MFRC522读取数据,为0时表示向MFRC522写入数据。bit6-bit1表示地址。bit0为0。
https://i-blog.csdnimg.cn/img_convert/b57f08eed176468f05dd0ad2684b4141.png
3.4、基础函数编写

关于MFRC522的利用教程博客有很多。这里保举直接参考野火基于stm32的MFRC522驱动。我们先以读取到IC卡的ID为阶段性目的,以此测试spi和mfrc522相关的基础函数是正常的。
如今还没介绍IC-S50,又怎样知道怎么读ID?没关系,先复制粘贴。由于关于mfrc522的利用函数太多了,先移植一部分。
3.4.1、MFRC522接线

MFRC522Linux板卡SDA(片选脚)任意gpio口MOSISPI_MISOMISOSPI_MOSISCK(时钟脚)SPI_CLKRST(复位脚)任意gpio口3.3V3.3VGNDGND 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;
        int status;
       
    if (spi == NULL)
      return;

    if (send_buf == NULL || recv_buf == NULL)
      return;

    memset(xfer, 0, sizeof(xfer));

        xfer.tx_buf = (unsigned long)send_buf;
        xfer.rx_buf = (unsigned long)recv_buf;
        xfer.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;
        int status;

    if (spi == NULL)
      return;

    if (send_buf == NULL || recv_buf == NULL)
      return;

    memset(xfer, 0, sizeof(xfer));

        xfer.tx_buf = (unsigned long)send_buf;
        xfer.len = send_buf_len;

        xfer.rx_buf = (unsigned long)recv_buf;
        xfer.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 = {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;
    unsigned char recv_buf;
        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.tx_buf = (unsigned long)send_buf;
    xfer.rx_buf = (unsigned long)recv_buf;
        xfer.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;

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

    // 根据上面对地址字节的要求:
    // bit7为1代表读数据,为0代表写数据
    // bit6-bit1表示寄存器地址(寄存器只用6个bit来表示)
    // bit0默认为0
    send_buf = ((reg << 1) & 0x7E ) | 0x80;
    send_buf = 0x00;

    spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);
    return recv_buf;
}
对上面两个函数再封装一下,得到如下:
/* 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复位脚的利用函数。关于复位引脚,数据手册对其的解释如下:
https://i-blog.csdnimg.cn/img_convert/ccc336d6d5e05c3ccbf91c8edfbb62d8.png
/* 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);            // 写数据进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 = 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;
    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 = req_code;                                          // 存入卡片命令字

    status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len);        // 寻卡

    if ((status == MI_OK) && (len == 0x10))                      // 寻卡成功返回卡类型
    {   
      *tag_type = com_buf;
      *(tag_type + 1) = com_buf;
    }
    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;
    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 = 0x93;                                // 卡片防冲突命令
    com_mfrc522_buf = 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;            // 读出UID
            snr_check ^= com_mfrc522_buf;
      }
      
      if (snr_check != com_mfrc522_buf)
            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;

    send_buf = (reg << 1) & 0x7E;
    send_buf = 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;
    unsigned char recv_buf = {0};

    send_buf = ((reg << 1) & 0x7E ) | 0x80;
    send_buf = 0x00;

    spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);
    return recv_buf;
}

// 将寄存器中指定的位置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);            // 写数据进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 = 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;
    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 = 0x93;                                // 卡片防冲突命令
    com_mfrc522_buf = 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;            // 读出UID
            snr_check ^= com_mfrc522_buf;
      }
      
      if (snr_check != com_mfrc522_buf)
            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;
    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 = req_code;                                          // 存入卡片命令字

    status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len);        // 寻卡

    if ((status == MI_OK) && (len == 0x10))                      // 寻卡成功返回卡类型
    {   
      *tag_type = com_buf;
      *(tag_type + 1) = com_buf;
    }
    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;
    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, id, id, id);
    }

    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

https://i-blog.csdnimg.cn/img_convert/ad3e45105de297a39b079d62357460c7.png
和手机“NFC工具”APP读取的同等:
https://i-blog.csdnimg.cn/img_convert/a9938f2836be88e3f0c4cc4da78c6166.png
4、IC-S50

如今开始介绍S50芯片。
4.1、存储结构

上面介绍过,S50芯片内部有一个eeprom是用来存储数据的。容量为8Kbit,即1024Byte。分为16个扇区,即每个扇区占64Byte。每个扇区又由4个块(块0、块1、块2、块3)组成,即每个块占16Byte。如下图所示:
https://i-blog.csdnimg.cn/img_convert/38b67a89712b90befcc03d994af8c2d2.png


[*]第0扇区的块0,它用于存放厂商代码,已经固化,不可更改。
[*]每个扇区的块0、块1、块2为数据块。可以用来存放数据。数据块有两种应用:

[*]用作一般的数据保存,可以举行读、写利用。
[*]用作数据值,可以举行初始化、加值、减值、读值利用。

[*]每个扇区的块3为尾块,存储了该扇区的访问控制信息和密钥,具体结构如下:
https://i-blog.csdnimg.cn/img_convert/5aef19827b670d773510c2ac6548613c.png
尾块的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和访问控制位的,那如今想访问尾块,分析我们是想要读取或修改密码与访问控制位。所以,得看看访问尾块时,有着什么样的权限:
https://i-blog.csdnimg.cn/img_convert/38fa3803786687a62bbc6c1fd9597d2c.png
条件真值表由C1X3、C2X3、C3X3构成(X为某个扇区的编号。X后面的数字为块号,这里是3,代表块3),它们的值可以在尾块的Byte6-Byte9找到:
https://i-blog.csdnimg.cn/img_convert/ba576dc92c6e332742802335650b95b1.png
如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后可写。
https://i-blog.csdnimg.cn/img_convert/3c6e88b92516b3a9b886b3ca60b1afb5.png
上面介绍的是尾块的利用权限。如今再看块0~块2的利用权限:
https://i-blog.csdnimg.cn/img_convert/a67106de634765f4e66cb8569fa98e8c.png
假设如今要访问扇区2的块1。那么就要知道C121、C221、C321的值:
https://i-blog.csdnimg.cn/img_convert/889e7cbb298263101344eb9b483ac924.png
如C121在byte7的bit5,C221在byte8的bit1,C321在byte8的bit5。假设C121、C221、C321的值分别为100时,会有如下意思:


[*]验证Key A或Key B后可读。
[*]验证Key B后可写。
[*]不可举行加值、减值利用。
https://i-blog.csdnimg.cn/img_convert/3177b0bbaf44ea9b06376dca95781a7a.png
4.2、M1卡与读卡器的通信流程

下图展示了M1卡与读卡器的通信流程:
https://i-blog.csdnimg.cn/img_convert/9d9e9d6979e934c79d364a78e3adee5d.png


[*]复位应答: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;

    send_buf = (reg << 1) & 0x7E;
    send_buf = 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;
    uint8_t recv_buf = {0};

    send_buf = ((reg << 1) & 0x7E) | 0x80;
    send_buf = 0x00;

    spi_write_and_read(mfrc522_spi, send_buf, sizeof(send_buf), recv_buf);
    return recv_buf;
}

/**
* @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); // 写数据进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 = 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;
    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 = 0x93; // 卡片防冲突命令
    com_buf = 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; // 读出UID
            snr_check ^= com_buf;
      }

      if (snr_check != com_buf)
            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;
    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 = req_code; // 存入卡片命令字

    status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 1, com_buf, &len); // 寻卡

    if ((status == MI_OK) && (len == 0x10)) // 寻卡成功返回卡类型
    {
      *tag_type = com_buf;
      *(tag_type + 1) = com_buf;
    }
    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 = mfrc522_read_reg(CRCResultRegL);
    out_data = 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;
    uint32_t len;

    com_buf = PICC_ANTICOLL1;
    com_buf = 0x70;
    com_buf = 0;

    for (i = 0; i < 4; i++)
    {
      com_buf = *(snr + i);
      com_buf ^= *(snr + i);
    }

    mfrc522_calculate_crc(com_buf, 7, &com_buf);

    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;
    uint32_t len;

    com_buf = auth_mode;
    com_buf = addr;

    for (i = 0; i < 6; i++)
      com_buf = *(key + i);

    for (i = 0; i < 6; i++)
      com_buf = *(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;
    uint32_t len;

    com_buf = PICC_WRITE;
    com_buf = addr;

    mfrc522_calculate_crc(com_buf, 2, &com_buf);

    status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 4, com_buf, &len);

    if ((status != MI_OK) || (len != 4) || ((com_buf & 0x0F) != 0x0A))
      status = MI_ERR;

    if (status == MI_OK)
    {
      for (i = 0; i < 16; i++)
            com_buf = *(data + i);

      mfrc522_calculate_crc(com_buf, 16, &com_buf);

      status = mfrc522_to_card(PCD_TRANSCEIVE, com_buf, 18, com_buf, &len);

      if ((status != MI_OK) || (len != 4) || ((com_buf & 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;
    uint32_t len;

    com_buf = PICC_READ;
    com_buf = addr;

    mfrc522_calculate_crc(com_buf, 2, &com_buf);

    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;
    }
    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;
    uint32_t len;

    com_buf = PICC_HALT;
    com_buf = 0;

    mfrc522_calculate_crc(com_buf, 2, &com_buf);
    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;
    com_buf = (data & ((uint32_t)0x000000ff));
    com_buf = (data & ((uint32_t)0x0000ff00)) >> 8;
    com_buf = (data & ((uint32_t)0x00ff0000)) >> 16;
    com_buf = (data & ((uint32_t)0xff000000)) >> 24;

    com_buf = ~(data & ((uint32_t)0x000000ff));
    com_buf = ~(data & ((uint32_t)0x0000ff00)) >> 8;
    com_buf = ~(data & ((uint32_t)0x00ff0000)) >> 16;
    com_buf = ~(data & ((uint32_t)0xff000000)) >> 24;

    com_buf = (data & ((uint32_t)0x000000ff));
    com_buf = (data & ((uint32_t)0x0000ff00)) >> 8;
    com_buf = (data & ((uint32_t)0x00ff0000)) >> 16;
    com_buf = (data & ((uint32_t)0xff000000)) >> 24;

    com_buf = addr;
    com_buf = ~addr;
    com_buf = addr;
    com_buf = ~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;

    status = mfrc522_read(addr, com_buf);
    if (status != MI_OK)
      return status;

    for (j = 0; j < 4; j++)
    {
      if ((com_buf != com_buf) && (com_buf != ~com_buf)) // 验证一下是不是钱包的数据
            break;
    }

    if (j == 4)
    {
      status = MI_OK;
      *data = com_buf + (com_buf << 8) + (com_buf << 16) + (com_buf << 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;                                       /*先后存放IC卡的类型和UID(IC卡序列号)*/
    uint8_t com_buf;
    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 = keya;

      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;                                       /*先后存放IC卡的类型和UID(IC卡序列号)*/
    uint8_t com_buf;
    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 = data;
            else
                com_buf = 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;                                       /*先后存放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;
    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, id, id, id);
                        printf("read value = %d\r\n", read_value);
                       
            mfrc522_halt();
            
            break;
      }

      sleep(0.1);
    }

    mfrc522_exit();

    return 0;
}
目前总共涉及的文件有:
https://i-blog.csdnimg.cn/img_convert/c05a0c5aa15c97406f53a62268f10968.png
执行如下命令编译步伐:
gcc -o build main.c mfrc522.c spi.c -lgpiod
执行如下命令运行步伐:
sudo ./build

https://i-blog.csdnimg.cn/img_convert/0aabfd9dba69c91a49823cfb2cf646fe.png
利用手机nfc工具检察,扇区4的块1被修改。0x64转成十进制就是100。
https://i-blog.csdnimg.cn/img_convert/8c3b18fbf324ffd6ad0d397c3bd32a35.png
7、总结

参考文章:基于STM32的RC522门禁系统步伐解读_mfrc522-CSDN博客
依稀记得小时间拿家里的门禁卡去外貌找人复制一个新的,那已是10几年前的事。在查阅MFRC522手册时,发现这款芯片诞生于2007年。其实用的产物功能我们不谈。只是如今2025年,在嵌入式教育的视野中另有它的存在。固然另有很多外设都经典传播至今。那对于嵌入式入门学习来说,到底什么是核心?我想更多是培养学者独立阅读数据手册的技能,熟悉常见的外设通讯协议,加强步伐编码或阅读能力等等。但初学时的这种习惯容易遗留到以后的工作中,一字一句的扣代码只会降低开发效率,也不会让你大富大贵。我只想表达,不必花太多心思在雷同于这种寄存器或外设驱动的研究上,更应该侧重于上层应用或框架性的东西。包括Linux驱动的学习,重点在理解装备驱动框架,各种子系统框架。
以上也只是我入行一年左右带给我的眼界而作出的讨论。不必理会。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 在ARM Linux应用层下驱动MFRC522