基础篇010.2 STM32驱动RC522 RFID模块之二:STM32硬件SPI驱动RC522

news2024/12/24 9:02:21

目录

基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识

1. 实验硬件及原理图

1.1 RFID硬件

1.2 硬件原理图

2. 单片机与RFID硬件模块分析

3. 利用STM32CubeMX创建MDK工程

3.1 STM32CubeMX工程创建

3.2 配置调试方式

 3.3 配置时钟电路

 3.4 配置时钟

3.5 配置GPIO

3.6 配置SPI

 3.7 配置串口

3.8 项目配置

4. MDK工程驱动代码调试

4.1 按键、LED程序

4.2 RC522驱动程序

5.调试与验证

6.总结

基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识


本实验的RFID信息显示是通过串口实现的,关于串行通信请参考博文:

基础篇007. 串行通信(一)--阻塞方式发送接收

关于RFID基础知识,请参考博文:

基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识

1. 实验硬件及原理图

1.1 RFID硬件

本实验使用的RFID-RC522模块采用MFRC522芯片,SPI通讯方式,支持Mifarel S50、S70 、Pro、Desfire等类型的卡,附带的白卡和钥匙扣是S50卡,每张卡都有自己的标识(UID)。

图1

1.2 硬件原理图

(1)MC522原理图

图2

(2)STM32F446RE Nucleo-64开发板原理图

核心板原理图:

图3

核心板自带的ST-Link原理图:

图4

核心板接口:

图5

底板原理图:

图6

(3)STM32与RC522模块的接口连接

//! Nucleo-F446RE与RC522接口定义

//SPI2_SCK              PB10---(接Arduino D6)

//SPI2_MISO             PC2----(接CN7左下2)

//SPI2_MOSI             PC1----(接Arduino A4)

//RCC522_RST(CE)        PC7----(接Arduino D9)

//RCC522_NSS(SDA)      PB6----(接Arduino D10)

//RCC522_IRQ            悬空

2. 单片机与RFID硬件模块分析

可实现各种不同主机接口的功能:

  1. SPI接口
  2. 串行UART(类似 RS232,电压电平取决于提供的管脚电压)
  3. I2C接口

RC522是一款高度集成的非接触式(13.56MHz)读写卡芯片。它采用了NXP公司的MFRC522为核心的处理芯片,此发送模块利用调制和解调的原理,支持各种非接触式的通信协议。RC522是采用的一种先进的RFID(Radio Fequency Identification,中文为无线射频识别)通信技术。其工作原理其实很简单:IC/ID磁卡进入到磁场后,接受读写器发出的射频信号,凭借感应电流所获得的能量发送出存储在芯片中的产品信息,读写器读取到信息并解码后,送至处理单元进行数据处理。

RC522模块引脚说明:

编号

名称

说明

1

VCC

电源正

2

RST

复位

3

IRQ

中断信号

4

GND

地线

5

MISO

主进从出数据引脚

6

MOSI

从进主出数据引脚

7

SCK

时钟

8

SDA

片选

分析上面图2和图3可知,Nucleo-446RE开发板中的核心芯片与板载ST-Link芯片U2的串行通信,连接的是串口2。ST-Link与电脑采用的是ST USB虚拟串口通信。在windows中,串口驱动可以自行加载。本实验可以采用SPI方式实现单片机与RC522模块的通信。

STM32采用硬件SPI2时,使用的接口是PB10、PC2、PC1。

3. 利用STM32CubeMX创建MDK工程

3.1 STM32CubeMX工程创建

选择File下的New Project:

选择芯片类型(本文为STM32F446RET6),选择下边的item,然后Start Project:

3.2 配置调试方式

点击左侧的System Core下的SYS,将Debug设置为Serial Wire:

 3.3 配置时钟电路

配置时钟:将RCC下的HSE设置为Crystal/Ceramic Resonator

 3.4 配置时钟

Nucleo-446RE开发板:

请结合开发版的硬件电路,从下面两种方式中二选一,选择第二种方式时,开发板中需要焊接相应元件(X3、C33、C34、R35、R37),或者你不能确定振荡电路,直接选第一种方式吧。

在STM32CubeMX中,做如下配置:

(1)采用内部8MHz时钟时选择Clock Configuration,做如下配置:

(2)使用外部时钟时,开发板需焊接的X3(8MHz)、C33、C34(20PF)、R36、R37),选择Clock Configuration,做如下配置:

3.5 配置GPIO

结合开发版的硬件电路,进行GPIO设置。RC522板有六个接口:SCK、MOSI、MISO、SDA、RST,前三项为SPI接口,后两项SDA(片选)、RST(复位)。SPI口可采用ARM芯片自带的硬件资源控制,也可以用软件模拟;

在左侧选择System Core/GPIO,依次将RST、SDA与LED连接的IO设置为GPIO_Output,将按键设置为GPIO_Input,按键对应的IO口设置为输入。电路图参考图6。

各IO口设置后的参数放大图如下:

3.6 配置SPI

核心板STM32采用内部时钟时,硬件SPI2的配置如下:

 3.7 配置串口

实验调试中的系统运行信息,可以通过串口输出。根据开发板的硬件电路,选中串口2。

USART2参数配置:

在 Connectivity 中选择 USART2 设置,并选择 Asynchronous 异步通信。

波特率为 115200 Bits/s。传输数据长度为8Bit。奇偶检验 None,停止位 1 ,接收和发送都使能。

本文的串口采用阻塞方式收发信息,无需设置中断。

3.8 项目配置

在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。取消勾选Use lastest available version,选择其他版本:

代码生成设置:

在Code Generate中选择第二个,然后Generate Code,即生成代码:

可以打开MDK工程编辑了。

4. MDK工程驱动代码调试

4.1 按键、LED程序

在工程文件夹内部新建“BSP”文件夹:

在BSP文件夹内建立自定义驱动的新文件夹:

本部分的代码从项目基础篇005. 按键控制中修改而来,程序与硬件必须匹配,为培养同学们在不同STM32芯片间移植程序的灵活性,本专栏中的课程采用几种不同的STM32芯片,请结合硬件电路修改代码。

Global文件夹内建立文件(红框内的文件用于一些全局变量函数,本文暂时不用)user.c和user.h:

User.h文件的代码如下:

#ifndef __USER_H
#define __USER_H

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

//#define uchar    unsigned char
typedef unsigned char uchar;

// ! --定义位带操作-->>>
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了.
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014
#define GPIOJ_ODR_ADDr    (GPIOJ_BASE+20) //0x40022414
#define GPIOK_ODR_ADDr    (GPIOK_BASE+20) //0x40022814

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010
#define GPIOJ_IDR_Addr    (GPIOJ_BASE+16) //0x40022410
#define GPIOK_IDR_Addr    (GPIOK_BASE+16) //0x40022810

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

#define PJout(n)   BIT_ADDR(GPIOJ_ODR_Addr,n)  //输出
#define PJin(n)    BIT_ADDR(GPIOJ_IDR_Addr,n)  //输入

#define PKout(n)   BIT_ADDR(GPIOK_ODR_Addr,n)  //输出
#define PKin(n)    BIT_ADDR(GPIOK_IDR_Addr,n)  //输入

// ! --汇编函数声明-->>>
void WFI_SET(void);    //执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void);  //开启所有中断
void MSR_MSP(uint32_t addr);  //设置堆栈地址

// ! --延时函数声明-->>>
void delay_init(uint8_t SYSCLK);
void delay_ms(uint16_t nms);
void delay_us(uint32_t nus);

void delaySoft_ns(uint32_t t_ns);   //ns级纯软件延时函数,不使用定时器,延时不准,需要调试
void delaySoft_us(uint32_t t_us);

#ifdef __cplusplus
}
#endif

#endif   /*__ USER_H__ */

User.c文件的代码如下:

#include "global/user.h"


#ifdef  USE_FULL_ASSERT
//当编译提示出错的时候此函数用来报告错误的文件和所在行
//file:指向源文件
//line:指向在文件中的行数
void assert_failed(uint8_t* file, uint32_t line)
{
	while (1)
	{
	}
}
#endif

// ! ------延时函数------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//使用SysTick的普通计数模式对延迟进行管理(支持ucosii/ucosiii)
//包括delay_us,delay_ms
//********************************************************************************

static uint32_t fac_us=0;							//us延时倍乘数

/**
 * @DESCRIPTION: 初始化延迟函数
 * @INPUT  ARGS: 系统时钟频率SYSCLK=主PLL时钟,即:SYSCLK= (外部晶振*PLLN)/(PLLM*PLLP)
 * @OUTPUT ARGS: none
 * @NOTE       : SYSTICK的时钟固定为AHB时钟
 * @param {uint8_t} SYSCLK
 * @return {*}
 */
void delay_init(uint8_t SYSCLK)
{
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);	//SysTick频率为HCLK
	fac_us=SYSCLK;						//不论是否使用OS,fac_us都需要使用
}

//延时nus
//nus为要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(uint32_t nus)
{
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;			//LOAD的值
	ticks=nus*fac_us; 						//需要的节拍数
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;
		if(tnow!=told)
		{
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}
	};
}

//延时nms
//nms:要延时的ms数
void delay_ms(uint16_t nms)
{
	uint32_t i;
	for(i=0;i<nms;i++) delay_us(1000);
}

// ! ------软件延时函数------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
 * @DESCRIPTION: us级纯软件延时函数,不使用定时器
 * @INPUT ARGS : none
 * @OUTPUT ARGS: none
 * @RETURNS    : none
 * @NOTES      : F407内部时钟为168MHz时,每个指令周期约6ns。
 * @param {uint32_t} t_us
 */
#define INS_CPU_CYCLES	8	//一条自增减指令所需的CPU周期数
#define ADJ_CPU_CYCLES	62	//延时函数自身需要的CPU周期数(根据需要调整)
void delaySoft_us(uint32_t t_us)
{
	uint32_t count;
	count = (HAL_RCC_GetHCLKFreq()/1000000*t_us - ADJ_CPU_CYCLES)/INS_CPU_CYCLES;
	while(count--);
}

/**
 * @DESCRIPTION: ns级纯软件延时函数,不使用定时器,延时不准,需要调试
 * @INPUT ARGS : none
 * @OUTPUT ARGS: none
 * @RETURNS    : none
 * @NOTES      : F407内部时钟为168MHz时,每个指令周期约6ns。
 * @param {uint32_t} t_ns
 */
void delaySoft_ns(uint32_t t_ns)
{
	do
	{
		;
	}
	while(t_ns--);
}

// ! ------汇编指令------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
#if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050)  	//AC6编译器
//以下为汇编函数(AC6)
void WFI_SET(void)		//执行WFI指令
{
	__ASM volatile("WFI");
}
void INTX_DISABLE(void)  //关闭所有中断
{
	__ASM volatile("CPSID   I");
	__ASM volatile("BX      LR");
}
void INTX_ENABLE(void)	//开启所有中断
{
	__ASM volatile("CPSIE   I");
	__ASM volatile("BX   LR");
}
void MSR_MSP(uint32_t addr)	//设置堆栈地址
{
	__ASM volatile("MSR MSP, r0");
	__ASM volatile("BX r14");
}
#elif   defined ( __CC_ARM )   	//AC5编译器
__asm void WFI_SET(void)
{
	WFI;
}
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
	CPSID   I
	BX      LR
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
	CPSIE   I
	BX      LR
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr)
{
	MSR MSP, r0 			//set Main Stack value
	BX r14
}
#endif

Key文件夹内为键盘程序key.c和key.h

key.h文件的代码如下:

#ifndef _KEY_H
#define _KEY_H
#include "main.h"

#define KEY_ON  0
#define KEY_OFF  1

void key_Init(void);
uint8_t Key_Scan(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin);

#endif

key.c文件的代码如下:

#include "main.h"
#include <stdio.h>
#include <string.h>
#include "global/user.h"
#include "key\key.h"

/**
 * @DESCRIPTION: 初始化SPI端口
 * @INPUT ARGS : none
 * @OUTPUT ARGS: none
 * @RETURNS    : none
 * @NOTES      : none
 */
void key_Init(void)
{
	  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();


  /*Configure GPIO pins : PCPin PCPin */
  GPIO_InitStruct.Pin = KEY2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = KEY1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);


  /*Configure GPIO pins : PBPin PBPin */
  GPIO_InitStruct.Pin = KEY3_Pin|KEY4_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/**
  * @brief   检测是否有按键按下
  *	@param 	GPIOx:具体的端口, x可以是(A...K)
	*	@param 	GPIO_PIN:具体的端口位, 可以是GPIO_PIN_x(x可以是0...15)
  * @retval  按键的状态
  *		@arg KEY_ON:按键按下(注意,key_up按键的电平定义时相反的)
  *		@arg KEY_OFF:按键没按下
  */
uint8_t Key_Scan(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin)
{
	/*检测是否有按键按下 */
	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )
	{
		/*等待按键释放 */
		while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);
		return 	KEY_ON;
	}
	else
		return KEY_OFF;
}

4.2 RC522驱动程序

在工程的“BSP”文件夹内,分别建立如下文件:

其中,RC522.c及RC522.h为MFRC522芯片的通用驱动程序,这部分程序可以移植到任何单片机上。RFID.h和RFID.c是针对Mifare 1卡的应用代码。

RC522.h文件的代码如下:

#ifndef _RC522_H
#define _RC522_H
#include "main.h"

/***********************************************************************************
*								MFRC522驱动程序			        				   *
************************************************************************************/
/*MFRC522寄存器定义*/
//PAGE0
#define MFRC_RFU00              	0x00
#define MFRC_CommandReg         	0x01
#define MFRC_ComIEnReg             	0x02
#define MFRC_DivlEnReg             	0x03
#define MFRC_ComIrqReg             	0x04
#define MFRC_DivIrqReg             	0x05
#define MFRC_ErrorReg              	0x06
#define MFRC_Status1Reg            	0x07
#define MFRC_Status2Reg            	0x08
#define MFRC_FIFODataReg           	0x09
#define MFRC_FIFOLevelReg          	0x0A
#define MFRC_WaterLevelReg         	0x0B
#define MFRC_ControlReg            	0x0C
#define MFRC_BitFramingReg         	0x0D
#define MFRC_CollReg               	0x0E
#define MFRC_RFU0F                 	0x0F
//PAGE1
#define MFRC_RFU10                 	0x10
#define MFRC_ModeReg               	0x11
#define MFRC_TxModeReg             	0x12
#define MFRC_RxModeReg             	0x13
#define MFRC_TxControlReg          	0x14
#define MFRC_TxAutoReg             	0x15 //中文手册有误
#define MFRC_TxSelReg              	0x16
#define MFRC_RxSelReg              	0x17
#define MFRC_RxThresholdReg        	0x18
#define MFRC_DemodReg              	0x19
#define MFRC_RFU1A                 	0x1A
#define MFRC_RFU1B                 	0x1B
#define MFRC_MifareReg             	0x1C
#define MFRC_RFU1D                 	0x1D
#define MFRC_RFU1E                 	0x1E
#define MFRC_SerialSpeedReg        	0x1F
//PAGE2
#define MFRC_RFU20                 	0x20
#define MFRC_CRCResultRegM         	0x21
#define MFRC_CRCResultRegL         	0x22
#define MFRC_RFU23                 	0x23
#define MFRC_ModWidthReg           	0x24
#define MFRC_RFU25                 	0x25
#define MFRC_RFCfgReg              	0x26
#define MFRC_GsNReg                	0x27
#define MFRC_CWGsCfgReg            	0x28
#define MFRC_ModGsCfgReg           	0x29
#define MFRC_TModeReg              	0x2A
#define MFRC_TPrescalerReg         	0x2B
#define MFRC_TReloadRegH           	0x2C
#define MFRC_TReloadRegL           	0x2D
#define MFRC_TCounterValueRegH     	0x2E
#define MFRC_TCounterValueRegL     	0x2F
//PAGE3
#define MFRC_RFU30                 	0x30
#define MFRC_TestSel1Reg           	0x31
#define MFRC_TestSel2Reg           	0x32
#define MFRC_TestPinEnReg          	0x33
#define MFRC_TestPinValueReg       	0x34
#define MFRC_TestBusReg            	0x35
#define MFRC_AutoTestReg           	0x36
#define MFRC_VersionReg            	0x37
#define MFRC_AnalogTestReg         	0x38
#define MFRC_TestDAC1Reg           	0x39
#define MFRC_TestDAC2Reg           	0x3A
#define MFRC_TestADCReg            	0x3B
#define MFRC_RFU3C                 	0x3C
#define MFRC_RFU3D                 	0x3D
#define MFRC_RFU3E                 	0x3E
#define MFRC_RFU3F                 	0x3F

/*MFRC522的FIFO长度定义*/
#define MFRC_FIFO_LENGTH       		64

/*MFRC522传输的帧长定义*/
#define MFRC_MAXRLEN                18

/*MFRC522命令集,中文手册P59*/
#define MFRC_IDLE              		0x00	//取消当前命令的执行
#define MFRC_CALCCRC           		0x03    //激活CRC计算
#define MFRC_TRANSMIT          		0x04    //发送FIFO缓冲区内容
#define MFRC_NOCMDCHANGE            0x07	//无命令改变
#define MFRC_RECEIVE           		0x08    //激活接收器接收数据
#define MFRC_TRANSCEIVE        		0x0C    //发送并接收数据
#define MFRC_AUTHENT           		0x0E    //执行Mifare认证(验证密钥)
#define MFRC_RESETPHASE        		0x0F    //复位MFRC522

/*MFRC522通讯时返回的错误代码*/
#define MFRC_OK                 	(char)0
#define MFRC_NOTAGERR            	(char)(-1)
#define MFRC_ERR                	(char)(-2)

