手把手从0到1教你做STM32+FreeRTOS智能家居--第9篇之RC522读卡器

一、硬件设计

硬件接线:

SPI_NSS(CS)  -- PA4

SPI_SCK-- PA5

SPI_MISO -- PA6

SPI_MOSI -- PA7

SPI_RST -- PD2

二、软件设计

本项目用到的是IO口模拟SPI,STM32Cubemx直接将PA4、PA5、PA7、PD2配置为推挽输出,PA6配置为输入即可。

RC522.c

#include "rc522.h"


#define   RC522_DELAY()  delay_us ( 2 )
#define fac_us 72   //时钟频率,单位MHZ

uint32_t RC522_ID1 = 0xED0B562D; //小圆球ID

void Get_RC522(void)
{
	uint32_t writeValue = 100;
	uint32_t readValue;
	char cStr [ 30 ];
  uint8_t ucArray_ID [ 4 ];    /*先后存放IC卡的类型和UID(IC卡序列号)*/                                                                                         
	uint8_t ucStatusReturn;      /*返回状态*/        	
	static uint8_t Rc_Timeout;
	uint8_t status;
	uint8_t Card_ID[4];
	
	HAL_Delay(100);
	/*寻卡*/
	while( (ucStatusReturn = PcdRequest ( PICC_REQIDL, ucArray_ID )) != MI_OK )
	{
		/*若失败再次寻卡*/
		Rc_Timeout++;
		HAL_Delay(100);
		//printf ( "寻卡失败\n" );
		ucStatusReturn = PcdRequest ( PICC_REQIDL, ucArray_ID ); //PICC_REQALL   PICC_REQIDL
		if(Rc_Timeout >= 10)
		{
			Rc_Timeout = 0;
			break;
		}
	}
	if ( ucStatusReturn == MI_OK  )
	{
		printf ( "寻卡成功\n" );
		status = PcdAnticoll(Card_ID);
		//判断读取的ID是否为的小圆球ID,是的话再做操作。
		if(status)
		{
			if(Card_ID[0] == ((RC522_ID1 & 0xFF000000) >> 24) )
				if(Card_ID[1] == ((RC522_ID1 & 0x00FF0000) >> 16))
					if(Card_ID[2] == ((RC522_ID1 & 0x0000FF00) >> 8))
						if(Card_ID[3] == ((RC522_ID1 & 0x000000FF) >>0 ))
							printf("小圆球\n");
		}
	}
}

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



/**
  * @brief  RC522_Init
  * @param  无
  * @retval 无
  */
void RC522_Init ( void )
{
//	RC522_Reset_Disable();	
//	RC522_CS_Disable();
	PcdReset ();/*RC522相关*/
	HAL_Delay(50);
	PcdReset();//复位RC522读卡器
	HAL_Delay(10);
	PcdReset();//复位RC522读卡器
	HAL_Delay(10);
	PcdAntennaOff();//关闭天线发射
	HAL_Delay(10);
  PcdAntennaOn();//开启天线发射
	
//	M500PcdConfigISOType ( 'A' );/*RC52射频模 设置工作方式*/
	printf("RFID-MFRC522 初始化完成\r\nFindCard Starting ...\r\n");
}


/**
  * @brief  IC测试函数
  * @param  无
  * @retval 无
  */
extern uint8_t KeyValue[];   // 卡A密钥
void IC_test ( void )
{
	uint32_t writeValue = 100;
	uint32_t readValue;
	char cStr [ 30 ];
  uint8_t ucArray_ID [ 4 ];    /*先后存放IC卡的类型和UID(IC卡序列号)*/                                                                                         
	uint8_t ucStatusReturn;      /*返回状态*/                   
	static uint8_t Rc_Timeout;
  while ( 1 )
  {    
    /*寻卡*/
		if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
    {
      /*若失败再次寻卡*/
			Rc_Timeout++;
			HAL_Delay(1000);
      printf ( "寻卡失败\n" );
			ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ); //PICC_REQALL   PICC_REQIDL
			if(Rc_Timeout >= 10)
			{
				Rc_Timeout = 0;
				break;
			}
    }
		if ( ucStatusReturn == MI_OK  )
		{
      printf ( "寻卡成功\n" );
			//Voice_Send("RC522 OK");
			//delay_ms(2000);
			HAL_Delay(2000);
			break;
//      /*防冲撞(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/
//			if ( PcdAnticoll ( ucArray_ID ) == MI_OK )                                                                   
//			{
//				PcdSelect(ucArray_ID);			
//		
//				PcdAuthState( PICC_AUTHENT1A, 0x11, KeyValue, ucArray_ID );//校验密码 
//        WriteAmount(0x11,writeValue); //写入金额
//        if(ReadAmount(0x11,&readValue) == MI_OK)	//读取金额
//				{		
//					writeValue +=100;
//				  sprintf ( cStr, "The Card ID is: %02X%02X%02X%02X",ucArray_ID [0], ucArray_ID [1], ucArray_ID [2],ucArray_ID [3] );
//					printf ( "%s\r\n",cStr );  //打印卡片ID
//					
//					printf ("余额为:%d\r\n",readValue);
//					sprintf ( cStr, "TThe residual amount: %d", readValue);				 										 	         
//          PcdHalt();
//				}
//			}				
		}		
  }	
}

/**
  * @brief  向RC522发送1 Byte 数据
  * @param  byte,要发送的数据
  * @retval RC522返回的数据
  */
void SPI_RC522_SendByte ( uint8_t byte )
{
  uint8_t counter;

  for(counter=0;counter<8;counter++)
  {     
    if ( byte & 0x80 )
      RC522_MOSI_1 ();
    else 
      RC522_MOSI_0 ();
    
    RC522_DELAY();
    RC522_SCK_0 ();
    
    RC522_DELAY();
    RC522_SCK_1();
    
    RC522_DELAY();
    byte <<= 1; 
  } 	
}


/**
  * @brief  从RC522发送1 Byte 数据
  * @param  无
  * @retval RC522返回的数据
  */
uint8_t SPI_RC522_ReadByte ( void )
{
  uint8_t counter;
  uint8_t SPI_Data;

  for(counter=0;counter<8;counter++)
  {
    SPI_Data <<= 1;
    RC522_SCK_0 ();
   
    RC522_DELAY();
    if ( RC522_MISO_GET() == 1)
     SPI_Data |= 0x01;
    
    RC522_DELAY();
    RC522_SCK_1 ();
    
    RC522_DELAY();
  }
  return SPI_Data;
	
}



/**
  * @brief  读RC522寄存器
  * @param  ucAddress,寄存器地址
  * @retval 寄存器的当前值
  */
uint8_t ReadRawRC ( uint8_t ucAddress )
{
	uint8_t ucAddr, ucReturn;
	
	ucAddr = ( ( ucAddress << 1 ) & 0x7E ) | 0x80;	
	RC522_CS_Enable();
  
	SPI_RC522_SendByte ( ucAddr );
	ucReturn = SPI_RC522_ReadByte ();
  
	RC522_CS_Disable();
	
	return ucReturn;	
}

/**
  * @brief  写RC522寄存器
  * @param  ucAddress,寄存器地址
  * @param  ucValue,写入寄存器的值
  * @retval 无
  */
void WriteRawRC ( uint8_t ucAddress, uint8_t ucValue )
{  
	uint8_t ucAddr;
	
	ucAddr = ( ucAddress << 1 ) & 0x7E;	
	RC522_CS_Enable();
	
	SPI_RC522_SendByte ( ucAddr );	
	SPI_RC522_SendByte ( ucValue );
  
	RC522_CS_Disable();		
}


/**
  * @brief  对RC522寄存器置位
  * @param  ucReg,寄存器地址
  * @param   ucMask,置位值
  * @retval 无
  */
void SetBitMask ( uint8_t ucReg, uint8_t ucMask )  
{
  uint8_t ucTemp;

  ucTemp = ReadRawRC ( ucReg );
  WriteRawRC ( ucReg, ucTemp | ucMask ); // set bit mask
}


/**
  * @brief  对RC522寄存器清位
  * @param  ucReg,寄存器地址
  * @param  ucMask,清位值
  * @retval 无
  */
void ClearBitMask ( uint8_t ucReg, uint8_t ucMask )  
{
  uint8_t ucTemp;

  ucTemp = ReadRawRC ( ucReg );
  WriteRawRC ( ucReg, ucTemp & ( ~ ucMask) ); // clear bit mask
}


/**
  * @brief  开启天线 
  * @param  无
  * @retval 无
  */
void PcdAntennaOn ( void )
{
  uint8_t uc;

  uc = ReadRawRC ( TxControlReg );
  if ( ! ( uc & 0x03 ) )
   SetBitMask(TxControlReg, 0x03);		
}


/**
  * @brief  关闭天线
  * @param  无
  * @retval 无
  */
void PcdAntennaOff ( void )
{
  ClearBitMask ( TxControlReg, 0x03 );	
}


