参考大佬文章:STM32—驱动RFID-RC522模块_rc522 f4-CSDN博客
硬件准备:
STM32f103c8t6开发板
RFID-RC522 射频模块(感应M1卡)
s90舵机(用于开门)
3.3v稳压模块(将6v电压转为3.3v给stm32 供电)
四节南孚电池加有盖电池盒(用于供电)
OLED显示屏(用于获取M1卡的卡号)
软件准备:
RFID-RC522模块读取门卡信息
RC522将信息存储到stm32中
stm32 对门卡信息与先前定义好的门卡信息匹配
匹配成功后驱动SG90舵机开门
输出一个PWM波形
通过设置PWM波形的占空比来使舵机转到不同的角度上(改变CRR的值来改变占空比)
棕色线:接地
红色线:接5v或5v以上的正极
橙色线:接PA0,接收输出的PWM波形
PWM.c
代码:
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{//开启时钟TIM2的外设时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//初始化PA0端口RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//选择内部时钟TIM_InternalClockConfig(TIM2);//初始化时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//初始化输出比较单元(PA1口对应的就是OC2通道)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 0; //设置CRR寄存器值TIM_OC2Init(TIM2, &TIM_OCInitStructure);//启动定时器TIM2TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);
}
PWM.h代码:
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif
servo.c 代码:
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Servo_Init(void)
{PWM_Init();
}
void Servo_SetAngle(float Angle)
{PWM_SetCompare2(Angle/180*2000+500);
}
Servo.h 代码:
#ifndef __SERVO_H__
#define __SERVO_H__
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
电气部分: 卡片的电气部分由一个天线和一个ASIC组成。 天线:就是几组绕线的线圈,体积小,已经封装在卡片内 ASIC:ASIC即专用集成电路,是指应特定用户要求和特定电子系统的需要而设计、制造的集成电路。 目前用CPLD(复杂可编程逻辑器件)和 FPGA(现场可编程逻辑阵列)来进行ASIC设计是最为流行的方式之一,它们的共性是都具有用户现场可编程特性,都支持边界扫描技术,但两者在集成度、速度以及编程方式上具有各自的特点,这样理解,ASIC就是卡片特点的一个集成电路。 卡片的ASIC包含了一个高速(106KB)的RF接口、一个控制单元、一个8K的EEPROM
工作过程: 读卡器会向M1卡发送一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其工作频率与读卡器发送的电磁波频率相同,遂在电磁波的激励下,LC串联谐振电路会发生共振,从而使电容内产生电荷,在电容的另一端接有一个单向导电的电子泵,电子泵将产生的电荷转移到另一个电容中存储。当存储电容中的电荷达到2V的时候,此时电容就作为电源为其他电路提供工作电压,所以卡片就可以向读卡器发送数据,或者从读卡器接收数据,实现了读卡器与卡片的通信。
RC522各个引脚的作用:
SDA:也叫CS,串行数据线,用于与主控设备进行数据通信
SCK:串行时钟线,用于同步数据传输。
MOSI:主输出从输入线,由主控设备发送数据到RC522模块上
MISO:主输入从输出线,由RC522 模块发送数据到主控设备上
RC522
通过SPI
接口和单片机(stm32)进行通信,通过单片机向 RC522
内的寄存器写入特定的指令,使 RC522
根据寄存器中的值来执行相关的操作,从而达到和 M1
的通信。
SPI(Serial Peripheral Interface 串行外设接口)
是一种用于在微控制器和外部设备之间进行串行通信的协议。
SPI协议通过四条线(或更多)进行通信:
SCK(Serial Clock):时钟线,由主设备提供,用于同步数据的传输
MOSI(Master Out Slave In):主输出从输入线,由主设备发送数据到外设(此处为stm32 向RC522 模块发送数据)
MISO(Master In Slave In):主输入从输出线,由外设数据到主设备。
NSS(Slave Select):片选线,由主设备选择与之通信的特定外设
串行通信
串行通信是指一种逐位进行数据传输的通信方式,其中每个数据为按顺序依次发送或接收,在串行通信中,每个数据位与时钟信号同步,以确保正确的数据传输。
一下是RC522的寄存器和一些相关指令:
/
//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
由于RC522 是通过 SPI 与STM32 进行通信的,所以我们要配置STM32 中的引脚,以及宏定义一些引脚的电平操作。
/* RC522引脚连接说明(SPI1的引脚) :
CS:PA4( 接的SDA引脚 )
SCK:PA5
MISO:PA6
MOSI:PA7
RST:PB0
*/
void RC522_GPIO_Init( void )
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE );GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_Init( GPIOB, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_Init( GPIOA, &GPIO_InitStructure );
}
/* IO口操作函数 */
#define RC522_CS_Enable() GPIO_ResetBits ( GPIOA, GPIO_Pin_4 ) //打开串行数据传输
#define RC522_CS_Disable() GPIO_SetBits ( GPIOA, GPIO_Pin_4 ) //关闭串行数据传输
#define RC522_Reset_Enable() GPIO_ResetBits( GPIOB, GPIO_Pin_0 )
#define RC522_Reset_Disable() GPIO_SetBits( GPIOB, GPIO_Pin_0 )
#define RC522_SCK_0() GPIO_ResetBits( GPIOA, GPIO_Pin_5 )
#define RC522_SCK_1() GPIO_SetBits( GPIOA, GPIO_Pin_5 )
#define RC522_MOSI_0() GPIO_ResetBits( GPIOA, GPIO_Pin_7 )
#define RC522_MOSI_1() GPIO_SetBits( GPIOA, GPIO_Pin_7 )
#define RC522_MISO_GET() GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_6 )
初始化好单片机的引脚后,接着是通过SPI 发送和接收STM32 的数据,具体函数代码如下(高位先行):
/* 软件模拟SPI发送一个字节数据,高位先行 */
void RC522_SPI_SendByte( uint8_t byte )
{uint8_t n;for( n=0;n<8;n++ ){if( byte&0x80 )RC522_MOSI_1();elseRC522_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;
}
如此,单片机和RC522之间的通信基础机制就建立起来了,下一步就是建立在通信基础上的操作了。
单片机是向RC522的寄存器操作来驱动RC522的,所以会有这几种基本操作:
读取RC522指定寄存器的值
向RC522指定寄存器中写入指定的数据
置位RC522指定寄存器的指定位
清位RC522指定寄存器的指定位
具体操作函数如下:
/* 软件模拟SPI发送一个字节数据,高位先行 */
void RC522_SPI_SendByte( uint8_t byte )
{uint8_t n;for( n=0;n<8;n++ ){if( byte&0x80 )RC522_MOSI_1();elseRC522_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;
}
上面说了寄存器、指令、对寄存器的操作,这里介绍一些对RC522的基本操作,包括:
开启天线
关闭天线
复位RC522
设置RC522工作方式
RC522与M1通信前必须开启天线,进行复位,然后设置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();}
}
/*** @brief :初始化RC522引脚,打开天线,复位RC522,设置RC522的工作方式* @param :无* @retval :无
*/
void RC522_Init(void)
{RC522_GPIO_Init();RC522_Antenna_On();RC522_Rese();RC522_Config_Type('A');
}
这部分是最重要的步骤,RC522与M1的通信是工程要实现的目的,而且要遵守前面提到的M1卡与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 允许空闲中断IdleIEnucWaitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位break;case PCD_TRANSCEIVE: //接收发送 发送接收ucIrqEn = 0x77; //允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEnucWaitFor = 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 [ ul ] ); //写数据进FIFOdataRC522_Write_Register ( CommandReg, ucCommand ); //写命令if ( ucCommand == PCD_TRANSCEIVE )RC522_SetBit_Register(BitFramingReg,0x80); //StartSend置位启动数据发送 该位与收发命令使用时才有效ul = 1000;//根据时钟频率调整,操作M1卡最大等待时间25msdo //认证 与寻卡等待时间 {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 [ ul ] = RC522_Read_Register ( FIFODataReg ); } } elsecStatus = MI_ERR; }RC522_SetBit_Register ( ControlReg, 0x80 ); // stop timer nowRC522_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 [ MAXRLEN ]; uint32_t ulLen;RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况RC522_Write_Register ( BitFramingReg, 0x07 ); // 发送的最后一个字节的 七位RC522_SetBit_Register ( TxControlReg, 0x03 ); //TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号
ucComMF522Buf [ 0 ] = ucReq_code; //存入寻卡方式/* PCD_TRANSCEIVE:发送并接收数据的命令,RC522向卡片发送寻卡命令,卡片返回卡的型号代码到ucComMF522Buf中 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, & ulLen ); //寻卡 if ( ( cStatus == MI_OK ) && ( ulLen == 0x10 ) ) //寻卡成功返回卡类型 { /* 接收卡片的型号代码 */* pTagType = ucComMF522Buf [ 0 ];* ( pTagType + 1 ) = ucComMF522Buf [ 1 ];}elsecStatus = 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 [ MAXRLEN ]; uint32_t ulLen;RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位RC522_Write_Register ( BitFramingReg, 0x00); //清理寄存器 停止收发RC522_ClearBit_Register ( CollReg, 0x80 ); //清ValuesAfterColl所有接收的位在冲突后被清除ucComMF522Buf [ 0 ] = 0x93; //卡片防冲突命令ucComMF522Buf [ 1 ] = 0x20;/* 将卡片防冲突命令通过RC522传到卡片中,返回的是被选中卡片的序列 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, & ulLen);//与卡片通信if ( cStatus == MI_OK) //通信成功{for ( uc = 0; uc < 4; uc ++ ){* ( pSnr + uc ) = ucComMF522Buf [ uc ]; //读出UIDucSnr_check ^= ucComMF522Buf [ uc ];}if ( ucSnr_check != ucComMF522Buf [ uc ] )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 [ 0 ] = RC522_Read_Register ( CRCResultRegL );pOutData [ 1 ] = RC522_Read_Register ( CRCResultRegM );}
/*** @brief :选定卡片* @param :pSnr:卡片序列号,4字节* @retval :状态值MI_OK,成功
*/
char PcdSelect ( uint8_t * pSnr )
{char ucN;uint8_t uc;uint8_t ucComMF522Buf [ MAXRLEN ]; uint32_t ulLen;/* PICC_ANTICOLL1:防冲突命令 */ucComMF522Buf [ 0 ] = PICC_ANTICOLL1;ucComMF522Buf [ 1 ] = 0x70;ucComMF522Buf [ 6 ] = 0;for ( uc = 0; uc < 4; uc ++ ){ucComMF522Buf [ uc + 2 ] = * ( pSnr + uc );ucComMF522Buf [ 6 ] ^= * ( pSnr + uc );}CalulateCRC ( ucComMF522Buf, 7, & ucComMF522Buf [ 7 ] );RC522_ClearBit_Register ( Status2Reg, 0x08 );
ucN = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, & ulLen );if ( ( ucN == MI_OK ) && ( ulLen == 0x18 ) )ucN = MI_OK; elseucN = 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 [ MAXRLEN ];uint32_t ulLen; ucComMF522Buf [ 0 ] = ucAuth_mode;ucComMF522Buf [ 1 ] = ucAddr;/* 前俩字节存储验证模式和块地址,2~8字节存储密码(6个字节),8~14字节存储序列号 */for ( uc = 0; uc < 6; uc ++ )ucComMF522Buf [ uc + 2 ] = * ( pKey + uc ); for ( uc = 0; uc < 6; uc ++ )ucComMF522Buf [ uc + 8 ] = * ( 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 [ MAXRLEN ];uint32_t ulLen;ucComMF522Buf [ 0 ] = PICC_WRITE;//写块命令ucComMF522Buf [ 1 ] = ucAddr;//写块地址/* 进行循环冗余校验,将结果存储在& ucComMF522Buf [ 2 ] */CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );/* PCD_TRANSCEIVE:发送并接收数据命令,通过RC522向卡片发送写块命令 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
/* 通过卡片返回的信息判断,RC522是否与卡片正常通信 */if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 0x0F ) != 0x0A ) )cStatus = MI_ERR; if ( cStatus == MI_OK ){//memcpy(ucComMF522Buf, pData, 16);/* 将要写入的16字节的数据,传入ucComMF522Buf数组中 */for ( uc = 0; uc < 16; uc ++ )ucComMF522Buf [ uc ] = * ( pData + uc ); /* 冗余校验 */CalulateCRC ( ucComMF522Buf, 16, & ucComMF522Buf [ 16 ] );/* 通过RC522,将16字节数据包括2字节校验结果写入卡片中 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, & ulLen );/* 判断写地址是否成功 */if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 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 [ MAXRLEN ]; uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_READ;ucComMF522Buf [ 1 ] = ucAddr;/* 冗余校验 */CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );/* 通过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 [ uc ]; }elsecStatus = MI_ERR; return cStatus;
}
/*** @brief :让卡片进入休眠模式* @param :无* @retval :状态值MI_OK,成功
*/
char PcdHalt( void )
{uint8_t ucComMF522Buf [ MAXRLEN ]; uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_HALT;ucComMF522Buf [ 1 ] = 0;CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
return MI_OK;}
RC522.c 代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
//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
/* RC522引脚连接说明(SPI1的引脚) :
CS:PA4( 接的SDA引脚 )
SCK:PA5
MISO:PA6
MOSI:PA7
RST:PB0
*/
void RC522_GPIO_Init( void )
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE );GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_Init( GPIOB, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_Init( GPIOA, &GPIO_InitStructure );
}
/* IO口操作函数 */
#define RC522_CS_Enable() GPIO_ResetBits ( GPIOA, GPIO_Pin_4 )
#define RC522_CS_Disable() GPIO_SetBits ( GPIOA, GPIO_Pin_4 )
#define RC522_Reset_Enable() GPIO_ResetBits( GPIOB, GPIO_Pin_0 )
#define RC522_Reset_Disable() GPIO_SetBits( GPIOB, GPIO_Pin_0 )
#define RC522_SCK_0() GPIO_ResetBits( GPIOA, GPIO_Pin_5 )
#define RC522_SCK_1() GPIO_SetBits( GPIOA, GPIO_Pin_5 )
#define RC522_MOSI_0() GPIO_ResetBits( GPIOA, GPIO_Pin_7 )
#define RC522_MOSI_1() GPIO_SetBits( GPIOA, GPIO_Pin_7 )
#define RC522_MISO_GET() GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_6 )
/*****************************************************************************************************************************/
/* 软件模拟SPI发送一个字节数据,高位先行 */
void RC522_SPI_SendByte( uint8_t byte )
{uint8_t n;for( n=0;n<8;n++ ){if( byte&0x80 )RC522_MOSI_1();elseRC522_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;
}
/*************************************** STM32对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的基础通信 **************************************************************************************/
/*** @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();}
}
/**
* @brief :初始化RC522引脚,打开天线,复位RC522,设置RC522的工作方式* @param :无* @retval :无
*/
void RC522_Init(void)
{RC522_GPIO_Init();RC522_Antenna_On();RC522_Rese();RC522_Config_Type('A');
}
/************************************ STM32控制RC522与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 允许空闲中断IdleIEnucWaitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位break;case PCD_TRANSCEIVE: //接收发送 发送接收ucIrqEn = 0x77; //允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEnucWaitFor = 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 [ ul ] ); //写数据进FIFOdataRC522_Write_Register ( CommandReg, ucCommand ); //写命令if ( ucCommand == PCD_TRANSCEIVE )RC522_SetBit_Register(BitFramingReg,0x80); //StartSend置位启动数据发送 该位与收发命令使用时才有效ul = 1000;//根据时钟频率调整,操作M1卡最大等待时间25msdo //认证 与寻卡等待时间 {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 [ ul ] = RC522_Read_Register ( FIFODataReg ); } } elsecStatus = MI_ERR; }RC522_SetBit_Register ( ControlReg, 0x80 ); // stop timer nowRC522_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 [ MAXRLEN ]; uint32_t ulLen;RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况RC522_Write_Register ( BitFramingReg, 0x07 ); // 发送的最后一个字节的 七位RC522_SetBit_Register ( TxControlReg, 0x03 ); //TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号
ucComMF522Buf [ 0 ] = ucReq_code; //存入寻卡方式/* PCD_TRANSCEIVE:发送并接收数据的命令,RC522向卡片发送寻卡命令,卡片返回卡的型号代码到ucComMF522Buf中 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, & ulLen ); //寻卡 if ( ( cStatus == MI_OK ) && ( ulLen == 0x10 ) ) //寻卡成功返回卡类型 { /* 接收卡片的型号代码 */* pTagType = ucComMF522Buf [ 0 ];* ( pTagType + 1 ) = ucComMF522Buf [ 1 ];}elsecStatus = 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 [ MAXRLEN ]; uint32_t ulLen;RC522_ClearBit_Register ( Status2Reg, 0x08 ); //清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位RC522_Write_Register ( BitFramingReg, 0x00); //清理寄存器 停止收发RC522_ClearBit_Register ( CollReg, 0x80 ); //清ValuesAfterColl所有接收的位在冲突后被清除ucComMF522Buf [ 0 ] = 0x93; //卡片防冲突命令ucComMF522Buf [ 1 ] = 0x20;/* 将卡片防冲突命令通过RC522传到卡片中,返回的是被选中卡片的序列 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, & ulLen);//与卡片通信if ( cStatus == MI_OK) //通信成功{for ( uc = 0; uc < 4; uc ++ ){* ( pSnr + uc ) = ucComMF522Buf [ uc ]; //读出UIDucSnr_check ^= ucComMF522Buf [ uc ];}if ( ucSnr_check != ucComMF522Buf [ uc ] )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 [ 0 ] = RC522_Read_Register ( CRCResultRegL );pOutData [ 1 ] = RC522_Read_Register ( CRCResultRegM );}
/*** @brief :选定卡片* @param :pSnr:卡片序列号,4字节* @retval :状态值MI_OK,成功
*/
char PcdSelect ( uint8_t * pSnr )
{char ucN;uint8_t uc;uint8_t ucComMF522Buf [ MAXRLEN ]; uint32_t ulLen;/* PICC_ANTICOLL1:防冲突命令 */ucComMF522Buf [ 0 ] = PICC_ANTICOLL1;ucComMF522Buf [ 1 ] = 0x70;ucComMF522Buf [ 6 ] = 0;for ( uc = 0; uc < 4; uc ++ ){ucComMF522Buf [ uc + 2 ] = * ( pSnr + uc );ucComMF522Buf [ 6 ] ^= * ( pSnr + uc );}CalulateCRC ( ucComMF522Buf, 7, & ucComMF522Buf [ 7 ] );RC522_ClearBit_Register ( Status2Reg, 0x08 );
ucN = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, & ulLen );if ( ( ucN == MI_OK ) && ( ulLen == 0x18 ) )ucN = MI_OK; elseucN = 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 [ MAXRLEN ];uint32_t ulLen; ucComMF522Buf [ 0 ] = ucAuth_mode;ucComMF522Buf [ 1 ] = ucAddr;/* 前俩字节存储验证模式和块地址,2~8字节存储密码(6个字节),8~14字节存储序列号 */for ( uc = 0; uc < 6; uc ++ )ucComMF522Buf [ uc + 2 ] = * ( pKey + uc ); for ( uc = 0; uc < 6; uc ++ )ucComMF522Buf [ uc + 8 ] = * ( 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 [ MAXRLEN ];uint32_t ulLen;ucComMF522Buf [ 0 ] = PICC_WRITE;//写块命令ucComMF522Buf [ 1 ] = ucAddr;//写块地址/* 进行循环冗余校验,将结果存储在& ucComMF522Buf [ 2 ] */CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );/* PCD_TRANSCEIVE:发送并接收数据命令,通过RC522向卡片发送写块命令 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
/* 通过卡片返回的信息判断,RC522是否与卡片正常通信 */if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 0x0F ) != 0x0A ) )cStatus = MI_ERR; if ( cStatus == MI_OK ){//memcpy(ucComMF522Buf, pData, 16);/* 将要写入的16字节的数据,传入ucComMF522Buf数组中 */for ( uc = 0; uc < 16; uc ++ )ucComMF522Buf [ uc ] = * ( pData + uc ); /* 冗余校验 */CalulateCRC ( ucComMF522Buf, 16, & ucComMF522Buf [ 16 ] );/* 通过RC522,将16字节数据包括2字节校验结果写入卡片中 */cStatus = PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, & ulLen );/* 判断写地址是否成功 */if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || ( ( ucComMF522Buf [ 0 ] & 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 [ MAXRLEN ]; uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_READ;ucComMF522Buf [ 1 ] = ucAddr;/* 冗余校验 */CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );/* 通过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 [ uc ]; }elsecStatus = MI_ERR; return cStatus;
}
/*** @brief :让卡片进入休眠模式* @param :无* @retval :状态值MI_OK,成功
*/
char PcdHalt( void )
{uint8_t ucComMF522Buf [ MAXRLEN ]; uint32_t ulLen;
ucComMF522Buf [ 0 ] = PICC_HALT;ucComMF522Buf [ 1 ] = 0;
CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );PcdComMF522 ( PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, & ulLen );
return MI_OK;
}
RC522.h 代码:
#ifndef __RC522_H__
#define __RC522_H__
#include "stm32f10x.h" // Device header
void RC522_Init(void); //xinzeng
void RC522_GPIO_Init( void );
void RC522_SPI_SendByte( uint8_t byte );
uint8_t RC522_Read_Register( uint8_t Address );
void RC522_Write_Register( uint8_t Address, uint8_t data );
void RC522_SetBit_Register( uint8_t Address, uint8_t mask );
void RC522_ClearBit_Register( uint8_t Address, uint8_t mask );
void RC522_Antenna_On( void );
void RC522_Rese( void );
void RC522_Config_Type( char Type );
char PcdComMF522 ( uint8_t ucCommand, uint8_t * pInData, uint8_t ucInLenByte, uint8_t * pOutData, uint32_t * pOutLenBit );
char PcdRequest ( uint8_t ucReq_code, uint8_t * pTagType );
char PcdAnticoll ( uint8_t * pSnr );
void CalulateCRC ( uint8_t * pIndata, u8 ucLen, uint8_t * pOutData );
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 );
char PcdRead ( uint8_t ucAddr, uint8_t * pData );
char PcdHalt( void );
void IC_test ( void );
#endif
将以下获取卡号的函数代码加进 RC522.c中,同时在RC522.h 中声明以下函数。
/* OLED显示屏vcc:PA6GND:PA7SCL:PA8SDA:PA9(PA6接上电源正极,PA7接电源负极)
*/
#include "OLED.h"
/* 卡的ID存储,32位,4字节 */
u8 ucArray_ID [ 4 ];
void IC_test ( void )
{ uint8_t ucStatusReturn; //返回状态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 ){}}OLED_Init();OLED_ShowHexNum(1,1,ucArray_ID[0],2);OLED_ShowHexNum(1,4,ucArray_ID[1],2);OLED_ShowHexNum(1,7,ucArray_ID[2],2);OLED_ShowHexNum(1,10,ucArray_ID[3],2);
}
}
OLED.c 文件代码
#include "stm32f10x.h"
#include "OLED_Font.h"/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))/*引脚初始化*/
void OLED_I2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_Init(GPIOB, &GPIO_InitStructure);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief I2C开始* @param 无* @retval 无*/
void OLED_I2C_Start(void)
{OLED_W_SDA(1);OLED_W_SCL(1);OLED_W_SDA(0);OLED_W_SCL(0);
}/*** @brief I2C停止* @param 无* @retval 无*/
void OLED_I2C_Stop(void)
{OLED_W_SDA(0);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief I2C发送一个字节* @param Byte 要发送的一个字节* @retval 无*/
void OLED_I2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i++){OLED_W_SDA(Byte & (0x80 >> i));OLED_W_SCL(1);OLED_W_SCL(0);}OLED_W_SCL(1); //额外的一个时钟,不处理应答信号OLED_W_SCL(0);
}/*** @brief OLED写命令* @param Command 要写入的命令* @retval 无*/
void OLED_WriteCommand(uint8_t Command)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78); //从机地址OLED_I2C_SendByte(0x00); //写命令OLED_I2C_SendByte(Command); OLED_I2C_Stop();
}/*** @brief OLED写数据* @param Data 要写入的数据* @retval 无*/
void OLED_WriteData(uint8_t Data)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78); //从机地址OLED_I2C_SendByte(0x40); //写数据OLED_I2C_SendByte(Data);OLED_I2C_Stop();
}/*** @brief OLED设置光标位置* @param Y 以左上角为原点,向下方向的坐标,范围:0~7* @param X 以左上角为原点,向右方向的坐标,范围:0~127* @retval 无*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{OLED_WriteCommand(0xB0 | Y); //设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}/*** @brief OLED清屏* @param 无* @retval 无*/
void OLED_Clear(void)
{ uint8_t i, j;for (j = 0; j < 8; j++){OLED_SetCursor(j, 0);for(i = 0; i < 128; i++){OLED_WriteData(0x00);}}
}/*** @brief OLED显示一个字符* @param Line 行位置,范围:1~4* @param Column 列位置,范围:1~16* @param Char 要显示的一个字符,范围:ASCII可见字符* @retval 无*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{ uint8_t i;OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容}
}/*** @brief OLED显示字符串* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param String 要显示的字符串,范围:ASCII可见字符* @retval 无*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{uint8_t i;for (i = 0; String[i] != '