Funpack2-6,基于nRF7002-DK的自定义nfc卡片
Funpack活动第二季第6期任务三。实现了自定义nfc卡片,手机触碰标签实现打开应用或显示文字。
标签
Funpack活动
NFC
Nordic
nRF7002-DK
Reast
更新2023-10-11
哈尔滨工程大学
801

项目介绍

本项目基于Nordic公司的nRF7002-DK板卡,即nRF7002 Wi-Fi 6协同IC的开发评估板,采用nRF5340多协议SoC作为主处理器,配合nRF7002 Wi-Fi协同芯片。可以同时支持低功耗蓝牙和Wi-Fi 应用的开发,并实现如 OFDMA、波束成形和目标唤醒时间等多项 Wi-Fi 6 功能。

本项目主要使用了板卡的NFC功能,实现了手机靠近nfc标签即可打开软件和读取卡片信息。

设计思路

首先,要实现NFC功能需要先简单了解一下什么是NFC。

NFC,全称是Near Field Communication,即“近场通信”,也叫“近距离无线通信”,是一种短距离高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输。有三种工作模式,即主动模式,被动模式,双向模式。简单来说,主动模式相当于是一个读卡器。被动模式则NFC设备被模拟成一张卡,只能被动响应。双向模式就是两个NFC终端都处于主动模式下。本项目中实现的就是被动模式。

那么NFC能用来干嘛呢?我们现在的手机基本都集成了NFC功能,平时可以用来移动支付,模拟门禁卡、公交卡等等。各个终端设备厂商也近期也开始推出了基于NFC的各种快捷连接功能,比如华为的超级终端功能就支持手机碰一碰连接电脑,碰一碰连接打印机等等。由于NFC通信的快速、安全的特性,这种“碰一碰”的方式极大地简化和缩短了设备间建立链接的步骤和时间。同时NFC协议的额外功能也可以实现一些快捷操作,比如碰一碰打开音乐,碰一碰读取数据等等。

接下来是手机识别NFC标签的实现原理和实现流程。

第一步,手机的NFC读卡器将会识别NFC标签中的信息,一般来说,NFC标签中的信息是以NFC数据交换格式(NDEF)的通用数据格式编码的。其由各种数据记录组成,而各个记录由报头(header)有效载荷(Payload)组成。其中NDEF记录的数据类型和大小由记录载荷的报头注明。

第二步,手机系统将会对NDEF信息进行解码。读取其中的报头和载荷。根据读取到的TNF标签,手机系统会在清单文件中寻找可以处理这个标签的Activity(关于Activity,我们可以大致理解为某个应用中的一个小功能)。举个例子,我们将设置TNF为0x04(NFC Forum external type),type为android.com:pkg,Payload为安卓应用的包名时,系统将会自动启动这个安卓应用。

补充一点细节,这里的INF标签有多中类型。一般来说,首先确定MIME 类型或 URI,然后将它和负载一起封装到Intent(运行时绑定(run-time binding)机制,可以应用于两个应用间的通讯交互,类似于应用间的桥梁),而安卓系统将会根据读取到的Intent来启动Activity。

最后是代码实现详解。

按照教程完成环境搭建并连接上板子以后,打开Nordic的NFC例程并编译烧录,等待烧录完成以后,如图所示,将下方的NFC标签插入到上方红框的槽内,使用手机触碰查看是否有效,如果弹出文本即环境搭建完成。

FtonZVlk92oWUjB-4vYZlTlTqEMe

通过例程,我们可以整理得到编写NFC的具体流程:

FkJYLSl8NXOGHwIOUl9nbRxcGBV6

以碰一碰打开程序为例:我们来分析例程代码:

首先是初始化NFC和设置NFC的回调函数。在这里,代码将前面设置好的callback函数传递给setup函数,即当NFC被读取和关闭时将调用callback。而如果调用失败将返回1,之后抛出异常。

err = nfc_t2t_setup(nfc_callback, NULL);
if (err) {
    printk("Cannot setup NFC T2T library!\n");
    goto fail;
}
接着是为NFC消息编码,在这个语句块中,代码将对想要打开的安卓包名和链接进行编码,并将编码储存到msg_buf数组中。
err = nfc_launchapp_msg_encode(android_pkg_name,  // 写入nfc标签编码
					sizeof(android_pkg_name),
					universal_link,
					sizeof(universal_link),
					ndef_msg_buf,
					&len);
if (err) {
	printk("Cannot encode message!\n");
	goto fail;
}
再然后是装载上方的msg_buf负载到NFC控制器。
err = nfc_t2t_payload_set(ndef_msg_buf, len);  //注册nfc读取负载
if (err) {
	printk("Cannot set payload!\n");
	goto fail;
}

最后启动nfc即可。由于封装完善,整体代码量非常小,只需要调用函数抛出异常即可。

代码实现

接下来就是我在本次项目中完成的工程代码。我在打开应用的例程的基础上添加了读取文字和连续触碰切换功能。也就是说,连续两次的NFC读取是不一样的。

首先要先改写打开的安卓包(这里选择打开的是美团):

static const uint8_t android_pkg_name[] = {
	'c','o','m','.','s','a','n','k','u','a','i','.','m','e','i','t','u','a','n' };

接着仿照读取编码的例程,设置需要读取的文字和编码方式:

static const uint8_t en_payload[] = {
	'R', 'e', 'a', 's', 't'
};
static const uint8_t en_code[] = {'e', 'n'};

而为了实现功能自动切换,我加入一个标志位作为两种功能的切换标志,并在回调函数处加入标志位的改变代码,并且加入延时防止改变的频率过快。以下就是改写后的回调函数,其中flag_case就是在0和1之间切换的标志位,同时保留了读取到NFC标签LED就会亮起的功能

static void nfc_callback(void *context,
			 nfc_t2t_event_t event,
			 const uint8_t *data,
			 size_t data_length)
{
	ARG_UNUSED(context);
	ARG_UNUSED(data);
	ARG_UNUSED(data_length);

	switch (event) {
	case NFC_T2T_EVENT_FIELD_ON:
		flag_case = 1 - flag_case;  // 使flag_case在0和1之间变化
		dk_set_led_on(NFC_FIELD_LED);
		k_sleep(K_MSEC(200));  //加入适当的延时,防止出现触碰不断切换卡死的bug
		break;
	case NFC_T2T_EVENT_FIELD_OFF:
		dk_set_led_off(NFC_FIELD_LED);
		break;
	default:
		break;
	}
}

 然后在main函数中加入死循环和分支语句。这里想要实现NFC功能的切换需要先停止NFC,否则后续的编码和装载会出错,同时,我在分支语句后面加入了变化检测。以使程序只有在flag_case变化之后才会刷新功能。

while (1)
{
	if(flag_case == 1)
	{
		err = nfc_t2t_emulation_stop();  // 停止nfc
		if (err) {
			printk("Cannot stop emulation!\n");
			goto fail;
		}

		printk("case = 1\n");
		/* Encode launch app data  */
		err = nfc_launchapp_msg_encode(android_pkg_name,  // 写入打开应用nfc标签编码
						sizeof(android_pkg_name),
						universal_link,
						sizeof(universal_link),
						ndef_msg_buf,
						&len);
		if (err) {
			printk("Cannot encode message!\n");
			goto fail;
		}

		/* Set created message as the NFC payload */
		err = nfc_t2t_payload_set(ndef_msg_buf, len);  //注册nfc读取负载
		if (err) {
			printk("Cannot set payload!\n");
			goto fail;
		}

		/* Start sensing NFC field */
		err = nfc_t2t_emulation_start();  // 激活nfc
		if (err) {
			printk("Cannot start emulation!\n");
			goto fail;
		}
		printk("NFC configuration done\n");
		while(flag_case == 1)   // 只有检测到flag_case变化时才会跳出该分支
		{printk("flag_case=1\n");}
	}else
	{
		err = nfc_t2t_emulation_stop();  // 停止nfc
		if (err) {
			printk("Cannot stop emulation!\n");
			goto fail;
		}

		printk("case = 0\n");
		/* Encode welcome message */
		if (welcome_msg_encode(ndef_msg_buf, &len) < 0) {  // 写入存储信息的NFC编码
			printk("Cannot encode message!\n");
			goto fail;
		}


		/* Set created message as the NFC payload */
		if (nfc_t2t_payload_set(ndef_msg_buf, len) < 0) {
			printk("Cannot set payload!\n");
			goto fail;
		}


		/* Start sensing NFC field */
		if (nfc_t2t_emulation_start() < 0) {
			printk("Cannot start emulation!\n");
			goto fail;
		}
		printk("NFC configuration done\n");
		while(flag_case == 0)   // 只有检测到flag_case变化时才会跳出该分支
		{printk("flag_case=0\n");}
	}
	k_sleep(K_MSEC(2000));  // 加入适当的延时防止刷新过快无法读取
}

通过标志位的改变来实现不同功能之间的切换,死循环和延时函数保证程序可以持续运行并且不会卡死。这样整个工程就完成了。

功能展示

虽然是华为鸿蒙系统,但是还是兼容了安卓的NFC功能。

初始化板卡时第一次触碰为打开美团app(点外卖快人一步!):

FhQBniMzEJOVSaEMNve989M2CNYR

当第二次触碰时将变为显示文字:

FphG5q6KzuBIKyTWBl1AIf3C7m99

详细功能演示见视频。

心得体会

  • 这是我第一次从零开始开发一块完全陌生且教程较少的板卡。虽然只是实现了简单的功能,但是极大地提升了我查阅官方文档和熟悉陌生的开发环境的能力。
  • 本次活动提升了我对于嵌入式系统的理解,让我了解到了wifi,蓝牙,mqtt,NFC等相关技术(虽然最后只做了NFC,之后有机会还会再尝试一下)。

最后,感谢硬禾举办这次活动。

附件下载
record_launch_app.zip
团队介绍
苦哈哈的电子破防人
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号