/*MFRC522函数声明*/
void MFRC_Init(void);
void MFRC_WriteReg(uint8_t addr, uint8_t data);
uint8_t MFRC_ReadReg(uint8_t addr);
void MFRC_SetBitMask(uint8_t addr, uint8_t mask);
void MFRC_ClrBitMask(uint8_t addr, uint8_t mask);
void MFRC_CalulateCRC(uint8_t *pInData, uint8_t len, uint8_t *pOutData);
char MFRC_CmdFrame(uint8_t cmd, uint8_t *pInData, uint8_t InLenByte, uint8_t *pOutData, uint16_t *pOutLenBit);


/***********************************************************************************
*							MFRC552与MF1卡通讯接口程序	 		     	     	   *
************************************************************************************/
/*Mifare1卡片命令字*/
#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_TRANSFER         	0xB0               	//转存(传送)
#define PICC_RESTORE          	0xC2               	//恢复(重储)
#define PICC_HALT             	0x50               	//休眠

/*PCD通讯时返回的错误代码*/
#define PCD_OK                 	(char)0					//成功
#define PCD_NOTAGERR            (char)(-1)			//无卡
#define PCD_ERR                	(char)(-2)			//出错

/*PCD函数声明*/
void PCD_Init(void);
void PCD_Reset(void);
void PCD_AntennaOn(void);
void PCD_AntennaOff(void);
char PCD_Request(uint8_t RequestMode, uint8_t *pCardType);  //寻卡,并返回卡的类型
char PCD_Anticoll(uint8_t *pSnr);                           //防冲突,返回卡号
char PCD_Select(uint8_t *pSnr);                             //选卡
char PCD_AuthState(uint8_t AuthMode, uint8_t BlockAddr, uint8_t *pKey, uint8_t *pSnr); //验证密码(密码A和密码B)
char PCD_WriteBlock(uint8_t BlockAddr, uint8_t *pData);   //写数据
char PCD_ReadBlock(uint8_t BlockAddr, uint8_t *pData);    //读数据
char PCD_Value(uint8_t mode, uint8_t BlockAddr, uint8_t *pValue);
char PCD_BakValue(uint8_t sourceBlockAddr, uint8_t goalBlockAddr);
char PCD_Halt(void);
void StartIDcardTask(void const * argument);

#endif

RC522.c文件的代码如下:

/**
MFRC522-AN模块采用Philips MFRC522芯片设计读卡电路,使用方便,成本低廉,适用
于设备开发、读卡器开发等高级应用的用户、需要进行射频卡终端设计/生产的用户。
模块参数:
①工作电压:3.3v
②工作频率:13.56MHz
③支持卡类型:mifare1 s50、mifare1s70、 mifareUltraLight、mifare Pro, mifare Desfire
④通信方式:SPI协议
⑤环境工作温度:-20°C——80°C

  M1卡分为16个扇区,每个扇区由四个块(块0、块1、块2、块3)组成
  将16个扇区的64个块按绝对地址编号为:0~63
  第0个扇区的块0(即绝对地址0块),用于存放厂商代码,已经固化不可更改
  每个扇区的块0、块1、块2为数据块,可用于存放数据
  每个扇区的块3为控制块(绝对地址为:块3、块7、块11.....)包括密码A,存取控制、密码B等

1、CPU选择
   STM32F446RE,内部时钟180MHz
2、STM32CubeMX 定义任意两个引脚,作为复位脚和片选脚,并对引脚作出如下配置:
   GPlO output level       --High
   GPIO mode               --Output Push Pull
   GPIO Pull-up/Pull-down  --No pull-up and no pull-down
   Maximum output speed    --LOW
   User label              --RC522_RST/RC522_SDA
   ---------------------------------------------------------
   开启SPI功能,模式选择-->Full-Duplex Master(全双工),其他配置如下:
   Basic Parameters
      Frame format-->Motorola
      Data size   -->8 Bits
      First bit   -->MSB First
   Clock Parameters
       Prescaler(for Baud Rate)-->8
       Baud rate               -->5.625MBits/s【RC522中的SPI最高速率为10MHz/s】
       Clock Polarity(CPOL)    -->LOW
       Clock Phase(CPHA)       -->1 Edge
   Advanced Parameters
       CRC Calculation   -->Disabled
       NSS Signal Type   -->Software

3、接线方式:
   SPI_MISO(MUC)--> MISO(器件)
   SPI_MOSI(MUC)--> MOSI(器件)
   其他引脚一一对应
//! Nucleo-F446RE接口
//SPI2_SCK              PB10---(接Arduino D6)
//SPI2_MISO             PC2----(接CN7左下2)
//SPI2_MOSI             PC1----(接Arduino A4)
//RCC522_RST(CE)        PC7----(接Arduino D9)
//RCC522_NSS(SDA)      PB6----(接Arduino D10)
//RCC522_IRQ            悬空

4、SPI模式说明:SPI总线传输的四种模式:
 *  SPI传输的模式由CPOL:clock polarity 时钟的极性,和CPHA:clock phase 时钟的相位控制。
 *  RC522采用的是CPOL=0,CPHA=0的工作模式。在CubeMX中,SPI_CPHA设置为1Edge。
 * ┌─────────┬───────┬───────┬─────────────────┬─────────────────┐
 * │ SPI模式 │  CPOL │  CPHA │ 空闲时间SCLK状态 │  采样时刻       │
 * │    0    │   0   │  0    │    低电平       │ 奇数边沿(上升沿) │
 * │    1    │   0   │  1    │    低电平       │ 偶数边沿(下降沿) │
 * │    2    │   1   │  0    │    高电平       │ 奇数边沿(下降沿) │
 * │    3    │   1   │  1    │    高电平       │ 偶数边沿(上升沿) │
 * └─────────┴───────┴───────┴─────────────────┴─────────────────┘

5、应用函数
    MFRC_Init();//初始化
    PCD_Reset();//器件复位
    PCD_Request(PICC_REQALL, RxBuffer);//返回值为0,代表寻卡成功;并把卡类型存入RxBuffer中
    PCD_Anticoll(RxBuffer);   //把(十六进制)的4个字节卡号存储在数组RxBuffer中
***********************************/

// #define RC522_SDA GPIO_Port       GPIOB
// #define RC522_SDA Pin             GPIO_PIN_6	//cs、nss、SDA指同一个口
// #define RC522_RST GPIO_Port       GPIOC
// #define RC522_RST Pin             GPIO_PIN_7

#include "main.h"
#include <stdio.h>
#include <string.h>
#include "global/user.h"
#include "usart.h"
#include "RC522\RC522.h"

extern SPI_HandleTypeDef hspi2;

// #define osDelay HAL_Delay
#define osDelay delay_ms

#define RS522_RST(N) HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)
#define RC522_SDA(N) HAL_GPIO_WritePin(RC522_SDA_GPIO_Port, RC522_SDA_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)

/**************************************************************************************
 * 函数名称:MFRC_Init
 * 功能描述:MFRC初始化
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:MFRC的SPI接口速率为0~10Mbps
 ***************************************************************************************/
void MFRC_Init(void)
{
    RC522_SDA(1);
    RS522_RST(1);
}

/**************************************************************************************
 * 函数名称: SPI_RW_Byte
 * 功能描述: 模拟SPI读写一个字节
 * 入口参数: -byte:要发送的数据
 * 出口参数: -byte:接收到的数据
 ***************************************************************************************/
static uint8_t ret; // 这些函数是HAL与标准库不同的地方【读写函数】
uint8_t SPI2_RW_Byte(uint8_t byte)
{
//硬件SPI
    HAL_SPI_TransmitReceive(&hspi2, &byte, &ret, 1, 10); // 把byte 写入,并读出一个值,把它存入ret
    return ret;                                          // 入口是byte 的地址,读取时用的也是ret地址,一次只写入一个值10
//下面是模拟SPI
//  if (byte == 0x00) // 读数据时
//  {
//      ret = RC522_SPI_ReadByte();
//  }
//  RC522_SPI_SendByte(byte);
//  return ret;
}

/**************************************************************************************
 * 函数名称:MFRC_WriteReg
 * 功能描述:写一个寄存器
 * 入口参数:-addr:待写的寄存器地址
 *           -data:待写的寄存器数据
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void MFRC_WriteReg(uint8_t addr, uint8_t data)
{
    uint8_t AddrByte;
    AddrByte = (addr << 1) & 0x7E; // 求出地址字节
    RC522_SDA(0);                  // NSS拉低
    SPI2_RW_Byte(AddrByte);        // 写地址字节
    SPI2_RW_Byte(data);            // 写数据
    RC522_SDA(1);                  // NSS拉高
}

/**************************************************************************************
 * 函数名称:MFRC_ReadReg
 * 功能描述:读一个寄存器
 * 入口参数:-addr:待读的寄存器地址
 * 出口参数:无
 * 返 回 值:-data:读到寄存器的数据
 * 说    明:无
 ***************************************************************************************/
