一、模块简短介绍
有关RC522模块的背景知识以及工作原理,本次实验暂时不做分享,这里贴上两位写得很好的博主,大家可自行查看。
(5条消息) RC522(RFID)读写驱动_桃成蹊2.0的博客-优快云博客https://blog.youkuaiyun.com/m0_51220742/article/details/123683745https://blog.youkuaiyun.com/m0_51220742/article/details/123683745(5条消息) STM32--RFID无线射频技术(RC522刷卡模块)_rc522射频模块详细资料_y黎好好的博客-优快云博客https://blog.youkuaiyun.com/weixin_45771489/article/details/124079134
https://blog.youkuaiyun.com/weixin_45771489/article/details/124079134
该模块价格普遍实惠,所以我买了一个自己玩玩。经过一段时间的摸爬滚打目前能够正常读写,后续会继续完善该模块的使用,并加入到其他嵌入式设计中。
二、开发资料的使用及经验分享
该模块的API函数淘宝有很多资源,数据手册也有,不过是英文版的。建议大家有时间还是要过一数据手册的重要部分,比如寄存器和指令集,通信协议等,可以使用知云文献查看,我习惯用它来看论文,适合翻译长段大段。也可以用WPS的翻译,小部分翻译,看个人使用习惯。要是有能力直接撸原文那就更好,节约时间。
找过很多资料,提供的API无非就是一套没有章法,毫无可读性的杂乱代码,十分影响学习效果。这里建议大家不要去死磕,可以看本次实验的代码逻辑和上文提到的两位博主,每一个底层函数都有注释,在一些底层函数的逻辑上可以结合这些API函数进行反推或者校验,可以帮助读者理解实现原理。
一开始是通过数据手册,硬着头皮啃无良店铺给的代码,没有注释,没有逻辑,没有章法。虽然没报错,但是能不能用完全不知道,就是这一步就花了好几天时间,所以建议大家不要像我一样埋头苦读,可以先看每一个函数的中文注释,整理出一个框架。
三、源码解析
代码总体逻辑没有变化,参考了两位博主的思路,我用官方的API函数进行修改。两位博主一位使用的软件实现SPI协议,一位使用板载SPI。两种方法我都试过,但是软件SPI协议总是通信失败,不知道是我协议写的不对还是通信速度和模块不匹配,暂时还未解决。
软件模拟SPI
/*
* 函数名:SOFT_SPI_RC522_SendByte
* 描述 :向RC522发送1 Byte 数据
* 输入 :write_dat,要发送的数据
* 返回 : RC522返回的数据
*/
void SOFT_SPI_RC522_SendByte(uint8_t write_dat)
{
uint8_t i;
for(i=0;i<8;i++)
{
RC522_SCK(0);
RC522_DELAY();
if (write_dat & 0x80)
RC522_MOSI(1);
else
RC522_MOSI(0);
write_dat <<= 1;
RC522_DELAY(); //delay_ms(1)
RC522_SCK(1);
RC522_DELAY();
}
RC522_SCK(0);//释放时钟线
}
/*
* 函数名:SOFT_SPI_RC522_ReadByte
* 描述 :从RC522发送1 Byte 数据
* 输入 :无
* 返回 : RC522返回的数据
*/
uint8_t SOFT_SPI_RC522_ReadByte(void)
{
uint8_t i;
uint8_t SPI_Data=0;
for(i=0;i<8;i++)
{
SPI_Data <<= 1;
RC522_SCK(0);
RC522_DELAY();//SCK low pulse width 最小延时
RC522_SCK(1);
if (RC522_MISO_GET==1)
SPI_Data++;
RC522_DELAY();//SCK high pulse width 最小延时
}
RC522_SCK(0);
return SPI_Data;
}
/*
* 函数名:SOFT_SPI_RC522_Read_Write
* 描述 :RC522同时读写,数据在上升沿读取
* 输入 :byte,要发送的数据
* 返回 : RC522返回的数据
*/
uint8_t SOFT_SPI_RC522_Read_Write(uint8_t write_dat)
{
uint8_t i,read_dat;
for(i=0;i<8;i++)
{
RC522_SCK(0);
if(write_dat&0x80)
RC522_MOSI(1);
else
RC522_MOSI(0);
write_dat <<= 1;
RC522_DELAY(); //data changes to SCK high 最小延时
RC522_SCK(1);
read_dat <<= 1;
if(RC522_MISO_GET==1)
read_dat++;
RC522_DELAY();
}
RC522_SCK(0);
return read_dat;
}
有大佬如果发现问题,还请给我留言,也不知道是什么原因导致通信失败。
板载SPI通信
/*
* 函数名:SPI_WriteNBytes
* 描述 :向RC522发送n Byte 数据
* 输入 :SPIx : 要发送数据的SPI
* 输入 :p_TxData : 要发送的数据
* 输入 :sendDataNum : 要发送的数据量(Byte)
* 返回 : 0
*/
int SPI_WriteNBytes(SPI_TypeDef* SPIx, uint8_t *p_TxData,uint32_t sendDataNum)
{
int retry=0;
while(sendDataNum--){
while((SPIx->SR&SPI_FLAG_TXE)==0)//等待发送区空
{
retry++;
if(retry>20000)return -1;
}
SPIx->DR=*p_TxData++;//发送一个byte
retry=0;
while((SPIx->SR&SPI_FLAG_RXNE)==0)//等待接收完一个byte
{
SPIx->SR = SPIx->SR;
retry++;
if(retry>20000)return -1;
}
SPIx->DR;
}
return 0;
}
/*
* 函数名:SPI_ReadNBytes
* 描述 :读取RC522 n Byte 数据
* 输入 :SPIx : 要读取数据的SPI
* 输入 :p_RxData : 要读取的数据
* 输入 :readDataNum : 要读取的数据量(Byte)
* 返回 : 0
*/
int SPI_ReadNBytes(SPI_TypeDef* SPIx, uint8_t *p_RxData,uint32_t readDataNum)
{
int retry=0;
while(readDataNum--){
SPIx->DR = 0xFF;
while(!(SPIx->SR&SPI_FLAG_TXE)){
retry++;
if(retry>20000)return -1;
}
retry = 0;
while(!(SPIx->SR&SPI_FLAG_RXNE)){
retry++;
if(retry>20000)return -1;
}
*p_RxData++ = SPIx->DR;
}
return 0;
}
本实验使用读取寄存器状态来完成SPI通信,未使用HAL_SPI_TransmitReceive库函数。
源码部分
主函数比较简略,调用一个操作函数,用按键触发的方式启动该函数实现读写。main.c 中,加入以下代码:
main.c
RC522_Init();
uint8_t key;
uint8_t Write_Card_Data[16]={0};//要写入的数据
while(1)
{
key = key_scan();//按键扫描函数,自己定义修改
if(key==1)
{
RC522_Start(5,readID,Write_Card_Data);//readID 读命令
}
else if(key==2)
{
RC522_Start(5,writeID,Write_Card_Data);//writeID 写命令
}
delay_ms(100);
LED0=!LED0;
}
RC522部分
RC522.c
其中RC522_Start是自定义函数,只要操作流程正确,可自行编写想要的操作方式,本实验只完成简单的单块读写操作。蓝卡和白卡的卡号根据自己的修改,可以通过手机NFC功能查看卡号,同时也能查看扇区数据,APP可以使用上文提到的博主推荐的NFC Writer。
#include "RC522.h"
#include "stdio.h"
#include "delay.h"
#include "spi.h"
#include "oled.h"
#include "string.h"
// M1卡分为16个扇区,每个扇区由四个块(块0、块1、块2、块3)组成
// 将16个扇区的64个块按绝对地址编号为:0~63,每块16个字节
// 第0个扇区的块0(即绝对地址0块),用于存放厂商代码,已经固化不可更改
// 每个扇区的块0、块1、块2为数据块,可用于存放数据
// 每个扇区的块3为控制块(绝对地址为:块3、块7、块11.....)包括密码A,存取控制、密码B等
void RC522_Init(void)
{
RC522_SPI_GPIO_Init();//初始化RC522的复位引脚和片选引脚
SPI1_Init();//初始化SPI
delay_ms(50);
PcdReset();//复位RC522读卡器
delay_ms(10);
PcdAntennaOff();//关闭天线发射
delay_ms(10);
PcdAntennaOn();//开启天线发射
printf("RFID-MFRC522 初始化完成\nPress KEY0 Or KEY1 To Start...\r\n"); //初始化完成
}
uint8_t IC_UID[4]; //UID卡片序列号,4字节
uint8_t IC_Type[2];//卡类型代码,2字节,0x0400,Mifare_One(S50)
uint8_t card_1[4]={0xC3,0xB2,0x37,0xC5};//蓝卡1卡号
uint8_t card_2[4]={0xA3,0x09,0x3C,0xFB};//白卡2卡号
uint8_t ID_num=0;//当前操作的序号
uint8_t Card_KEY[6]={0xff,0xff,0xff,0xff,0xff,0xff};//验证密码
uint8_t Card_Data[16];//读取出的块数据
/*
* 函数名:RC522_Start
* 输入 block:要操作的块地址
* 输入 option:读操作或写操作
* readID 1//读
* writeID 2//写
* 输入 block:要操作的块地址
* 描述 :RC522操作主函数,可完成对卡的读写操作
* * 返回 : 状态值
* = 1,成功
*/
void RC522_Start(uint8_t block,uint8_t option,uint8_t *Write_Card_Data)
{
if(PcdRequest(PICC_REQALL,IC_Type) == MI_OK)//寻卡
{
uint16_t cardType = (IC_Type[0] << 8) | IC_Type[1];
switch (cardType)
{
case 0x4400:
printf("\r\nMifare UltraLight\r\n");
break;
case 0x0400:
printf("\r\nMifare One(S50)\r\n");
break;
case 0x0200:
printf("\r\nMifare One(S70)\r\n");
break;
case 0x0800:
printf("\r\nMifare Pro(X)\r\n");
break;
case 0x4403:
printf("\r\nMifare DESFire\r\n");
break;
default:
printf("\r\nUnknown Card\r\n");
break;
}
if(PcdAnticoll(IC_UID)==MI_OK)//防冲撞
{
if((IC_UID[0]==card_1[0])&&(IC_UID[1]==card_1[1])&&(IC_UID[2]==card_1[2])&&(IC_UID[3]==card_1[3]))
{
ID_num=1;
printf("The User is: %d, Blue card",ID_num);
}
else if((IC_UID[0]==card_2[0])&&(IC_UID[1]==card_2[1])&&(IC_UID[2]==card_2[2])&&(IC_UID[3]==card_2[3]))
{
ID_num=2;
printf("The User is: %d, White card",ID_num);
}
printf("\r\ncard_ID: %02X:%02X:%02X:%02X\r\n",IC_UID[0],IC_UID[1],IC_UID[2],IC_UID[3]); //打印卡的UID号
if(PcdSelect(IC_UID)==MI_OK)//选卡
{
if(PcdAuthState(PICC_AUTHENT1A,block,Card_KEY,IC_UID)==MI_OK)//验证A密钥,对应块
{
// memset(Card_Data,1,16);
if(option==readID)//读操作
{
if(PcdRead(block,Card_Data)==MI_OK)
{
printf("\n读取结果: \n");
printf("block %d date:\r\n",block);
for(int i=0;i<16;i++)
{
printf("%02X ",Card_Data[i]);
}
printf("\r\n");
}
}
else if(option==writeID)//写操作
{
if(PcdWrite(block,Write_Card_Data)==MI_OK)
{
delay_ms(8);
if(PcdRead(block,Card_Data)==MI_OK)
{
printf("\n写入完成\n");
printf("block %d date:\r\n",block);
for(int i=0;i<16;i++)
{
printf("%02X ",Card_Data[i]);
}
printf("\r\n");
}
}
}
}
else
{
printf("\r\n验证失败\r\n");
}
}
else
{
printf("\r\n选卡失败\r\n");
}
}
else
{
printf("\r\n防冲撞失败\r\n");
}
}
else
{
printf("\r\n寻卡失败\r\n");
}
// PcdHalt();
PcdAntennaOff();//关闭天线发射
delay_ms(10);
PcdAntennaOn();//开启天线发射
}
/*
* 函数名:RC522_SPI_GPIO_Init
* 描述 :初始化RC522的复位引脚和片选引脚
* 片选引脚采用软件管理模式,自定义片选引脚
*/
void RC522_SPI_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
RC522_GPIO_Reset_CLK_ENABLE();
RC522_GPIO_CS_CLK_ENABLE();
GPIO_Initure.Pin=RC522_RST_Pin;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;
GPIO_Initure.Pull=GPIO_PULLUP; //复位和片选引脚低电平有效,默认上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(RC522_RST_GPIO_Port,&GPIO_Initure);
GPIO_Initure.Pin=RC522_GPIO_CS_PIN;
HAL_GPIO_Init(RC522_GPIO_CS_PORT,&GPIO_Initure);
}
/*
* 函数名:SPI_WriteNBytes
* 描述 :向RC522发送n Byte 数据
* 输入 :SPIx : 要发送数据的SPI
* 输入 :p_TxData : 要发送的数据
* 输入 :sendDataNum : 要发送的数据量(Byte)
* 返回 : 0
*/
int SPI_WriteNBytes(SPI_TypeDef* SPIx, uint8_t *p_TxData,uint32_t sendDataNum)
{
int retry=0;
while(sendDataNum--){
while((SPIx->SR&SPI_FLAG_TXE)==0)//等待发送区空
{
retry++;
if(retry>20000)return -1;
}
SPIx->DR=*p_TxData++;//发送一个byte
retry=0;
while((SPIx->SR&SPI_FLAG_RXNE)==0)//等待接收完一个byte
{
SPIx->SR = SPIx->SR;
retry++;
if(retry>20000)return -1;
}
SPIx->DR;
}
return 0;
}
/*
* 函数名:SPI_ReadNBytes
* 描述 :读取RC522 n Byte 数据
* 输入 :SPIx : 要读取数据的SPI
* 输入 :p_RxData : 要读取的数据
* 输入 :readDataNum : 要读取的数据量(Byte)
* 返回 : 0
*/
int SPI_ReadNBytes(SPI_TypeDef* SPIx, uint8_t *p_RxData,uint32_t readDataNum)
{
int retry=0;
while(readDataNum--){
SPIx->DR = 0xFF;
while(!(SPIx->SR&SPI_FLAG_TXE)){
retry++;
if(retry>20000)return -1;
}
retry = 0;
while(!(SPIx->SR&SPI_FLAG_RXNE)){
retry++;
if(retry>20000)return -1;
}
*p_RxData++ = SPIx->DR;
}
return 0;
}
/*
* 函数名:PcdComMF522
* 描述 :通过RC522和ISO14443卡通讯
* 输入 :ucCommand,RC522命令字
* pInData,通过RC522发送到卡片的数据
* ucInLenByte,发送数据的字节长度
* pOutData,接收到的卡片返回数据
* pOutLenBit,返回数据的位长度
* 返回 : 状态值
* = MI_OK,成功
* 调用 :内部调用
*/
char PcdComMF522(uint8_t Command,uint8_t *pInData,uint8_t InLenByte,uint8_t *pOutData,uint32_t *pOutLenBit)
{
char status = MI_ERR;
uint8_t irqEn = 0x00;
uint8_t waitFor = 0x00;
uint8_t lastBits;
uint8_t n;
uint32_t i;
switch (Command)
{
case PCD_AUTHENT: //Mifare认证
irqEn = 0x12; //允许错误中断请求ErrIEn 允许空闲中断IdleIEn
waitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位
break;
case PCD_TRANSCEIVE://接收发送 发送接收
irqEn = 0x77; //允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
waitFor = 0x30; //寻卡等待时候 查询接收中断标志位与 空闲中断标志位
break;
default:
break;
}
WriteRawRC(ComIEnReg, irqEn | 0x80); //IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反
ClearBitMask(ComIrqReg, 0x80); //Set1该位清零时,CommIRqReg的屏蔽位清零
WriteRawRC(CommandReg, PCD_IDLE); //写空闲命令
SetBitMask(FIFOLevelReg, 0x80); //置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除
for (i = 0; i < InLenByte; i++)
{
WriteRawRC(FIFODataReg, pInData[i]);//写数据进FIFOdata
}
WriteRawRC(CommandReg, Command);//写命令
if (Command == PCD_TRANSCEIVE)
{
SetBitMask(BitFramingReg, 0x80);//StartSend置位启动数据发送 该位与收发命令使用时才有效
}
i = 800;//根据时钟频率调整,操作M1卡最大等待时间25ms
do
{
n = ReadRawRC(ComIrqReg);//查询事件中断
i--;
} while ((i != 0) && !(n & 0x01) && !(n & waitFor));//退出条件i=0,定时器中断,与写空闲命令
ClearBitMask(BitFramingReg, 0x80);//清理允许StartSend位
if (i != 0)
{
if (!(ReadRawRC(ErrorReg) & 0x1B))//读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
{
status = MI_OK;
if (n & irqEn & 0x01)//是否发生定时器中断
{
status = MI_NOTAGERR;
}
if (Command == PCD_TRANSCEIVE)
{
n = ReadRawRC(FIFOLevelReg);//读FIFO中保存的字节数
lastBits = ReadRawRC(ControlReg) & 0x07;//最后接收到得字节的有效位数
if (lastBits)
{
*pOutLenBit = (n - 1) * 8 + lastBits;//N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
}
else
{
*pOutLenBit = n * 8;//最后接收到的字节整个字节有效
}
if (n == 0)
{
n = 1;
}
if (n > MAXRLEN)
{
n = MAXRLEN;
}
for (i = 0; i < n; i++)
{
pOutData[i] = ReadRawRC(FIFODataReg);
}
}
}
else
{
status = MI_ERR;
}
}
SetBitMask(ControlReg, 0x80); // stop timer now
WriteRawRC(CommandReg, PCD_IDLE);
return status;
}
/*
* 函数名:PcdRequest
* 描述 :寻卡
* 输入 :ucReq_code,寻卡方式
* = 0x52,寻感应区内所有符合14443A标准的卡
* = 0x26,寻未进入休眠状态的卡
* pTagType,卡片类型代码
* = 0x4400,Mifare_UltraLight
* = 0x0400,Mifare_One(S50)
* = 0x0200,Mifare_One(S70)
* = 0x0800,Mifare_Pro(X))
* = 0x4403,Mifare_DESFire
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdRequest(uint8_t req_code, uint8_t *pTagType)
{
char status;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ClearBitMask(Status2Reg, 0x08); //清除RC522寄存位,清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况
WriteRawRC(BitFramingReg, 0x07); //写RC522寄存器,发送的最后一个字节的七位
SetBitMask(TxControlReg, 0x03); 写RC522寄存位,TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号
ucComMF522Buf[0] = req_code;//存入寻卡方式
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x10))//寻卡成功返回卡类型
{
*pTagType = ucComMF522Buf[0];
*(pTagType + 1) = ucComMF522Buf[1];
}
else
{
status = MI_ERR;
}
return status;
}
/*
* 函数名:PcdAnticoll
* 描述 :防冲撞
* 输入 :pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdAnticoll(uint8_t *pSnr)
{
char status;
uint8_t i, snr_check = 0;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ClearBitMask(Status2Reg, 0x08);//清MFCryptol On位,只有成功执行MFAuthent命令后,该位才能置位
WriteRawRC(BitFramingReg, 0x00);//清理寄存器,停止收发
ClearBitMask(CollReg, 0x80);//清ValuesAfterColl所有接收的位在冲突后被清除
ucComMF522Buf[0] = PICC_ANTICOLL1;//卡片防冲突命令
ucComMF522Buf[1] = 0x20;
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &unLen);
if (status == MI_OK)
{
for (i = 0; i < 4; i++)
{
*(pSnr + i) = ucComMF522Buf[i];
snr_check ^= ucComMF522Buf[i];
}
if (snr_check != ucComMF522Buf[i])
{
status = MI_ERR;
}
}
SetBitMask(CollReg, 0x80);
return status;
}
/*
* 函数名:PcdSelect
* 描述 :选定卡片
* 输入 :pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdSelect(uint8_t *pSnr)
{
char status;
uint8_t i;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_ANTICOLL1;//防冲撞
ucComMF522Buf[1] = 0x70;
ucComMF522Buf[6] = 0;
for (i = 0; i < 4; i++)
{
ucComMF522Buf[i + 2] = *(pSnr + i);
ucComMF522Buf[6] ^= *(pSnr + i);
}
CalulateCRC(ucComMF522Buf, 7, &ucComMF522Buf[7]);
ClearBitMask(Status2Reg, 0x08);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x18))
{
status = MI_OK;
}
else
{
status = MI_ERR;
}
return status;
}
/*
* 函数名:PcdAuthState
* 描述 :验证卡片密码
* 输入 :ucAuth_mode,密码验证模式
* = 0x60,验证A密钥
* = 0x61,验证B密钥
* uint8_t ucAddr,块地址
* pKey,密码
* pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdAuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = auth_mode;
ucComMF522Buf[1] = addr;
for (i = 0; i < 6; i++)
{
ucComMF522Buf[i + 2] = *(pKey + i);
}
for (i = 0; i < 6; i++)
{
ucComMF522Buf[i + 8] = *(pSnr + i);
}
status = PcdComMF522(PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (!(ReadRawRC(Status2Reg) & 0x08)))
{
status = MI_ERR;
}
return status;
}
/*
* 函数名:PcdRead
* 描述 :读取M1卡一块数据
* 输入 :uint8_t ucAddr,块地址
* pData,读出的数据,16字节
* 返回 : 状态值
* = MI_OK,成功
* 调用 :外部调用
*/
char PcdRead(uint8_t addr, uint8_t *pData)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_READ;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x90))
{
for (i = 0; i < 16; i++)
{
*(pData + i) = ucComMF522Buf[i];
}
}
else
{
status = MI_ERR;
}
return status;
}
/*
* 函数名:PcdWrite
* 描述 :写数据到M1卡一块
* 输入 :uint8_t ucAddr,块地址
* pData,写入的数据,16字节
* 返回 : 状态值
* = MI_OK,成功
* 调用 :外部调用
*/
char PcdWrite(uint8_t addr, uint8_t *pData)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_WRITE;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
for (i = 0; i < 16; i++)
{
ucComMF522Buf[i] = *(pData + i);
}
CalulateCRC(ucComMF522Buf, 16, &ucComMF522Buf[16]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
}
return status;
}
/*
* 函数名:PcdValue
* 描述 :扣款和充值
* 输入 :dd_mode[IN]:命令字
* 0xC0 = 扣款
* 0xC1 = 充值
* addr[IN]:钱包地址
* pValue[IN]:4字节增(减)值,低位在前
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdValue(uint8_t dd_mode, uint8_t addr, uint8_t *pValue)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = dd_mode;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
for (i = 0; i < 16; i++)
{
ucComMF522Buf[i] = *(pValue + i);
}
CalulateCRC(ucComMF522Buf, 4, &ucComMF522Buf[4]);
unLen = 0;
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 6, ucComMF522Buf, &unLen);
if (status != MI_ERR)
{
status = MI_OK;
}
}
if (status == MI_OK)
{
ucComMF522Buf[0] = PICC_TRANSFER;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
}
return status;
}
/*
* 函数名:PcdBakValue
* 描述 :备份钱包
* 输入 :sourceaddr[IN]:源地址
* goaladdr[IN]:目标地址
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdBakValue(uint8_t sourceaddr, uint8_t goaladdr)
{
char status;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_RESTORE;
ucComMF522Buf[1] = sourceaddr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
ucComMF522Buf[0] = 0;
ucComMF522Buf[1] = 0;
ucComMF522Buf[2] = 0;
ucComMF522Buf[3] = 0;
CalulateCRC(ucComMF522Buf, 4, &ucComMF522Buf[4]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 6, ucComMF522Buf, &unLen);
if (status != MI_ERR)
{
status = MI_OK;
}
}
if (status != MI_OK)
{
return MI_ERR;
}
ucComMF522Buf[0] = PICC_TRANSFER;
ucComMF522Buf[1] = goaladdr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
return status;
}
/*
* 函数名:PcdHalt
* 描述 :命令卡片进入休眠状态
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdHalt(void)
{
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_HALT;
ucComMF522Buf[1] = 0;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
return MI_OK;
}
/*
* 函数名:CalulateCRC
* 描述 :用RC522计算CRC16
* 输入 :pIndata,计算CRC16的数组
* ucLen,计算CRC16的数组字节长度
* pOutData,存放计算结果存放的首地址
* 返回 : 无
* 调用 :内部调用
*/
void CalulateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
uint8_t i, n;
ClearBitMask(DivIrqReg, 0x04);
WriteRawRC(CommandReg, PCD_IDLE);
SetBitMask(FIFOLevelReg, 0x80);
for (i = 0; i < len; i++)
{
WriteRawRC(FIFODataReg, *(pIndata + i));
}
WriteRawRC(CommandReg, PCD_CALCCRC);
i = 0xFF;
do
{
n = ReadRawRC(DivIrqReg);
i--;
} while ((i != 0) && !(n & 0x04));
pOutData[0] = ReadRawRC(CRCResultRegL);
pOutData[1] = ReadRawRC(CRCResultRegM);
}
/*
* 函数名:PcdRese
* 描述 :复位RC522
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdReset(void)
{
RC522_Reset_Disable();
delay_ms(10);
RC522_Reset_Enable();
delay_ms(60);
RC522_Reset_Disable();
delay_ms(500);
WriteRawRC(CommandReg, PCD_RESETPHASE);//复位指令
delay_ms(2);
WriteRawRC(ModeReg, 0x3D);//定义发送和接收常用模式,和Mifare卡通讯,CRC初始值0x6363
WriteRawRC(TReloadRegL, 30);//16位定时器低位
WriteRawRC(TReloadRegH, 0);//16位定时器高位
WriteRawRC(TModeReg, 0x8D);//定义内部定时器的设置
WriteRawRC(TPrescalerReg, 0x3E);//设置定时器分频系数
WriteRawRC(TxAutoReg, 0x40);//调制发送信号为100%ASK
ClearBitMask(TestPinEnReg, 0x80);
WriteRawRC(TxAutoReg, 0x40);
return MI_OK;
}
/*
* 函数名:ReadRawRC
* 描述 :读RC522寄存器
* 输入 :ucAddress,寄存器地址
* 返回 : 寄存器的当前值
*/
uint8_t ReadRawRC(uint8_t Address)
{
uint8_t ucAddr;
uint8_t ucResult = 0;
ucAddr = ((Address << 1) & 0x7E) | 0x80;//最高位置1,读操作;最低为默认0
delay_ms(1);
RC522_CS_Enable();
SPI_WriteNBytes(SPI1_SPI, &ucAddr, 1); //向总线写多个数据
SPI_ReadNBytes(SPI1_SPI, &ucResult, 1); //向总线读多个数据
RC522_CS_Disable();
return ucResult;
}
/*
* 函数名:WriteRawRC
* 描述 :写RC522寄存器
* 输入 :ucAddress,寄存器地址
* ucValue,写入寄存器的值
*/
void WriteRawRC(uint8_t Address, uint8_t value)
{
uint8_t ucAddr;
uint8_t write_buffer[2] = {0};
ucAddr = ((Address << 1) & 0x7E);//最高位置0,写操作;最低为默认0
write_buffer[0] = ucAddr;
write_buffer[1] = value;
delay_ms(1);
RC522_CS_Enable();
SPI_WriteNBytes(SPI1_SPI, write_buffer, 2);
RC522_CS_Disable();
}
/*
* 函数名:SetBitMask
* 描述 :对RC522寄存器置位,可对多个位同时操作
* 输入 :ucReg,寄存器地址
* ucMask,置位值,例0x13,0001 0011
*/
void SetBitMask(uint8_t reg, uint8_t mask)
{
uint8_t temp = 0x00;
temp = ReadRawRC(reg); //读寄存器
WriteRawRC(reg, temp | mask); //将目标位置1
}
/*
* 函数名:ClearBitMask
* 描述 :对RC522寄存器清位
* 输入 :ucReg,寄存器地址
* ucMask,清位值
*/
void ClearBitMask(uint8_t reg, uint8_t mask)
{
uint8_t temp = 0x00;
temp = ReadRawRC(reg);
WriteRawRC(reg, temp & ~mask);
}
/*
* 函数名:PcdAntennaOn
* 描述 :开启天线
* TxControlReg寄存器位0置1,引脚TX1上的输出信号将传输由传输数据调制的13.56 MHz能量载波。
* TxControlReg寄存器位1置1,引脚TX2上的输出信号将发送由传输数据调制的13.56 MHz能量载波。
*/
void PcdAntennaOn(void)
{
uint8_t i;
i = ReadRawRC(TxControlReg);
if (!(i & 0x03))
{
SetBitMask(TxControlReg, 0x03);
}
}
/*
* 函数名:PcdAntennaOff
* 描述 :关闭天线
*/
void PcdAntennaOff(void)
{
ClearBitMask(TxControlReg, 0x03);
}
/*
* 函数名:RC522_PcdConfig_Type
* 描述 :设置RC522的工作方式
* 工作方式ISO14443_A
* 复位中已经设置过,该函数暂时未调用
*/
void RC522_Config(uint8_t Card_Type)
{
ClearBitMask(Status2Reg, 0x08);
WriteRawRC(ModeReg, 0x3D);
WriteRawRC(RxSelReg, 0x86);
WriteRawRC(RFCfgReg, 0x7F);
WriteRawRC(TReloadRegL, 30);
WriteRawRC(TReloadRegH, 0);
WriteRawRC(TModeReg, 0x8D);
WriteRawRC(TPrescalerReg, 0x3E);
delay_ms(5);
PcdAntennaOn();
}
RC522.h
头文件内容与API大致相同,定义了指令集和寄存器。
这里的sys.h是正点原子的F1系列头文件。
加入了引脚定义和自定义函数。
#ifndef __RC522_H
#define __RC522_H
#include "sys.h"
/***************************引脚定义****************************/
/*
RC522SPI引脚,RST和CS可随意更改
RST-PC5
CS-PA4
SCK-PA5
MISO-PA6
MOSI-PA7
*/
//Reset
#define RC522_RST_Pin GPIO_PIN_5
#define RC522_RST_GPIO_Port GPIOC
#define RC522_GPIO_Reset_CLK_ENABLE() \
do { __HAL_RCC_GPIOA_CLK_ENABLE(); } while(0)
//CS片选,模块SDA引脚,SPI通信时用作NSS
#define RC522_GPIO_CS_PIN GPIO_PIN_4
#define RC522_GPIO_CS_PORT GPIOA
#define RC522_GPIO_CS_CLK_ENABLE() \
do { __HAL_RCC_GPIOA_CLK_ENABLE(); } while(0)
/***********************RC522 函数宏定义**********************/
#define RC522_Reset_Disable() HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_SET);
#define RC522_Reset_Enable() HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_RESET);
#define RC522_CS_Enable() HAL_GPIO_WritePin(RC522_GPIO_CS_PORT, RC522_GPIO_CS_PIN, GPIO_PIN_RESET);
#define RC522_CS_Disable() HAL_GPIO_WritePin(RC522_GPIO_CS_PORT, RC522_GPIO_CS_PIN, GPIO_PIN_SET);
#define readID 1//读
#define writeID 2//写
/***************************自定义函数****************************/
void RC522_Init(void);
void RC522_Start(uint8_t block,uint8_t option,uint8_t *Write_Card_Data);
void RC522_SPI_GPIO_Init(void);
/***************************RC522指令集****************************/
//MF522命令字
#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 //休眠
//MF522 FIFO长度定义
#define DEF_FIFO_LENGTH 64 //FIFO size=64byte
//MF522寄存器定义
// PAGE 0
#define RFU00 0x00
#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivlEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define RFU0F 0x0F
// PAGE 1
#define RFU10 0x10
#define ModeReg 0x11
#define TxModeReg 0x12
#define RxModeReg 0x13
#define TxControlReg 0x14
#define TxAutoReg 0x15
#define TxSelReg 0x16
#define RxSelReg 0x17
#define RxThresholdReg 0x18
#define DemodReg 0x19
#define RFU1A 0x1A
#define RFU1B 0x1B
#define MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// PAGE 2
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsCfgReg 0x28
#define ModGsCfgReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadRegH 0x2C
#define TReloadRegL 0x2D
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F
// PAGE 3
#define RFU30 0x30
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B
#define RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F
//和MF522通讯时返回的错误代码
#define MI_OK 0
#define MI_NOTAGERR 1
#define MI_ERR 2
//缓冲数组大小
#define MAXRLEN 18
/*******************************内部调用函数**************************************/
char PcdRequest(uint8_t req_code,uint8_t *pTagType);
char PcdAnticoll(uint8_t *pSnr);
char PcdSelect(uint8_t *pSnr);
char PcdAuthState(uint8_t auth_mode,uint8_t addr,uint8_t *pKey,uint8_t *pSnr);
char PcdRead(uint8_t addr,uint8_t *pData);
char PcdWrite(uint8_t addr,uint8_t *pData);
char PcdValue(uint8_t dd_mode,uint8_t addr,uint8_t *pValue);
char PcdBakValue(uint8_t sourceaddr, uint8_t goaladdr);
char PcdHalt(void);
void CalulateCRC(uint8_t *pIndata,uint8_t len,uint8_t *pOutData);
char PcdReset(void);
uint8_t ReadRawRC(uint8_t Address);
void WriteRawRC(uint8_t Address, uint8_t value);
void SetBitMask(uint8_t reg,uint8_t mask);
void ClearBitMask(uint8_t reg,uint8_t mask);
char PcdComMF522(uint8_t Command,uint8_t *pInData,uint8_t InLenByte,uint8_t *pOutData,unsigned int *pOutLenBit);
void PcdAntennaOn(void);
void PcdAntennaOff(void);
void RC522_Config(uint8_t Card_Type);
#endif
SPI定义
spi的初始化默认即可,注意以下两项的设置,才能保持和模块的正常通信:
CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平
CLKPhase=SPI_PHASE_1EDGE; //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
片选引脚改为软件管理,这样方便更改引脚,
NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理
SPI.c
#include "spi.h"
SPI_HandleTypeDef SPI1_Handler; //SPI句柄
//以下是SPI模块的初始化代码,配置成主机模式
//SPI口初始化
//这里针是对SPI1的初始化
void SPI1_Init(void)
{
SPI1_Handler.Instance=SPI1_SPI; //SPI1
SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置SPI工作模式,设置为主模式
SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置SPI单向或者双向的数据模式:SPI设置为双线模式
SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平
SPI1_Handler.Init.CLKPhase=SPI_PHASE_1EDGE; //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为256
SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭TI模式
SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
SPI1_Handler.Init.CRCPolynomial=7; //CRC值计算的多项式,默认值为7
HAL_SPI_Init(&SPI1_Handler);//初始化
__HAL_SPI_ENABLE(&SPI1_Handler); //使能SPI1
SPI1_ReadWriteByte(0Xff); //启动传输,产生8个时钟脉冲, 达到清空DR的作用, 非必需
}
//SPI底层驱动,时钟使能,引脚配置
//此函数会被HAL_SPI_Init()调用
//hspi:SPI句柄
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Initure;
SPI1_SPI_CLK_ENABLE();
SPI1_SCK_GPIO_CLK_ENABLE();
SPI1_MISO_GPIO_CLK_ENABLE();
SPI1_MOSI_GPIO_CLK_ENABLE();
GPIO_Initure.Pin=SPI1_SCK_GPIO_PIN;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
GPIO_Initure.Pull=GPIO_PULLDOWN;
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI1_SCK_GPIO_PORT,&GPIO_Initure);
GPIO_Initure.Pin=SPI1_MISO_GPIO_PIN;
HAL_GPIO_Init(SPI1_MISO_GPIO_PORT,&GPIO_Initure);
GPIO_Initure.Pin=SPI1_MOSI_GPIO_PIN;
HAL_GPIO_Init(SPI1_MOSI_GPIO_PORT,&GPIO_Initure);
}
SPI.h
#ifndef __SPI_H
#define __SPI_H
#include "sys.h"
extern SPI_HandleTypeDef SPI1_Handler; //SPI句柄
/* SPI1 引脚 定义 */
#define SPI1_SCK_GPIO_PORT GPIOA
#define SPI1_SCK_GPIO_PIN GPIO_PIN_5
#define SPI1_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define SPI1_MISO_GPIO_PORT GPIOA
#define SPI1_MISO_GPIO_PIN GPIO_PIN_6
#define SPI1_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define SPI1_MOSI_GPIO_PORT GPIOA
#define SPI1_MOSI_GPIO_PIN GPIO_PIN_7
#define SPI1_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
/* SPI1相关定义 */
#define SPI1_SPI SPI1
#define SPI1_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI1_CLK_ENABLE(); }while(0)
void SPI1_Init(void);
#endif
四、实验结果
本实验在于经验分享和学习记录,有不正确的地方请读者指正。
五、更新源码:
源码已上传至gitee:
GitCode:
项目首页 - RC_522:RC522 SPI RF module based on STM32 - GitCodehttps://gitcode.com/plmm__/RC_522Github: