物联网之RFID三(NFC)

(360) 2024-04-08 19:01:01

内容提纲
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、计算机和手机之间都可以交换资料或者服务

物联网之RFID三(NFC) (https://mushiming.com/)  第1张

 

NFC与RFID区别:

• 第一、NFC将非接触读卡器、非接触卡和点对点功能整合进一块单芯片,而rfid必须有阅读器和标签组成。RFID只能实现信息的读取以及判定,而NFC技术则强调的是信息交互。通俗的说NFC就是RFID的演进版本,双方可以近距离交换信息。NFC手机内置NFC芯片,组成RFID模块的一部分,可以当作RFID无源标签使用进行支付费用;也可以当作RFID读写器,用作数据交换与采集,还可以进行NFC手机之间的数据通信。

• 第二、NFC传输范围比RFID小,RFID的传输范围可以达到几米、甚至几十米,但由于NFC采取了独特的信号衰减技术,相对于RFID来说NFC具有距离近、带宽高、能耗低等特点。

• 第三、应用方向不同。NFC看更多的是针对于消费类电子设备相互通讯,有源RFID则更擅长在长距离识别。

NFC与BLE区别:

物联网之RFID三(NFC) (https://mushiming.com/)  第2张

内容提纲
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个字节,而长格式(下面有讲解)可以发送更多的字节)

物联网之RFID三(NFC) (https://mushiming.com/)  第3张

对于PN532的控制,只需要按照帧格式写入数据就可以了

0x00, 前序

0x00, 0xff, 包头

LEN, 数据长度,包含TFI和PD

LCS, 长度校验和,LEN + LCS = 0x00;

TFI, 传输方向,0xd4传到卡片,0xd5卡片返回

PD0, 数据

PD1, 数据

PDn, 数据

DCS, 数据校验和,DCS+TFI+PD0+…+PDn = 0x00;

 

长格式帧(P10) :

物联网之RFID三(NFC) (https://mushiming.com/)  第4张

 

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;            //后序 
};
 

物联网之RFID三(NFC) (https://mushiming.com/)  第5张

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, 尾序

内容提纲
1、唤醒-掌握

2、扫卡-掌握

3、认证-掌握

4、读写-掌握

唤醒芯片(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代表选择了普通模式

物联网之RFID三(NFC) (https://mushiming.com/)  第6张

应答: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不起作用,可以不用管

物联网之RFID三(NFC) (https://mushiming.com/)  第7张

每个字节的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结尾的位取反之后才是最终的控制模式

物联网之RFID三(NFC) (https://mushiming.com/)  第8张

每个字节的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)
————————————————
 

物联网之RFID三(NFC) (https://mushiming.com/)  第8张

 

Block3由bit7控制,出厂默认的是001控制

秘钥A永远不可读,读出来的是0x00

修改秘钥A的过程:

第6、7、8字节最高位(第7位)出厂时分别为0 0 1,则控制模式如下所示:需要修改秘钥A,需要先传入最初的秘钥A

物联网之RFID三(NFC) (https://mushiming.com/)  第10张

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代表选择了普通模式

物联网之RFID三(NFC) (https://mushiming.com/)  第11张

命令格式(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
 
 

THE END

发表回复