uint8_t MFRC_ReadReg(uint8_t addr)
{
    uint8_t AddrByte, data;
    AddrByte = ((addr << 1) & 0x7E) | 0x80; // 求出地址字节
    RC522_SDA(0);                           // NSS拉低
    SPI2_RW_Byte(AddrByte);                 // 写地址字节
    data = SPI2_RW_Byte(0x00);              // 读数据
    RC522_SDA(1);                           // NSS拉高
    return data;
}

/**************************************************************************************
 * 函数名称:MFRC_SetBitMask
 * 功能描述:设置寄存器的位
 * 入口参数:-addr:待设置的寄存器地址
 *           -mask:待设置寄存器的位(可同时设置多个bit)
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void MFRC_SetBitMask(uint8_t addr, uint8_t mask)
{
    uint8_t temp;
    temp = MFRC_ReadReg(addr);        // 先读回寄存器的值
    MFRC_WriteReg(addr, temp | mask); // 处理过的数据再写入寄存器
}

/**************************************************************************************
 * 函数名称:MFRC_ClrBitMask
 * 功能描述:清除寄存器的位
 * 入口参数:-addr:待清除的寄存器地址
 *           -mask:待清除寄存器的位(可同时清除多个bit)
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void MFRC_ClrBitMask(uint8_t addr, uint8_t mask)
{
    uint8_t temp;
    temp = MFRC_ReadReg(addr);         // 先读回寄存器的值
    MFRC_WriteReg(addr, temp & ~mask); // 处理过的数据再写入寄存器
}

/**************************************************************************************
 * 函数名称:MFRC_CalulateCRC
 * 功能描述:用MFRC计算CRC结果
 * 入口参数:-pInData:带进行CRC计算的数据
 *           -len:带进行CRC计算的数据长度
 *           -pOutData:CRC计算结果
 * 出口参数:-pOutData:CRC计算结果
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void MFRC_CalulateCRC(uint8_t *pInData, uint8_t len, uint8_t *pOutData)
{
    // 0xc1 1        2           pInData[2]
    uint8_t temp;
    uint32_t i;
    MFRC_ClrBitMask(MFRC_DivIrqReg, 0x04);     // 使能CRC中断
    MFRC_WriteReg(MFRC_CommandReg, MFRC_IDLE); // 取消当前命令的执行
    MFRC_SetBitMask(MFRC_FIFOLevelReg, 0x80);  // 清除FIFO及其标志位
    for (i = 0; i < len; i++)                  // 将待CRC计算的数据写入FIFO
    {
        MFRC_WriteReg(MFRC_FIFODataReg, *(pInData + i));
    }
    MFRC_WriteReg(MFRC_CommandReg, MFRC_CALCCRC); // 执行CRC计算
    i = 100000;
    do
    {
        temp = MFRC_ReadReg(MFRC_DivIrqReg); // 读取DivIrqReg寄存器的值
        i--;
    } while ((i != 0) && !(temp & 0x04));           // 等待CRC计算完成
    pOutData[0] = MFRC_ReadReg(MFRC_CRCResultRegL); // 读取CRC计算结果
    pOutData[1] = MFRC_ReadReg(MFRC_CRCResultRegM);
}

/**************************************************************************************
 * 函数名称:MFRC_CmdFrame
 * 功能描述:MFRC522和ISO14443A卡通讯的命令帧函数
 * 入口参数:-cmd:MFRC522命令字
 *           -pIndata:MFRC522发送给MF1卡的数据的缓冲区首地址
 *           -InLenByte:发送数据的字节长度
 *           -pOutdata:用于接收MF1卡片返回数据的缓冲区首地址
 *           -pOutLenBit:MF1卡返回数据的位长度
 * 出口参数:-pOutdata:用于接收MF1卡片返回数据的缓冲区首地址
 *           -pOutLenBit:用于MF1卡返回数据位长度的首地址
 * 返 回 值:-status:错误代码(MFRC_OK、MFRC_NOTAGERR、MFRC_ERR)
 * 说    明:无
 ***************************************************************************************/
char MFRC_CmdFrame(uint8_t cmd, uint8_t *pInData, uint8_t InLenByte, uint8_t *pOutData, uint16_t *pOutLenBit)
{
    uint8_t lastBits;
    uint8_t n;
    uint32_t i;
    char status = MFRC_ERR;
    uint8_t irqEn = 0x00;
    uint8_t waitFor = 0x00;

    /*根据命令设置标志位*/
    switch (cmd)
    {
    case MFRC_AUTHENT: // Mifare认证
        irqEn = 0x12;
        waitFor = 0x10; // idleIRq中断标志
        break;
    case MFRC_TRANSCEIVE: // 发送并接收数据
        irqEn = 0x77;
        waitFor = 0x30; // RxIRq和idleIRq中断标志
        break;
    }

    /*发送命令帧前准备*/
    MFRC_WriteReg(MFRC_ComIEnReg, irqEn | 0x80); // 开中断
    MFRC_ClrBitMask(MFRC_ComIrqReg, 0x80);       // 清除中断标志位SET1
    MFRC_WriteReg(MFRC_CommandReg, MFRC_IDLE);   // 取消当前命令的执行
    MFRC_SetBitMask(MFRC_FIFOLevelReg, 0x80);    // 清除FIFO缓冲区及其标志位

    /*发送命令帧*/
    for (i = 0; i < InLenByte; i++) // 写入命令参数
    {
        MFRC_WriteReg(MFRC_FIFODataReg, pInData[i]); // 写数据进 FIFODataReg
    }
    MFRC_WriteReg(MFRC_CommandReg, cmd); // 执行命令
    if (cmd == MFRC_TRANSCEIVE)
    {
        MFRC_SetBitMask(MFRC_BitFramingReg, 0x80); // 启动发送
    }
    i = 300000; // 根据时钟频率调整,操作M1卡最大等待时间25ms
    do          // 认证 与寻卡等待时间
    {
        n = MFRC_ReadReg(MFRC_ComIrqReg); // 查询事件中断
        i--;
    } while ((i != 0) && !(n & 0x01) && !(n & waitFor)); // 等待命令完成
    MFRC_ClrBitMask(MFRC_BitFramingReg, 0x80);           // 停止发送

    /*处理接收的数据*/
    if (i != 0)
    {
        // 读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
        if (!(MFRC_ReadReg(MFRC_ErrorReg) & 0x1B))
        {
            status = MFRC_OK;
            if (n & irqEn & 0x01) // 是否发生定时器中断
            {
                status = MFRC_NOTAGERR;
            }
            if (cmd == MFRC_TRANSCEIVE)
            {
                // 读FIFO中保存的字节数
                n = MFRC_ReadReg(MFRC_FIFOLevelReg);
                lastBits = MFRC_ReadReg(MFRC_ControlReg) & 0x07; // 最后接收到得字节的有效位数
                if (lastBits)
                {
                    *pOutLenBit = (n - 1) * 8 + lastBits; // N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
                }
                else
                {
                    *pOutLenBit = n * 8; // 最后接收到的字节整个字节有效
                }
                if (n == 0)
                {
                    n = 1;
                }
                if (n > MFRC_MAXRLEN)
                {
                    n = MFRC_MAXRLEN;
                }
                for (i = 0; i < n; i++)
                {
                    pOutData[i] = MFRC_ReadReg(MFRC_FIFODataReg);
                }
            }
        }
        else
        {
            status = MFRC_ERR;
        }
    }

    MFRC_SetBitMask(MFRC_ControlReg, 0x80);    // 停止定时器运行
    MFRC_WriteReg(MFRC_CommandReg, MFRC_IDLE); // 取消当前命令的执行

    return status;
}

/**************************************************************************************
 * 函数名称:PCD_Reset
 * 功能描述:PCD复位
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void PCD_Reset(void)
{
    /*硬复位*/
    RS522_RST(1); // 用到复位引脚
    osDelay(2);
    RS522_RST(0);
    osDelay(2);
    RS522_RST(1);
    osDelay(2);

    /*软复位*/
    MFRC_WriteReg(MFRC_CommandReg, MFRC_RESETPHASE);
    osDelay(2);

    /*复位后的初始化配置*/
    MFRC_WriteReg(MFRC_ModeReg, 0x3D);   // CRC初始值0x6363
    MFRC_WriteReg(MFRC_TReloadRegL, 30); // 定时器重装值
    MFRC_WriteReg(MFRC_TReloadRegH, 0);
    MFRC_WriteReg(MFRC_TModeReg, 0x8D);      // 定义内部定时器的设置
    MFRC_WriteReg(MFRC_TPrescalerReg, 0x3E); // 设置定时器预分频值
    MFRC_WriteReg(MFRC_TxAutoReg, 0x40);     // 调制发送信号为100%ASK

    PCD_AntennaOff(); // 关天线
    osDelay(2);
    PCD_AntennaOn(); // 开天线
}

