目录
1. 实验硬件及原理图
2. 利用STM32CubeMX创建MDK工程
2.1 STM32CubeMX工程创建
2.2 配置调试方式
2.3 配置时钟电路
2.4 配置时钟
2.5 配置GPIO
2.6 配置串口
2.7 项目配置
3. MDK工程驱动代码调试
3.1 按键、LED程序
3.2 SPI软件模拟程序
3.3 RC522驱动程序
3.4 UART串口printf,scanf函数串口重定向
3.5 main()函数修改
4. 调试与验证
5. 总结
为增强程序可移植性,本文采用软件模拟方式驱动RC522。
本实验的RFID信息显示是通过串口实现的,关于串行通信请参考博文:
基础篇007. 串行通信(一)--阻塞方式发送接收
关于RFID基础知识,请参考博文:
基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识
关于利用STM32自带的硬件SPI接口驱动RC522,参见博文。
基础篇010.2 STM32驱动RC522 RFID模块之二:STM32硬件SPI驱动RC522
1. 实验硬件及原理图
本实验主要讲述采用软件模拟SPI的方式,驱动RFID-RC522模块。关于RFID的基础知识见STM32驱动RC522 RFID模块之一:基础知识,ARM自带的SPI口驱动RFID见STM32硬件SPI驱动RC522。
本实验中的原理与硬件与上一实验完全相同,请自行参考,本文只讲实现方法。
实验采用SPI方式实现单片机与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. 利用STM32CubeMX创建MDK工程
2.1 STM32CubeMX工程创建
选择File下的New Project:
选择芯片类型(本文为STM32F446RET6),选择下边的item,然后Start Project:
2.2 配置调试方式
点击左侧的System Core下的SYS,将Debug设置为Serial Wire:
2.3 配置时钟电路
配置时钟:将RCC下的HSE设置为Crystal/Ceramic Resonator
2.4 配置时钟
Nucleo-446RE开发板:
请结合开发版的硬件电路,从下面两种方式中二选一,选择第二种方式时,开发板中需要焊接相应元件(X3、C33、C34、R35、R37),或者你不能确定振荡电路,直接选第一种方式吧。
在STM32CubeMX中,做如下配置:
(1)采用内部8MHz时钟时选择Clock Configuration,做如下配置:
(2)使用外部时钟时,开发板需焊接的X3(8MHz)、C33、C34(20PF)、R36、R37),选择Clock Configuration,做如下配置:
2.5 配置GPIO
结合开发版的硬件电路,进行GPIO设置。RC522板有六个接口:SCK、MOSI、MISO、SDA、RST,前三项为SPI接口,后两项SDA(片选)、RST(复位)。SPI口可采用软件模拟。可以用ARM芯片自带的硬件SPI资源,方法见上一节。
在左侧选择System Core/GPIO,依次将SCK、MOSI、MISO、SDA、RST与LED连接的IO设置为GPIO_Output,将按键设置为GPIO_Input,按键对应的IO口设置为输入。电路图参考图6。
各IO口设置后的参数放大图如下:
2.6 配置串口
实验调试中的系统运行信息,可以通过串口输出。根据开发板的硬件电路,选中串口2。
USART2参数配置:
在 Connectivity 中选择 USART2 设置,并选择 Asynchronous 异步通信。
波特率为 115200 Bits/s。传输数据长度为8Bit。奇偶检验 None,停止位 1 ,接收和发送都使能。
本文的串口采用阻塞方式收发信息,无需设置中断。
2.7 项目配置
在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。
代码生成设置:
在Code Generate中选择第二个,然后Generate Code,即生成代码:
可以打开MDK工程编辑了。
3. MDK工程驱动代码调试
3.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 = RC522_MISO_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:按键按下
* @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;
}
3.2 SPI软件模拟程序
本实验采用软件模拟SPI的方式,驱动R522。在工程的“BSP”文件夹内,建立SPI文件夹,在文件夹内分别建立如下文件:
SPI.h文件的代码如下:
#ifndef _SPI_H
#define _SPI_H
#include "main.h"
void SPI_Init(void);
/* 软件模拟SPI发送一个字节数据,高位先行 */
void RC522_SPI_SendByte( uint8_t byte );
/* 软件模拟SPI读取一个字节数据,先读高位 */
uint8_t RC522_SPI_ReadByte( void );
#endif
SPI.c文件的代码如下:
#include <stdio.h>
#include "global/user.h"
#include "RC522\RC522.h"
#include "RC522\RFID.h"
#include "SPI\SPI.h"
/*************
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 │ 高电平 │ 偶数边沿(上升沿) │
* └─────────┴───────┴───────┴─────────────────┴─────────────────┘
**************/
//! 采用模拟方式实现SPI
/* IO口操作函数 */
#define RC522_MISO_GET() HAL_GPIO_ReadPin(RC522_MISO_GPIO_Port, RC522_MISO_Pin )
#define RC522_SCK(N) HAL_GPIO_WritePin(RC522_SCK_GPIO_Port, RC522_SCK_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)
#define RC522_MOSI(N) HAL_GPIO_WritePin(RC522_MOSI_GPIO_Port, RC522_MOSI_Pin, N == 1 ? GPIO_PIN_SET : GPIO_PIN_RESET)
/**
* @DESCRIPTION: 初始化SPI端口
* @INPUT ARGS : none
* @OUTPUT ARGS: none
* @RETURNS : none
* @NOTES : none
*/
void SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, RC522_MOSI_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(RC522_SCK_GPIO_Port, RC522_SCK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, RC522_SDA_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : PCPin PCPin */
GPIO_InitStruct.Pin = RC522_MOSI_Pin|RC522_RST_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = RC522_MISO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(RC522_MISO_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PBPin PBPin */
GPIO_InitStruct.Pin = RC522_SCK_Pin|RC522_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/**
* @DESCRIPTION: 软件模拟SPI发送一个字节数据,高位先行
* @INPUT ARGS : uint8_t byte:发送的字节
* @OUTPUT ARGS: none
* @RETURNS : none
* @NOTES : none
*/
void RC522_SPI_SendByte( uint8_t byte )
{
uint8_t n;
for( n=0;n<8;n++ )
{
if( byte&0x80 )
RC522_MOSI(1);
else
RC522_MOSI(0);
delay_us(5);
RC522_SCK(0);
delay_us(5);
RC522_SCK(1);
delay_us(5);
byte<<=1;
}
}
/**
* @DESCRIPTION: 软件模拟SPI读取一个字节数据,先读高位
* @INPUT ARGS : none
* @OUTPUT ARGS: none
* @RETURNS : uint8_t data,接收到的数据
* @NOTES : none
*/
uint8_t RC522_SPI_ReadByte( void )
{
uint8_t n,data;
for( n=0;n<8;n++ )
{
data<<=1;
RC522_SCK(0);
delay_us(5);
if( RC522_MISO_GET()==1 )
data|=0x01;
delay_us(5);
RC522_SCK(1);
delay_us(5);
}
return data;
}
3.3 RC522驱动程序
在工程的“BSP”文件夹内,分别建立如下文件:
其中,RC522.c及RC522.h为MFRC522芯片的通用驱动程序,这部分程序可以移植到任何单片机上。RFID.h和RFID.c是针对Mifare 1卡的应用代码。
RC522.h文件的代码如下:
#ifndef _RC522_H
#define _RC522_H
#include "main.h"
#include "stm32f4xx_hal.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 <stdio.h>
#include <string.h>
#include "global/user.h"
#include "RC522\RC522.h"
#include "RC522\RFID.h"
#include "SPI\SPI.h"
#define osDelay HAL_Delay
#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)
//extern SPI_HandleTypeDef hspi2;
/**************************************************************************************
* 函数名称: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)
{
// 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]);
}
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)
{
if (!(MFRC_ReadReg(MFRC_ErrorReg) & 0x1B))
{
status = MFRC_OK;
if (n & irqEn & 0x01)
{
status = MFRC_NOTAGERR;
}
if (cmd == MFRC_TRANSCEIVE)
{
n = MFRC_ReadReg(MFRC_FIFOLevelReg);
lastBits = MFRC_ReadReg(MFRC_ControlReg) & 0x07;
if (lastBits)
{
*pOutLenBit = (n - 1) * 8 + lastBits;
}
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);
MFRC_WriteReg(MFRC_BitFramingReg, 0x00);
MFRC_ClrBitMask(MFRC_CollReg, 0x80);
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];
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:块地址
* -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] = {0xd7, 0x6e, 0xaa, 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;
}
3.4 UART串口printf,scanf函数串口重定向
因本实验中的调试信息需要通过串口输出,Nucleo-446RE提供了利用USB的虚拟串口(串口2)。
这部分内容与基础篇007. 串行通信(一)--阻塞方式发送接收基本相同,只是把UART1换成UART2。以下仅提供代码截图,请大家参考前文实验。
在usart.c文件的user code 0 区域内:
输入如下内容:
#include <stdio.h>
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1 // 方便调试,改为“#if 0”不要以下功能
#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) // AC6编译器
// 加入以下代码,支持printf函数,而不需要选择use MicroLIB
__asm(".global __use_no_semihosting\n\t");
void _sys_exit(int x)
{ // 定义_sys_exit()以避免使用半主机模式
x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{
ch = ch;
}
FILE __stdout;
#elif defined(__CC_ARM) // AC5编译器
// #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#pragma import(__use_no_semihosting)
// 标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
#endif
//重定义fputc函数
// int fputc(int ch, FILE *f)
//{
// while((USART2->SR&0X40)==0);//循环发送,直到发送完毕
// USART2->DR = (u8) ch;
// return ch;
// }
// 重定向 c 库函数 printf 到串口 USARTx,重定向后可使用 printf 函数
// 重定向 c 库函数 scanf 到串口 USARTx,重写向后可使用 scanf、 getchar 等函数
// 使用 printf、 scanf 函数需要在文件中包含 stdio.h 头文件。
#if defined(__GNUC__) && !defined(__clang__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(FILE *f)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif
// 改写fputc函数。【有多个串口是,需要修改下面的代码】
PUTCHAR_PROTOTYPE
{
// 发送一个字节数据到串口USARTx
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1000); // 发送数据到串口
return ch;
}
GETCHAR_PROTOTYPE
{
uint8_t ch = 0;
// 等待串口输入数据
// while (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) == RESET);
HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
return ch;
}
#endif
//! 用户添加的变量定义--->>>
uint8_t Rx1_Byte; // 定义串口1接收字节寄存器
uint8_t Rx1_Buff[256]; // 定义串口1接收缓冲器,此处默认最大缓存为256字节。
uint16_t Rx1_Count; // 定义串口1接收计数器
3.5 main()函数修改
修改Main.c函数头文件:
在下图红框区域添加代码:
#define Debug_CodeArea7 // 定义调试的程序段
#if defined Debug_CodeArea7
// 门禁
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");
}
}
4. 调试与验证
如果你需要AC5编译器,请参考如下博文安装设置:
Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载
程序编译通过后,可将其下载到开发板进行验证
实验需要使用串口调试助手验证。
串口调试助手: 下载地址
程序编译通过后,打开串口调试助手,用RFID卡在读卡器上测试,实验结果如下:
5. 总结
本实验是STM32驱动RFID模块的第二部分,基础知识已在上一篇讲述:
关于RFID基础知识,请参考博文:
基础篇010.1 STM32驱动RC522 RFID模块之一:基础知识
关于利用STM32自带的硬件SPI接口驱动RC522,参见博文。
基础篇010.2 STM32驱动RC522 RFID模块之二:STM32硬件SPI驱动RC522