内容提纲
1、NFC的概念-熟悉
2、NFC和RFID区别-掌握
3、NFC的工作模式-掌握
nfc概述:
NFC (Near Field Communication)近场通信,这个技术由非接触式射频识别(RFID)演变而来,由飞利浦半导体(现恩智浦半导体公司)、诺基亚和索尼共同研制开发,其基础是RFID及互连技术。NFC是一种短距离高频的无线电技术,在13.56Mhz频率运行于20cm距离内。其传输速度有106Kbit/s,212Kbit/s或者424Kbit/s三种。目前近场通信已通过并成为ISO/IEC IS 18092国际标准、ECMA-340标准与ETSI TS 102 190标准。NFC采用主动和被动两种读取模式。
Nfc工作模式:
卡模式:这个模式其实就是相当于一张采用RFID技术的IC卡。可以替代大量的IC卡(包括信用卡)场合商场刷卡、公交卡、门禁管制,车票,门票等等。此种方式下,有一个极大的优点,那就是卡片通过非接触读卡器的RF域来供电,即便是寄主设备(如手机)没电也可以工作。
读写器模式:这个模式可以模拟读读卡器功能,读取MIFARE和FeliCa卡的信息
点对点模式:这个模式和红外线差不多,可用于数据交换,只是传输距离较短,传输创建速度较快,传输速度可快些,功耗低(蓝牙也类似)。将两个具备NFC功能的设备链接,能实现数据点对点传输,如下载音乐、交换图片或者同步设备地址簿。一次通过NFC,多个设备如数码相机、PDA、计算机和手机之间都可以交换资料或者服务
NFC与RFID区别:
• 第一、NFC将非接触读卡器、非接触卡和点对点功能整合进一块单芯片,而rfid必须有阅读器和标签组成。RFID只能实现信息的读取以及判定,而NFC技术则强调的是信息交互。通俗的说NFC就是RFID的演进版本,双方可以近距离交换信息。NFC手机内置NFC芯片,组成RFID模块的一部分,可以当作RFID无源标签使用进行支付费用;也可以当作RFID读写器,用作数据交换与采集,还可以进行NFC手机之间的数据通信。
• 第二、NFC传输范围比RFID小,RFID的传输范围可以达到几米、甚至几十米,但由于NFC采取了独特的信号衰减技术,相对于RFID来说NFC具有距离近、带宽高、能耗低等特点。
• 第三、应用方向不同。NFC看更多的是针对于消费类电子设备相互通讯,有源RFID则更擅长在长距离识别。
NFC与BLE区别:
内容提纲
1、PN532介绍-了解
2、PN532帧格式-掌握
PN532概述:
• 随着互联网的普及,手机作为互联网最直接的智能终端,必将会引起一场技术上的革命,如同以前蓝牙、USB、GPS等标配,NFC将成为日后手机最重要的标配,通过NFC技术,手机支付、看电影、坐地铁都能实现,将在我们的日常生活中发挥更大的作用。
• 我们这里使用的NFC芯片为PN532,它是一款高度集成的非接触式通讯收发模块,基于8051单片机核心。它支持6个不同的操作模式:ISO/IEC14443A/MIFARE 读/写器、FeliCa 读/写器、ISO/IEC 14443B 读/写器、ISO/IEC14443A MIFARE卡模拟模式、FeliCa卡模拟模式、ISO/IEC 18092 ECMA 340点对点;这款芯片提供3中和主机通信的接口:SPI\I2C\USART。
SPI\I2C\USART三种通信方式有该芯片的P16引脚和P17引脚配置:
P16 P17
UART 0(GND) 0(GND)
I2C 1(DVDD) 0(GND)
SPI 0(GND) 1(DVDD)
PN532普通帧(P9):(普通格式最大发送255个字节,而长格式(下面有讲解)可以发送更多的字节)
对于PN532的控制,只需要按照帧格式写入数据就可以了
0x00, 前序
0x00, 0xff, 包头
LEN, 数据长度,包含TFI和PD
LCS, 长度校验和,LEN + LCS = 0x00;
TFI, 传输方向,0xd4传到卡片,0xd5卡片返回
PD0, 数据
PD1, 数据
…
PDn, 数据
DCS, 数据校验和,DCS+TFI+PD0+…+PDn = 0x00;
长格式帧(P10) :
0x00, 前序
0x00, 0xff, 包头
0xff, 0xff 短格式中的长度和校验 (普通格式最大发送255个字节,而长格式可以发送更多的字节)
LENH, 数据长度高字节,包含TFI和PD
LENL, 数据长度低字节,包含TFI和PD
LCS, 长度校验和,LENH + LENL + LCS = 0x00;
TFI, 传输方向,0xd4传到卡片,0xd5卡片返回
PD0, 数据
PD1, 数据
…
PDn, 数据
DCS, 数据校验和,DCS+TFI+PD0+…+PDn = 0x00;
以上帧格式可用一个数据结构表示,方便写代码:
/* PN532操作命令结构定义 */
struct Pn532Cmd{
uint8_t Preamble; //前序
uint8_t StartCode[2]; //包头
uint8_t LEN; //包长 TFI+PD0...PDn
uint8_t LCS; //长度校验 LEN + LCS = 0x00
uint8_t TFI; //命令帧识别位 (0xD4:input) (0xD5:output)
uint8_t *data; //数据域
uint8_t DCS; //数据校验 [TFI + PD0 + PD1 + ... + PDn + DCS] = 0x00
uint8_t Postamble; //后序
};
struct Pn532Cmd cmd ={
.Preamble = 0x00,
.StartCode[0] = 0x00,
.StartCode[1] = 0xff,
.TFI = 0xd4, //传输方向,0xd4传到卡片,0xd5卡片返回
.Postamble = 0x00,
};
/*******************************************************************************
* Function Name : GetCmd
* Description : 拿到命令包,在buf中存放一个完整的数据包
* Input : 命令,命令长度
* Output : none
* Return : none
* date :
* modify :
*******************************************************************************/
void GetCmd(uint8_t *data, uint8_t l)
{
uint8_t temp=0;
len = 0;
memset(buf,0,100);
cmd.LEN = l+1; //计算长度,包括数据长度和传输标志
cmd.LCS = 0-cmd.LEN; //长度校验和
cmd.data = data;
for(int i=0; i<l; i++) //拿到数据
temp += data[i];
temp += cmd.TFI;
cmd.DCS = 0-temp; //数据校验和
len = 1+2+1+1+cmd.LEN+1+1;
buf[0] = 0x00; //包前序
buf[1] = 0x00; //包头
buf[2] = 0xff;
buf[3] = cmd.LEN; //长度
buf[4] = cmd.LCS; //校验和
buf[5] = 0xd4; //输出
memcpy(&buf[6], cmd.data, l); //命令,注意这里是将l个(不是1个)字节数据从buf[6]开始往后面填写一直填写到buf[6+l-1]
buf[6+l] = cmd.DCS; //校验和
buf[7+l] = cmd.Postamble; //后序
}
PN532应答(P9):
0x00, 前序
0x00, 0xff, 包头
00, 数据长度, 这里没有任何数据
ff, 长度校验和,LEN + LCS = 0x00;
传输方向,0xd4传到卡片,0xd5卡片返回
数据
数据
…
PDn, 数据
DCS, 数据校验和,DCS+TFI+PD0+…+PDn = 0x00;
00, 尾序
唤醒芯片(P23,P99) :
0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,(这些字符就是唤醒头)
0x00,0x00,0xFF,0x03,0xFD,0xD4,0x14,0x01,0x17,0x00(在唤醒头后面加上了一个普通帧)
从手册可以看出,唤醒命令要在原有的数据包之前加入唤醒头,这个比较特殊一点
0xd4代表主机向PN532写入数据
0x14,0x01代表选择了普通模式
应答:0x00,0x00,0xff,0x02,0xfe,0xd5(方向:0xd5卡片返回数据到单片机),0x15(应答码,在发送的基础上加1,因为唤醒命令中使用的是0x14所以这里加1变成0x15),0x16,0x00
命令格式(P35) :
• 扫描卡片
0x4a, 0x02, 0x00 //扫描命令,卡片个数(2个),波特率
uint8_t SCAN[3] = {0x4a, 0x02, 0x00}; //扫描指令,卡片个数,波特率
• 应答
0x4b, 应答码
0x02, 卡片个数
0x01, 第一个卡片
0x04, 0x00, 卡片类型
0x08, 卡片容量
0x04, id长度
0x01, 0x02, 0x03, 0x04 卡片id(该环节已经拿到了卡片ID,所以不需要同RFID一样再增加一个防冲突环节)
uint8_t WAKE[24] = {0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,
0xFD,0xD4,0x14,0x01,0x17,0x00}; //唤醒模块
uint8_t WAKEBACK[9] = {0x00,0x00,0xFF,0x02,0xFE,0xD5,0x15,0x16,0x00}; //应答
• 认证卡片
0x40, 0x01, 0x60, 0x02, 数据交换命令,1号卡片,A认证(认证A密码是0x60,认证B密码是0x61),2地址
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 秘钥
0x01, 0x02, 0x03, 0x04 卡片id
• 应答
0x41, 0x00 无错
uint8_t AUTHOR[14] = {0x40,0x01,0x60,0x02,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x02,0x03,0x04};/*交换数据,
1卡,认证命令,2地址,key,id*/
/*41 0 应答,无措*/
• 读卡
0x40, 0x01, 0x30, 0x02 //交换数据,1号卡,读取块,2地址
• 应答
0x41, 0x00, 16bytes 应答,无错,16个数据
uint8_t READ[4] = {0x40, 0x01, 0x30, 0x02};//交换数据,1号卡,读取块,2地址
/*41, 0, 16bytes 应答,无错,16个数据*/
• 写卡
0x40, 0x01, 0xa0, 0x02, 交换数据,1号卡,写入块,2地址
1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6 数据
• 应答
0x41, 0x00 应答,无错
uint8_t WRITE[20] = {0x40, 0x01, 0xa0, 0x02, 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6};//交换数据,1号卡,写入块,2地址,数据
/*41, 0 应答,无错*/
• 充值
0x40, 0x01, 0xc1, 0x02, 1,0,0,0 //交换数据,1号卡,充值,2地址,数据
0x40, 0x01, 0xB0, 0x02;//交换数据,1号卡,保存,2地址
• 应答
0x41, 0x00 应答,无错
uint8_t INCREMENT[8] = {0x40, 0x01, 0xc1, 0x02, 1,0,0,0};//交换数据,1号卡,充值,2地址,数据
uint8_t TRANSFER[4] = {0x40, 0x01, 0xB0, 0x02};//交换数据,1号卡,保存,2地址
/*41, 0 应答,无错*/
• 扣款
0x40, 0x01, 0xc0, 0x02, 1,0,0,0;//交换数据,1号卡,扣款,2地址,数据
0x40, 0x01, 0xB0, 0x02;//交换数据,1号卡,保存,2地址
• 应答
0x41, 0x00 应答,无错
uint8_t DECREMENT[8] = {0x40, 0x01, 0xc0, 0x02, 1,0,0,0};//交换数据,1号卡,扣款,2地址,数据
uint8_t TRANSFER[4] = {0x40, 0x01, 0xB0, 0x02};//交换数据,1号卡,保存,2地址
/*41, 0 应答,无错*/
注意:充值或者扣款操作一定要加上保存动作
读写卡片(P35,P33) :
读写卡片的每一条命令都要按照帧格式
• 唤醒芯片,将芯片设置为普通模式
• 扫描卡片,一次最多2张。成功可以得到ID
• 认证,需要发送秘钥和ID。注意这里没有防冲突环节,因为在扫描的时候已经拿到卡片的ID,在认证的时候指明ID就可以了
• 读、写、充值、扣款
PN532读取卡片的过程要比MFRC522简单很多
修改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、认证(此时必须传入秘钥A,才能修改秘钥A)
4、修改秘钥A(16bytes,注意,不要修改本密码以外的其他地方)
5、保存修改
6、重新认证
7、数据的读写
内容提纲
1、读写卡流程-掌握
2、读卡(BLOCK0)实战-掌握
3、写卡(BLOCK3)实战-掌握
命令格式(P35,P33) :
• 唤醒
• 扫描
• 认证
• 读卡
• 唤醒
• 扫描
• 认证
• 写卡
唤醒芯片(P23,P99) :
0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0xFF,0x03,0xFD,0xD4,0x14,0x01,0x17,0x00
从手册可以看出,唤醒命令要在原有的数据包之前加入唤醒头,这个比较特殊一点
0xd4代表主机向PN532写入数据
0x14,0x01代表选择了普通模式
命令格式(P35) :
• 扫描卡片
0x4a, 0x02, 0x00 //扫描命令,卡片个数,波特率
• 应答
0x4b, 应答码
0x02, 卡片个数
0x01, 第一个卡片
0x04, 0x00, 卡片类型
0x08, 卡片容量
0x04, id长度
0x01, 0x02, 0x03, 0x04 卡片id
• 认证卡片
0x40, 0x01, 0x60, 0x02, 数据交换命令,1号卡片,A认证,2地址
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 秘钥
0x01, 0x02, 0x03, 0x04 卡片id
• 应答
0x41, 0x00 无错
• 读卡
0x40, 0x01, 0x30, 0x02 //交换数据,1号卡,读取块,2地址
• 应答
0x41, 0x00, 16bytes 应答,无错,16个数据
• 写卡
0x40, 0x01, 0xa0, 0x02, 交换数据,1号卡,写入块,2地址
1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6 数据
• 应答
0x41, 0x00 应答,无错
读写卡片(P35,P33) :
读写卡片的每一条命令都要按照帧格式
• 唤醒芯片,将芯片设置为普通模式
• 扫描卡片,一次最多2张。成功可以得到ID
• 认证,需要发送秘钥和ID。注意这里没有防冲突环节,因为在扫描的时候已经拿到卡片的ID,在认证的时候指明ID就可以了
• 读、写、充值、扣款
• PN532读取卡片的过程要比MFRC522简单很多
修改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、认证(此时必须传入秘钥A,才能修改秘钥A)
4、修改秘钥A(16bytes,注意,不要修改本密码以外的其他地方)
5、保存修改
6、重新认证
7、数据的读写
1、读写卡流程-掌握
2、读卡(BLOCK0)实战-掌握
3、写卡(BLOCK3)实战-掌握
命令格式(P35,P33) :
• 唤醒
• 扫描
• 认证
• 读卡
• 唤醒
• 扫描
• 认证
• 写卡
唤醒芯片(P23,P99) :
0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0xFF,0x03,0xFD,0xD4,0x14,0x01,0x17,0x00
从手册可以看出,唤醒命令要在原有的数据包之前加入唤醒头,这个比较特殊一点
0xd4代表主机向PN532写入数据
0x14,0x01代表选择了普通模式
命令格式(P35) :
• 扫描卡片
0x4a, 0x02, 0x00 //扫描命令,卡片个数,波特率
• 应答
0x4b, 应答码
0x02, 卡片个数
0x01, 第一个卡片
0x04, 0x00, 卡片类型
0x08, 卡片容量
0x04, id长度
0x01, 0x02, 0x03, 0x04 卡片id
• 认证卡片
0x40, 0x01, 0x60, 0x02, 数据交换命令,1号卡片,A认证,2地址
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 秘钥
0x01, 0x02, 0x03, 0x04 卡片id
• 应答
0x41, 0x00 无错
• 读卡
0x40, 0x01, 0x30, 0x02 //交换数据,1号卡,读取块,2地址
• 应答
0x41, 0x00, 16bytes 应答,无错,16个数据
• 写卡
0x40, 0x01, 0xa0, 0x02, 交换数据,1号卡,写入块,2地址
1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6 数据
• 应答
0x41, 0x00 应答,无错
————————————————
值段可以实现电子钱包的功能 有效的命令有 读 写 增 减 恢复 发送
值段有一个固定的数据格式 可以进行错误检测和纠正并备份管理
值段只能在值段格式的写操作时产生
• 值 表示一个带符号 4 字节值 这个值的最低一个字节保存在最低的地址中 取反的字节以标准2 的格式保存 为了保证数据的正确性和保密性 值被保存了 3 次 两次不取反保存 一次取反保存
• Adr 表示一个 1 字节地址 当执行强大的备份管理时用于保存存储段的地址 地址字节保存了 4次 取反和不取反各保存两次 在执行增 减 恢复 传送操作时 地址保持不变 它只能通过写命令改变
• 初始化为固定的格式(使用Write_Card函数初始化)
以 block1(地址为1)为例: 00,00,00,00, ff,ff,ff,ff, 00,00,00,00, 1,fe,1,fe
值:初始化为0 值取反 值 两次写地址,两次地址取反
• 唤醒
• 扫描
• 认证
命令格式(P35,P33) :
• 充值
0x40, 0x01, 0xc1, 0x02, 1,0,0,0 //交换数据,1号卡,充值,2地址,数据
0x40, 0x01, 0xB0, 0x02;//交换数据,1号卡,保存,2地址
• 应答
0x41, 0x00 应答,无错
• 扣款
0x40, 0x01, 0xc0, 0x02, 1,0,0,0;//交换数据,1号卡,扣款,2地址,数据
0x40, 0x01, 0xB0, 0x02;//交换数据,1号卡,保存,2地址
• 应答
0x41, 0x00 应答,无错
注意:充值或者扣款操作一定要加上保存动作
充值框架(扣款类似操作):
//while(1)
uint8_ t value[4]= {0xff, 0x00, 0x00, 0x00};
uint8_ t data[16]= {0x0a, 0x00, 0x00, 0x00, 0xf5, 0xff,0xff, 0xff, 0x0a, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x0l, 0xfe};
uint8_ t key[6]= {0xff, 0xff,0xff, 0xff, 0xff,0xff};
HAL_Delay(l000) ;
Wake_Card();//唤醒
Scan_Card(); //扫描
author(1, key) ; //地址1认证
write(1, data) ; //地址1初始化为充值付款的固定格式
increment(1, value); //地址1充值
transfer(1); //地址1保存
read(1) ; //读取地址1的数据
如下为全部相关参考代码:
532.c 功能:掌握NFC的操作流程和相关功能函数
#include "PN532.h"
#include "stm32f0xx_hal.h"
#include "dma.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "lcd.h"
#include "string.h"
#include "logo.h"
#include "stdlib.h"
#include "stdio.h"
#define BUFFER_SIZE 128
uint8_t buf[100];
uint8_t len = 0;
uint8_t write_data[16];
uint8_t write_value[4];
uint8_t ID[4];
uint8_t KEY_A[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
extern uint8_t rx_buffer[128];
uint8_t WAKE[24] = {0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0xFD,0xD4,0x14,0x01,0x17,0x00}; //唤醒模块
uint8_t WAKEBACK[9] = {0x00,0x00,0xFF,0x02,0xFE,0xD5,0x15,0x16,0x00};
/* PN532操作命令结构定义 */
struct Pn532Cmd{
uint8_t Preamble; //前序
uint8_t StartCode[2]; //包头
uint8_t LEN; //包长 TFI+PD0...PDn
uint8_t LCS; //长度校验 LEN + LCS = 0x00
uint8_t TFI; //命令帧识别位 (0xD4:input) (0xD5:output)
uint8_t *data; //数据域
uint8_t DCS; //数据校验 [TFI + PD0 + PD1 + ... + PDn + DCS] = 0x00
uint8_t Postamble; //后序
};
struct Pn532Cmd cmd ={
.Preamble = 0x00,
.StartCode[0] = 0x00,
.StartCode[1] = 0xff,
.TFI = 0xd4,
.Postamble = 0x00,
};
uint8_t SCAN[3] = {0x4a, 0x02, 0x00}; //扫描指令,卡片个数,波特率
/*
4B 应答
02 目标个数
01 第一个目标
04 00 选卡应答
08 防冲突应答
04 id长度
12 67 58 32 id
02
44 00
00
08
88 04 B6 E4 00 00 00 00
*/
uint8_t AUTHOR[14] = {0x40,0x01,0x60,0x02,0xff,0xff,0xff,0xff,0xff,0xff,0x01,0x02,0x03,0x04};//交换数据,1卡,认证命令,2地址,key,id
/*41 0 应答,无措*/
uint8_t READ[4] = {0x40, 0x01, 0x30, 0x02};//交换数据,1号卡,读取块,2地址
/*41, 0, 16bytes 应答,无错,16个数据*/
uint8_t WRITE[20] = {0x40, 0x01, 0xa0, 0x02, 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6};//交换数据,1号卡,写入块,2地址,数据
/*41, 0 应答,无错*/
uint8_t INCREMENT[8] = {0x40, 0x01, 0xc1, 0x02, 1,0,0,0};//交换数据,1号卡,充值,2地址,数据
uint8_t DECREMENT[8] = {0x40, 0x01, 0xc0, 0x02, 1,0,0,0};//交换数据,1号卡,扣款,2地址,数据
uint8_t TRANSFER[4] = {0x40, 0x01, 0xB0, 0x02};//交换数据,1号卡,保存,2地址
/*******************************************************************************
* Function Name : GetCmd
* Description : 拿到命令包,在buf中存放一个完整的数据包
* Input : 命令,命令长度
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
void GetCmd(uint8_t *data, uint8_t l)
{
uint8_t temp=0;
len = 0;
memset(buf,0,100);
cmd.LEN = l+1; //计算长度,包括数据长度和传输标志
cmd.LCS = 0-cmd.LEN; //长度校验和
cmd.data = data;
for(int i=0; i<l; i++) //拿到数据
temp += data[i];
temp += cmd.TFI;
cmd.DCS = 0-temp; //数据校验和
len = 1+2+1+1+cmd.LEN+1+1;
buf[0] = 0x00; //包前序
buf[1] = 0x00; //包头
buf[2] = 0xff;
buf[3] = cmd.LEN; //长度
buf[4] = cmd.LCS; //校验和
buf[5] = 0xd4; //输出
memcpy(&buf[6], cmd.data, l); //命令
buf[6+l] = cmd.DCS; //校验和
buf[7+l] = cmd.Postamble; //后序
}
/*******************************************************************************
* Function Name : SendCmd
* Description : 串口发送命令,模块使用高速串口和主机通信
* Input : none
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
void SendCmd(uint8_t *command, uint8_t num)
{
while(HAL_UART_Transmit(&huart2, command, num, 100));
}
/*******************************************************************************
* Function Name : Wake_Card
* Description : 唤醒卡片,这个命令特殊
* Input : none
* Output : none
* Return : 成功0,失败1
* date : 2016,12,28
* modify :
*******************************************************************************/
uint8_t Wake_Card()
{
memset(rx_buffer,0,sizeof(rx_buffer));
SendCmd(WAKE, 24);
HAL_Delay(50);
if(!memcmp(WAKEBACK, rx_buffer, 9))
{
printf("wake success\n");
return 0;
}
printf("wake failed\n");
return 1;
}
/*******************************************************************************
* Function Name : Scan_Card()
* Description : 扫描卡片,可以拿到卡片的ID
* Input : none
* Output : none
* Return : 成功返回0,失败返回1
* date : 2016.12.28
* modify :
*******************************************************************************/
uint8_t Scan_Card()
{
memset(rx_buffer,0,sizeof(rx_buffer));
GetCmd(SCAN, 3);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x4b)
{
printf("scan success\n");
memcpy(ID, &rx_buffer[13], 4); //拿到卡片ID
// for(int i=0; i<32; i++)
// printf("0x%x,", rx_buffer[i]);
// printf("\n");
return 0;
}
printf("scan failed\n");
return 1;
}
/*******************************************************************************
* Function Name : Author_Card
* Description : 认证卡片秘钥A,需要秘钥和id
* Input : none
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
uint8_t Author_Card()
{
memset(rx_buffer,0,sizeof(rx_buffer));
memcpy(&AUTHOR[10], ID, 4); //传入id
memcpy(&AUTHOR[4], KEY_A, 6); //传入key A
GetCmd(AUTHOR, 14);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("author success\n"); //认证成功
return 0;
}
printf("author failed\n");
return 1;
}
/*******************************************************************************
* Function Name : Read_Card()
* Description : 读取卡片内容
* Input : none
* Output : none
* Return : 成功返回0,失败返回1
* date :
* modify :
*******************************************************************************/
uint8_t Read_Card()
{
memset(rx_buffer,0,sizeof(rx_buffer));
READ[3] = cmd_addr; //设置地址
GetCmd(READ, 4);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("read success\n"); //读取成功
for(int i=0; i<16; i++)
printf("0x%x,", rx_buffer[8+i]);
printf("\n");
return 0;
}
return 1;
}
/*******************************************************************************
* Function Name : Write_Card
* Description : 向卡片写入内容
* Input : none
* Output : none
* Return : 成功返回0,失败返回1
* date :
* modify :
*******************************************************************************/
uint8_t Write_Card()
{
memset(rx_buffer,0,sizeof(rx_buffer));
WRITE[3] = cmd_addr; //设置地址
memcpy(&WRITE[4], write_data, 16); //设置要写入的数据
GetCmd(WRITE, 20);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("write success\n"); //写入成功
Read_Card();
return 0;
}
return 1;
}
/*******************************************************************************
* Function Name : Increment_Value
* Description : 充值
* Input : none
* Output : none
* Return : 成功返回0,失败返回1
* date :
* modify :
*******************************************************************************/
uint8_t Increment_Value()
{
memset(rx_buffer,0,sizeof(rx_buffer));
INCREMENT[3] = cmd_addr; //充值地址
memcpy(&INCREMENT[4], write_value, 4); //充值金额
GetCmd(INCREMENT, 8);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("increment success\n");
return 0;
}
return 1;
}
/*******************************************************************************
* Function Name : Decrement_Value
* Description : 扣款
* Input : none
* Output : none
* Return : 成功返回0,失败返回1
* date :
* modify :
*******************************************************************************/
uint8_t Decrement_Value()
{
memset(rx_buffer,0,sizeof(rx_buffer));
INCREMENT[3] = cmd_addr; //扣款地址
memcpy(&DECREMENT[4], write_value, 4); //扣款金额
GetCmd(DECREMENT, 8);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("decrement success\n");
return 0;
}
return 1;
}
/*******************************************************************************
* Function Name : Transfer_Value()
* Description : 将缓存区数据保存
* Input : none
* Output : none
* Return : none
* date :
* modify :
*******************************************************************************/
uint8_t Transfer_Value()
{
memset(rx_buffer,0,sizeof(rx_buffer));
TRANSFER[3] = cmd_addr; //保存地址
GetCmd(TRANSFER, 4);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x41 && rx_buffer[7]==0x00)
{
printf("Transfer success\n");
Read_Card();
return 0;
}
return 1;
}
/*******************************************************************************
* Function Name : PN532_Read()
* Description : 从卡片读内容
* Input : none
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
void PN532_Read()
{
if(!Wake_Card()) //唤醒卡片
{
if(!Scan_Card()) //扫描卡片
{
if(!Author_Card()) //认证卡片
{
Read_Card(); //读取卡片的数据
}
}
}
}
/*******************************************************************************
* Function Name : PN532_Write()
* Description : 向卡片写入内容
* Input : none
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
void PN532_Write()
{
if(!Wake_Card()) //唤醒卡片
{
if(!Scan_Card()) //扫描卡片
{
if(!Author_Card()) //认证卡片
{
Write_Card(); //向卡片写数据
}
}
}
}
/*******************************************************************************
* Function Name : PN532_Value(uint8_t mode)
* Description : 卡片钱包操作,根据参数选择充值或者扣款
* Input : 钱包模式,充值或者扣款
* Output : none
* Return : none
* date : 2016.12.28
* modify :
*******************************************************************************/
void PN532_Value(uint8_t mode)
{
if(!Wake_Card())
{
if(!Scan_Card())
{
if(!Author_Card())
{
if(mode==0xc0)
Increment_Value();
if(mode==0xc1)
Decrement_Value();
Transfer_Value();
}
}
}
}
void CardData_screen_LCD_show(uint8_t *data, uint8_t num)
{
char buf1[16];
snprintf(buf1,4*num,"%02x%02x%02x%02x",data[0],data[1],data[2],data[3]);
Gui_DrawFont_GBK16(44,80,BLACK,GREEN,(uint8_t*)buf1);
memset(buf1,0,sizeof(buf1));
}
/************************
*func : 开机初始化
*************************/
void BoardInit(void)
{
printf("NFC read card!\n");
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET); //beep off
Lcd_Init(); //lcd init:clear screen green, show logo, show words
Lcd_Clear(GREEN);
showimage_farsight(gImage_logo);
Gui_DrawFont_GBK16(20,50,BLUE,GREEN,(uint8_t *)"NFC");
Gui_DrawFont_GBK16(76,50,BLUE,GREEN,(uint8_t *)"RFID");
Gui_DrawFont_GBK16(15,80,RED,GREEN,(uint8_t *)"ID:");
__HAL_I2C_ENABLE_IT(&hi2c1,I2C_IT_ADDRI);
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart2,rx_buffer,BUFFER_SIZE);
BeepTickDouble();
}
main.c文件 对nfc做具体操作实验
#include "stm32f0xx_hal.h"
#include "dma.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "PN532.h"
void SystemClock_Config(void);
void Error_Handler(void);
extern uint8_t KEY2_FLAG;
extern uint8_t KEY3_FLAG;
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_B[6];//秘钥B
unsigned char CMD_OVER = 0;
unsigned char DATA_OVER = 0;
unsigned char error = 0;
int main(void)
{
uint8_t j;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_TIM6_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
BoardInit();//开机初始化
PN532_Read();
//在中断里接收串口数据
HAL_UART_Receive_IT(&huart1,aRxBuffer,1);
while (1)
{
if(error)
{
printf("指令错误\n");
error = 0;
continue;
}
if(CMD_OVER)
{
if(cmd_type == 'r')
{
if(cmd_addr<=63 && cmd_addr>=0)
PN532_Read();
else
printf("地址不对\n");
CMD_OVER = 0;
cmd_type = 0;
}
else if(cmd_type == 'w')
{
if(DATA_OVER)
{
printf("data over\n");
j = Write_Check();
printf("j = %d\n", j);
if(j==0)
{
if(cmd_check==0||cmd_check==1)
PN532_Write();
else if(cmd_check==2)
PN532_Value(0xc1);
else if(cmd_check==3)
PN532_Value(0xc0);
}
else if(j==1)
printf("要写入的地址不对\n");
else if(j==2)
printf("要写入的控制字不对\n");
CMD_OVER = 0;
DATA_OVER = 0;
cmd_type = 0;
cmd_check = 0;
}
}
}
}
}
/*******************************************************************************
* Function Name :
* Description : 串口接收回调函数,串口接收中断最终会调用这里
* Input :
* Output :
* Return :
* date : 2016.12.27
* modify :
*******************************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// printf("r");
if (huart == &huart1)
{
cmd_buf[count] = USART1->RDR;
count++;
if(cmd_buf[count-2]==0x0d && cmd_buf[count-1]==0x0a) //结束符
{
if(cmd_buf[0]=='c' && count==18) //命令
{
cmd_type = cmd_buf[1]; //获取命令 读/写
cmd_check = cmd_buf[2]; //获取命令级别 写普通区还是特殊区 或者是充值
cmd_addr = cmd_buf[3]; //获取操作地址
memcpy(KEY_A, &cmd_buf[4], 6);//获取秘钥A
memcpy(KEY_B, &cmd_buf[10], 6);//获取秘钥B
CMD_OVER = 1;
}
else if(cmd_buf[0]=='d' && count==19) //数据
{
memcpy(cmd_dat, &cmd_buf[1], 16);
DATA_OVER = 1;
}
else if(cmd_buf[0]=='d' && count==7) //数据
{
memcpy(cmd_dat, &cmd_buf[1], 4);
DATA_OVER = 1;
}
else
error = 1; //格式完全不正确
count = 0;
memset(cmd_buf, 0, 100);
}
}
HAL_UART_Receive_IT(&huart1,aRxBuffer,1);
}
/*******************************************************************************
* Function Name : Write_Check()
* Description : 卡片写入检测函数,目前暂不支持控制字的修改
* Input : none
* Output : none
* Return : 成功0,地址错误1,控制字错误2
* date : 2016.12.27
* modify :
*******************************************************************************/
uint8_t Write_Check()
{
if(cmd_addr>=0 && cmd_addr<=63)
{
if(cmd_check==0 || cmd_check==2 || cmd_check==3) //普通写入,不包含block3,因此要检测地址
{
if(cmd_addr!=0 && (cmd_addr+1)%4!=0)
return 0;
else
return 1;
}
else if(cmd_check==1) //修改秘钥A,控制区不可以改动
{
if(cmd_addr==0 || (cmd_addr+1)%4!=0) //地址不正确
return 1;
if(cmd_dat[6]!=0xff || cmd_dat[7]!=0x07 || cmd_dat[8]!=0x80 || cmd_dat[9]!=0x69) //控制字不正确
return 2;
else
return 0;
}
}
else
return 1; //地址不正确
}
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
#endif