/**
  * @brief  复位RC522 
  * @param  无
  * @retval 无
  */
void PcdReset ( void )
{
	RC522_Reset_Disable();
	delay_us ( 1 );
	
	RC522_Reset_Enable();
	delay_us ( 1 );
	
	RC522_Reset_Disable();
	delay_us ( 1 );
	
	WriteRawRC ( CommandReg, 0x0f );
	
	while ( ReadRawRC ( CommandReg ) & 0x10 );
	
	delay_us ( 1 );
  
	//定义发送和接收常用模式 和Mifare卡通讯,CRC初始值0x6363
  WriteRawRC ( ModeReg, 0x3D );        
	
  WriteRawRC ( TReloadRegL, 30 );      //16位定时器低位    
	WriteRawRC ( TReloadRegH, 0 );			 //16位定时器高位
	
  WriteRawRC ( TModeReg, 0x8D );			 //定义内部定时器的设置
	
  WriteRawRC ( TPrescalerReg, 0x3E );	 //设置定时器分频系数
	
	WriteRawRC ( TxAutoReg, 0x40 );			 //调制发送信号为100%ASK	
	
}



/**
  * @brief  设置RC522的工作方式
  * @param  ucType,工作方式
  * @retval 无
  */
void M500PcdConfigISOType ( uint8_t ucType )
{
	if ( ucType == 'A')                     //ISO14443_A
  {
		ClearBitMask ( Status2Reg, 0x08 );
		
    WriteRawRC ( ModeReg, 0x3D );         //3F
		
		WriteRawRC ( RxSelReg, 0x86 );        //84
		
		WriteRawRC( RFCfgReg, 0x7F );         //4F
		
		WriteRawRC( TReloadRegL, 30 );        
		
		WriteRawRC ( TReloadRegH, 0 );
		
		WriteRawRC ( TModeReg, 0x8D );
		
		WriteRawRC ( TPrescalerReg, 0x3E );
		
		delay_us ( 2 );
		
		PcdAntennaOn ();//开天线
		
   }	 
}



/**
  * @brief  通过RC522和ISO14443卡通讯
  * @param  ucCommand,RC522命令字
  * @param  pInData,通过RC522发送到卡片的数据
  * @param  ucInLenByte,发送数据的字节长度
  * @param  pOutData,接收到的卡片返回数据
  * @param  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;     
  }
  //IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反 
  WriteRawRC ( ComIEnReg, ucIrqEn | 0x80 );
  //Set1该位清零时,CommIRqReg的屏蔽位清零
  ClearBitMask ( ComIrqReg, 0x80 );	 
  //写空闲命令
  WriteRawRC ( CommandReg, PCD_IDLE );		 
  
  //置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除
  SetBitMask ( FIFOLevelReg, 0x80 );			

  for ( ul = 0; ul < ucInLenByte; ul ++ )
    WriteRawRC ( FIFODataReg, pInData [ ul ] ); //写数据进FIFOdata
    
  WriteRawRC ( CommandReg, ucCommand );					//写命令


  if ( ucCommand == PCD_TRANSCEIVE )
    
    //StartSend置位启动数据发送 该位与收发命令使用时才有效
    SetBitMask(BitFramingReg,0x80);  				  

  ul = 1000;                             //根据时钟频率调整,操作M1卡最大等待时间25ms

  do 														         //认证 与寻卡等待时间	
  {
       ucN = ReadRawRC ( ComIrqReg );		 //查询事件中断
       ul --;
  } while ( ( ul != 0 ) && ( ! ( ucN & 0x01 ) ) && ( ! ( ucN & ucWaitFor ) ) );	

  ClearBitMask ( BitFramingReg, 0x80 );	 //清理允许StartSend位

  if ( ul != 0 )
  {
    //读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
    if ( ! ( ReadRawRC ( ErrorReg ) & 0x1B ) )	
    {
      cStatus = MI_OK;
      
      if ( ucN & ucIrqEn & 0x01 )				//是否发生定时器中断
        cStatus = MI_NOTAGERR;   
        
      if ( ucCommand == PCD_TRANSCEIVE )
      {
        //读FIFO中保存的字节数
        ucN = ReadRawRC ( FIFOLevelReg );		          
        
        //最后接收到得字节的有效位数
        ucLastBits = ReadRawRC ( ControlReg ) & 0x07;	
        
        if ( ucLastBits )
          
          //N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
          * pOutLenBit = ( ucN - 1 ) * 8 + ucLastBits;   	
        else
          * pOutLenBit = ucN * 8;      //最后接收到的字节整个字节有效
        
        if ( ucN == 0 )		
          ucN = 1;    
        
        if ( ucN > MAXRLEN )
          ucN = MAXRLEN;   
        
        for ( ul = 0; ul < ucN; ul ++ )
          pOutData [ ul ] = ReadRawRC ( FIFODataReg );   
        
        }        
    }   
    else
      cStatus = MI_ERR;       
  }

  SetBitMask ( ControlReg, 0x80 );           // stop timer now
  WriteRawRC ( CommandReg, PCD_IDLE ); 
   
  return cStatus;
}

/**
  * @brief 寻卡
  * @param  ucReq_code,寻卡方式 = 0x52,寻感应区内所有符合14443A标准的卡;
            寻卡方式= 0x26,寻未进入休眠状态的卡
  * @param  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;

  //清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况
  ClearBitMask ( Status2Reg, 0x08 );
	//发送的最后一个字节的 七位
  WriteRawRC ( BitFramingReg, 0x07 );
  //TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号
  SetBitMask ( TxControlReg, 0x03 );	

  ucComMF522Buf [ 0 ] = ucReq_code;		//存入 卡片命令字

  cStatus = PcdComMF522 ( PCD_TRANSCEIVE,	
                          ucComMF522Buf,
                          1, 
                          ucComMF522Buf,
                          & ulLen );	//寻卡  

  if ( ( cStatus == MI_OK ) && ( ulLen == 0x10 ) )	//寻卡成功返回卡类型 
  {    
     * pTagType = ucComMF522Buf [ 0 ];
     * ( pTagType + 1 ) = ucComMF522Buf [ 1 ];
  }

  else
   cStatus = MI_ERR;

  return cStatus;	 
}

/**
  * @brief  防冲撞
  * @param  pSnr,卡片序列号,4字节
  * @retval 状态值= MI_OK,成功
  */
char PcdAnticoll ( uint8_t * pSnr )
{
  char cStatus;
  uint8_t uc, ucSnr_check = 0;
  uint8_t ucComMF522Buf [ MAXRLEN ]; 
  uint32_t ulLen;
  
  //清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位
  ClearBitMask ( Status2Reg, 0x08 );
  //清理寄存器 停止收发
  WriteRawRC ( BitFramingReg, 0x00);	
	//清ValuesAfterColl所有接收的位在冲突后被清除
  ClearBitMask ( CollReg, 0x80 );			  
 
  ucComMF522Buf [ 0 ] = 0x93;	          //卡片防冲突命令
  ucComMF522Buf [ 1 ] = 0x20;
 
  cStatus = PcdComMF522 ( PCD_TRANSCEIVE, 
                          ucComMF522Buf,
                          2, 
                          ucComMF522Buf,
                          & ulLen);      //与卡片通信

  if ( cStatus == MI_OK)		            //通信成功
  {
    for ( uc = 0; uc < 4; uc ++ )
    {
       * ( pSnr + uc )  = ucComMF522Buf [ uc ]; //读出UID
       ucSnr_check ^= ucComMF522Buf [ uc ];
    }
    
    if ( ucSnr_check != ucComMF522Buf [ uc ] )
      cStatus = MI_ERR;    				 
  }
  
  SetBitMask ( CollReg, 0x80 );
      
  return cStatus;		
}


/**
  * @brief  用RC522计算CRC16
  * @param  pIndata,计算CRC16的数组
  * @param  ucLen,计算CRC16的数组字节长度
  * @param  pOutData,存放计算结果存放的首地址
  * @retval 无
  */
void CalulateCRC ( uint8_t * pIndata, 
                 uint8_t ucLen, 
                 uint8_t * pOutData )
{
  uint8_t uc, ucN;

  ClearBitMask(DivIrqReg,0x04);

  WriteRawRC(CommandReg,PCD_IDLE);

  SetBitMask(FIFOLevelReg,0x80);

  for ( uc = 0; uc < ucLen; uc ++)
    WriteRawRC ( FIFODataReg, * ( pIndata + uc ) );   

  WriteRawRC ( CommandReg, PCD_CALCCRC );

  uc = 0xFF;

  do 
  {
      ucN = ReadRawRC ( DivIrqReg );
      uc --;
  } while ( ( uc != 0 ) && ! ( ucN & 0x04 ) );
  
  pOutData [ 0 ] = ReadRawRC ( CRCResultRegL );
  pOutData [ 1 ] = ReadRawRC ( 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;
  
  
  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 ] );

  ClearBitMask ( 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密钥
  * @param  uint8_t ucAddr,块地址
  * @param  pKey,密码 
  * @param  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;

  for ( uc = 0; uc < 6; uc ++ )
    ucComMF522Buf [ uc + 2 ] = * ( pKey + uc );   

  for ( uc = 0; uc < 6; uc ++ )
    ucComMF522Buf [ uc + 8 ] = * ( pSnr + uc );   

  cStatus = PcdComMF522 ( PCD_AUTHENT,
                          ucComMF522Buf, 
                          12,
                          ucComMF522Buf,
                          & ulLen );

  if ( ( cStatus != MI_OK ) || ( ! ( ReadRawRC ( Status2Reg ) & 0x08 ) ) )
    cStatus = MI_ERR;   
    
  return cStatus;
}


/**
  * @brief  写数据到M1卡一块
  * @param  ucAddr,块地址(0-63)。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
  * @param  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;

  CalulateCRC ( ucComMF522Buf, 2, & ucComMF522Buf [ 2 ] );

  cStatus = PcdComMF522 ( PCD_TRANSCEIVE,
                          ucComMF522Buf,
                          4, 
                          ucComMF522Buf,
                          & ulLen );

  if ( ( cStatus != MI_OK ) || ( ulLen != 4 ) || 
         ( ( ucComMF522Buf [ 0 ] & 0x0F ) != 0x0A ) )
    cStatus = MI_ERR;   
      
  if ( cStatus == MI_OK )
  {
    //memcpy(ucComMF522Buf, pData, 16);
    for ( uc = 0; uc < 16; uc ++ )
      ucComMF522Buf [ uc ] = * ( pData + uc );  
    
    CalulateCRC ( ucComMF522Buf, 16, & ucComMF522Buf [ 16 ] );

    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,块地址(0-63)。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
  * @param  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 ] );
 
  cStatus = PcdComMF522 ( PCD_TRANSCEIVE,
                          ucComMF522Buf,
                          4, 
                          ucComMF522Buf,
                          & ulLen );

  if ( ( cStatus == MI_OK ) && ( ulLen == 0x90 ) )
  {
    for ( uc = 0; uc < 16; uc ++ )
      * ( pData + uc ) = ucComMF522Buf [ uc ];   
  }
  
  else
    cStatus = MI_ERR;   
   
  return cStatus;		
}





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

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

  return MI_ERR;
}

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

  return MI_ERR;
}




/**
  * @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;	
}

/
//功    能:写入钱包金额
//参数说明: ucAddr[IN]:块地址
//          pData:写入的金额
//返    回: 成功返回MI_OK
/
char WriteAmount( uint8_t ucAddr, uint32_t pData )
{
	char status;
	uint8_t ucComMF522Buf[16];
	ucComMF522Buf[0] = (pData&((uint32_t)0x000000ff));
	ucComMF522Buf[1] = (pData&((uint32_t)0x0000ff00))>>8;
	ucComMF522Buf[2] = (pData&((uint32_t)0x00ff0000))>>16;
	ucComMF522Buf[3] = (pData&((uint32_t)0xff000000))>>24;	
	
	ucComMF522Buf[4] = ~(pData&((uint32_t)0x000000ff));
	ucComMF522Buf[5] = ~(pData&((uint32_t)0x0000ff00))>>8;
	ucComMF522Buf[6] = ~(pData&((uint32_t)0x00ff0000))>>16;
	ucComMF522Buf[7] = ~(pData&((uint32_t)0xff000000))>>24;	
	
	ucComMF522Buf[8] = (pData&((uint32_t)0x000000ff));
	ucComMF522Buf[9] = (pData&((uint32_t)0x0000ff00))>>8;
	ucComMF522Buf[10] = (pData&((uint32_t)0x00ff0000))>>16;
	ucComMF522Buf[11] = (pData&((uint32_t)0xff000000))>>24;	
	
	ucComMF522Buf[12] = ucAddr;
	ucComMF522Buf[13] = ~ucAddr;
	ucComMF522Buf[14] = ucAddr;
	ucComMF522Buf[15] = ~ucAddr;
  status = PcdWrite(ucAddr,ucComMF522Buf);
	return status;
}

/
//功    能:读取钱包金额
//参数说明: ucAddr[IN]:块地址
//          *pData:读出的金额
//返    回: 成功返回MI_OK
/
char ReadAmount( uint8_t ucAddr, uint32_t *pData )
{
	
	char status = MI_ERR;
	uint8_t j;
	uint8_t ucComMF522Buf[16];
  status = PcdRead(ucAddr,ucComMF522Buf);
	if(status != MI_OK)
		return status;
	for(j=0;j<4;j++)
	{
		if((ucComMF522Buf[j] != ucComMF522Buf[j+8]) && (ucComMF522Buf[j] != ~ucComMF522Buf[j+4]))//验证一下是不是钱包的数据
		break;
	}
	if(j == 4)
	{
		  status = MI_OK;
			*pData = ucComMF522Buf[0] + (ucComMF522Buf[1]<<8) + (ucComMF522Buf[2]<<16) + (ucComMF522Buf[3]<<24);
	}
	else
	{
		status = MI_ERR;
		*pData = 0;
	}
  return status;	
}






/**
 * @brief 修改控制块 ucAddr 的密码A。注意 ucAddr 指的是控制块的地址。
 *        必须要校验密码B,密码B默认为6个0xFF,如果密码B也忘记了,那就改不了密码A了
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 * @param ucAddr:[控制块]所在的地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pKeyA:指向新的密码A字符串,六个字符,比如 "123456"
 * @retval 成功返回 MI_OK
 */
char ChangeKeyA( uint8_t ucAddr, uint8_t *pKeyA )
{
	uint8_t KeyBValue[]={0xFF ,0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
  uint8_t ucArray_ID [ 4 ];    /*先后存放IC卡的类型和UID(IC卡序列号)*/                                                                                         
  uint8_t ucComMF522Buf[16];
  uint8_t j;
  
  /*寻卡*/
  while ( PcdRequest ( PICC_REQALL, ucArray_ID ) != MI_OK )
  {    
    printf( "寻卡失败\r\n" );
    delay_us(1000000);
  }
  
  printf ( "寻卡成功\n" );
  
  /* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/
  if ( PcdAnticoll ( ucArray_ID ) == MI_OK )                                                                   
  {
    /* 选中卡 */
    PcdSelect(ucArray_ID);			

    /* 校验 B 密码 */
    if( PcdAuthState( PICC_AUTHENT1B, ucAddr, KeyBValue, ucArray_ID ) != MI_OK )
    {
      printf( "检验密码B失败\r\n" );
    }
    
    // 读取控制块里原本的数据(只要修改密码A,其他数据不改)
    if( PcdRead(ucAddr,ucComMF522Buf) != MI_OK)
    {
      printf( "读取控制块数据失败\r\n" );
      return MI_ERR;
    }
    
    /* 修改密码A */
    for(j=0; j<6; j++)
      ucComMF522Buf[j] = pKeyA[j];
    
    if( PcdWrite(ucAddr,ucComMF522Buf) != MI_OK)
    {
      printf( "写入数据到控制块失败\r\n" );
      return MI_ERR;
    }
    
    printf( "密码A修改成功!\r\n" );
    PcdHalt();
    
    return MI_OK;
  }
  
  return MI_ERR;
}


/**
 * @brief 按照RC522操作流程写入16字节数据到块 ucAddr
 *        函数里校验的是密码B,密码B默认为6个0xFF,也可以校验密码A
 *        用法:WriteDataBlock( 1, "123456789\n", 10); //字符串不够16个字节的后面补零写入
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 *        注意:使用该函数要注意 ucAddr 是块0、数据块还是控制块,该函数内部不对此做判断
 * @param ucAddr:任意块地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pData:指向要写入的数据,最大16个字符
 * @param Len:要写入数据的字节数
 * @retval 成功返回 MI_OK
 */
char WriteDataBlock( uint8_t ucAddr, uint8_t *pData, uint8_t Len)
{
	uint8_t KeyBValue[]={0xFF ,0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
  uint8_t ucArray_ID [ 4 ];    /*先后存放IC卡的类型和UID(IC卡序列号)*/                                                                                         
  uint8_t ucComMF522Buf[16];
  uint8_t j;
  
  /*寻卡*/
  while ( PcdRequest ( PICC_REQALL, ucArray_ID ) != MI_OK )
  {    
    printf( "寻卡失败\r\n" );
    delay_us(1000000);
  }
  
  printf ( "寻卡成功\n" );
  
  /* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/
  if ( PcdAnticoll ( ucArray_ID ) == MI_OK )                                                                   
  {
    /* 选中卡 */
    PcdSelect(ucArray_ID);			

    /* 校验 B 密码 */
    if( PcdAuthState( PICC_AUTHENT1B, ucAddr, KeyBValue, ucArray_ID ) != MI_OK )
    {
      printf( "检验密码B失败\r\n" );
    }
    
    /* 拷贝 pData 里的 Len 个字符到 ucComMF522Buf */
    for(j=0; j<16; j++)
    {
      if( j < Len ) ucComMF522Buf[j] = pData[j];
      else ucComMF522Buf[j] = 0; //16个字节若是未填满的字节置0
    }
    
    /* 写入字符串 */
    if( PcdWrite(ucAddr,ucComMF522Buf) != MI_OK)
    {
      printf( "写入数据到数据块失败\r\n" );
      return MI_ERR;
    }
    
    printf( "写入数据成功!\r\n" );
    PcdHalt();
    
    return MI_OK;
  }
  
  return MI_ERR;
}


/**
 * @brief 按照RC522操作流程读取块 ucAddr
 *        函数里校验的是密码B,密码B默认为6个0xFF,也可以校验密码A
 *        用法:ReadDataBlock( 1, databuf);  // databuf 至少为16字节:uint8_t databuf[16];
 * @note  注意:该函数仅适用于默认的存储控制模式,若是其他的话可能出现问题
 *        注意:使用该函数要注意 ucAddr 是块0、数据块还是控制块,该函数内部不对此做判断
 * @param ucAddr:任意块地址。M1卡总共有16个扇区(每个扇区有:3个数据块+1个控制块),共64个块
 * @param pData:指向读取到的数据,包含16个字符
 * @retval 成功返回 MI_OK
 */
char ReadDataBlock( uint8_t ucAddr, uint8_t *pData)
{
	uint8_t KeyBValue[]={0xFF ,0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // B密钥
  uint8_t ucArray_ID [ 4 ];    /*先后存放IC卡的类型和UID(IC卡序列号)*/                                                                                         
  
  /*寻卡*/
  while ( PcdRequest ( PICC_REQALL, ucArray_ID ) != MI_OK )
  {    
    printf( "寻卡失败\r\n" );
    delay_us(1000000);
  }
  
  printf ( "寻卡成功\n" );
  
  /* 防冲突(当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作)*/
  if ( PcdAnticoll ( ucArray_ID ) == MI_OK )                                                                   
  {
    /* 选中卡 */
    PcdSelect(ucArray_ID);			

    /* 校验 B 密码 */
    if( PcdAuthState( PICC_AUTHENT1B, ucAddr, KeyBValue, ucArray_ID ) != MI_OK )
    {
      printf( "检验密码B失败\r\n" );
    }
    
    // 读取数据块里的数据到 pData
    if( PcdRead(ucAddr, pData) != MI_OK)
    {
      printf( "读取数据块失败\r\n" );
      return MI_ERR;
    }
    
    printf( "读取数据成功!\r\n" );
    PcdHalt();
    
    return MI_OK;
  }
  
  return MI_ERR;
}




RC522.h

#ifndef _RC522_H_
#define _RC522_H_

#include "main.h"

#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define          macDummy_Data              0x00


/
//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
#define MAXRLEN  18

/
//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                 0x26
#define 	MI_NOTAGERR           0xcc
#define 	MI_ERR                0xbb

 
/*********************************** RC522 引脚定义 *********************************************/
//RC522模块有除了电源还有6个数据引脚,其中IRQ不需要使用,悬空即可,剩下的5个数据引脚连接如下:
//如果RC522需要修改与STM32的连接,则修改这些IO即可,但必须连接到STM32的SPI引脚
//片选,即RC522模块的SDA引脚
#define               RC522_GPIO_CS_CLK_FUN                  RCC_APB2PeriphClockCmd
#define               RC522_GPIO_CS_CLK                      RCC_APB2Periph_GPIOA
#define               RC522_GPIO_CS_PORT    	               GPIOA			   
#define               RC522_GPIO_CS_PIN		                   GPIO_PIN_4
#define               RC522_GPIO_CS_Mode		                 GPIO_Mode_Out_PP

//时钟,即RC522模块的SCK引脚,接STM32的SPI的SCK引脚
#define               RC522_GPIO_SCK_CLK_FUN                 RCC_APB2PeriphClockCmd
#define               RC522_GPIO_SCK_CLK                     RCC_APB2Periph_GPIOA
#define               RC522_GPIO_SCK_PORT    	               GPIOA			   
#define               RC522_GPIO_SCK_PIN		                 GPIO_PIN_5
#define               RC522_GPIO_SCK_Mode		                 GPIO_Mode_Out_PP

// 数据输入,即即RC522模块的MOSI引脚,接STM32的SPI的MOSI引脚
#define               RC522_GPIO_MOSI_CLK_FUN                RCC_APB2PeriphClockCmd
#define               RC522_GPIO_MOSI_CLK                    RCC_APB2Periph_GPIOA
#define               RC522_GPIO_MOSI_PORT    	             GPIOA			   
#define               RC522_GPIO_MOSI_PIN		                 GPIO_PIN_7
#define               RC522_GPIO_MOSI_Mode		               GPIO_Mode_Out_PP

// 数据输出,即即RC522模块的MISO引脚,接STM32的SPI的MISO引脚
#define               RC522_GPIO_MISO_CLK_FUN                RCC_APB2PeriphClockCmd
#define               RC522_GPIO_MISO_CLK                    RCC_APB2Periph_GPIOA
#define               RC522_GPIO_MISO_PORT    	             GPIOA			   
#define               RC522_GPIO_MISO_PIN		                 GPIO_PIN_6
#define               RC522_GPIO_MISO_Mode		               GPIO_Mode_IN_FLOATING

//复位,即即RC522模块的RST引脚,接STM32的普通IO即可
#define               RC522_GPIO_RST_CLK_FUN                 RCC_APB2PeriphClockCmd
#define               RC522_GPIO_RST_CLK                     RCC_APB2Periph_GPIOD
#define               RC522_GPIO_RST_PORT    	               GPIOD		   
#define               RC522_GPIO_RST_PIN		                 GPIO_PIN_2
#define               RC522_GPIO_RST_Mode		                 GPIO_Mode_Out_PP




/*********************************** RC522 函数宏定义*********************************************/
#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          RC522_Reset_Enable()      HAL_GPIO_WritePin(RC522_GPIO_RST_PORT, RC522_GPIO_RST_PIN, GPIO_PIN_RESET)
#define          RC522_Reset_Disable()     HAL_GPIO_WritePin(RC522_GPIO_RST_PORT, RC522_GPIO_RST_PIN, GPIO_PIN_SET)

#define          RC522_SCK_0()             HAL_GPIO_WritePin( RC522_GPIO_SCK_PORT, RC522_GPIO_SCK_PIN , GPIO_PIN_RESET)
#define          RC522_SCK_1()             HAL_GPIO_WritePin ( RC522_GPIO_SCK_PORT, RC522_GPIO_SCK_PIN, GPIO_PIN_SET )

#define          RC522_MOSI_0()            HAL_GPIO_WritePin( RC522_GPIO_MOSI_PORT, RC522_GPIO_MOSI_PIN, GPIO_PIN_RESET )
#define          RC522_MOSI_1()            HAL_GPIO_WritePin ( RC522_GPIO_MOSI_PORT, RC522_GPIO_MOSI_PIN, GPIO_PIN_SET )

#define          RC522_MISO_GET()          HAL_GPIO_ReadPin( RC522_GPIO_MISO_PORT, RC522_GPIO_MISO_PIN )



/*********************************** 函数 *********************************************/
void             RC522_Init                 ( void );
void 						 IC_test 										( void );

void             PcdReset                   ( void );                       //复位
void             M500PcdConfigISOType       ( u8 type );                    //工作方式
char             PcdRequest                 ( u8 req_code, u8 * pTagType ); //寻卡
char             PcdAnticoll                ( u8 * pSnr);                   //读卡号
char             PcdHalt                    ( void );
char             PcdSelect                  ( uint8_t * pSnr );
char             PcdAuthState               ( uint8_t ucAuth_mode, uint8_t ucAddr, uint8_t * pKey, uint8_t * pSnr );
char             WriteAmount                ( uint8_t ucAddr, uint32_t pData );
char             ReadAmount                 ( uint8_t ucAddr, uint32_t *pData );
void 						 PcdAntennaOn ( void );
void 						 PcdAntennaOff ( void );

char IsDataBlock( uint8_t ucAddr );
char PcdWriteString( uint8_t ucAddr, uint8_t *pData);
char PcdReadString( uint8_t ucAddr, uint8_t *pData);

char ChangeKeyA( uint8_t ucAddr, uint8_t *pKeyA );
char WriteDataBlock( uint8_t ucAddr, uint8_t *pData, uint8_t Len);
char ReadDataBlock( uint8_t ucAddr, uint8_t *pData);
void Get_RC522(void);


#endif

 main函数

  /* USER CODE BEGIN WHILE */
  while (1)
  {
		Get_RC522();
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

三、实验效果

stm32 RC522

结尾:

对这个项目感兴趣的小伙伴,完全可以根据本项目的文章教程,一步步自己搭建属于自己的STM32-FreeRTOS智能家居项目,如果需要完整资料完整硬件的可私信我(有偿):原理图、STM32源码、Android app源码、操作教程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值