1、ISO14443介绍-了解
2、14443-A帧格式-掌握
3、唤醒、防冲突、选卡-掌握
ISO14443协议:
ISO14443协议是Contactless card standards(非接触式IC卡标准)协议。有英文版原版由4个部分组成:
第一部分:物理特性
第二部分:频谱功率和信号接口
第三部分:初始化和防冲突算法
第四部分:通讯协议
注意14443并没有规定具体的读写细节
引用标准:
ISO/IEC 3309:1993 信息技术-系统间的远程通信和信息交换-高级数据链接控制(HDLC)规程-帧结构
ISO/IEC 7810:1995 识别卡 物理特性 ISO/IEC 7816-3 识别卡 带触点的集成电路卡 第3部分:电信号和传输协议
ISO/IEC 7816-4 识别卡 带触点的集成电路卡 第4部分:行业间交换用命令
ISO/IEC 7816-5 识别卡 带触点的集成电路卡 第5部分:应用标识符的编号体系和注册规程
IEC 61000-4-2 电磁兼容性(EMC) 第4部分:测试和测量技术 第2节:抗静电放电测试
ISO/IEC 10373-6 识别卡-测试方法
ISO/IEC 14443:1997 识别卡-非接触式集成电路卡-接近式卡
14443术语:
• 接近式卡 Proximity card(PICC)
• 接近式耦合设备 Proximity coupling device(PCD)
• 防冲突环 anticollision loop
• 比特冲突检测协议 bit collision detection protocol
• 冲突 collision
• 帧 frame
PICC初始对话:
PICC的初始对话 PCD和PICC之间的初始对话通过下列连续操作:
——PCD的RF工作场激活PICC
——PICC静待来自PCD的命令
——PCD传输命令
——PICC传输响应
这些操作使用下列条款中规定的射频功率和信号接口:
PCD应产生给予能量的RF场,为传送功率,该RF场与PICC进行耦合,为了通信,该RF场应被调制。
RF工作场频率(fc)应为13.56MHz±7kHz。
REQA和WAKE-UP帧(P13,18):(一个字节)
请求和唤醒帧用来初始化通信并按以下次序组成:
通信开始
7个数据位发送
LSB首先发送
标准REQA的数据内容是0x26,WAKE-UP请求的数据内容是0x52
通信结束 不加奇偶校验位。
标准帧(P13):(n个字节)
标准帧用于数据交换并按以下次序组成
通信开始
n*(8个数据位+奇数奇偶校验位),n≥1。
每个数据字节的LSB首先被发送。
每个数据字节后面跟随一个奇数奇偶校验位。
通信结束
面向比特的防冲突帧(P14):(n个字节,第一个字节规定为SEL,第二个字节规定为NVB,后面的字节为ID)
NVB:
长度:1字节
较高4位称为字节计数,规定所有被8分开的有效数据位的数,包括被PCD发送的NVB和SEL。这样,字节计数的最小值是2而最大值是7。 较高4位称为字节计数,指定所有有效数据位(包括被PCD发送的NVB和SEL)的数目被8除后所得的整数。这样,字节计数的最小值是2而最大值是7。
较低4位称为比特计数,规定由PCD发送的模8(取余)所有有效数据位的数。 较低4位称为比特计数,指定所有有效数据位(包括被PCD发送的NVB和SEL)的数目被8除后所得的余数。
PICC-A状态:
POWER-OFF状态 在POWER-OFF状态中,由于缺少载波能量,PICC不能被激励并且应不发射副载波。
IDLE状态 在这种状态中,PICC被加电,并且能够解调和识别从PCD来的有效REQA和WAKE-UP命令。
READY状态 一旦收到有效REQA或WAKE-UP报文则立即进入该状态,用其UID选择了PICC时则退出该状态。在这种状态中,比特帧防冲突或其他任选的防冲突方法都可以使用。所有串联级别都在这一状态内处理以取得所有UID CLn。
ACTIVE状态 通过使用其完整UID选择PICC来进入该状态。
HALT状态 在这种状态中,PICC应仅响应使PICC转换为READY状态的WAKE-UP命令。
注:处于HALT状态的PICC将不参与任何进一步的通信,除非使用了WAKE-UP命令。
命令:
PCD用来管理与几个PICC通信的命令是:
REQA(请求) 0x26 (请求没有睡眠的卡片)
WAKE-UP(唤醒) 0x52 (唤醒所有的卡片包括睡眠的)
ANTICOLLISION(防冲突) 0x93或0x95或0x97 + 0x20
SELECT (选择某个卡片) 0x93 + 0x70 + ID1 + ID2 + ID3 + ID4 + checksum + CRC16
HALT(关机) 0x50
这些命令使用上面描述的字节和帧格式。
REQA命令: REQA命令由PCD(接近式耦合设备)发出,以探测用于类型A PICC(接近式卡片)的工作场
WAKE-UP命令由PCD发出,使已经进入HALT状态的PICC回到READY状态。它们应当参与进一步的防冲突和选择规程。
ANTICOLLISION命令和SELECT命令:这些命令在防冲突环期间使用
ANTICOLLISION和SELECT命令由下列内容组成:
SEL规定了串联级别CLn。 (SEL可以取值0x93或0x95或0x97:0x93代表ID用4个字节表示(本例中使用的就是4个字节表示ID);0x95代表ID用7个字节表示;0x97代表ID用10个字节表示)
NVB规定了PCD所发送的CLn的有效位的数目。
注:只要NVB没有规定40个有效位,若PICC保持在READY状态中,该命令就被称为ANTICOLLISION命令。
如果NVB规定了UID CLn的40个数据位(NVB='70‘),则应添加CRC_A。该命令称为SELECT命令。如果PICC已发送了完整的UID,则它从READY状态转换到ACTIVE状态并在其SAK-响应中指出UID(唯一标识符)完整。否则,PICC保持在READY状态中并且该PCD应以递增串联级别启动一个新的防冲突环。
HALT命令:
HALT命令由四个字节组成并应使用标准帧来发送。
• 如果PICC在HALT帧结束后1ms周期期间以任何调制表示响应,则该响应应解释为'不确认‘。
基于14443-A的操作帧格式:
请求卡 :0x26
唤醒所有卡 :0x52
防冲突 :0x93,0x20 得到卡ID
选择卡片 :0x93,0x70, ID1,ID2,ID3,ID4, checksum, CRC16
1、RC522组成-了解
2、SPI接口下的通信-掌握
3、基本工作原理-掌握
4、寄存器-了解
5、命令-掌握
6、帧格式-掌握
RC522概述:
●MF RC522 是应用于13.56MHz 非接触式通信中高集成度读写卡系列芯片中的一员。是NXP 公司针对“三表”应用推出的一款低 电压、低成本、体积小的非接触式读写卡芯片,是智能仪表和便携 式手持设备研发的较好选择[1] 。
●读写器,支持 ISO 14443A / MIFARE®
MFRC522 的内部发送器部分可驱动读写器天线与 ISO 14443A/MIFARE®卡和应答机的通信,无需其它的电路。
●可实现各种不同主机接口的功能
SPI 接口
串行 UART(类似 RS232,电压电平取决于提供的管脚电压)
I2C 接口
● 64 字节的发送和接收 FIFO 缓冲区。
● 灵活的中断模式。
●可编程定时器。
●内部振荡器,连接 27.12MHz 的晶体。
框图:
从图中可以看到,主机与芯片的通信最终要通过FIFO来传递到其他地方。
RC522寄存器:
• CommandReg 启动和停止命令的执行。
• ComIrqReg 包含中断请求标志
• ErrorReg 错误标志,指示执行的上个命令的错误状态
• Status2Reg 包含接收器和发送器的状态标志
• FIFODtataReg 64 字节 FIFO 缓冲区的输入和输出
• FIFOLevelReg 指示 FIFO 中存储的字节数
• ControlReg 不同的控制寄存器
• BitFramingReg 面向位的帧的调节
• CollReg RF 接口上检测到的第一个位冲突的位的位置
• ModeReg 定义发送和接收的常用模式
• TxModeReg 定义发送过程的数据传输速率
• TxControlReg 控制天线驱动器管脚 TX1 和 TX2 的逻辑特性
• TModeReg
TPrescalerReg定义内部定时器的设置
RC522功能:
SPI接口(P44)
支持串行外围接口 (兼容 SPI)来使能到主机的高速通信。SPI 接口可处理高达 10Mbit/s的数据速率。在与主机微控制器通信时, MFRC522 用作从机
• 在 SPI 通信中 MFRC522 模块用作从机。 SPI 时钟 SCK 由主机产生。数据通过 MOSI线从主机传输到从机;数据通过 MISO 线从 MFRC522 发回到主机。
• MOSI 和 MISO 传输每个字节时都是高位在前。MOSI 上的数据在时钟的上升沿保持不变,在时钟的下降沿改变。MISO 也与之类似,在时钟的下降沿,MISO 上的数据由 MFRC522来提供,在时钟的上升沿数据保持不变。
SPI地址
地址字节按下面的格式传输。第一个字节的 MSB 位设置使用的模式。 MSB 位为 1 时从 MFRC522 读出数据; MSB 位为 0 时将数据写入 MFRC522。 第一个字节的位 6-1 定义地址,最后一位应当设置为 0
SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。
假设下面的8位寄存器装的是待发送的数据10101010,上升沿发送、下降沿接收、高位先发送。
那么第一个上升沿来的时候 数据将会是sdo=1;寄存器中的10101010左移一位,后面补入送来的一位未知数x,成了0101010x。下降沿到来的时候,sdi上的电平将锁存到寄存器中去,那么这时寄存器=0101010sdi,这样在 8个时钟脉冲以后,两个寄存器的内容互相交换一次。这样就完成了一个spi时序。
上图为RC522芯片的SPI时序图:时钟上升沿读取,下降沿输出(写数据)。
RC522.c文件 功能:RC522相关的操作函数
#include "RC522.h"
#include "logo.h"
#define MAXRLEN 18
extern uint8_t ID[4];
extern uint8_t idcs; //id校验字节
extern uint8_t card_type[2]; //卡类型
extern uint8_t cmd_data[16]; //数据
unsigned char ReadRawRC(unsigned char addr)//使用软件模拟读取寄存器的值:上升沿发送,下降沿接收、高位先发送
{
unsigned char i, ucaddr;
unsigned char ucResult =0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13,GPIO_PIN_RESET); //时钟CLK引脚为低电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12,GPIO_PIN_RESET); //CSS引脚为低电平,使能SPI
ucaddr = ((addr<<1)&0x7e)| 0x80; /*bit7读写位(这里设置为1表示读取寄存器的值),
bit6-1为要读取的寄存器的地址,bit0必须为0(因为从设备是这样要求的)*/
for (i=8;i>0;i--)
{
if (ucaddr & 0x80)
{
//SDA=1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_SET);//低电平时将数据写入
}
else
{
// SDA=0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_RESET);//低电平时将数据写入
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET) ;/*CLK设置为高电平产生一个
上升沿,从设备读取数据*/
ucaddr <<= 1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET ) ;/*CLK设置为低电平如果
是最后一次循环则产生一个下降沿,从设备向主设备发送第一个位(最高位)
数据,待下一个高电平时主设备接收该数据*/
}
for (i=8;i>0;i--)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET ) ;//CLK高电平,此时主设备可以读取从设备发送过来的数据
ucResult <<= 1;
ucResult |= HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) ;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET ) ;//CLK产生一个下降沿,从设备发送数据
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12,GPIO_PIN_SET); // CSS设置为高电平,关闭SPI
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET ) ;//时钟引脚设置为高电平
delay(10);
return ucResult;
}
void WriteRawRC(unsigned char addr, unsigned char val)//模拟SPI向寄存器中写入值(上升沿读取数据,下降沿写入数据)
{
unsigned char i, ucaddr;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET ) ;//时钟CLK引脚为低电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET ) ;//CSS引脚为低电平,使能SPI
ucaddr = ((addr<<1)&0x7e); /*bit7读写位(这里设置为0表示向寄存器中写入一个值),
bit6-1为要读取的寄存器的地址,bit0必须为0(因为从设备是这样要求的)*/
for (i=8;i>0;i--)
{
if (ucaddr & 0x80)
{
//SDA=1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_SET);//低电平时将数据写入
}
else
{
// SDA=0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_RESET);//低电平时将数据写入
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET ) ;/*CLK设置为高电平产生一个
上升沿,从设备读取数据*/
ucaddr <<= 1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET ) ;/*CLK设置为低电平如果
是最后一次循环则产生一个下降沿,从设备向主设备发送第一个位(最高位)
数据,待下一个高电平时主设备接收该数据*/
}
for (i=8;i>0;i--)
{
if (val & 0x80)
{
//SDA=1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_SET);//此时CLK为低电平,将val的高位写入
}
else
{
//SDA=0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15,GPIO_PIN_RESET);//此时CLK为低电平,将val的高位写入
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET ) ;//产生一个上升沿,从设备读取数据
val <<= 1;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET ) ;//CLK设置为低电平,主设备可以写入数据
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET ) ;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET ) ;
delay(10);
}
/*******************************************************************************
//功能:置位RC522寄存器的某些位
//参数说明:reg:寄存器地址
// mask:置位值
*******************************************************************************/
void SetBitMask(unsigned char reg,unsigned char mask)//给寄存器某些位置位
{
char tmp = 0x0;
tmp = ReadRawRC(reg); //读reg寄存器的状态
WriteRawRC(reg,tmp | mask); //给reg中的某些位置位,但是不影响其他位
}
/*******************************************************************************
//功能:复位RC522寄存器的某些位
//参数说明:reg:寄存器地址
// mask:复位值
*******************************************************************************/
void ClearBitMask(unsigned char reg,unsigned char mask)//给寄存器某些位复位
{
char tmp = 0x0;
tmp = ReadRawRC(reg); //读reg寄存器的状态
WriteRawRC(reg, tmp & ~mask); //给reg中的某些位复位,但是不影响其他位
}
void PcdAntennaOff(void)//天线关闭
{
ClearBitMask(TxControlReg, 0x03);
}
void PcdAntennaOn(void)//天线打开
{
unsigned char i;
i = ReadRawRC(TxControlReg);
if(((i&0x01)==0)|((i&0x02)==0))//if (!(i&0x03))
{
SetBitMask(TxControlReg, 0x03);
}
}
char PcdReset(void)//初始化射频芯片
{
// GPIOSetValue( PORT0, RC522_RESET, 1); // reset rc522;
// __nop_();
// GPIOSetValue( PORT0, RC522_RESET, 0);
// __nop_();
// GPIOSetValue( PORT0, RC522_RESET, 1);
// __nop_();
WriteRawRC(CommandReg, PCD_RESETPHASE);//复位CommandReg
printf("pcdreset in\n");
delay(10);
WriteRawRC(ModeReg, 0x3d);
delay(10);
WriteRawRC(TReloadRegL, 30);//48
WriteRawRC(TReloadRegH, 0);
WriteRawRC(TModeReg, 0x8d);
printf("pcdreset in1\n");
WriteRawRC(TPrescalerReg, 0x3e);//分频参数3990,得到2K,定时24ms
WriteRawRC(TxAskReg, 0x40);
return MI_OK;
}
/*******************************************************************************
//功 能:通过RC522和ISO14443卡通讯
//参数说明:Command[IN]:RC522命令字
// pInData[IN]:通过RC522发送到卡片的数据
// InLenByte[IN]:发送数据的字节长度
// pOutData[OUT]:接收到的卡片返回数据
// *pOutLenBit[OUT]:返回数据的位长度
*******************************************************************************/
char PcdComMF522(unsigned char Command,unsigned char *pInData,unsigned char InLenByte,unsigned char *pOutData,unsigned int *pOutLenBit)//操作FIFO
{
char status = MI_ERR;
unsigned char irqEn = 0x00;
unsigned char waitFor = 0x00;
unsigned char lastBits;
unsigned char n;
unsigned int i;
unsigned char m;
switch (Command)
{
case PCD_AUTHENT: // 0x0E 验证密钥
irqEn = 0x12; // 0001 0010
waitFor = 0x10; // 0001 0000
break;
case PCD_TRANSCEIVE://0x0C 发送并接收数据
irqEn = 0x77; // 0111 0111
waitFor = 0x30; // 0011 0000
break;
default:
break;
}
//初始化
WriteRawRC(ComIEnReg,irqEn|0x80); //使能相应中断,错误、定时器、发送、接收。默认没有中断
ClearBitMask(ComIrqReg,0x80); //清零所有中断标志
WriteRawRC(CommandReg,PCD_IDLE); //命令寄存器复零,取消当前的所有命令 P59
SetBitMask(FIFOLevelReg,0x80); //复位FIFO读写指针, P18
//写入协议数据
for (i=0; i MAXRLEN)
{
n = MAXRLEN; //最多可接收18个字节
}
for (i=0; i
FIFO缓冲区
• FIFO 缓冲区的输入和输出数据总线连接到 FIFODataReg 寄存器。通过写 FIFODataReg寄存器来将一个字节的数据存入 FIFO 缓冲区,之后内部 FIFO 缓冲区写指针加 1。
• 除了读写 FIFO 缓冲区外, FIFO 缓冲区指针还可通过置位寄存器 FIFOLevelReg 的FlushBuffer 位来复位。从而, FIFOLevel 位被清零,寄存器 ErrorReg 的 BufferOvfl 位被清零,实际存储的字节不能再访问
• 已经存放在 FIFO 缓冲区中的字节数:寄存器 FIFOLevelReg 的 FIFOLevel 字段
RC522命令集:
通用特性
• FIFO每个需要数据流(或数据字节流)作为输入的命令在发现 FIFO 缓冲区有数据时会立刻处理,但收发命令除外。收发命令的发送由寄存器 BitFramingReg 的StartSend 位来启动。
• 每个需要某一数量的参数的命令只有在它通过 FIFO 缓冲区接收到正确数量的参数时才能开始处理。
• FIFO 缓冲区不能在命令启动时自动清除。而且,也有可能要先将命令参数和/或数据字节写入 FIFO 缓冲区,再启动命令。
• 每个命令的执行都可能由微控制器向命令寄存器写入一个新的命令代码(如 idle命令)来中断。
• IDLE 命令 ,MFRC522 处于空闲模式。该命令也用来终止实际正在执行的命令
• CALCCRC 命令 ,FIFO 的内容被传输到 CRC 协处理器并执行 CRC 计算这个命令必须通过向命令寄存器写入任何一个命令(如空闲命令)来软件清除
• TRANSMIT 命令 ,发送 FIFO 的内容。在发送 FIFO 的内容之前必须对所有相关的寄存器进行设置。该命令在 FIFO 变成空后自动终止
• RECEIVE 命令 ,该命令在接收到的数据流结束时自动终止。
• TRANSCEIVE 命令 ,该循环命令重复发送 FIFO 的数据,并不断接收 RF 场的数据。第一个动作是发送,发送结束后命令变为接收数据流。
• MFAUTENT 命令(P69,17) ,该命令用来处理 Mifare 认证以使能到任何 Mifare 普通卡的安全通信。在命令激活前以下数据必须被写入 FIFO: 认证命令码,块地址,秘钥,序列号。该命令在 Mifare 卡被认证且 Status2Reg 寄存器的 MFCrypto1On 位置位时自动终止。
• SOFTRESET 命令 ,所有寄存器都设置成复位值。命令完成后自动终止。
基于14443-A命令集:
• 寻卡 0x26 或者0x52
寻卡的协议内容:一个寻卡命令就可以了,接着就可以发送
返回:2byte卡类型(4,0)
• 防冲突 0x93
协议:防冲突命令 + 0x20
返回:4byte卡ID,1byte校验(异或)(62 A8 2B B EA)
• 选卡
协议:命令字 + 0x70 + 4byte卡号 + 1byte校验 + 2byte CRC16校验
返回:卡校验0x08
main.c文件 功能:了解寻卡、防冲突、选卡功能函数的使用(while循环中的函数)
#include "stm32f0xx_hal.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "dma.h"
#include "RC522.h"
#include "string.h"
void SystemClock_Config(void);
extern uint8_t KEY2_FLAG;//KEY2按键标志位
extern uint8_t KEY3_FLAG;//KEY3按键标志位
extern uint8_t I2C_FLAG;
uint8_t ID[4];//卡ID共4个字节
uint8_t idcs;//ID校验字节
uint8_t card_type[2];//卡类型
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
unsigned char cmd_dat[16];//卡片要写的数据
unsigned char aRxBuffer[2];
unsigned char cmd_buf[100]; //串口数据缓冲区
unsigned char count = 0;
unsigned char cmd_type; //命令类型,读或者写,r/w
unsigned char cmd_addr; //操作的地址0~63
unsigned char cmd_check;//二级命令,0正常区域,1改秘钥,2充值,3扣款,4改控制字
unsigned char KEY_A[6]; //秘钥A
unsigned char KEY_B[6];//秘钥B
unsigned char CMD_OVER = 0;
unsigned char DATA_OVER = 0;
unsigned char error = 0;
int main(void)
{
uint8_t i;
uint8_t j;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM14_Init();
MX_TIM16_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
BoardInit();
__HAL_I2C_ENABLE_IT(&hi2c1,I2C_IT_ADDRI);
/* USER CODE END 2 */
//delay(65000);
printf("start\n");
//复位设备,这一步是必须的,如果不复位,那么下面的操作都无法完成
PcdReset();
while(1)
{
//功 能:寻卡(请求卡片,得到卡片的类型;S50的类型是0x04, 0x00)
//参数说明: req_code[IN]:寻卡方式(参数一)
// 0x52 = 寻感应区内所有符合14443A标准的卡
// 0x26 = 寻未进入休眠状态的卡
// pTagType[OUT]:卡片类型代码(参数二)
// 0x4400 = Mifare_UltraLight
// 0x0400 = Mifare_One(S50)
// 0x0200 = Mifare_One(S70)
// 0x0800 = Mifare_Pro(X)
// 0x4403 = Mifare_DESFire
//返 回: 成功返回MI_OK
PcdRequest(0x26, card_type);
//功 能:防冲撞(防冲突,得打卡片的ID,我们的卡片是4个节)
//参数说明: pSnr[OUT]:卡片序列号,4字节
//返 回: 成功返回MI_OK
//协 议:SEL NVB(0X20)
PcdAnticoll(ID);
//功 能:选定卡片(选择卡片,S50卡的返回值是0x8)
//参数说明: pSnr[IN]:卡片序列号,4字节
//返 回: 成功返回MI_OK
//协 议:SEL NVB(0X70) ID(4个byte) IDCHECK CRC16
PcdSelect(ID);
//延时,避免快速打印
HAL_Delay(1000);
}
}
void SystemClock_Config(void)//该函数由CubeMX创建,无需人工操作,故函数代码不列举出来了
{
...
}
int fputc(int ch, FILE *f)//printf重定向
{
while ((USART1->ISR&0X40)==0);
USART1->TDR = (uint8_t) ch;
return ch;
}
#ifdef USE_FULL_ASSERT
#endif
项目中高频RFID原理图:
1、S50卡概述-了解
2、S50卡存储区分布-掌握
3、S50卡控制字-掌握
M1卡是谁:
所谓的M1芯片,是指菲利浦下属子公司恩智浦出品的芯片缩写,全称为NXP Mifare1系列,常用的有S50及S70两种型号,属于非接触式IC卡
非接触式IC卡又称射频卡,成功地解决了无源(卡中无电源)和免接触这一难题,是电子器件领域的一大突破。主要用于公交、轮渡、地铁的自动收费系统,也应用
M1卡,优点是可读可写的多功能卡,缺点是:价格稍贵,感应距离短,适合非定额消费系统、停车场系统、门禁考勤系统等。在门禁管理、身份证明和电子钱包。
M1卡工作原理:
向M1卡发一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下,LC谐振电路产生共振,从而使电容内有了电荷,在这个电容的另一端,接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到2V时,此电容可做为电源为其它电路提供工作电压,将卡内数据发射出去或接取读写器的数据。
S50卡概述:
特征
• MIFARE RF 接口 ISO/IEC 14443A
• 无线传送数据和能量 不需要电池
• 工作距离 最高可达 100mm 由天线的结构 geometry 决定
• 工作频率 13.56MHz
• 数据传送速度快 106kbit/s
• 数据高度可靠 正确 16 位 CRC 奇偶校验 位编码 位计数
• 真正的反冲突
• 典型的购票处理 ticketing transaction <100ms 包括备份管理
EEPROM
_x0003_ 1K 字节 分成 16 个区 每区又分成 4 段 每一段中有 16 个字节
_x0003_ 用户可以定义每一个存储器段的访问条件
_x0003_ 数据可以保持 10 年
_x0003_ 可写 100,000 次
保密性
• 需要通过 3 轮确
• RF 信道的数据加密 有重放攻击保护
• 每个区有两套独立的密钥 每应用 支持带密钥层次的多应用
• 每个设备有唯一的序列号
• 在运输过程中访问EEPROM有传输密钥保护
结构:
操作流程(P5):注意读写操作并不是单个字节读写,而是以块(Block)为单位进行读写操作
关于认证:在命令激活前,以下数据必须被写入FIFO:
1、认证命令代码(0x60、0x61可选:0x60验证A密钥,0x61验证B密钥)
2、块地址(需要操作卡片对应扇区的哪个块)
3、扇区秘钥字节0(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节10)
4、扇区秘钥字节1(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节11)
5、扇区秘钥字节2(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节12)
6、扇区秘钥字节3(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节13)
7、扇区秘钥字节4(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节14)
8、扇区秘钥字节5(这里是选择A秘钥认证。如果选择B秘钥验证,则是扇区秘钥字节15)
9、卡序列号字节0(ID[0])
10、卡序列号字节1(ID[1])
11、卡序列号字节2(ID[2])
12、卡序列号字节3(ID[3])
总共12个字节应当写入FIFO中。
S5以下0卡存储器结构(P6):根据下图可知:共16个扇区(Sector),每个扇区包含4个块(Block),每个块的第4个块(Block[3])存储的有密码之类的数据,具有特殊用途,不可以随便使用。Block[0]-Block[2]为普通区,可以随便使用。最下面的扇区(Sector[0])中Block[0]写入的是厂家的相关信息(该数据块的具体分析请往下看)。(存储器最下面Sector[0]中Block[0]地址为0,到存储器最上面Sector[15]中Block[3]地址为63,依次递增)
S50存储结构(P7):
厂商段(Sector[0]中的Block[0]位置是厂商段,具体见上图)
• 厂商段是存储器第一个区的第一个数据段 段 0 它包含了 IC 卡厂商的数据 基于保密性和系统的安全性 这一段在 IC 卡厂商编程之后被置为写保护
数据段:
所有的区都包含 3 个段 每段 16 字节 保存数据 区 0 只有两个数据段和一个只读的厂商段
数据段可以被以下的访问位 access bits 配置
读 写段 用于譬如无线访问控制
值段 用于譬如电子钱包 它需要额外的命令 像直接控制保存值的增加和减少
在执行任何存储器操作前都要先执行确认命令
值段
值段可以实现电子钱包的功能 有效的命令有 读 写 增 减 恢复 发送
值段有一个固定的数据格式 可以进行错误检测和纠正并备份管理
值段只能在值段格式的写操作时产生
• 值 表示一个带符号 4 字节值 这个值的最低一个字节保存在最低的地址中 取反的字节以标准2 的格式保存 为了保证数据的正确性和保密性 值被保存了 3 次 两次不取反保存 一次取反保存
• Adr 表示一个 1 字节地址 当执行强大的备份管理时用于保存存储段的地址 地址字节保存了 4次 取反和不取反各保存两次 在执行增 减 恢复 传送操作时 地址保持不变 它只能通过写命令改变
区尾(Block[3])
每个区都有一个区尾 它包括
• 密钥 A 和 B 可选 读密钥时返回逻辑 0
• 访问这个区中 4 个段的条件 保存在第 6 字节 第 9 字节 访问位 access bits 也可以指出数据段的类型 读 写或值1961—1970年。RFID技术的理论得到了发展,开始了一些应用尝试。(控制字详细信息请往下看)
如果不需要密钥 B 那么段 3 的最后 6 字节可以作为数据字节
用户数据可以使用区尾的第 9 字节 这个字节具有和字节 6 7 和 8 一样的访问权
S50控制字:控制秘钥位共四个字节,但是只有三个字节是有效的,Byte9不起作用,可以不用管
每个字节的bit7控制block3,Bit6控制block2,Bit5控制block1
Bit4控制block0,Bit3~0其实就是bit7~4取反的结果
Block3出厂默认 ff ff ff ff ff ff ff 07 80 69 ff ff ff ff ff ff
S50控制字:注意_b结尾的位取反之后才是最终的控制模式
每个字节的bit7控制block3,Bit6控制block2,Bit5控制block1
Bit4控制block0,Bit3~0其实就是bit7~4取反的结果
Block3出厂默认 ff ff ff ff ff ff ff 07 80 69 ff ff ff ff ff ff
秘钥区控制(block3)
Block3由bit7控制,出厂默认的是001控制
秘钥A永远不可读,读出来的是0x00
修改秘钥A的过程:
第6、7、8字节最高位(第7位)出厂时分别为0 0 1,则控制模式如下所示:需要修改秘钥A,需要先传入最初的秘钥A
1、唤醒
2、寻卡
3、防冲突
4、认证(此时必须传入秘钥A,才能修改秘钥A)
5、修改秘钥A
6、重新认证
7、数据的读写
普通数据段控制(Block0-3,分析过程同上)
普通区的出厂控制位是000
1、S50卡操作流程-掌握
2、S50卡默认控制方式-掌握
3、S50读卡操作-掌握
S50操作流程(P5):(同上)
S50卡存储器结构(P6):(同上)
区尾(同上)
每个区都有一个区尾 它包括
• 密钥 A 和 B 可选 读密钥时返回逻辑 0
• 访问这个区中 4 个段的条件 保存在第 6 字节 第 9 字节 访问位 access bits 也可以指出数据段的类型 读 写或值1961—1970年。RFID技术的理论得到了发展,开始了一些应用尝试。
如果不需要密钥 B 那么段 3 的最后 6 字节可以作为数据字节
用户数据可以使用区尾的第 9 字节 这个字节具有和字节 6 7 和 8 一样的访问权
S50控制字:(同上)
每个字节的bit7控制block3,Bit6控制block2,Bit5控制block1
Bit4控制block0,Bit3~0其实就是bit7~4取反的结果
Block3出厂默认 ff ff ff ff ff ff ff 07 80 69 ff ff ff ff ff ff
秘钥区控制
Block3由bit7控制,出厂默认的是001控制
秘钥A永远不可读,读出来的是0x00
普通数据段控制
普通区的出厂控制位是000
S50读卡命令:(MCU写 “0x0c(发送到卡片)+14443的读卡命令”到RC522的FIFO中)
请求 0x26
防冲突 0x93,0x70
A认证 0x60,addr(Block地址),keyA, ID
读0x30,addr(Block地址), CRC16
普通区的出厂控制位是000
对RC522.c文件添加关于读卡片操作的函数
/*******************************************************************************
//功 能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
// 0x60 = 验证A密钥
// 0x61 = 验证B密钥
// addr[IN]:块地址
// pKey[IN]:密码
// pSnr[IN]:卡片序列号,4字节
//返 回: 成功返回MI_OK
//发 送:authorA/authorB addr keyA/B(6bytes) ID(4bytes)
*******************************************************************************/
char PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{
char status;
unsigned int unLen;
unsigned char i,ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = auth_mode;//auth_mode=0x60,PICC验证A密钥指令
ucComMF522Buf[1] = addr;//块地址
for (i=0; i<6; i++)
{
ucComMF522Buf[i+2] = *(pKey+i); //向缓冲区填充密码
}
for (i=0; i<4; i++)
{
ucComMF522Buf[i+8] = *(pSnr+i); //向缓冲区填充与密码对应的卡的序列号,有效4个字节
}
// memcpy(&ucComMF522Buf[2], pKey, 6);
// memcpy(&ucComMF522Buf[8], pSnr, 4);
status = PcdComMF522(PCD_AUTHENT/*0x0e验证秘钥指令*/,ucComMF522Buf,12,ucComMF522Buf,&unLen);//验证密码和卡号
if ((status != MI_OK) || (!(ReadRawRC(Status2Reg) & 0x08)))//密码验证是否成功
{
status = MI_ERR;
}
return status;
}
/*******************************************************************************
//功 能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
// pData[OUT]:读出的数据,16字节
//返 回: 成功返回MI_OK
//发 送:cmd_read addr CRC16
//返回16B, 如果读block3, 返回的keyA是0
*******************************************************************************/
char PcdRead(unsigned char addr,unsigned char *pData)
{
char status;
unsigned int unLen;
unsigned char i,ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_READ;//0x30读命令
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;
}
void RFID_Read()//从发送请求到读取卡片某个数据块的值的完整操作过程
{
uint8_t i;
unsigned char status = 0;
PcdReset(); //初始化射频芯片
PcdAntennaOff(); //关闭天线
PcdAntennaOn(); //打开天线
while (1)
{
//寻卡
printf("req\n");
status = PcdRequest(PICC_REQALL, card_type);
if (status != MI_OK)
{
break;
}
printf("anti\n");
//防冲撞处理
status = PcdAnticoll(ID);//执行成功得到卡的序列号
if (status != MI_OK)
{
break;
}
//选择卡片
printf("select\n");
status = PcdSelect(ID);
if (status != MI_OK)
{
break;
}
//验证卡片密码
printf("ahthor\n");
status = PcdAuthState(PICC_AUTHENT1A, cmd_addr, KEY_A, ID);
if (status != MI_OK)
{
break;
}
//读地址的数据
memset(cmd_dat,0,sizeof(cmd_dat));
status = PcdRead(cmd_addr, cmd_dat);
if (status != MI_OK)
{
break;
}
printf("Card_Data:\n");
for (i=0;i<16;i++) {
printf(" %02x ",cmd_dat[i]);
}printf("\n");
TIP_SUCCESS();
break;
}
}
main文件 功能:了解读卡片的操作函数及流程
#include "stm32f0xx_hal.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "dma.h"
#include "RC522.h"
#include "string.h"
void SystemClock_Config(void);
extern uint8_t KEY2_FLAG;//KEY2按键标志位
extern uint8_t KEY3_FLAG;//KEY3按键标志位
extern uint8_t I2C_FLAG;
uint8_t ID[4];
uint8_t idcs;
uint8_t card_type[2];
unsigned char cmd_dat[16];//卡片要写的数据
unsigned char aRxBuffer[2];
unsigned char cmd_buf[100]; //串口数据缓冲区
unsigned char count = 0;
unsigned char cmd_type; //命令类型,读或者写,r/w
unsigned char cmd_addr; //操作的地址0~63
unsigned char cmd_check;//二级命令,0正常区域,1改秘钥,2充值,3扣款,4改控制字
unsigned char KEY_A[6]; //秘钥A
unsigned char KEY_B[6];//秘钥B
unsigned char CMD_OVER = 0;
unsigned char DATA_OVER = 0;
unsigned char error = 0;
int main(void)
{
uint8_t i;
uint8_t j;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM14_Init();
MX_TIM16_Init();
MX_USART1_UART_Init();
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
BoardInit();
__HAL_I2C_ENABLE_IT(&hi2c1,I2C_IT_ADDRI);
delay(65000);
printf("start\n");
//在中断里接收串口数据
HAL_UART_Receive_IT(&huart1,aRxBuffer,1);
cmd_addr=0;
RFID_Read();
cmd_addr=3;
RFID_Read();
}
void SystemClock_Config(void)
{
...
}
int fputc(int ch, FILE *f)
{
while ((USART1->ISR&0X40)==0);
USART1->TDR = (uint8_t) ch;
return ch;
}
#ifdef USE_FULL_ASSERT
S50写卡命令:
请求 0x26
防冲突 0x93,0x70
A认证 0x60,addr(Block地址),keyA, ID
写0xA0,addr(Block地址) ,CRC16
16bytes,CRC16
普通区的出厂控制位是000
对RC522.c文件添加关于写卡片操作的函数
/*******************************************************************************
//功 能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
// pData[IN]:写入的数据,16字节
//返 回: 成功返回MI_OK
//发 送:分两次发送
// cmd_write addr CRC16
// data(16bytes) CRC16
*******************************************************************************/
char PcdWrite(unsigned char addr,unsigned char *pData)
{
char status;
unsigned int unLen;
unsigned char i,ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_WRITE;//PICC_WRITE 0xA0 写块
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);//对数据进行CRC校验
//存放于ucCoMF522Buf【0,1】
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)
{
//memcpy(ucComMF522Buf, pData, 16);
for (i=0; i<16; i++)
{
ucComMF522Buf[i] = *(pData+i); // 要充值的内容
}
CalulateCRC(ucComMF522Buf,16,&ucComMF522Buf[16]);//对数据进行CRC校验校验值
//存放于ucCoMF522Buf【0,1】
status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,18,ucComMF522Buf,&unLen); //发送数据,并接收卡返回的数据
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
}
return status;
}
void RFID_Write()
{
uint8_t i;
unsigned char status = 0;
PcdReset(); //初始化射频芯片
PcdAntennaOff(); //关闭天线
PcdAntennaOn(); //打开天线
while (1)
{
//寻卡
status = PcdRequest(PICC_REQALL, card_type);
if (status != MI_OK)
{
continue;
}
//防冲撞处理
status = PcdAnticoll(ID);//执行成功得到卡的序列号
if (status != MI_OK)
{
continue;
}
//选择卡片
status = PcdSelect(ID);
if (status != MI_OK)
{
continue;
}
//验证卡片密码
status = PcdAuthState(PICC_AUTHENT1A, cmd_addr, KEY_A, ID);
if (status != MI_OK)
{
continue;
}
//向卡写数据
status = PcdWrite(cmd_addr, cmd_dat); //地址满足 (地址+1)/4 != int
if (status != MI_OK)
{
continue;
}
//读地址的数据
memset(cmd_dat,0,sizeof(cmd_dat));
status = PcdRead(cmd_addr, cmd_dat);
if (status != MI_OK)
{
continue;
}
printf("Card_Data:\n");
for (i=0;i<16;i++) {
printf(" %02x ",cmd_dat[i]);
}printf("\n");
TIP_WRITE_SUCCESS();
break;
}
}
main.c文件 功能:熟悉写卡片相关流程和操作函数
#include "stm32f0xx_hal.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "dma.h"
#include "RC522.h"
#include "string.h"
void SystemClock_Config(void);
extern uint8_t KEY2_FLAG;//KEY2按键标志位
extern uint8_t KEY3_FLAG;//KEY3按键标志位
extern uint8_t I2C_FLAG;
uint8_t ID[4];
uint8_t idcs;
uint8_t card_type[2];
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
unsigned char cmd_dat[16];//卡片要写的数据
unsigned char aRxBuffer[2];
unsigned char cmd_buf[100]; //串口数据缓冲区
unsigned char count = 0;
unsigned char cmd_type; //命令类型,读或者写,r/w
unsigned char cmd_addr; //操作的地址0~63
unsigned char cmd_check;//二级命令,0正常区域,1改秘钥,2充值,3扣款,4改控制字
unsigned char KEY_A[6]; //秘钥A
unsigned char KEY_B[6];//秘钥B
unsigned char CMD_OVER = 0;
unsigned char DATA_OVER = 0;
unsigned char error = 0;
int main(void)
{
uint8_t i;
uint8_t j;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM14_Init();
MX_TIM16_Init();
MX_USART1_UART_Init();
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);
BoardInit();
__HAL_I2C_ENABLE_IT(&hi2c1,I2C_IT_ADDRI);
delay(65000);
printf("start\n");
//在中断里接收串口数据
HAL_UART_Receive_IT(&huart1,aRxBuffer,1);
//初始化数据
for(int i=0; i<16; i++)
cmd_dat[i] = i;
//写入数据
RFID_Write();
}
void SystemClock_Config(void)
{
...
}
int fputc(int ch, FILE *f)
{
while ((USART1->ISR&0X40)==0);
USART1->TDR = (uint8_t) ch;
return ch;
}
#ifdef USE_FULL_ASSERT
1、S50卡操作流程-掌握
2、S50卡默认控制方式-掌握
3、S50写卡操作-掌握
4、S50改密码-掌握
S50操作流程(P5):
S50控制字:
每个字节的bit7控制block3,Bit6控制block2,Bit5控制block1
Bit4控制block0,Bit3~0其实就是bit7~4取反的结果
Block3出厂默认 ff ff ff ff ff ff ff 07 80 69 ff ff ff ff ff ff
秘钥区控制
Block3由bit7控制,出厂默认的是001控制
秘钥A永远不可读,读出来的是0x00
普通数据段控制
普通区的出厂控制位是000
S50写卡命令:
请求 0x26
防冲突 0x93,0x70
A认证 0x60,addr,keyA, ID
写0xA0,addr
16bytes
普通区的出厂控制位是000
S50改密码:
请求 0x26
防冲突 0x93,0x70
A认证 0x60,addr,keyA, ID
写0xA0,addr
16bytes,注意,不要修改本密码以外的其他地方
普通区的出厂控制位是000