/**************************************************************************************
 * 函数名称:PCD_AntennaOn
 * 功能描述:开启天线,使能PCD发送能量载波信号
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:每次开启或关闭天线之间应至少有1ms的间隔
 ***************************************************************************************/
void PCD_AntennaOn(void)
{
    uint8_t temp;
    temp = MFRC_ReadReg(MFRC_TxControlReg);
    if (!(temp & 0x03))
    {
        MFRC_SetBitMask(MFRC_TxControlReg, 0x03);
    }
}

/**************************************************************************************
 * 函数名称:PCD_AntennaOff
 * 功能描述:关闭天线,失能PCD发送能量载波信号
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:每次开启或关闭天线之间应至少有1ms的间隔
 ***************************************************************************************/
void PCD_AntennaOff(void)
{
    MFRC_ClrBitMask(MFRC_TxControlReg, 0x03);
}

/***************************************************************************************
 * 函数名称:PCD_Init
 * 功能描述:读写器初始化
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void PCD_Init(void)
{
    MFRC_Init();      // MFRC管脚配置
    PCD_Reset();      // PCD复位  并初始化配置
    PCD_AntennaOff(); // 关闭天线
    PCD_AntennaOn();  // 开启天线
}

/***************************************************************************************
 * 函数名称:PCD_Request
 * 功能描述:寻卡
 * 入口参数: -RequestMode:寻卡方式
 *                         PICC_REQIDL:寻天线区内未进入休眠状态
 *                         PICC_REQALL:寻天线区内全部卡
 *           -pCardType:  用于保存卡片类型
 * 出口参数:-pCardType:卡片类型
 *                       0x4400:Mifare_UltraLight
 *                       0x0400:Mifare_One(S50)
 *                       0x0200:Mifare_One(S70)
 *                       0x0800:Mifare_Pro(X)
 *                       0x4403:Mifare_DESFire
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_Request(uint8_t RequestMode, uint8_t *pCardType)
{
    int status;
    uint16_t unLen;
    uint8_t CmdFrameBuf[MFRC_MAXRLEN];

    MFRC_ClrBitMask(MFRC_Status2Reg, 0x08);   // 关内部温度传感器
    MFRC_WriteReg(MFRC_BitFramingReg, 0x07);  // 存储模式,发送模式,是否启动发送等
    MFRC_SetBitMask(MFRC_TxControlReg, 0x03); // 配置调制信号13.56MHZ

    CmdFrameBuf[0] = RequestMode; // 存入 卡片命令字

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 1, CmdFrameBuf, &unLen);

    if ((status == PCD_OK) && (unLen == 0x10)) // 寻卡成功返回卡类型
    {
        *pCardType = CmdFrameBuf[0];
        *(pCardType + 1) = CmdFrameBuf[1];
    }

    return status;
}

/***************************************************************************************
 * 函数名称:PCD_Anticoll
 * 功能描述:防冲撞,获取卡号
 * 入口参数:-pSnr:用于保存卡片序列号,4字节
 * 出口参数:-pSnr:卡片序列号,4字节
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_Anticoll(uint8_t *pSnr)
{
    char status;
    uint8_t i, snr_check = 0;
    uint16_t unLen;
    uint8_t CmdFrameBuf[MFRC_MAXRLEN];

    MFRC_ClrBitMask(MFRC_Status2Reg, 0x08);  // 清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位
    MFRC_WriteReg(MFRC_BitFramingReg, 0x00); // 清理寄存器 停止收发
    MFRC_ClrBitMask(MFRC_CollReg, 0x80);     // 清ValuesAfterColl所有接收的位在冲突后被清除

    CmdFrameBuf[0] = PICC_ANTICOLL1; // 卡片防冲突命令
    CmdFrameBuf[1] = 0x20;

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 2, CmdFrameBuf, &unLen); // 与卡片通信

    if (status == PCD_OK) // 通信成功
    {
        for (i = 0; i < 4; i++)
        {
            *(pSnr + i) = CmdFrameBuf[i]; // 读出UID
            snr_check ^= CmdFrameBuf[i];
        }
        if (snr_check != CmdFrameBuf[i])
        {
            status = PCD_ERR;
        }
    }

    MFRC_SetBitMask(MFRC_CollReg, 0x80);
    return status;
}

/***************************************************************************************
 * 函数名称:PCD_Select
 * 功能描述:选定卡片
 * 入口参数:-pSnr:卡片序列号,4字节
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_Select(uint8_t *pSnr)
{
    char status;
    uint8_t i;
    uint16_t unLen;
    uint8_t CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = PICC_ANTICOLL1;
    CmdFrameBuf[1] = 0x70;
    CmdFrameBuf[6] = 0;
    for (i = 0; i < 4; i++)
    {
        CmdFrameBuf[i + 2] = *(pSnr + i);
        CmdFrameBuf[6] ^= *(pSnr + i);
    }
    MFRC_CalulateCRC(CmdFrameBuf, 7, &CmdFrameBuf[7]);

    MFRC_ClrBitMask(MFRC_Status2Reg, 0x08);

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 9, CmdFrameBuf, &unLen);

    if ((status == PCD_OK) && (unLen == 0x18))
    {
        status = PCD_OK;
    }
    else
    {
        status = PCD_ERR;
    }
    return status;
}

/***************************************************************************************
 * 函数名称:PCD_AuthState
 * 功能描述:验证卡片密码
 * 入口参数:-AuthMode:验证模式
 *                   PICC_AUTHENT1A:验证A密码
 *                   PICC_AUTHENT1B:验证B密码
 *           -BlockAddr:块地址(0~63)
 *           -pKey:密码
 *           -pSnr:卡片序列号,4字节
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:验证密码时,以扇区为单位,BlockAddr参数可以是同一个扇区的任意块
 ***************************************************************************************/
char PCD_AuthState(uint8_t AuthMode, uint8_t BlockAddr, uint8_t *pKey, uint8_t *pSnr)
{
    char status;
    uint16_t unLen;
    uint8_t i, CmdFrameBuf[MFRC_MAXRLEN];
    CmdFrameBuf[0] = AuthMode;
    CmdFrameBuf[1] = BlockAddr;
    for (i = 0; i < 6; i++)
    {
        CmdFrameBuf[i + 2] = *(pKey + i);
    }
    for (i = 0; i < 4; i++)
    {
        CmdFrameBuf[i + 8] = *(pSnr + i);
    }

    status = MFRC_CmdFrame(MFRC_AUTHENT, CmdFrameBuf, 12, CmdFrameBuf, &unLen);
    if ((status != PCD_OK) || (!(MFRC_ReadReg(MFRC_Status2Reg) & 0x08)))
    {
        status = PCD_ERR;
    }

    return status;
}

/***************************************************************************************
 * 函数名称:PCD_WriteBlock
 * 功能描述:写MF1卡数据块
 * 入口参数:-BlockAddr:块地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 *           -pData: 用于保存待写入的数据,16字节
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_WriteBlock(uint8_t BlockAddr, uint8_t *pData)
{
    char status;
    uint16_t unLen;
    uint8_t i, CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = PICC_WRITE;
    CmdFrameBuf[1] = BlockAddr;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);

    if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
    {
        status = PCD_ERR;
    }

    if (status == PCD_OK)
    {
        for (i = 0; i < 16; i++)
        {
            CmdFrameBuf[i] = *(pData + i);
        }
        MFRC_CalulateCRC(CmdFrameBuf, 16, &CmdFrameBuf[16]);

        status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 18, CmdFrameBuf, &unLen);

        if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
        {
            status = PCD_ERR;
        }
    }

    return status;
}

/***************************************************************************************
 * 函数名称:PCD_ReadBlock
 * 功能描述:读MF1卡数据块
 * 入口参数:-BlockAddr:块地址
 *           -pData: 用于保存读出的数据,16字节
 * 出口参数:-pData: 用于保存读出的数据,16字节
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_ReadBlock(uint8_t BlockAddr, uint8_t *pData)
{
    char status;
    uint16_t unLen;
    uint8_t i, CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = PICC_READ;
    CmdFrameBuf[1] = BlockAddr;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);
    if ((status == PCD_OK) && (unLen == 0x90))
    {
        for (i = 0; i < 16; i++)
        {
            *(pData + i) = CmdFrameBuf[i];
        }
    }
    else
    {
        status = PCD_ERR;
    }

    return status;
}

/***************************************************************************************
 * 函数名称:PCD_Value
 * 功能描述:对MF1卡数据块增减值操作
 * 入口参数:
 *           -BlockAddr:块地址
 *           -pValue:四字节增值的值,低位在前
 *           -mode:数值块操作模式
 *                  PICC_INCREMENT:增值
 *                  PICC_DECREMENT:减值
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_Value(uint8_t mode, uint8_t BlockAddr, uint8_t *pValue)
{
    // 0XC1        1           Increment[4]={0x03, 0x01, 0x01, 0x01};
    char status;
    uint16_t unLen;
    uint8_t i, CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = mode;
    CmdFrameBuf[1] = BlockAddr;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);

    if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
    {
        status = PCD_ERR;
    }

    if (status == PCD_OK)
    {
        for (i = 0; i < 16; i++)
        {
            CmdFrameBuf[i] = *(pValue + i);
        }
        MFRC_CalulateCRC(CmdFrameBuf, 4, &CmdFrameBuf[4]);
        unLen = 0;
        status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 6, CmdFrameBuf, &unLen);
        if (status != PCD_ERR)
        {
            status = PCD_OK;
        }
    }

    if (status == PCD_OK)
    {
        CmdFrameBuf[0] = PICC_TRANSFER;
        CmdFrameBuf[1] = BlockAddr;
        MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);

        status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);

        if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
        {
            status = PCD_ERR;
        }
    }
    return status;
}

/***************************************************************************************
 * 函数名称:PCD_BakValue
 * 功能描述:备份钱包(块转存)
 * 入口参数:-sourceBlockAddr:源块地址
 *                -goalBlockAddr   :目标块地址
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:只能在同一个扇区内转存
 ***************************************************************************************/
char PCD_BakValue(uint8_t sourceBlockAddr, uint8_t goalBlockAddr)
{
    char status;
    uint16_t unLen;
    uint8_t CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = PICC_RESTORE;
    CmdFrameBuf[1] = sourceBlockAddr;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);
    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);
    if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
    {
        status = PCD_ERR;
    }

    if (status == PCD_OK)
    {
        CmdFrameBuf[0] = 0;
        CmdFrameBuf[1] = 0;
        CmdFrameBuf[2] = 0;
        CmdFrameBuf[3] = 0;
        MFRC_CalulateCRC(CmdFrameBuf, 4, &CmdFrameBuf[4]);
        status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 6, CmdFrameBuf, &unLen);
        if (status != PCD_ERR)
        {
            status = PCD_OK;
        }
    }

    if (status != PCD_OK)
    {
        return PCD_ERR;
    }

    CmdFrameBuf[0] = PICC_TRANSFER;
    CmdFrameBuf[1] = goalBlockAddr;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);
    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);
    if ((status != PCD_OK) || (unLen != 4) || ((CmdFrameBuf[0] & 0x0F) != 0x0A))
    {
        status = PCD_ERR;
    }

    return status;
}

/***************************************************************************************
 * 函数名称:PCD_Halt
 * 功能描述:命令卡片进入休眠状态
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:-status:错误代码(PCD_OK、PCD_NOTAGERR、PCD_ERR)
 * 说    明:无
 ***************************************************************************************/
char PCD_Halt(void)
{
    char status;
    uint16_t unLen;
    uint8_t CmdFrameBuf[MFRC_MAXRLEN];

    CmdFrameBuf[0] = PICC_HALT;
    CmdFrameBuf[1] = 0;
    MFRC_CalulateCRC(CmdFrameBuf, 2, &CmdFrameBuf[2]);

    status = MFRC_CmdFrame(MFRC_TRANSCEIVE, CmdFrameBuf, 4, CmdFrameBuf, &unLen);

    return status;
}

RC522.c中的函数说明:

IO口定义:

#define RS522_RST(N) HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)

#define RC522_SDA(N) HAL_GPIO_WritePin(RC522_SDA_GPIO_Port, RC522_SDA_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)

通过SPI总线读写RC522模块函数:uint8_t SPI2_RW_Byte(uint8_t byte)

RFID.h文件的代码如下:

#ifndef _RFID_H
#define _RFID_H
#include "main.h"

extern uint8_t readUid[5];
extern uint8_t UID[5];		//定义一张已知卡号,可以通过串口打印通过下面读取到的打印到上位机,再把那个读取的卡号填入数组
extern uint8_t DefaultKey[6]; // 默认秘钥

/*函数声明*/
void RC522_Init(void);
uint8_t EntranceGuard(uint8_t *readUid,void(*funCallBack)(void));
void DoorSensor(void);
void RfidIndicator(void);
//void notarize_type1(void);

char WriteAmount(uint8_t addr, uint32_t pData);
char ReadAmount(uint8_t addr, uint32_t *pData);
char ReadAmount(uint8_t addr, uint32_t *pData);
char WriteDataBlock(uint8_t addr, uint8_t *pData, uint8_t Len);
char ReadDataBlock(uint8_t addr, uint8_t *pData);

#endif

RFID.c文件的代码如下:

#include <stdio.h>
#include <string.h>
#include "global/user.h"
#include "RC522\RC522.h"
#include "RC522\RFID.h"

uint8_t readUid[5];
uint8_t UID[5] = {0x37, 0x7e, 0xbc, 0xfd};							// 自定义的卡号,用于比较
uint8_t DefaultKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // 默认秘钥

/***************************************************************************************
 * 函数名称:RC522_Init
 * 功能描述:初始化
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void RC522_Init(void)
{
	MFRC_Init();
	PCD_Reset();
	printf("RC522初始化完成\n");
}

/***************************************************************************************
 * 函数名称:门禁开门
 * 功能描述:只读取并显示卡号,成功读取到卡号就退出,并调用回调功能函数
 * 入口参数:-readUid:用于保存卡片序列号,4字节
			 -funCallBack:函数传参,无需会掉功能函数时填NULL即可
 * 出口参数:
 * 返 回 值:读到卡号返回0,失败返回1
 * 说    明:无
 ***************************************************************************************/
uint8_t EntranceGuard(uint8_t *readUid, void (*funCallBack)(void))
{
	uint8_t Temp[5];												 // 存放IC卡的类型和UID(IC卡序列号)
	if (PCD_Request(PICC_REQALL, Temp) == PCD_OK) // 寻卡
	{//成功
		if (Temp[0] == 0x04 && Temp[1] == 0x00)
			printf("Mifare1-S50\n");
		else if (Temp[0] == 0x02 && Temp[1] == 0x00)
			printf("Mifare1-S70");
		else if (Temp[0] == 0x44 && Temp[1] == 0x00)
			printf("Mifare-UltraLight(MF0)");
		else if (Temp[0] == 0x08 && Temp[1] == 0x00)
			printf("Mifare-Pro(MF2)");
		else if (Temp[0] == 0x44 && Temp[1] == 0x03)
			printf("Mifare Desire(MF3)");
		else
			printf("Unknown");
		if (PCD_Anticoll(readUid) == PCD_OK) // 防冲撞,获取卡号,存入readUid
		{									 // 防冲撞成功
			if (funCallBack != NULL)
				funCallBack(); // 调用功能执行函数,如指示灯信号
			return 0;
		}
	}
	return 1;
}

/***************************************************************************************
 * 函数名称:DoorSensor
 * 功能描述:门磁控制信号
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void DoorSensor(void)
{
	// 【STM32F446,NUCLEO-F446RE板】使用STM32CubeMX创建MDK工程,实现流水灯
	HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // LED亮
	HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);	 // LED灭
	HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); // LED灭
	HAL_Delay(500);												 // 延时 500ms
	HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);	 // LED灭
	HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); // LED亮
	HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);	 // LED灭
}

/***************************************************************************************
 * 函数名称:RfidIndicator
 * 功能描述:指示灯信号
 * 入口参数:无
 * 出口参数:无
 * 返 回 值:无
 * 说    明:无
 ***************************************************************************************/
void RfidIndicator(void)
{
	HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_RESET); // LED1亮
	HAL_Delay(1000);											 // 延时 500ms
	HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);	 // LED1灭
}

/**
 * @brief  判断 addr 是否数据块
 * @param  addr,块绝对地址(0-63)
 * @retval 返回值 1:是数据块;0:不是数据块
 */
char IsDataBlock(uint8_t addr)
{
	if (addr == 0)
	{
		printf("第0扇区的块0不可更改,不应对其进行操作\n");
		return 0;
	}
	// 如果是数据块(不包含数据块0)
	if ((addr < 64) && (((addr + 1) % 4) != 0))
	{
		return 1;
	}

	printf("块地址不是指向数据块\n");
	return 0;
}

/**
 * @brief  写 pData 字符串到M1卡中的数据块
 * @param  addr,数据块地址(不能写入控制块)
 * @param  pData,写入的数据,16字节
 * @retval 状态值= PCD_OK,成功
 */
char PCD_WriteString(uint8_t addr, uint8_t *pData)
{
	/* 如果是数据块(不包含数据块0),则写入 */
	if (IsDataBlock(addr))
	{
		return PCD_WriteBlock(addr, pData);
	}

	return PCD_ERR;
}

/**
 * @brief  读取M1卡中的一块数据到 pData
 * @param  addr,数据块地址(不读取控制块)
 * @param  pData,读出的数据,16字节
 * @retval 状态值= PCD_OK,成功
 */
char PCD_ReadString(uint8_t addr, uint8_t *pData)
{
	/* 如果是数据块(不包含数据块0),则读取 */
	if (IsDataBlock(addr))
	{
		return PCD_ReadBlock(addr, pData);
	}

	return PCD_ERR;
}

