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_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[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寄存器的宏界说命名利用驼峰命名是为了和数据手册保持同等,方便查阅:
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、测试
执行如下命令测试:
和手机“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
复制代码 执行如下命令运行步伐:
利用手机nfc工具检察,扇区4的块1被修改。0x64转成十进制就是100。
7、总结
参考文章:基于STM32的RC522门禁系统步伐解读_mfrc522-CSDN博客
依稀记得小时间拿家里的门禁卡去外貌找人复制一个新的,那已是10几年前的事。在查阅MFRC522手册时,发现这款芯片诞生于2007年。其实用的产物功能我们不谈。只是如今2025年,在嵌入式教育的视野中另有它的存在。固然另有很多外设都经典传播至今。那对于嵌入式入门学习来说,到底什么是核心?我想更多是培养学者独立阅读数据手册的技能,熟悉常见的外设通讯协议,加强步伐编码或阅读能力等等。但初学时的这种习惯容易遗留到以后的工作中,一字一句的扣代码只会降低开发效率,也不会让你大富大贵。我只想表达,不必花太多心思在雷同于这种寄存器或外设驱动的研究上,更应该侧重于上层应用或框架性的东西。包括Linux驱动的学习,重点在理解装备驱动框架,各种子系统框架。
以上也只是我入行一年左右带给我的眼界而作出的讨论。不必理会。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |