前言

    初次接触NFC协议,很多人都会感觉困惑,搜索相关资料,大多数都介绍协议的理论及应用领域,数据传输的内容却很少,但对与开发人员来说,除了理论知识外,数据传输也是非常重要环节,尤其是对底层开发人员来说,数据如何封包/解包才是重中之重,本文通过相关的示例展示,希望能给初次接触NFC开发底层开发者带来帮助.

一、NFC数据传输格式

       NFC数据交互格式采用的是NDEF,下面分解说明NDEF数据格式:

                    Bit7    Bit6    Bit5    Bit4    Bit3    Bit2    Bit1    Bit0
第1个字节    MB    ME     CF      SR      IL      [  TNF : bit0~bit2   ]
第2个字节    TYPE  LENGTH
第3个字节    PAYLOAD  LENGTH3
第4个字节    PAYLOAD  LENGTH2
第5个字节    PAYLOAD  LENGTH1
第6个字节    PAYLOAD  LENGTH0
第7个字节    ID  LENGTH
   =========== TYPE ============
   ============= ID =============
   ===========PYLOAD===========

其中:

MB:消息开始,标志指示这是否是NDEF消息的开始
ME:消息结束,标志指示这是否是消息中的最后一条记录。
CF:块标志,标志指示这是第一个记录块还是中间记录块。
SR:短记录位,如果PAYLOAD LENGTH字段为1字节(8位/ 0-255)或更小,则SR标志设置为1,这允许更紧凑的记录。
IL:ID长度字段,标志指示ID长度字段是否存在。如果将其设置为0,则ID长度字段将在记录中省略。
TNF:类型名称格式,它描述记录的类型,并设置对其余记录的结构和内容的期望

二、TNF数据类型

目前NFC支持七种TNF数据类型:
0x00:  Empty:表示该Record中没有数据,即相当于一个空的NFC Record;

0x01:  NFC Forum Well-Known Type:由NFC Forum定义的一些较为常用的数据类型,包括URI、TEXT等,其格式遵循NFC Forum RTD(Record Type Definition)规范。下文将详细介绍它。目前常用类型如下:
          URI Record Type:用于存储URI数据,对应Type字段取值为"U"。
          Text Record Type:用于存储文本数据,对应Type字段取值为"T"。
          Signature Record Type:用于存储数字签名数据,对应Type字段取值为"Sig"。
          Smart Poster Record Type:智能海报,用于存储与该海报相关的一些资讯信息,如图片、                相关介绍等,对应Type字段取值为"Sp"
        Generic Control Record Type:用于传递控制信息,对应Type字段取值为"Gc"
        External Type:为第三方组织定义的类型,目前NFC Forum没有定义相关的数据类型;


0x02:  MIME:它是Multipurpose Internet Mail Extensions的缩写,遵循RFC2046规范。例如,当TNF取值为MIME时,其Type字段取值可为"text/plain"或"image/png"等。

0x03:  Absolute URI:绝对URI,遵循RFC 3986规范。例如某文件的绝对URI为"http://baidu.com/tls.txt",而其相对URI则为"tls.txt"。

0x04:NFC Forum External Type:也由NFC Forum的RTD规范定义,下文将介绍它。

0x05: Unknown:代表Payload中的数据类型未知,它和MIME类型"application/octet-stream"有些类似,这种类型的数据由相应的应用程序来解析。

0x06:Unchanged:这种类型的数据用于NFC Record分片。例如一个大的数据需要通过多个NFC Record来承载,除第一个NFC Record分片外,该数据对应的其他NFC Record分片都必须设置TNF为Unchanged。

三、实例解析:

以下以短数据文本、URL为例来讲解,值得注意的是,payload的第一个直接在文本、URI数据中表示状态码,解释如下:
   文本数据状态码:
                Bit7    Bit6    Bit5    Bit4    Bit3    Bit2    Bit1    Bit0
1个字节    

      Bit7:   0为UTF-8, 1为UTF-16    

      Bit6:  固定为0    

      Bit0~Bit5:  为 language code 长度,如 “zh”、"en"、"rf-CA"、"jp" 等 US-ASCII编码格式
以下以短数据文本、URL为例来讲解,值得注意的是,payload的第一个直接在文本、URI数据中表示状态码,解释如下:

URI数据状态码:
                Bit7    Bit6    Bit5    Bit4    Bit3    Bit2    Bit1    Bit0
1个字节    URL Type 系数,如1:”http://www.” ; 2:“https://www.” 等

   

1、文体数据:  ”abc123456”
     NDEF数据为:d1 01 0c 54 02 7a 68 61 62 63 31 32 33 34 35 36
        d1:  信息头部 1101 0001;
                MB:1--信息开始
                ME:1--信息结束
                CF:0--第一个记录块
                SR:1--短记录,数据长度小于255个字节
                IL: 0--ID长度为0,表示不成在ID
                TNF:1--表示总所周知类型
        01:TNF类型长度;
        0c:payload长度 12个字节
        54:RTD类型,“T”的ASCLL码,文本类型
        02:文本数据状态码:0000010
                Bit7: 0---UTF8类型

                Bit0~bit5:2---语言码长度为2个字节
        7a 86:语言码;“zh”的ASCLL码值
         68 61 62 63 31 32 33 34 35 36: 分别为”abc123456”对应的ASCLL值


2、URI数据:  “https://www.baidu.com/”
     NDEF数据为:d1 01 0b 55 02 62 61 69 64 75 2e 63 6f 6d 2f
        d1:  信息头部 1101 0001;
                MB:1--信息开始
                ME:1--信息结束
                CF:0--第一个记录块
                SR:1--短记录,数据长度小于255个字节
                IL: 0--ID长度为0,表示不成在ID
                TNF:1--表示总所周知类型
        01:TNF类型长度;
        0b:payload长度 11个字节
        55:RTD类型,“U”的ASCLL码,URI类型
        02:URI数据状态码,表示URL类型为 “https://www.”
        62 61 69 64 75 2e 63 6f 6d 2f: 分别为”baidu.com/”对应的ASCLL码值;

四、示例代码

1、头文件ndef.h

#ifndef __NFC_NDEF_H__
#define __NFC_NDEF_H__


#ifdef __cplusplus
extern "C" {
#endif

#define NDEF_RECORD_SIZE 255
#define NDEF_ID_SIZE 32
#define NDEF_HEAD_SIZE 32
#define NDEF_LG_SIZE 8



// NDEF NTF类型
typedef enum{
    TNF_NULL = 0,        // 空
    TNF_INSIDE_RTD = 1,  // NFC 论坛知名类型,包括UTF8、ASCII、URI...
    TNF_RFC2406 = 2,     // 定义于RFC 2046[RFC 2046]的媒体类型
    TNF_RFC3986 = 3,     // 定义于 RFC 3986[RFC 3986]的绝对 URI
    TNF_OUTSIDE_RTD = 4, // NFC 论坛外部自定义类型
    TNF_UNKNOW = 5,      // 未知
    TNF_STATIC = 6,      // 不可更改(参考2.3.3节)
    TNF_DEFAULT = 7      // 保留
} TnfType;

// NDEF TNF_INSIDE_RTD类型
typedef enum{
    RTD_UTF8 = 'T', // UTF8类型
    RTD_URI = 'U',  // URI类型
} RtdType;

typedef enum{
    START_PACK,     // 分包开始包
    MIDDLE_PACK,    // 分包中间包
    END_PACK,       // 分包结束包
    START_END_PACK  // 不分包,开始包也是结束包
}PackType;

// NDEF URI
typedef enum{
    RTD_URI_freeForm,     // 0x00     No prepending is done ... the entire URI is contained in the URI Field
    RTD_URI_httpWWW,      // 0x01     http://www.
    RTD_URI_httpsWWW,     // 0x02     https://www.
    RTD_URI_http,         // 0x03     http://
    RTD_URI_https,        // 0x04     https://
    RTD_URI_tel,          // 0x05     tel:
    RTD_URI_mailto,       // 0x06     mailto:
    RTD_URI_ftpAnonymous, // 0x07     ftp://anonymous:anonymous@
    RTD_URI_ftpFtp,       // 0x08     ftp://ftp.
    RTD_URI_ftps,         // 0x09     ftps://
    RTD_URI_sftp,         // 0x0A     sftp://
    RTD_URI_smb,          // 0x0B     smb://
    RTD_URI_nfs,          // 0x0C     nfs://
    RTD_URI_ftp,          // 0x0D     ftp://
    RTD_URI_dav,          // 0x0E     dav://
    RTD_URI_news,         // 0x0F     news:
    RTD_URI_telnet,       // 0x10     telnet://
    RTD_URI_imap,         // 0x11     imap:
    RTD_URI_rtps,         // 0x12     rtsp://
    RTD_URI_urn,          // 0x13     urn:
	RTD_URI_pop,		  // 0x14     pop:
	RTD_URI_sip,		  // 0x15     sip:
	RTD_URI_sips,		  // 0x16     sips:
	RTD_URI_tftp,		  // 0x17     tftp:
	RTD_URI_btspp,		  // 0x18     btspp://
	RTD_URI_btl2cap,	  // 0x19     btl2cap://
	RTD_URI_btgoep,		  // 0x1A     btgoep://
	RTD_URI_tcpobex,	  // 0x1B     tcpobex://
	RTD_URI_irdaobex,	  // 0x1C     irdaobex://
	RTD_URI_file,		  // 0x1D     file://
	RTD_URI_URNEPC_id,    // 0x1E     urn:epc:id:
	RTD_URI_RUNEPC_tag,	  // 0x1F     urn:epc:tag:
	RTD_URI_RUNEPC_pat,	  // 0x20     urn:epc:pat:
	RTD_URI_RUNEPC_raw,	  // 0x21     urn:epc:raw:
	RTD_URI_RUN_raw,	  // 0x22     urn:epc:
	RTD_URI_RUN_nfc,      // 0x23     urn:nfc
} UriType;


// NDEF首字节解码
typedef struct {
    unsigned char MB : 1;  // MB 标志是 1-bit 字段,置 1 表示 NDEF 消息的开始
	unsigned char ME : 1;  // ME 标志是 1-bit 字段,置 1 表示 NDEF 消息的结束
    unsigned char CF : 1;  // CF 标志是 1-bit 字段,置 1 表示 NDEF 消息分块传输
	unsigned char SR : 1;  // SR 标志是 1-bit 字段,置 1 表示PAYLOAD_LENGTH字段为单字节,payload 数据长度在255个字节以内
    unsigned char IL : 1;  // IL 标志是 1-bit 字段,置 1 表示在首部存在一个单字节ID_LENGTH字段
    unsigned char TNF: 3; // TNF标志是 3-bit 字段,指示TYPE字段值的结构
} NdefHead;


// NDEF 参数  文本类结构体
typedef struct{
	NdefHead      Head;
	unsigned char TnfTypeLen;               // 类型长度
	unsigned int  PayloadLen;               // 有效数据长度
	unsigned char IdLen;                    // 类型ID长度
	unsigned char RtdType;                  // Rtd类型
	unsigned char Id[NDEF_ID_SIZE];         // 如果包含ID(记录头中的IL位设置为1),则ID字段的值。如果IL位设置为0,则忽略此字段
    unsigned char StatusCode;               // 内容状态码;如果RTD为"U",状态码为UriType;如果RTD为"T",则状态码为语言编码描述
	unsigned char PackType;                 // 包类型
	unsigned char LgCode[NDEF_LG_SIZE];     // 语言编码类型
	unsigned char Payload[NDEF_RECORD_SIZE];
} NdefFrame;


typedef struct {
	unsigned int   Len;
	unsigned char  Data[NDEF_RECORD_SIZE + NDEF_ID_SIZE + NDEF_HEAD_SIZE + NDEF_LG_SIZE];
}NfcData_t;


#ifdef __cplusplus
}
#endif


#endif
    

2、C文件ndef.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ndef.h"


const char *pUirVal[32] = {
    "",
    "http://www.",
    "https://www.",
    "http://",
    "https://",
    "tel:",
    "mailto:",
    "ftp://anonymous:anonymous@",
    "ftp://ftp.",
    "ftps://",
    "sftp://",
    "smb://",
    "nfs://",
    "ftp://",
    "dav://",
    "news:",
    "telnet://",
    "imap:",
    "rtsp://",
    "urn:",
    "pop:",
	"sip:",
	"sips:",
	"tftp:",
	"btspp://",
	"btl2cap://",
	"btgoep://",
	"tcpobex://",
	"irdaobex://",
	"file://",
	"urn:epc:id:",
	"urn:epc:tag:",
	"urn:epc:pat:",
	"urn:epc:raw:",
	"urn:epc:",
	"urn:nfc"
};

typedef union {
	unsigned long ul;
	unsigned char uc[4];
} VARLONG;

static void depart_long_htol(unsigned long ul, unsigned char *str)
{
	VARLONG var;

	var.ul = ul;

	str[0] = var.uc[3];
	str[1] = var.uc[2];
	str[2] = var.uc[1];
	str[3] = var.uc[0];
}


int nfc_ndef_package(NdefFrame *pNdef, NfcData_t *pNfc)
{
	unsigned int i;
	unsigned int payloadlenIndex = 0;
	
	if(pNdef == NULL || pNfc == NULL)
		return -1;

	// 当前只处理数据包为NDEF_RECORD_SIZE个字节以内的NDEF
	if(pNdef->PayloadLen > NDEF_RECORD_SIZE) {
		printf("pNdef->PayloadLen: %d > %d NDEF_RECORD_SIZE\r\n", pNdef->PayloadLen , NDEF_RECORD_SIZE);
		return -1;
	}

	unsigned char head = 0;
	head = (pNdef->Head.MB << 7);
	head |= (pNdef->Head.ME << 6);
	head |= (pNdef->Head.CF << 5);
	head |= (pNdef->Head.SR << 4);
	head |= (pNdef->Head.IL << 3);
	head |= pNdef->Head.TNF;

	// head
	pNfc->Data[pNfc->Len++] = head;

	// tnf len
	pNfc->Data[pNfc->Len++] = pNdef->TnfTypeLen;

	payloadlenIndex = pNfc->Len;

	// payload len
	if(pNdef->PayloadLen > 255) {
		pNfc->Len += 4;
	} else {
		pNfc->Len++;
	}

	// ID len
	if(pNdef->Head.IL == 1) {
		pNfc->Data[pNfc->Len++] = pNdef->IdLen;
	}

	switch(pNdef->Head.TNF)
	{
		case TNF_INSIDE_RTD:
		{
			// rtd type
			switch (pNdef->RtdType)
			{
				case RTD_UTF8:
				{
					pNfc->Data[pNfc->Len++] = 0x54; //'T';
					
					// ID
					if(pNdef->Head.IL == 1) {
						for(i = 0; i < pNdef->IdLen; i++) {
							pNfc->Data[pNfc->Len++] = pNdef->Id[i];
						}
					}

					pNfc->Data[pNfc->Len++] = pNdef->StatusCode;
					unsigned char lglen = 0;
					lglen = pNdef->StatusCode & 0x1F;
					
					for(i = 0; i < lglen; i++)
						pNfc->Data[pNfc->Len++] = pNdef->LgCode[i];

					pNdef->PayloadLen += (1 + lglen);
					
					break;
				}
					
				case RTD_URI:
				{
					pNfc->Data[pNfc->Len++] = 0x55; //'U';
					// ID
					if(pNdef->Head.IL == 1) {
						for(i = 0; i < pNdef->IdLen; i++) {
							pNfc->Data[pNfc->Len++] = pNdef->Id[i];
						}
					}

					// uri type
					pNfc->Data[pNfc->Len++] = pNdef->StatusCode;
					pNdef->PayloadLen ++;

					break;
				}
					
				default:
					printf("RtdType: %d unkonw\r\n", pNdef->RtdType);
					return -1;
			}

			break;
		}
		case TNF_NULL:
		case TNF_RFC3986:
		case TNF_RFC2406:
		case TNF_OUTSIDE_RTD:
		case TNF_UNKNOW:
		case TNF_STATIC:
		case TNF_DEFAULT:
		default:
			printf("TNF: %d unkonw\r\n", pNdef->Head.TNF);
			return -1;
	}

	// payload 
	for(i = 0; i < pNdef->PayloadLen; i++) {
		pNfc->Data[pNfc->Len++] = pNdef->Payload[i];
	}

	// payload len
	if(pNdef->PayloadLen > 255) {
		depart_long_htol(pNdef->PayloadLen, &pNfc->Data[payloadlenIndex]);
		pNfc->Len += 4;
	} else {
		pNfc->Data[payloadlenIndex] = pNdef->PayloadLen;
	}
	
	return 0;
}


int nfc_ndef_prase(NfcData_t *pNfc, NdefFrame *pNdef)
{
	unsigned int i = 0;
	unsigned int index = 0;
	
	if(pNdef == NULL || pNfc == NULL)
		return -1;

	unsigned char head = pNfc->Data[index++];
	pNdef->Head.MB = (head >> 7) & 0x01;
	pNdef->Head.ME = (head >> 6) & 0x01;
	pNdef->Head.CF = (head >> 5) & 0x01;
	pNdef->Head.SR = (head >> 4) & 0x01;
	pNdef->Head.IL = (head >> 3) & 0x01;
	pNdef->Head.TNF = head & 0x03;

	printf("MB:%d ME:%d CF:%d SR:%d IL:%d TNF:%d \r\n", pNdef->Head.MB,pNdef->Head.ME,pNdef->Head.CF,pNdef->Head.SR,pNdef->Head.IL,pNdef->Head.TNF);

	// 当前不解析长度大于255个字节的NDEF包
	if(pNdef->Head.MB != 1 && pNdef->Head.ME != 1 && pNdef->Head.SR != 1) {
		pNfc->Len = 0;
		return -1;
	}

	pNdef->TnfTypeLen = pNfc->Data[index++];
	pNdef->PayloadLen = pNfc->Data[index++];

	if(pNdef->Head.IL == 1)
	{
		pNdef->IdLen = pNfc->Data[index++];
	}

	pNdef->RtdType = pNfc->Data[index++];

	switch(pNdef->Head.TNF)
	{
		case TNF_INSIDE_RTD:
		{
			if(pNdef->Head.IL == 1) {
				
				for(i = 0; i < pNdef->IdLen; i++) {
					pNdef->Id[i] = pNfc->Data[index++];
				}
				
			}

			if(pNdef->RtdType == RTD_UTF8) 
			{
				unsigned lgLen = 0;
				pNdef->StatusCode = pNfc->Data[index++];

				lgLen = (pNdef->StatusCode & 0x1F);

				// 跳过语言代码长度
				index += lgLen;

				for(i = 0; i < pNdef->PayloadLen; i++)
					pNdef->Payload[i] = pNfc->Data[index++];

				printf("%s\r\n", pNdef->Payload);
			} 
			else if(pNdef->RtdType == RTD_URI)
			{
				pNdef->StatusCode = pNfc->Data[index++];
				for(i = 0; i < pNdef->PayloadLen; i++)
					pNdef->Payload[i] = pNfc->Data[index++];

				printf("%s%s\r\n", pUirVal[pNdef->StatusCode], pNdef->Payload);
			}
			
			pNfc->Len = 0;
			break;
		}

		case TNF_NULL:
		case TNF_RFC3986:
		case TNF_RFC2406:
		case TNF_OUTSIDE_RTD:
		case TNF_UNKNOW:
		case TNF_STATIC:
		case TNF_DEFAULT:
		default:
			break;
	}

	return 0;
	
}

3 、测试main.c

int main(int argc, char *argv[])
{
    int ret;
	unsigned int i = 0;
	unsigned int plen = 0;
	
	NdefFrame NdefInfo = {0};
	NfcData_t SendData = {0};
    NdefFrame NdefPrase = {0};
	
	NdefInfo.Head.MB = 1;
	NdefInfo.Head.ME = 1;
	NdefInfo.Head.CF = 0;
	NdefInfo.Head.SR = 1;
	NdefInfo.Head.IL = 0;
	NdefInfo.Head.TNF = 1;

	NdefInfo.TnfTypeLen = 1;

    // abc123456
	NdefInfo.RtdType = RTD_UTF8;
	NdefInfo.StatusCode = 0x02;
	NdefInfo.LgCode[0] = 0x7a;
	NdefInfo.LgCode[1] = 0x68;
	NdefInfo.Payload[NdefInfo.PayloadLen++] = 'a';
	NdefInfo.Payload[NdefInfo.PayloadLen++] = 'b';
	NdefInfo.Payload[NdefInfo.PayloadLen++] = 'c';
	memcpy(&NdefInfo.Payload[NdefInfo.PayloadLen], "123456", strlen("123456"));
	NdefInfo.PayloadLen += strlen("123456");
	
    ret = nfc_ndef_package(&NdefInfo, &SendData);
	if(ret != 0) {
		printf("=========nfc_ndef_packge error ========\r\n");
		return -1;
	}

	printf("send data len: %d \r\n", SendData.Len);
	for(i = 0; i < SendData.Len; i++)
	{
		printf("0x%02x,", SendData.Data[i]);
	}
	printf("\r\n");
    
    ret = nfc_ndef_prase(&SendData, &NdefPrase);
    if(ret != 0) {
        printf( "nfc_ndef_prase err \r\n");
    }

    // https://www.baidu.com
	NdefInfo.PayloadLen = 0;
	NdefInfo.RtdType = RTD_URI;
	NdefInfo.StatusCode = 0x02;
	memcpy(&NdefInfo.Payload[NdefInfo.PayloadLen], "baidu.com/", strlen("baidu.com/"));
	NdefInfo.PayloadLen += strlen("baidu.com/");

    ret = nfc_ndef_package(&NdefInfo, &SendData);
	if(ret != 0) {
		printf("=========nfc_ndef_packge error ========\r\n");
		return -1;
	}

    printf("send data len: %d \r\n", SendData.Len);
	for(i = 0; i < SendData.Len; i++)
	{
		printf("0x%02x,", SendData.Data[i]);
	}
	printf("\r\n");

    memset(&NdefPrase, 0, sizeof(NdefPrase));
    ret = nfc_ndef_prase(&SendData, &NdefPrase);
    if(ret != 0) {
        printf("nfc_ndef_prase err \r\n");
    }

    return 0;
}

4、编译 及 运行结果

gcc main.c ndef.c

./a.out

send data len: 19
0xd1,0x01,0x0c,0x54,0x02,0x7a,0x68,0x61,0x62,0x63,0x31,0x32,0x33,0x34,0x35,0x36,0x00,0x00,0x00,
MB:1 ME:1 CF:0 SR:1 IL:0 TNF:1
abc123456
send data len: 16
0xd1,0x01,0x0b,0x55,0x02,0x62,0x61,0x69,0x64,0x75,0x2e,0x63,0x6f,0x6d,0x2f,0x00,
MB:1 ME:1 CF:0 SR:1 IL:0 TNF:1
https://www.baidu.com/

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