/**
 * @DESCRIPTION: 写入钱包金额
 * @INPUT  ARGS: none
 * @OUTPUT ARGS: none
 * @NOTE       : none
 * @param {uint8_t} addr:块地址
 * @param {uint32_t} pData:写入的金额
 * @return {*} 成功返回PCD_OK
 */
char WriteAmount(uint8_t addr, uint32_t pData)
{
	char status;
	uint8_t ucComMF522Buf[16];
	ucComMF522Buf[0] = (pData & ((uint32_t)0x000000ff));
	ucComMF522Buf[1] = (pData & ((uint32_t)0x0000ff00)) >> 8;
	ucComMF522Buf[2] = (pData & ((uint32_t)0x00ff0000)) >> 16;
	ucComMF522Buf[3] = (pData & ((uint32_t)0xff000000)) >> 24;

	ucComMF522Buf[4] = ~(pData & ((uint32_t)0x000000ff));
	ucComMF522Buf[5] = ~(pData & ((uint32_t)0x0000ff00)) >> 8;
	ucComMF522Buf[6] = ~(pData & ((uint32_t)0x00ff0000)) >> 16;
	ucComMF522Buf[7] = ~(pData & ((uint32_t)0xff000000)) >> 24;

	ucComMF522Buf[8] = (pData & ((uint32_t)0x000000ff));
	ucComMF522Buf[9] = (pData & ((uint32_t)0x0000ff00)) >> 8;
	ucComMF522Buf[10] = (pData & ((uint32_t)0x00ff0000)) >> 16;
	ucComMF522Buf[11] = (pData & ((uint32_t)0xff000000)) >> 24;

	ucComMF522Buf[12] = addr;
	ucComMF522Buf[13] = ~addr;
	ucComMF522Buf[14] = addr;
	ucComMF522Buf[15] = ~addr;
	status = PCD_WriteBlock(addr, ucComMF522Buf);
	return status;
}

/**
 * @DESCRIPTION: 读取钱包金额
 * @INPUT  ARGS: none
 * @OUTPUT ARGS: none
 * @NOTE       : none
 * @param {uint8_t} addr:块地址
 * @param {uint32_t} *pData:读出的金额
 * @return {*}: 成功返回PCD_OK
 */
char ReadAmount(uint8_t addr, uint32_t *pData)
{

	char status = PCD_ERR;
	uint8_t j;
	uint8_t ucComMF522Buf[16];
	status = PCD_ReadBlock(addr, ucComMF522Buf);
	if (status != PCD_OK)
		return status;
	for (j = 0; j < 4; j++)
	{
		if ((ucComMF522Buf[j] != ucComMF522Buf[j + 8]) && (ucComMF522Buf[j] != ~ucComMF522Buf[j + 4])) // 验证一下是不是钱包的数据
			break;
	}
	if (j == 4)
	{
		status = PCD_OK;
		*pData = ucComMF522Buf[0] + (ucComMF522Buf[1] << 8) + (ucComMF522Buf[2] << 16) + (ucComMF522Buf[3] << 24);
	}
	else
	{
		status = PCD_ERR;
		*pData = 0;
	}
	return status;
}

/**
 * @brief 修改控制块 addr 的密码A。注意 addr 指的是控制块的地址。
 *        必须要校验密码B,密码B默认为6个0xFF,如果密码B也忘记了,那就改不了密码A了
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 * @param addr:[控制块]所在的地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pKeyA:指向新的密码A字符串,六个字符,比如 "123456"
 * @retval 成功返回 PCD_OK
 */
