一、项目介绍
本次funpack第二季第六期使用的板卡是来自nordic半导体的nRF7002 DK,可用于nRF7002 Wi-Fi 6双频辅助IC的开发套件,采用nRF5340多协议ic作为主处理器,配合nRF7002 Wi-Fi协同芯片,可以同时支持低功耗蓝牙和Wi-Fi 应用的开发,并实现如 OFDMA、波束成形和目标唤醒时间等多项 Wi-Fi 6 功能。
本项目使用板上的nRF5340实现一个自定义NFC卡片的功能,nRF5340是支持低功耗蓝牙、蓝牙Mesh、NFC、Thread和Zigbee的双核蓝牙5.3 SoC,非常适合用来开发无线连接应用。
提到NFC,大家肯定都不陌生,这几年越来越多的手机和智能手环上都加入了NFC功能。NFC即近距离无线通讯技术(Near Field Communication),可以在两个设备之间进行无线短距离传输通信。目前在智能手机上的NFC应用大致可以分为接触通过、接触支付、接触浏览、接触连接、下载接触,应用模式分为卡模式、读卡器式、点对点式。
本项目将采用读卡器式应用模式,即具备读写功能的NFC手机只能从tag中采集数据,不能向tag中写入数据(目前NFC中的Type 2 Tag协议支持读写功能,但是nordic官方提供参考的Type 2 Tag库只支持读功能,不支持外部设备写入的功能)。当具备NFC功能的手机设备接触到板卡上的tag,可以从该tag中获取到当前商品的出厂日期、官网、活动名称等信息。
二、设计思路
参考nordic官方提供的Libraries for NFC,其中包括NFC Data Exchange Format(NDEF)、Type 2 Tag、Type 4 Tag、Tag NDEF Exchange Protocol(TNEP)四个库,本项目主要使用NDEF和Type 2 Tag两个库,其中NDEF用于将需要传输的数据转换成NFC通讯时用到的NDEF格式数据,使用Type 2 Tag完成使能NFC Tag并配置NFC Tag。
根据nordic提供的Type 2 Tag库使用说明,编程一个tag,并使其存储有对应的信息主要分为三步:1.声明一个回调函数并且处理从Type 2 Tag库内部产生的事件,并调用
nfc_t2t_steup(nfc_callback,NULL)
2.配置tag中保存的数据,可以使用NDEF库直接将原始数据编码成NDEF格式的数据,也可以设置一个TLV格式的原始数据,推荐直接使用NDEF库,方便省事不用深入理解协议。
nfc_t2t_payload_set(ndef_msg_buf,len)
3.激活启动NFC tag,此时tag开始感应附近(<10cm)是否有其他的NFC域,如果有响应它,并且将tag中的数据发送到对应的NFC设备上。
nfc_t2t_emulation_start()
三、硬件介绍
关于NFC部分的硬件原理图部分比较简单,关键在于如何进行PCB布线,以及tag的画法,这些最终会实际影响到NFC识别的灵敏度等。
关于板卡上NFC部分的硬件电路如下
nRF5340的NFC功能引脚NFC1、NFC2分别通过两个0Ω电阻和300pF的匹配电容连接到NFC天线的连接器上,在这个连接器上插上对应的天线即可
四、软件介绍
4.1 NFC文本标签
结合nordic提供的Type 2 Tag库使用说明以及官方例程,可以很轻松实现一个NFC卡片。具体代码如下
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:
dk_set_led_on(NFC_FIELD_LED);
printk("NFC field is exist!\n");
break;
case NFC_T2T_EVENT_FIELD_OFF:
dk_set_led_off(NFC_FIELD_LED);
printk("NFC field doesn't exist!\n");
break;
default:
break;
}
}
通过nrf_callback判断当前的事件为什么,当有NFC域存在时,触发打印print;当NFC域离开时,触发打印print。关于nrf_callback返回的事件分别有
static int nrf5340_msg_encode(uint8_t *buffer, uint32_t *len)
{
int err;
/* Create NFC NDEF text record description in English */
NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_eetree_text_rec,
UTF_8,
en_code,
sizeof(en_code),
eetree_payload,
sizeof(eetree_payload));
/* Create NFC NDEF text record description in Norwegian */
NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_funpack_text_rec,
UTF_8,
en_code,
sizeof(en_code),
funpack_payload,
sizeof(funpack_payload));
/* Create NFC NDEF text record description in Polish */
NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_board_text_rec,
UTF_8,
en_code,
sizeof(en_code),
board_payload,
sizeof(board_payload));
/* Create NFC NDEF message description, capacity - MAX_REC_COUNT
* records
*/
NFC_NDEF_MSG_DEF(nfc_text_msg, MAX_REC_COUNT);
/* Add text records to NDEF text message */
err = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg),
&NFC_NDEF_TEXT_RECORD_DESC(nfc_eetree_text_rec));
if (err < 0) {
printk("Cannot add first record!\n");
return err;
}
err = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg),
&NFC_NDEF_TEXT_RECORD_DESC(nfc_funpack_text_rec));
if (err < 0) {
printk("Cannot add second record!\n");
return err;
}
err = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg),
&NFC_NDEF_TEXT_RECORD_DESC(nfc_board_text_rec));
if (err < 0) {
printk("Cannot add third record!\n");
return err;
}
err = nfc_ndef_msg_encode(&NFC_NDEF_MSG(nfc_text_msg),
buffer,
len);
if (err < 0) {
printk("Cannot encode message!\n");
}
return err;
}
使用nrf5340_msg_encode对tag中保存的信息编码为NDEF数据
int main(void)
{
uint32_t len = sizeof(ndef_msg_buf);
printk("Starting NFC Text Record example\n");
/* Configure LED-pins as outputs */
if (dk_leds_init() < 0) {
printk("Cannot init LEDs!\n");
goto fail;
}
/* Set up NFC */
if (nfc_t2t_setup(nfc_callback, NULL) < 0) {
printk("Cannot setup NFC T2T library!\n");
goto fail;
}
if (nrf5340_msg_encode(ndef_msg_buf, &len) < 0) {
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");
return 0;
fail:
#if CONFIG_REBOOT
sys_reboot(SYS_REBOOT_COLD);
#endif /* CONFIG_REBOOT */
return -EIO;
}
main函数中依次进行使能NFC——>将要保存的tag信息编码成NDEF格式——>将NDEF数据填充到payload里面——>开始感应NFC域。当调用nfc_t2t_emulation_start后,当感应到NFC域后,库内部自己将编码后的NDEF数据发送给对应的NFC手机端。
4.2 NFC拨打紧急电话(紧急联系人可自定义)
根据官方的NFC Launch app例程进行修改,首先将手机上的安卓应用名字找到,放置到android_pkg_name_tel
中,然后将对应的紧急联系人电话添加到m_url_tel
中,main函数中通过nfc_launchapp_msg_encode
函数将nfc_launchapp_msg_encode
和nfc_launchapp_msg_encode
编码为NDEF格式的数据。
对于nfc_launchapp_msg_encode
函数,首先创建两个记录描述符,一个用来保存nfc_launchapp_msg_encode
,一个用来保存m_url_tel
,然后判断传入的实参是否为1,可以对应url和android_pkg_name都有,也可以二者中包含任意一个。实参为1之后,调用nfc_ndef_msg_record_add
将record添加到msg中,最后调用nfc_ndef_msg_encode
进行编码,将msg编码为NDEF数据。
关于如何要手机将url的信息识别为电话号码,关键需要在创建uri记录描述符的宏中将URI 记录类型修改为NFC_URI_TEL
。
代码片段如下:
static const uint8_t android_pkg_name_tel[] = {
'c', 'o', 'm', '.', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '.', 'p', 'h', 'o', 'n', 'e'};
static const uint8_t m_url_tel[] = {'1','2','3','1','5'};
NFC_NDEF_MSG_DEF(nfc_launchapp_msg, 2);
/* Create NFC NDEF Android Application Record description */
NFC_NDEF_ANDROID_LAUNCHAPP_RECORD_DESC_DEF(nfc_and_launchapp_rec,
android_package_name,
android_package_name_len);
/* Create NFC NDEF URI Record description */
NFC_NDEF_URI_RECORD_DESC_DEF(nrf_universal_link_rec,
NFC_URI_TEL,
universal_link,
universal_link_len);
4.3 NFC启动高德地图
需要更改的地方与4.2基本相同,但是这里在高德开放平台上找到一个URI,将其添加到程序中对应m_url_map
里
代码片段如下:
static const uint8_t android_pkg_name_map[] = {
'c', 'o', 'm', '.', 'a', 'u', 't', 'o', 'n', 'a', 'v', 'i', '.', 'm', 'i', 'n', 'i','m','a','p'};
static const uint8_t m_url_map[] = "androidamap://navi?sourceApplication=amap&poiname=fangheng&lat=36.547901&lon=104.258354&dev=1&style=2";
err = nfc_launchapp_msg_encode(android_pkg_name_map,
sizeof(android_pkg_name_map),
m_url_map,
sizeof(m_url_map),
m_ndef_msg_buf,
&len);
五、功能展示
功能展示 4.1
板卡复位后,通过手机端的NFC功能可以读取出板上tag中保留的三条record信息
同时,当手机靠近板上的tag,串口也会有对应的信息输出。
功能展示 4.2
这里不知道为什么,明明添加的是电话的pkg_name,靠近nfc tag时,总是会唤醒手机上的google商店,很奇怪。先把pkg_name设为NULL,只使用URI实现。效果如下:
如果没有将URI类型设置为NFC_URI_TEL
,手机只会把“12315”识别为文本。
功能展示 4.3
靠近nfc tag会启动手机上的高德地图APP,并跳转到导航页面。
六、心得体会
非常感谢硬禾和得捷提供的大厂板卡学习(白嫖)机会,这块板子的可玩性非常多,特别是关于zephyr的应用,基本上大部分例程都用到了,有时候多看些大厂提供的代码示例也会开拓许多思维。这次应该不是股东了!不过这些大厂的板卡有时候搭建环境很麻烦,windows下从github拉源码总是拉取不完,一直编译失败(已经开启科学上网了),换到linux下拉源码就很顺利。