一.项目介绍
本项目使用Funpack2-6活动的开发板,来自Nordic的nRF7002DK。
本项目制作的是活动预设的任务三:“使用板卡的NFC功能,模拟出一个自定义功能的卡片,使用手机靠近并能读取卡片信息”。
我在该题目的基础上增加了按键控制NFC切换卡片、启动停止NFC的功能。
板卡上电默认处于NFC文本模式且关闭NFC使能。button1用于切换NFC工作模式(文本、打开B站个人主页),button2用于启动/停止NFC工作,LED1用于指示NFC是否正在通讯,LED2用于指示NFC是否正在工作
二.设计思路
按照活动任务三的要求,板卡上电后用手机靠近,能读取到NFC信息即为完成。
那么首先第一步就是需要搭建开发环境,我参考了Nordic的官方文档(https://www.nordicsemi.cn/tools/nrfconnectsdk/)、硬禾学堂的直播课程(https://class.eetree.cn/live_pc/l_647d47a9e4b0cf39e6d3d397)以及网上其他人分享的教程
搭建完成开发环境后跑官方的NFC Demo,一共有7个,官方SDK也有这些demo的说明(https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.4.1/nrf/samples/nfc.html),让我可以更快的熟悉Demo,跑完所有Demo后就开始阅读代码和各个函数的说明,理解其中的运行逻辑和函数各个参数的含义。
其实运行完Demo就基本可以完成任务要求了,但是我还是希望在当前板卡硬件的基础上增加一些内容,于是我使用了板卡自带的2个button和LED灯。用于实现NFC切换卡片、启动停止NFC、LED指示NFC是否处于运行状态。
现在我需要做的就是学习一下如何在现有工程中如何添加GPIO以及GPIO相关函数如何使用。由于没有找到现成的demo,于是我把所有的历程代码都导入vscode,直接查询哪些demo使用了按键和LED,参考他们的代码。再使用到自己的工程中
三.代码解析
本项目的代码主要有以下几个文件main.c、nfc_openApp.c、nfc_openApp.h、nfc_text.c、nfc_text.h。其中main.c存放main函数和切换NFC、启停NFC的代码。我将NFC的具体业务放到了nfc_xxx中。
首先 我们来看一下main函数,流程图如下
int main(void)
{
	int ret = -1;
	
	/* 初始化LED灯 */
	if (dk_leds_init() < 0) {
		printk("Cannot init LEDs!\n");
		goto fail;
	}
	/* 初始化按钮 */
	if (dk_buttons_init(button_handler)) {
		printk("Cannot init buttons\n");
		goto fail;
	}
	return 0;
fail:
#if CONFIG_REBOOT
	sys_reboot(SYS_REBOOT_COLD);
#endif /* CONFIG_REBOOT */
	return -EIO;
}mian对LED和button做了初始化,如果任一初始化失败,系统将重启。在初始化button时注册了回调函数button_handler
static void button_handler(uint32_t button_state, uint32_t has_changed)
{
	int ret = -1;
	uint32_t button = button_state & has_changed;
	if (button & DK_BTN1_MSK) 
	{
		if (nfcMode == NFC_MODE_TEXT)
		{
			printk("switch nfc mode to open app\r\n");
			nfcMode = NFC_MODE_OPEN_APP;
		}
		else
		{
			printk("switch nfc mode to text\r\n");
			nfcMode = NFC_MODE_TEXT;
		}
	}
	if (button & DK_BTN2_MSK) 
	{
		if (nfcWorkState == true)
		{
			
			ret = nfc_t2t_emulation_stop();
			if (ret != 0)
			{
				printk("nfc_t2t_emulation_stop ret = %d\r\n", ret);
				return;
			}
			ret = nfc_t2t_done();
			if (ret != 0)
			{
				printk("nfc_t2t_done ret = %d\r\n", ret);
				return;
			}
			printk("nfc deinit success\r\n");
			nfcWorkState = false;
			dk_set_led_off(DK_LED2);
		}
		else
		{
			switch (nfcMode)
			{
			case NFC_MODE_TEXT:
				/* 设置NFC为一个文本 */
				ret = set_nfc_as_text_record();
				if (ret == -1)
				{
					printk("set_nfc_as_text_record fail\r\n");
				}
				break;
			
			case NFC_MODE_OPEN_APP:
				/* 设置NFC为打开一个APP */
				ret = set_nfc_as_open_app();
				if (ret == -1)
				{
					printk("set_nfc_as_open_app fail\r\n");
				}
				break;
			}
			printk("nfc start work\r\n");
			nfcWorkState = true;
			dk_set_led_on(DK_LED2);
		}
	}
}/**
 * @brief		设置NFC为一个文本
 * @param[in]	none
 * @return      err	0:正常;-1:异常
 */
int set_nfc_as_text_record()
{
	uint32_t len = sizeof(temp_ndef_msg_buffer);
	/* 设置NFC为T2T,注册回调 */
	if (nfc_t2t_setup(nfc_callback, NULL) < 0) {
		printk("Cannot setup NFC T2T library!\n");
		return -1;
	}
	/* 根据文本信息,组装成NDEF格式数据 */
	if (text_msg_encode(temp_ndef_msg_buffer, &len) < 0) {
		printk("Cannot encode message!\n");
		return -1;
	}
	/* 将组装好的NEDF数据载入NFC payload */
	if (nfc_t2t_payload_set(temp_ndef_msg_buffer, len) < 0) {
		printk("Cannot set payload!\n");
		return -1;
	}
	/* 启动NFC */
	if (nfc_t2t_emulation_start() < 0) {
		printk("Cannot start emulation!\n");
		return -1;
	}
	printk("NFC configuration done\n");
	return 0;
}设置一个NFC大致分为以下几步,
1.初始化NFC模式,我这边使用的T2T,还有T4T等,他们有什么区别我这边就不赘述了,有兴趣的可以上网学习一下。
2.组装NDEF数据,什么是NDEF数据,有兴趣的也可以去自学一下,不过不理解也没关系,官方SDK都有对应的函数,我们可以参考demo直接使用
3.将刚才组装好的数据加载到NFC
4.启动NFC
在初始化NFC模式时,还要注册一个回调函数nfc_callback,当NFC有事件时可以进入回调函数
/**
 * @brief		NFC回调函数
 * @param[in]	context:回调执行的应用程序上下文
 * @param[in]	event: 是什么事件触发的回调
 * @param[in]	data: 要发送到app的数据(特定于事件)
 * @param[in]	data_length: data的长度
 * @return      err	0:正常;-1:异常
 */
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) {
	/* 检测到有外部NFC */
	case NFC_T2T_EVENT_FIELD_ON:
		/* 点亮LED1 */
		dk_set_led_on(DK_LED1);
		break;
	/* 检测到外部NFC已移除 */
	case NFC_T2T_EVENT_FIELD_OFF:
		/* 熄灭LED1 */
		dk_set_led_off(DK_LED1);
		break;
	default:
		break;
	}
}这里的回调函数我没有修改 ,直接复用demo的,实现的功能是当有NFC设备于开发板通讯时,LED1亮起,结束通讯时,LED1熄灭
组装一个文本NDEF函数如下
/**
 * @brief		组装文本数据为NDEF数据
 * @param[out]	buffer:存放组装好的NDEF数据
 * @param[out]	len:组装好的NDEF数据的长度
 * @return      err	0:正常;-1:异常
 */
static int text_msg_encode(uint8_t *buffer, uint32_t *len)
{
	int err;
	/* 创建一个 NFC NDEF 文本 */
	NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_text_rec,
				      UTF_8,
				      text_code,
				      sizeof(text_code),
				      text_payload,
				      sizeof(text_payload));
	/* 创建 NFC NDEF 描述 */
	NFC_NDEF_MSG_DEF(nfc_text_msg, MAX_REC_COUNT);
	/* 新增一条文本信息到NFC NDEF信息中 */
	err = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg),
				   &NFC_NDEF_TEXT_RECORD_DESC(nfc_text_rec));
	if (err < 0) {
		printk("Cannot add first 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;
}这里的函数基本参考demo中的代码,将一条文本信息组装成NDEF数据
如何组装打开APP的NDEF数据我就不再赘述了,见附件代码即可,大同小异
四.功能展示
LED2亮起,指示NFC正在工作中
手机扫描FNC读取文本信息
停止NFC,切换NFC为“打开B站个人主页”模式
手机扫描NFC,打开B站个人主页
五.心得体会
这是我第一次学习使用nordic的新SDK,之前在52832上学习BLE时还是用的Keil环境,也有卖家写好的全套教程,按部就班的学。这次活动让我自己摸索,看官方文档,找其他人的分享,自己一步一步,从环境搭建,到编译成功,到实现功能,其中踩了很多坑,也学习到了很多新的东西,这一个多月的自学时光,让我收获颇丰。非常感谢硬禾学堂举办的funpack活动,让大家免费玩到各种各样的开发板。