char ChangeKeyA(uint8_t addr, uint8_t *pKeyA)
{
	uint8_t KeyBValue[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
	uint8_t ucArrayID[4];										// 先后存放IC卡的类型和UID(IC卡序列号)
	uint8_t ucComMF522Buf[16];
	uint8_t j;

	// 寻卡
	while (PCD_Request(PICC_REQALL, ucArrayID) != PCD_OK)
	{
		printf("寻卡失败\n");
		delay_ms(1000);
	}

	printf("寻卡成功\n");

	// 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)
	if (PCD_Anticoll(ucArrayID) == PCD_OK)
	{
		// 选中卡
		PCD_Select(ucArrayID);

		// 校验 B 密码
		if (PCD_AuthState(PICC_AUTHENT1B, addr, KeyBValue, ucArrayID) != PCD_OK)
		{
			printf("检验密码B失败\n");
		}

		// 读取控制块里原本的数据(只要修改密码A,其他数据不改)
		if (PCD_ReadBlock(addr, ucComMF522Buf) != PCD_OK)
		{
			printf("读取控制块数据失败\n");
			return PCD_ERR;
		}

		// 修改密码A
		for (j = 0; j < 6; j++)
			ucComMF522Buf[j] = pKeyA[j];

		if (PCD_WriteBlock(addr, ucComMF522Buf) != PCD_OK)
		{
			printf("写入数据到控制块失败\n");
			return PCD_ERR;
		}

		printf("密码A修改成功!\n");
		PCD_Halt();

		return PCD_OK;
	}

	return PCD_ERR;
}

/**
 * @brief 按照RC522操作流程写入16字节数据到块 addr
 *        函数里校验的是密码B,密码B默认为6个0xFF,也可以校验密码A
 *        用法:WriteDataBlock( 1, "123456789\n", 10); //字符串不够16个字节的后面补零写入
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 *        注意:使用该函数要注意 addr 是块0、数据块还是控制块,该函数内部不对此做判断
 * @param addr:任意块地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pData:指向要写入的数据,最大16个字符
 * @param Len:要写入数据的字节数
 * @retval 成功返回 PCD_OK
 */
char WriteDataBlock(uint8_t addr, uint8_t *pData, uint8_t Len)
{
	uint8_t KeyBValue[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
	uint8_t ucArrayID[4];										// 先后存放IC卡的类型和UID(IC卡序列号)
	uint8_t ucComMF522Buf[16];
	uint8_t j;

	// 寻卡
	while (PCD_Request(PICC_REQALL, ucArrayID) != PCD_OK)
	{
		printf("寻卡失败\n");
		delay_ms(1000);
	}

	printf("寻卡成功\n");

	// 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)
	if (PCD_Anticoll(ucArrayID) == PCD_OK)
	{
		// 选中卡
		PCD_Select(ucArrayID);

		// 校验 B 密码
		if (PCD_AuthState(PICC_AUTHENT1B, addr, KeyBValue, ucArrayID) != PCD_OK)
		{
			printf("检验密码B失败\n");
		}

		// 拷贝 pData 里的 Len 个字符到 ucComMF522Buf
		for (j = 0; j < 16; j++)
		{
			if (j < Len)
				ucComMF522Buf[j] = pData[j];
			else
				ucComMF522Buf[j] = 0; // 16个字节若是未填满的字节置0
		}

		// 写入字符串
		if (PCD_WriteBlock(addr, ucComMF522Buf) != PCD_OK)
		{
			printf("写入数据到数据块失败\n");
			return PCD_ERR;
		}

		printf("写入数据成功!\n");
		PCD_Halt();

		return PCD_OK;
	}

	return PCD_ERR;
}

/**
 * @brief 按照RC522操作流程读取块 addr
 *        函数里校验的是密码B,密码B默认为6个0xFF,也可以校验密码A
 *        用法:ReadDataBlock( 1, databuf);  // databuf 至少为16字节:uint8_t databuf[16];
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 *        注意:使用该函数要注意 addr 是块0、数据块还是控制块,该函数内部不对此做判断
 * @param addr:任意块地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pData:指向读取到的数据,包含16个字符
 * @retval 成功返回 PCD_OK
 */
char ReadDataBlock(uint8_t addr, uint8_t *pData)
{
	uint8_t KeyBValue[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
	uint8_t ucArrayID[4];										// 先后存放IC卡的类型和UID(IC卡序列号)

	// 寻卡
	while (PCD_Request(PICC_REQALL, ucArrayID) != PCD_OK)
	{
		printf("寻卡失败\n");
		delay_ms(1000);
	}

	printf("寻卡成功\n");

	// 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)
	if (PCD_Anticoll(ucArrayID) == PCD_OK)
	{
		// 选中卡
		PCD_Select(ucArrayID);

		// 校验 B 密码
		if (PCD_AuthState(PICC_AUTHENT1B, addr, KeyBValue, ucArrayID) != PCD_OK)
		{
			printf("检验密码B失败\n");
		}
		// 读取数据块里的数据到 pData
		if (PCD_ReadBlock(addr, pData) != PCD_OK)
		{
			printf("读取数据块失败\n");
			return PCD_ERR;
		}
		printf("读取数据成功!\n");
		PCD_Halt();
		return PCD_OK;
	}
	return PCD_ERR;
}

4.3 UART串口printf,scanf函数串口重定向

因本实验中的调试信息需要通过串口输出,Nucleo-446RE提供了利用USB的虚拟串口(串口2)。

这部分内容与基础篇007. 串行通信(一)--阻塞方式发送接收基本相同,只是把UART1换成UART2。以下仅提供代码截图,请大家参考前文实验。

在usart.c文件的user code 0 区域内:

输入如下内容:

 4.4 main()函数修改

修改Main.c函数头文件:

在下图红框区域添加代码:

 HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    delay_ms(1000);

    // if(!EntranceGuard(readUid, RfidIndicator))
    if (!EntranceGuard(readUid, NULL))
    {
      printf("当前卡号:%x-%x-%x-%x\n", readUid[0], readUid[1], readUid[2], readUid[3]);
      if (!strncmp((char *)readUid, (char *)UID, 4))
      {
        // TODO
        // 插入比对卡号正确时的处理程序,如打开门禁
        printf("已认证的卡\n");
        DoorSensor();
      }
      else
      {
        // TODO
        // 插入比对卡号错误时的处理程序
        printf("未认证卡\n");
      }
	HAL_Delay(2000);
    }

5.调试与验证

如果你需要AC5编译器,请参考如下博文安装设置:

Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载

程序编译通过后,可将其下载到开发板进行验证

实验需要使用串口调试助手验证。

串口调试助手下载地址

程序编译通过后,下载到开发板:

打开串口调试助手,用RFID卡在读卡器上测试,实验结果如下:

6.总结

本实验是STM32驱动RFID模块的第二部分,基础知识已在上一篇讲述:

关于RFID基础知识,请参考博文:

基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/579735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】Map、Set 模拟实现

文章目录 &#x1f4d5; 概念&#x1f4d5; 实现框架Find()★ 迭代器 ★反向迭代器map 的 operator[ ] &#x1f4d5; 源代码rb_tree.hset.hmap.h &#x1f4d5; 概念 map、set 是 C 中的关联式容器&#xff0c;由于 map 和set所开放的各种操作接口&#xff0c;RB-tree 也都提…

2023.05.28 学习周报

文章目录 摘要文献阅读1.题目2.现有方法存在的局限性3.SR-GNN模型4.模型的组成部分4.1 构图4.2 item向量表示4.3 session向量表示4.4 预测模块 5.实验与分析5.1 数据集5.2 比较方法5.3 评估指标5.4 实验结果 6.结论 有限元法1.一个例子2.进一步 深度学习1.张量场2.对流-扩散方程…

Linux(基础IO详解)

在基础IO这篇博客中&#xff0c;我们将了解到文件系统的构成&#xff0c;以及缓冲区究竟是个什么东东&#xff0c;我们都知道缓冲区&#xff0c;有时也谈论缓冲区&#xff0c;但不一定真的去深入了解过缓冲区。为什么内存和磁盘交互速度如此之慢&#xff1f;为什么都说Linux中一…

Dom解析与Sax解析的区别

1.Dom解析&#xff1a; Dom解析的时候&#xff0c;首先要把整个文件读取完毕&#xff0c;装载到内存中。然后进行解析&#xff0c;在解析的过程中&#xff0c;你可以直接获取某个节点&#xff0c;进行操作&#xff0c;也可以获取根节点然后进行遍历操作&#xff0c;得到所有的…

一台服务器通过apache安装多个web应用

当我们只有一台linux服务器资源但有创建多个网站的需求时&#xff0c;我们可以通过安装一个网站服务器Apache进行搭建&#xff0c;此次服务器使用Centos 7 下面分别介绍一个域名多个端口和多个域名用Apache来搭建多个网站的操作过程。 一、使用apache 服务器 &#xff08;一…

HCIA-MSTP替代技术之链路捆绑(LACP模式)

目录 手工链路聚合的不足&#xff1a; LACP链路聚合的原理 LACP模式&#xff1a; LACPDU&#xff1a; 1&#xff0c;设备优先级&#xff1a; 设备优先级的比较是&#xff1a;先比较优先级大小&#xff0c;0到32768&#xff0c;越小优先级越高&#xff0c;如果优先级相同&a…

华为FinalMLP

FinalMLP:An Enhanced Two-Stream MLP model for CTR Prediction 摘要 Two-Stream model&#xff1a;因为一个普通的MLP网络不足以学到丰富的特征交叉信息&#xff0c;因此大家提出了很多实用MLP和其他专用网络结合来学习。 MLP是隐式地学习特征交叉&#xff0c;当前很多工作…

分布式网络通信框架(二)——RPC通信原理和技术选型

项目实现功能 技术选型 黄色部分&#xff1a;设计rpc方法参数的打包和解析&#xff0c;也就是数据的序列化和反序列化&#xff0c;用protobuf做RPC方法调用的序列化和反序列化。 使用protobuf的好处: protobuf是二进制存储&#xff0c;xml和json是文本存储&#xff1b; pro…

哈希应用: 位图 + 布隆过滤器

文章目录 哈希应用: 位图 布隆过滤器1. 位图1.1 提出问题1.2 位图概念1.3 位图实现1.4 位图应用1.4.1 变形题1代码 1.4.2 变形题21.4.3 找文件交集思路1思路2 1.4.4 总结 1.5 位图优缺点 2. 哈希切割3. 布隆过滤器3.1 提出问题3.2 布隆过滤器概念3.3 布隆过滤器的各个接口3.3.…

MySQL---优化(insert、order by 、group by 、limit、子查询)

1. insert语句优化 当进行数据的insert操作的时候&#xff0c;可以考虑采用以下几种优化方案: -- 如果需要同时对一张表插入很多行数据时&#xff0c;应该尽量使用多个值表的insert语句&#xff0c;这种方式将大大的缩减 -- 客户端与数据库之间的连接、关闭等消耗。使得效率比…

R-Meta分析与【文献计量分析、贝叶斯、机器学习等】多技术融合实践与拓展

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

ARM体系结构

目录 ARM体系架构 一、ARM公司概述 二、ARM产品系列 三、指令、指令集 指令 指令集 ARM指令集 ARM指令集 Thumb指令集 &#xff08;属于ARM指令集&#xff09; 四、编译原理 五、ARM数据类型 字节序 大端对齐 小端对齐 六、ARM工作模式 1.AR…

Java中synchronized锁的深入理解

使用范围 synchronized使用上用于同步方法或者同步代码块在锁实现上是基于对象去实现使用中用于对static修饰的便是class类锁使用中用于对非static修饰的便是当前对象锁 synchronized的优化 在jdk1.6中对synchronized做了相关的优化 锁消除 在synchronized修饰的代码块中…

如何实现局域网下设备之间的互通互联和外网访问?

两台电脑怎么在同一路由下访问共享文件夹&#xff1f;两台不同系统的电脑在同一个路由器下访问共享文件夹进行数据共享&#xff0c;从本质上说就是在同一个局域网下设备之间的互通互联&#xff0c;这就需要我们搭建一个内网文件共享服务器来实现此功能 &#xff0c;比如常见的W…

linux系统中通配符与常用转义字符

通配符 在平时我们使用使用linux系统的过程中会遇到忘记文件名称的问题&#xff0c;这时候呢&#xff0c;通配符就发挥它的作用啦。 顾名思义啦&#xff0c;通配符就是用来匹配信息的符号&#xff0c;如何&#xff08;*&#xff09;代表零个或多个字符&#xff0c;&#xff08;…

Unity烟花特效实现(附源码)

Unity烟花特效 附代码 写在前面效果代码地址核心步骤 写在后面 写在前面 朋友过生&#xff0c;不知道送什么礼物&#xff0c;就想着用自己所学知识做个特效当礼物吧&#xff0c;嘿。 主要参考了 这位up的视频 &#xff0c;感谢 效果 代码地址 https://github.com/hahahappyb…

【LeetCode热题100】打开第5天:最长回文子串

文章目录 最长回文子串⛅前言&#x1f512;题目&#x1f511;题解 最长回文子串 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识…

linux高级---k8s中的五种控制器

文章目录 一、k8s的控制器类型二、pod与控制器之间的关系三、状态与无状态化对特点四、Deployment1、Deployment的资源清单文件2、在配置清单中调用deployment控制器3、镜像更新4、金丝雀发布5、删除Deployment 五、Statefulset六、DaemonSet1、daemonset的资源清单文件2、在配…

车载T-BOX

Telematics BOX&#xff0c;简称车载T-BOX&#xff0c;车载T-BOX主要用于和后台系统/手机APP通信&#xff0c;实现手机APP的车辆信息显示与控制 目录 1、车载T-BOX的定义 2、车载T-BOX的主要功能 2.1、数据采集和存储 2.2、远程查询和控制 2.3、道路救援 2.4、故障诊断 …

vue2_计算属性

目录 计算属性 计算属性缓存vs方法 计算属性vs侦听属性 getter和setter 计算属性和监听器 前端调用api实现问答 侦听器 计算属性 鉴于能在插值表达式中写js表达式&#xff1b;这样做也一定程度上违背了设计插值表达式的初衷&#xff1b;特别是&#xff1a; 其实就相当于…