RC522射频识别IC卡感应模块
近场通信(NEAR FIELD COMMUNICATION, NFC),又称近距离无线通信,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输(在十厘米内)交换数据。这个技术由免接触式射频识别(RFID)演变而来,并向下兼容RFID,最早由SONY和PHILIPS各自开发成功,主要用于手机等手持设备中提供M2M(MACHINE TO MACHINE)的通信。由于近场通讯具有天然的安全性,因此,NFC技术被认为在手机支付等领域具有很大的应用前景。同时,NFC也因为其相比于其他无线通讯技术较好的安全性被中国物联网校企联盟比作机器之间的“安全对话”。
模块来源
采购链接:
https://item.taobao.com/item.htm?spm=a21n57.1.0.0.33fb523cor6YaQ&id=19376862391&ns=1&abbucket=0#detail
资料下载:
https://pan.baidu.com/s/1pGSaohXnOi8tu6M3i3KFcQ
提取码: suah
规格参数
工作电压:3.3V
工作电流:10-26mA
模块尺寸:40mm×60mm
支持的卡类型:mifare1 S50、mifare1 S70、mifare UltraLight、mifare Pro、mifare Desfire
控制方式:SPI
移植过程
我们的目标是在天空星HC32F4A0PITB上能够识别IC卡的ID并进行读写的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
查看资料
S50非接触式IC卡,分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,(我们也将16个扇区的64个块按绝对地址编号为0~63,存贮结构如下图所示:
- 第0扇区的块0(即绝对地址0块),它用于存放厂商代码,已经固化,不可更改。
- 每个扇区的块0、块1、块2为数据块,可用于存贮数据。 数据块可作两种应用:
- 用作一般的数据保存,可以进行读、写操作。
- 用作数据值,可以进行初始化值、加值、减值、读值操作。
- 每个扇区的块3为控制块,包括了密码A、存取控制、密码B。具体结构如下:
A0 A1 A2 A3 A4 A5
FF 07 80 69
B0 B1 B2 B3 B4 B5
密码A(6字节)
存取控制(4字节)
密码B(6字节) - 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位,定义如下:
块0: C10 C20 C30
块1: C11 C21 C31
块2: C12 C22 C32
块3: C13 C23 C33
三个控制位以正和反两种形式存在于存取控制字节中,决定了该块的访问权限(如
进行减值操作必须验证KEY A,进行加值操作必须验证KEY B,等等)。三个控制
位在存取控制字节中的位置,以块0为例:
对块0的控制:
bit 7 6 5 4 3 2 1 0
2
3
4
5
6
7
8
9
( 注: C10_b表示C10取反 )
存取控制(4字节,其中字节9为备用字节)结构如下所示:
bit 7 6 5 4 3 2 1 0
( 注: _b表示取反 )
2
3
4
5
6
7
8
- 数据块(块0、块1、块2)的存取控制如下:
(KeyA|B 表示密码A或密码B,Never表示任何条件下不能实现)
例如:当块0的存取控制位C10 C20 C30=1 0 0时,验证密码A或密码B正确后可读;验证密码B正确后可写;不能进行加值、减值操作。
- 控制块块3的存取控制与数据块(块0、1、2)不同,它的存取控制如下:
例如:当块3的存取控制位C13 C23 C33=1 0 0时,表示:
密码A:不可读,验证KEYA或KEYB正确后,可写(更改)。
存取控制:验证KEYA或KEYB正确后,可读、可写。
密码B:验证KEYA或KEYB正确后,可读、可写。
- M1射频卡与读写器的通讯
引脚选择
移植至工程
移植步骤中的导入.c和.h文件与第二章的第1小节【DHT11温湿度传感器】相同,只是将.c和.h文件更改为bsp_rc522.c与bsp_rc522.h。这里不再过多讲述,移植完成后面修改相关代码。
在文件bsp_rc522.c中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-05-21 LCKFB-LP first version
*/
#include "bsp_rc522.h"
#include "board.h"
/******************************************************************
* 函 数 名 称:RC522_Init
* 函 数 说 明:IC卡感应模块配置
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:
******************************************************************/
void RC522_Init(void)
{
stc_gpio_init_t stcGpioInit; // 定义GPIO结构体
// 关闭寄存器写保护
LL_PERIPH_WE(LL_PERIPH_ALL);
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinState = PIN_STAT_SET; // 状态选择高电平
stcGpioInit.u16PinDir = PIN_DIR_OUT; // 输出模式
stcGpioInit.u16PinDrv = PIN_HIGH_DRV; // 选择高驱动力
stcGpioInit.u16PinAttr = PIN_ATTR_DIGITAL; // 数字模式
stcGpioInit.u16PullUp = PIN_PU_ON; // 上拉开启
stcGpioInit.u16PinOutputType = PIN_OUT_TYPE_CMOS; // 推挽输出
stcGpioInit.u16PinInputType = PIN_IN_TYPE_SMT;
// 初始化 CS 引脚
(void)GPIO_Init(PORT_CS, GPIO_CS, &stcGpioInit);
GPIO_SetPins(PORT_CS,GPIO_CS);
// SCK
(void)GPIO_Init(PORT_SCK, GPIO_SCK, &stcGpioInit);
GPIO_SetPins(PORT_SCK,GPIO_SCK);
// MOSI
(void)GPIO_Init(PORT_MOSI, GPIO_MOSI, &stcGpioInit);
GPIO_SetPins(PORT_MOSI,GPIO_MOSI);
// RST
(void)GPIO_Init(PORT_RST, GPIO_RST, &stcGpioInit);
GPIO_SetPins(PORT_RST,GPIO_RST);
// MISO
stcGpioInit.u16PinDir = PIN_DIR_IN; // 输入模式
stcGpioInit.u16PullUp = PIN_PU_OFF; // 上拉关闭
(void)GPIO_Init(PORT_MISO, GPIO_MISO, &stcGpioInit);
}
////////////////软件模拟SPI与RC522通信///////////////////////////////////////////
/* 软件模拟SPI发送一个字节数据,高位先行 */
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(200);
RC522_SCK_0();
delay_us(200);
RC522_SCK_1();
delay_us(200);
byte<<=1;
}
}
/* 软件模拟SPI读取一个字节数据,先读高位 */
uint8_t RC522_SPI_ReadByte( void )
{
uint8_t n,data;
for( n=0;n<8;n++ )
{
data<<=1;
RC522_SCK_0();
delay_us(200);
if( RC522_MISO_GET()==1 )
data|=0x01;
delay_us(200);
RC522_SCK_1();
delay_us(200);
}
return data;
}
//////////////////////////HC32对RC522寄存器的操作//////////////////////////////////
/* 读取RC522指定寄存器的值
向RC522指定寄存器中写入指定的数据
置位RC522指定寄存器的指定位
清位RC522指定寄存器的指定位
*/
/**
* @brief :读取RC522指定寄存器的值
* @param :Address:寄存器的地址
* @retval :寄存器的值
*/
uint8_t RC522_Read_Register( uint8_t Address )
{
uint8_t data,Addr;
Addr = ( (Address<<1)&0x7E )|0x80;
RC522_CS_Enable();
RC522_SPI_SendByte( Addr );
data = RC522_SPI_ReadByte();//读取寄存器中的值
RC522_CS_Disable();
return data;
}
/**
* @brief :向RC522指定寄存器中写入指定的数据
* @param :Address:寄存器地址
data:要写入寄存器的数据
* @retval :无
*/
void RC522_Write_Register( uint8_t Address, uint8_t data )
{
uint8_t Addr;
Addr = ( Address<<1 )&0x7E;
RC522_CS_Enable();
RC522_SPI_SendByte( Addr );
RC522_SPI_SendByte( data );
RC522_CS_Disable();
}
/**
* @brief :置位RC522指定寄存器的指定位
* @param :Address:寄存器地址
mask:置位值
* @retval :无
*/
void RC522_SetBit_Register( uint8_t Address, uint8_t mask )
{
uint8_t temp;
/* 获取寄存器当前值 */
temp = RC522_Read_Register( Address );
/* 对指定位进行置位操作后,再将值写入寄存器 */
RC522_Write_Register( Address, temp|mask );
}
/**
* @brief :清位RC522指定寄存器的指定位
* @param :Address:寄存器地址
mask:清位值
* @retval :无
*/
void RC522_ClearBit_Register( uint8_t Address, uint8_t mask )
{
uint8_t temp;
/* 获取寄存器当前值 */
temp = RC522_Read_Register( Address );
/* 对指定位进行清位操作后,再将值写入寄存器 */
RC522_Write_Register( Address, temp&(~mask) );
}
///////////////////STM32对RC522的基础通信///////////////////////////////////
/*
开启天线
关闭天线
复位RC522
设置RC522工作方式
*/
/**
* @brief :开启天线
* @param :无
* @retval :无
*/
void RC522_Antenna_On( void )
{
uint8_t k;
k = RC522_Read_Register( TxControlReg );
/* 判断天线是否开启 */
if( !( k&0x03 ) )
RC522_SetBit_Register( TxControlReg, 0x03 );
}
/**
* @brief :关闭天线
* @param :无
* @retval :无
*/
void RC522_Antenna_Off( void )
{
/* 直接对相应位清零 */
RC522_ClearBit_Register( TxControlReg, 0x03 );
}
/**
* @brief :复位RC522
* @param :无
* @retval :无
*/
void RC522_Rese( void )
{
RC522_Reset_Disable();
delay_us ( 1 );
RC522_Reset_Enable();
delay_us ( 1 );
RC522_Reset_Disable();
delay_us ( 1 );
RC522_Write_Register( CommandReg, 0x0F );
while( RC522_Read_Register( CommandReg )&0x10 )
;
/* 缓冲一下 */
delay_us ( 1 );
RC522_Write_Register( ModeReg, 0x3D ); //定义发送和接收常用模式
RC522_Write_Register( TReloadRegL, 30 ); //16位定时器低位
RC522_Write_Register( TReloadRegH, 0 ); //16位定时器高位
RC522_Write_Register( TModeReg, 0x8D ); //内部定时器的设置
RC522_Write_Register( TPrescalerReg, 0x3E ); //设置定时器分频系数
RC522_Write_Register( TxAutoReg, 0x40 ); //调制发送信号为100%ASK
}
/**
* @brief :设置RC522的工作方式
* @param :Type:工作方式
* @retval :无
M500PcdConfigISOType
*/
void RC522_Config_Type( char Type )
{
if( Type=='A' )
{
RC522_ClearBit_Register( Status2Reg, 0x08 );
RC522_Write_Register( ModeReg, 0x3D );
RC522_Write_Register( RxSelReg, 0x86 );
RC522_Write_Register( RFCfgReg, 0x7F );
RC522_Write_Register( TReloadRegL, 30 );
RC522_Write_Register( TReloadRegH, 0 );
RC522_Write_Register( TModeReg, 0x8D );
RC522_Write_Register( TPrescalerReg, 0x3E );
delay_us(2);
/* 开天线 */
RC522_Antenna_On();
}
}
/////////////////////////STM32控制RC522与M1卡的通信///////////////////////////////////////
/*
通过RC522和M1卡通讯(数据的双向传输)
寻卡
防冲突
用RC522计算CRC16(循环冗余校验)
选定卡片
校验卡片密码
在M1卡的指定块地址写入指定数据
读取M1卡的指定块地址的数据
让卡片进入休眠模式
*/
/**
* @brief :通过RC522和ISO14443卡通讯
* @param :ucCommand:RC522命令字
* pInData:通过RC522发送到卡片的数据
* ucInLenByte:发送数据的字节长度
* pOutData:接收到的卡片返回数据
* pOutLenBit:返回数据的位长度
* @retval :状态值MI_OK,成功
*/
char PcdComMF522 ( uint8_t ucCommand, uint8_t * pInData, uint8_t ucInLenByte, uint8_t * pOutData, uint32_t * pOutLenBit )
{
char cStatus = MI_ERR;
uint8_t ucIrqEn = 0x00;
uint8_t ucWaitFor = 0x00;
uint8_t ucLastBits;
uint8_t ucN;
uint32_t ul;
switch ( ucCommand )
{
case PCD_AUTHENT: //Mifare认证
ucIrqEn = 0x12; //允许错误中断请求ErrIEn 允许空闲中断IdleIEn
ucWaitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位
break;
case PCD_TRANSCEIVE: //接收发送 发送接收
ucIrqEn = 0x77; //允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
ucWaitFor = 0x30; //寻卡等待时候 查询接收中断标志位与 空闲中断标志位
break;
default:
break;
}
RC522_Write_Register ( ComIEnReg, ucIrqEn | 0x80 ); //IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反
RC522_ClearBit_Register ( ComIrqReg, 0x80 ); //Set1该位清零时,CommIRqReg的屏蔽位清零
RC522_Write_Register ( CommandReg, PCD_IDLE ); //写空闲命令
RC522_SetBit_Register ( FIFOLevelReg, 0x80 ); //置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除
for ( ul = 0; ul < ucInLenByte; ul ++ )
RC522_Write_Register ( FIFODataReg, pInData [] ); //写数据进FIFOdata
RC522_Write_Register ( CommandReg, ucCommand ); //写命令
if ( ucCommand == PCD_TRANSCEIVE )
RC522_SetBit_Register(BitFramingReg,0x80); //StartSend置位启动数据发送 该位与收发命令使用时才有效
ul = 1000;//根据时钟频率调整,操作M1卡最大等待时间25ms
do //认证 与寻卡等待时间
{
ucN = RC522_Read_Register ( ComIrqReg ); //查询事件中断
ul --;
} while ( ( ul != 0 ) && ( ! ( ucN & 0x01 ) ) && ( ! ( ucN & ucWaitFor ) ) ); //退出条件i=0,定时器中断,与写空闲命令
RC522_ClearBit_Register ( BitFramingReg, 0x80 ); //清理允许StartSend位
if ( ul != 0 )
{
if ( ! ( RC522_Read_Register ( ErrorReg ) & 0x1B ) ) //读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
{
cStatus = MI_OK;
if ( ucN & ucIrqEn & 0x01 ) //是否发生定时器中断
cStatus = MI_NOTAGERR;
if ( ucCommand == PCD_TRANSCEIVE )
{
ucN = RC522_Read_Register ( FIFOLevelReg ); //读FIFO中保存的字节数
ucLastBits = RC522_Read_Register ( ControlReg ) & 0x07; //最后接收到得字节的有效位数
if ( ucLastBits )
* pOutLenBit = ( ucN - 1 ) * 8 + ucLastBits; //N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
else
* pOutLenBit = ucN * 8; //最后接收到的字节整个字节有效
if ( ucN == 0 )
ucN = 1;
if ( ucN > MAXRLEN )
ucN = MAXRLEN;
for ( ul = 0; ul < ucN; ul ++ )
pOutData [] = RC522_Read_Register ( FIFODataReg );
}
}
else
cStatus = MI_ERR;
}
RC522_SetBit_Register ( ControlReg, 0x80 ); // stop timer now
RC522_Write_Register ( CommandReg, PCD_IDLE );
return cStatus;
}
/**
* @brief :寻卡
* @param ucReq_code,寻卡方式
* = 0x52:寻感应区内所有符合14443A标准的卡
* = 0x26:寻未进入休眠状态的卡
* pTagType,卡片类型代码
* = 0x4400:Mifare_UltraLight
* = 0x0400:Mifare_One(S50)
* = 0x0200:Mifare_One(S70)
* = 0x0800:Mifare_Pro(X))
* = 0x4403:Mifare_DESFire
* @retval :状态值MI_OK,成功
*/
char PcdRequest ( uint8_t ucReq_code, uint8_t * pTagType )
{
char cStatus;
uint8_t ucComMF522Buf [];
uint32_t ulLen;
RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况
RC522_Write_Register ( BitFramingReg, 0x07 ); // 发送的最后一个字节的 七位
RC522_SetBit_Register ( TxControlReg, 0x03 ); //TX1,TX2管脚的输出信号传递经发送调制的13.46的能量载波信号
ucComMF522Buf [] = ucReq_code; //存入寻卡方式
/* PCD_TRANSCEIVE:发送并接收数据的命令,RC522向卡片发送寻卡命令,卡片返回卡的型号代码到ucComMF522Buf中 */
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, & ulLen ); //寻卡
if ( ( cStatus == MI_OK ) && ( ulLen == 0x10 ) ) //寻卡成功返回卡类型
{
/* 接收卡片的型号代码 */
* pTagType = ucComMF522Buf [];
* ( pTagType + 1 ) = ucComMF522Buf [];
}
else
cStatus = MI_ERR;
return cStatus;
}
/**
* @brief :防冲突
* @param :Snr:卡片序列,4字节,会返回选中卡片的序列
* @retval :状态值MI_OK,成功
*/
char PcdAnticoll ( uint8_t * pSnr )
{
char cStatus;
uint8_t uc, ucSnr_check = 0;
uint8_t ucComMF522Buf [];
uint32_t ulLen;
RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位
RC522_Write_Register ( BitFramingReg, 0x00); //清理寄存器 停止收发
RC522_ClearBit_Register ( CollReg, 0x80 ); //清ValuesAfterColl所有接收的位在冲突后被清除
ucComMF522Buf [] = 0x93; //卡片防冲突命令
ucComMF522Buf [] = 0x20;
/* 将卡片防冲突命令通过RC522传到卡片中,返回的是被选中卡片的序列 */
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, & ulLen);//与卡片通信
if ( cStatus == MI_OK) //通信成功
{
for ( uc = 0; uc < 4; uc ++ )
{
* ( pSnr + uc ) = ucComMF522Buf []; //读出UID
ucSnr_check ^= ucComMF522Buf [];
}
if ( ucSnr_check != ucComMF522Buf [] )
cStatus = MI_ERR;
}
RC522_SetBit_Register ( CollReg, 0x80 );
return cStatus;
}
/**
* @brief :用RC522计算CRC16(循环冗余校验)
* @param :pIndata:计算CRC16的数组
* ucLen:计算CRC16的数组字节长度
* pOutData:存放计算结果存放的首地址
* @retval :状态值MI_OK,成功
*/
void CalulateCRC ( uint8_t * pIndata, u8 ucLen, uint8_t * pOutData )
{
uint8_t uc, ucN;
RC522_ClearBit_Register(DivIrqReg,0x04);
RC522_Write_Register(CommandReg,PCD_IDLE);
RC522_SetBit_Register(FIFOLevelReg,0x80);
for ( uc = 0; uc < ucLen; uc ++)
RC522_Write_Register ( FIFODataReg, * ( pIndata + uc ) );
RC522_Write_Register ( CommandReg, PCD_CALCCRC );
uc = 0xFF;
do
{
ucN = RC522_Read_Register ( DivIrqReg );
uc --;
} while ( ( uc != 0 ) && ! ( ucN & 0x04 ) );
pOutData [] = RC522_Read_Register ( CRCResultRegL );
pOutData [] = RC522_Read_Register ( CRCResultRegM );
}
/**
* @brief :选定卡片
* @param :pSnr:卡片序列号,4字节
* @retval :状态值MI_OK,成功
*/
char PcdSelect ( uint8_t * pSnr )
{
char ucN;
uint8_t uc;
uint8_t ucComMF522Buf [];
uint32_t ulLen;
/* PICC_ANTICOLL1:防冲突命令 */
ucComMF522Buf [] = PICC_ANTICOLL1;
ucComMF522Buf [] = 0x70;
ucComMF522Buf [] = 0;
for ( uc = 0; uc < 4; uc ++ )
{
ucComMF522Buf [] = * ( pSnr + uc );
ucComMF522Buf [] ^= * ( pSnr + uc );
}
CalulateCRC ( ucComMF522Buf, 7, & ucComMF522Buf [] );
RC522_ClearBit_Register ( Status2Reg, 0x08 );
ucN = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, & ulLen );
if ( ( ucN == MI_OK ) && ( ulLen == 0x18 ) )
ucN = MI_OK;
else
ucN = MI_ERR;
return ucN;
}
/**
* @brief :校验卡片密码
* @param :ucAuth_mode:密码验证模式
* = 0x60,验证A密钥
* = 0x61,验证B密钥
* ucAddr:块地址
* pKey:密码
* pSnr:卡片序列号,4字节
* @retval :状态值MI_OK,成功
*/
char PcdAuthState ( uint8_t ucAuth_mode, uint8_t ucAddr, uint8_t * pKey, uint8_t * pSnr )
{
char cStatus;
uint8_t uc, ucComMF522Buf [];
uint32_t ulLen;
ucComMF522Buf [] = ucAuth_mode;
ucComMF522Buf [] = ucAddr;
/* 前俩字节存储验证模式和块地址,2~8字节存储密码(6个字节),8~14字节存储序列号 */
for ( uc = 0; uc < 6; uc ++ )
ucComMF522Buf [] = * ( pKey + uc );
for ( uc = 0; uc < 6; uc ++ )
ucComMF522Buf [] = * ( pSnr + uc );
/* 进行冗余校验,14~16俩个字节存储校验结果 */
cStatus = PcdComMF522 ( PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, & ulLen );
/* 判断验证是否成功 */
if ( ( cStatus != MI_OK ) || ( ! ( RC522_Read_Register ( Status2Reg ) & 0x08 ) ) )
cStatus = MI_ERR;
return cStatus;
}
/**
* @brief :在M1卡的指定块地址写入指定数据
* @param :ucAddr:块地址
* pData:写入的数据,16字节
* @retval :状态值MI_OK,成功
*/
char PcdWrite ( uint8_t ucAddr, uint8_t * pData )
{
char cStatus;
uint8_t uc, ucComMF522Buf [];
uint32_t ulLen;
ucComMF522Buf [] = PICC_WRITE;//写块命令
ucComMF522Buf [] = ucAddr;//写块地址
/* 进行循环冗余校验,将结果存储在& ucComMF522Buf [] */
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [] );
/* PCD_TRANSCEIVE:发送并接收数据命令,通过RC522向卡片发送写块命令 */
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
/* 通过卡片返回的信息判断,RC522是否与卡片正常通信 */
if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [] & 0x0F ) != 0x0A ) )
cStatus = MI_ERR;
if ( cStatus == MI_OK )
{
//memcpy(ucComMF522Buf, pData, 16);
/* 将要写入的16字节的数据,传入ucComMF522Buf数组中 */
for ( uc = 0; uc < 16; uc ++ )
ucComMF522Buf [] = * ( pData + uc );
/* 冗余校验 */
CalulateCRC ( ucComMF522Buf, 16, & ucComMF522Buf [] );
/* 通过RC522,将16字节数据包括2字节校验结果写入卡片中 */
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, & ulLen );
/* 判断写地址是否成功 */
if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [] & 0x0F ) != 0x0A ) )
cStatus = MI_ERR;
}
return cStatus;
}
/**
* @brief :读取M1卡的指定块地址的数据
* @param :ucAddr:块地址
* pData:读出的数据,16字节
* @retval :状态值MI_OK,成功
*/
char PcdRead ( uint8_t ucAddr, uint8_t * pData )
{
char cStatus;
uint8_t uc, ucComMF522Buf [];
uint32_t ulLen;
ucComMF522Buf [] = PICC_READ;
ucComMF522Buf [] = ucAddr;
/* 冗余校验 */
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [] );
/* 通过RC522将命令传给卡片 */
cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
/* 如果传输正常,将读取到的数据传入pData中 */
if ( ( cStatus == MI_OK ) && ( ulLen == 0x90 ) )
{
for ( uc = 0; uc < 16; uc ++ )
* ( pData + uc ) = ucComMF522Buf [];
}
else
cStatus = MI_ERR;
return cStatus;
}
/**
* @brief :让卡片进入休眠模式
* @param :无
* @retval :状态值MI_OK,成功
*/
char PcdHalt( void )
{
uint8_t ucComMF522Buf [];
uint32_t ulLen;
ucComMF522Buf [] = PICC_HALT;
ucComMF522Buf [] = 0;
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [] );
PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
return MI_OK;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
在文件bsp_rc522.h中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-05-21 LCKFB-LP first version
*/
#ifndef _BSP_RC522_H
#define _BSP_RC522_H
#include "hc32_ll.h"
#ifndef u8
#define u8 uint8_t
#endif
#ifndef u16
#define u16 uint16_t
#endif
#ifndef u32
#define u32 uint32_t
#endif
//SDA
#define PORT_CS GPIO_PORT_A
#define GPIO_CS GPIO_PIN_05
//SCK
#define PORT_SCK GPIO_PORT_A
#define GPIO_SCK GPIO_PIN_07
//MOSI
#define PORT_MOSI GPIO_PORT_C
#define GPIO_MOSI GPIO_PIN_04
//RST
#define PORT_RST GPIO_PORT_A
#define GPIO_RST GPIO_PIN_06
//MISO
#define PORT_MISO GPIO_PORT_A
#define GPIO_MISO GPIO_PIN_04
/* IO口操作函数 */
#define RC522_CS_Enable() GPIO_ResetPins(PORT_CS, GPIO_CS)
#define RC522_CS_Disable() GPIO_SetPins(PORT_CS, GPIO_CS)
#define RC522_Reset_Enable() GPIO_ResetPins(PORT_RST, GPIO_RST)
#define RC522_Reset_Disable() GPIO_SetPins(PORT_RST, GPIO_RST)
#define RC522_SCK_0() GPIO_ResetPins(PORT_SCK, GPIO_SCK)
#define RC522_SCK_1() GPIO_SetPins(PORT_SCK, GPIO_SCK)
#define RC522_MOSI_0() GPIO_ResetPins(PORT_MOSI, GPIO_MOSI)
#define RC522_MOSI_1() GPIO_SetPins(PORT_MOSI, GPIO_MOSI)
#define RC522_MISO_GET() GPIO_ReadInputPins(PORT_MISO,GPIO_MISO)
//RC522命令字
#define PCD_IDLE 0x00 //取消当前命令
#define PCD_AUTHENT 0x0E //验证密钥
#define PCD_RECEIVE 0x08 //接收数据
#define PCD_TRANSMIT 0x04 //发送数据
#define PCD_TRANSCEIVE 0x0C //发送并接收数据
#define PCD_RESETPHASE 0x0F //复位
#define PCD_CALCCRC 0x03 //CRC计算
//Mifare_One卡片命令字
#define PICC_REQIDL 0x26 //寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 //寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 //防冲撞
#define PICC_ANTICOLL2 0x95 //防冲撞
#define PICC_AUTHENT1A 0x60 //验证A密钥
#define PICC_AUTHENT1B 0x61 //验证B密钥
#define PICC_READ 0x30 //读块
#define PICC_WRITE 0xA0 //写块
#define PICC_DECREMENT 0xC0 //扣款
#define PICC_INCREMENT 0xC1 //充值
#define PICC_RESTORE 0xC2 //调块数据到缓冲区
#define PICC_TRANSFER 0xB0 //保存缓冲区中数据
#define PICC_HALT 0x50 //休眠
/* RC522 FIFO长度定义 */
#define DEF_FIFO_LENGTH 64 //FIFO size=64byte
#define MAXRLEN 18
/* RC522寄存器定义 */
// PAGE 0
#define RFU00 0x00 //保留
#define CommandReg 0x01 //启动和停止命令的执行
#define ComIEnReg 0x02 //中断请求传递的使能(Enable/Disable)
#define DivlEnReg 0x03 //中断请求传递的使能
#define ComIrqReg 0x04 //包含中断请求标志
#define DivIrqReg 0x05 //包含中断请求标志
#define ErrorReg 0x06 //错误标志,指示执行的上个命令的错误状态
#define Status1Reg 0x07 //包含通信的状态标识
#define Status2Reg 0x08 //包含接收器和发送器的状态标志
#define FIFODataReg 0x09 //64字节FIFO缓冲区的输入和输出
#define FIFOLevelReg 0x0A //指示FIFO中存储的字节数
#define WaterLevelReg 0x0B //定义FIFO下溢和上溢报警的FIFO深度
#define ControlReg 0x0C //不同的控制寄存器
#define BitFramingReg 0x0D //面向位的帧的调节
#define CollReg 0x0E //RF接口上检测到的第一个位冲突的位的位置
#define RFU0F 0x0F //保留
// PAGE 1
#define RFU10 0x10 //保留
#define ModeReg 0x11 //定义发送和接收的常用模式
#define TxModeReg 0x12 //定义发送过程的数据传输速率
#define RxModeReg 0x13 //定义接收过程中的数据传输速率
#define TxControlReg 0x14 //控制天线驱动器管教TX1和TX2的逻辑特性
#define TxAutoReg 0x15 //控制天线驱动器的设置
#define TxSelReg 0x16 //选择天线驱动器的内部源
#define RxSelReg 0x17 //选择内部的接收器设置
#define RxThresholdReg 0x18 //选择位译码器的阈值
#define DemodReg 0x19 //定义解调器的设置
#define RFU1A 0x1A //保留
#define RFU1B 0x1B //保留
#define MifareReg 0x1C //控制ISO 14443/MIFARE模式中106kbit/s的通信
#define RFU1D 0x1D //保留
#define RFU1E 0x1E //保留
#define SerialSpeedReg 0x1F //选择串行UART接口的速率
// PAGE 2
#define RFU20 0x20 //保留
#define CRCResultRegM 0x21 //显示CRC计算的实际MSB值
#define CRCResultRegL 0x22 //显示CRC计算的实际LSB值
#define RFU23 0x23 //保留
#define ModWidthReg 0x24 //控制ModWidth的设置
#define RFU25 0x25 //保留
#define RFCfgReg 0x26 //配置接收器增益
#define GsNReg 0x27 //选择天线驱动器管脚(TX1和TX2)的调制电导
#define CWGsCfgReg 0x28 //选择天线驱动器管脚的调制电导
#define ModGsCfgReg 0x29 //选择天线驱动器管脚的调制电导
#define TModeReg 0x2A //定义内部定时器的设置
#define TPrescalerReg 0x2B //定义内部定时器的设置
#define TReloadRegH 0x2C //描述16位长的定时器重装值
#define TReloadRegL 0x2D //描述16位长的定时器重装值
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F //显示16位长的实际定时器值
// PAGE 3
#define RFU30 0x30 //保留
#define TestSel1Reg 0x31 //常用测试信号配置
#define TestSel2Reg 0x32 //常用测试信号配置和PRBS控制
#define TestPinEnReg 0x33 //D1-D7输出驱动器的使能管脚(仅用于串行接口)
#define TestPinValueReg 0x34 //定义D1-D7用作I/O总线时的值
#define TestBusReg 0x35 //显示内部测试总线的状态
#define AutoTestReg 0x36 //控制数字自测试
#define VersionReg 0x37 //显示版本
#define AnalogTestReg 0x38 //控制管脚AUX1和AUX2
#define TestDAC1Reg 0x39 //定义TestDAC1的测试值
#define TestDAC2Reg 0x3A //定义TestDAC2的测试值
#define TestADCReg 0x3B //显示ADCI和Q通道的实际值
#define RFU3C 0x3C //保留
#define RFU3D 0x3D //保留
#define RFU3E 0x3E //保留
#define RFU3F 0x3F //保留
/* 和RC522通信时返回的错误代码 */
#define MI_OK 0x26
#define MI_NOTAGERR 0xcc
#define MI_ERR 0xbb
/**********************************************************************/
void RC522_Init(void);/* IO口初始化 */
////////////////软件模拟SPI与RC522通信///////////////////////////////////////////
void RC522_SPI_SendByte( uint8_t byte );/* 软件模拟SPI发送一个字节数据,高位先行 */
uint8_t RC522_SPI_ReadByte( void );/* 软件模拟SPI读取一个字节数据,先读高位 */
uint8_t RC522_Read_Register( uint8_t Address );//读取RC522指定寄存器的值
void RC522_Write_Register( uint8_t Address, uint8_t data );//向RC522指定寄存器中写入指定的数据
void RC522_SetBit_Register( uint8_t Address, uint8_t mask );//置位RC522指定寄存器的指定位
void RC522_ClearBit_Register( uint8_t Address, uint8_t mask );//清位RC522指定寄存器的指定位
/////////////////////STM32对RC522的基础通信///////////////////////////////////
void RC522_Antenna_On( void );//开启天线
void RC522_Antenna_Off( void );//关闭天线
void RC522_Rese( void );//复位RC522
void RC522_Config_Type( char Type );//设置RC522的工作方式
/////////////////////////STM32控制RC522与M1的通信///////////////////////////////////////
char PcdComMF522 ( uint8_t ucCommand, uint8_t * pInData, uint8_t ucInLenByte, uint8_t * pOutData, uint32_t * pOutLenBit );//通过RC522和ISO14443卡通讯
char PcdRequest ( uint8_t ucReq_code, uint8_t * pTagType );//寻卡
char PcdAnticoll ( uint8_t * pSnr );//防冲突
void CalulateCRC ( uint8_t * pIndata, u8 ucLen, uint8_t * pOutData );//用RC522计算CRC16(循环冗余校验)
char PcdSelect ( uint8_t * pSnr );//选定卡片
char PcdAuthState ( uint8_t ucAuth_mode, uint8_t ucAddr, uint8_t * pKey, uint8_t * pSnr );//校验卡片密码
char PcdWrite ( uint8_t ucAddr, uint8_t * pData );//在M1卡的指定块地址写入指定数据
char PcdRead ( uint8_t ucAddr, uint8_t * pData );//读取M1卡的指定块地址的数据
char PcdHalt( void );//让卡片进入休眠模式
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
移植验证
在自己工程中的main主函数中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-05-21 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include "bsp_rc522.h"
#include <stdio.h>
#include "string.h"
/* 卡的ID存储,32位,4字节 */
u8 ucArray_ID [];
uint8_t ucStatusReturn; //返回状态
int32_t main(void)
{
int i = 0;
uint8_t read_write_data[]={0};//读写数据缓存
uint8_t card_KEY[] ={0xff,0xff,0xff,0xff,0xff,0xff};//默认密码
board_init();
uart1_init(115200U);
printf ("Init....\r\n");
RC522_Init( );//IC卡IO口初始化
RC522_Rese( );//复位RC522
printf ("Start!\r\n");
while(1)
{
/* 寻卡(方式:范围内全部),第一次寻卡失败后再进行一次,寻卡成功时卡片序列传入数组ucArray_ID中 */
if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
{
ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID );
}
if ( ucStatusReturn == MI_OK )
{
/* 防冲突操作,被选中的卡片序列传入数组ucArray_ID中 */
if ( PcdAnticoll ( ucArray_ID ) == MI_OK )
{
//输出卡ID
printf("ID: %X %X %X %X\r\n", ucArray_ID [], ucArray_ID [], ucArray_ID [], ucArray_ID []);
//选卡
if( PcdSelect(ucArray_ID) != MI_OK )
{ printf("PcdSelect failure\r\n"); }
//校验卡片密码
//数据块6的密码A进行校验(所有密码默认为16个字节的0xff)
if( PcdAuthState(PICC_AUTHENT1B, 6, card_KEY, ucArray_ID) != MI_OK )
{ printf("PcdAuthState failure\r\n"); }
//往数据块4写入数据read_write_data
read_write_data[] = 0xaa;//将read_write_data的第一位数据改为0xaa
if( PcdWrite(4,read_write_data) != MI_OK )
{ printf("PcdWrite failure\r\n"); }
//将read_write_data的16位数据,填充为0(清除数据的意思)
memset(read_write_data,0,16);
delay_us(8);
//读取数据块4的数据
if( PcdRead(4,read_write_data) != MI_OK )
{ printf("PcdRead failure\r\n"); }
//输出读出的数据
for( i = 0; i < 16; i++ )
{
printf("%x ",read_write_data[]);
}
printf("\r\n");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
移植现象:串口输出读取到的卡ID,然后写入0xaa,之后将数据读出发送至串口。
模块移植成功案例代码: