引言
非常高兴能够参加本次Funpack活动!在这次作业中,我将使用nRF7002DK,实现一个带有按键切换功能的NFC卡片,并通过手机读取NFC卡片实现对应功能。在这里就整个活动过程中的收获、感悟,做一点微不足道的分享,还望大家多多批评指正。
开发板介绍
首先简单介绍一下今天的主角,nRF7002-DK开发板。
在硬件方面,nRF7002-DK开发板包含了nRF5340、nRF7002两颗芯片。 其中,nRF5340是一款 all-in-one SoC器件,其嵌入了一个 128MHz 的 Arm Cortex-M33 应用处理器和一个 64MHz 的高效率网络处理器,支持包括低功耗蓝牙、Thread、Zigbee和802.15.4在内的多种无线协议,同时也支持NFC。 而nRF7002 Wi-Fi 6协同IC是一款高集成度IC,用于将Wi-Fi 6连接功能集成到各种应用中。 这款IC将最新的Wi-Fi 6技术添加到产品中,实现最新Wi-Fi标准可提供的超快速率、更广 的覆盖范围和更高的可靠性,从而提供了具有高性价比的低功耗解决方案。
nRF7002-DK开发板提开发板提供了多种外设和接口,最常用的包括Ardurin连接器、两颗可编程按键以及两颗LED。 更重要的是,nRF7002-DK开发板板载了J-Link,开发时仅通过一条线即可实现对板卡的供电、烧写、调试,可以说是在单一的电路板上包含了开发工作所需的一切。唯一美中不足的是,都三二零二年了,竟然还在使用micro-USB接口。
在软件方面,nRF7002-DK开发板使用了nRF Connect SDK作为软件开发套件,该套件集成了Zephyr RTOS、协议栈、应用示例和硬件驱动程序。nRF Connect SDK自带了工具链管理工具,配合官方提供的VS-code插件,可以高效、畅快的进行开发工作。
项目需求
本项目中,我们以活动方给出的——“任务三: 使用板卡的NFC功能,模拟出一个自定义功能的卡片,使用手机靠近并能读取卡片信息”这个题目为基础,实现一个可以通过板载按键进行切换的多功能NFC卡片,并通过板载LED灯来显示卡片状态。具体可以分为四条:
- 利用nRF7002模拟一个NFC卡片,并通过按键在多张卡片中进行切换
- 功能选择分为两层,通过Button 1(代码中实际对应sw0)实现功能层的切换
- 每层分为两个功能,通过Button 2(代码中实际对应sw1)实现层内功能的切换
- 通过两枚LED反馈功能层、功能以及NFC的状态
对应功能与LED表示如下
功能1 (LED2常亮) |
功能2 (LED2常亮) |
|
功能层1 (LED1在NFC靠近时常量) |
传输“This is nRF7002DK”消息 | 连接WIFI |
功能层2 (LED1在NFC靠近时熄灭) |
打开Funpack活动页面 | 打开播放器,播放歌曲 |
功能展示
项目实现具体功能展示可详见项目视频 ~ !
程序设计
- 学习路线
抛开物理层面的相关内容,NFC和常见的TCP、HTTP等协议一样,有着对应的报文标准,称为NFC Data Exchange Format,即NDEF。通过装载不同的报文,即可实现对应的NFC功能。 因此项目实现的主要难点在于NDEF报文协议的获取与验证上(事后也证明了整个项目最难的点就在于理解NDEF协议)。其余如按键切换等功能,则需要通过学习样例程序,以快速上手SDK。
- 程序逻辑
1.main函数
main函数在本项目中充当了最强工具人的角色,负责将所有硬件初始化,并把对应的回调函数注册到系统中。
int main(void) {
printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD,
SystemCoreClock / MHZ(1));
if (funpack_init_nfc()) {
printk("NFC init failed!\n");
sys_reboot(SYS_REBOOT_COLD);
} else {
printk("NFC init done!\n");
}
if (funpack_init_buttons()) {
printk("Buttons init failed!\n");
sys_reboot(SYS_REBOOT_COLD);
} else {
printk("Buttons init done!\n");
}
printk("App init done!\n");
return 0;
}
2.按键回调函数
按键回调函数是本次项目的主力函数,主要的功能切换均由按键回调处理。其主要逻辑是,先切换系统状态,随后根据系统状态切换相应的功能和LED显示。下面展示了按键1的回调函数:
static void button_1_callback(const struct device *dev,
struct gpio_callback *cb, uint32_t pins) {
if (k_uptime_delta(&last_button_time) < BUTTON_MIN_TIME) {
return;
}
printk("Button 1 pressed at %" PRIu32 "\n", k_cycle_get_32());
button_1_status = !button_1_status;
funpack_update_nfc();
funpack_update_led();
last_button_time = k_uptime_get();
printk("Button 1 callback done\n");
}
需要注意的是,这并不是一种优雅的回调函数写法,特别是对系统调度规则不够了解、NFC功能切换耗时不明确、zephyr内置mutex不支持在中断下调用这些因素的加持下,使用上述方法实现功能切换很可能引入中断函数重入的问题,在实际使用时需要谨慎对待。不过按键等待时间的特性,在一定程度上缓解了这个问题,这个后文会提到。
回到按键回调函数。其中funpack_update_nfc()和funpack_update_led()两个函数负责具体的根据状态变量进行切换。以funpack_update_nfc()为例:
int funpack_update_nfc() {
int err;
size_t len = sizeof(ndef_msg_buf);
nfc_t2t_emulation_stop();
if (button_1_status == false && button_2_status == false) {
// share text
err = nfc_text_msg_encode(
nfc_text_string, sizeof(nfc_text_string) - 1, nfc_text_lang_code,
sizeof(nfc_text_lang_code) - 1, ndef_msg_buf, &len);
printk("Encoding text\n");
} else if (button_1_status == false && button_2_status == true) {
// share wifi
err = nfc_wifi_msg_encode(nfc_wifi_ssid, sizeof(nfc_wifi_ssid),
nfc_wifi_password, sizeof(nfc_wifi_password),
ndef_msg_buf, &len);
printk("Encoding wifi\n");
} else if (button_1_status == true && button_2_status == false) {
// share url
err = nfc_ndef_uri_msg_encode(NFC_URI_HTTPS_WWW, nfc_web_url_link,
sizeof(nfc_web_url_link) - 1,
ndef_msg_buf, &len);
printk("Encoding URL\n");
} else {
// share music
err = nfc_ndef_uri_msg_encode(NFC_URI_NONE, nfc_app_url_link,
sizeof(nfc_app_url_link) - 1,
ndef_msg_buf, &len);
printk("Encoding music\n");
}
if (err) {
printk("Cannot encode payload with code %d!\n", err);
return err;
}
err = nfc_t2t_payload_set(ndef_msg_buf, len);
if (err) {
printk("Cannot set payload with code %d!\n", err);
return err;
}
nfc_t2t_emulation_start();
return 0;
}
可以看出,其核心部分在于NFC报文的编码之上,而其他功能开关、报文装载等均为通用步骤。实际上,针对当前的功能需求来说,此处存在一个可以改进耗时的点:由于所有报文实际为写死在代码中的,因此可以在系统初始化时全部编码好(预编码),这样在进行功能切换时可以节省时间。(对于通过mqtt等方式在线拉取信息生成报文的,可以采用本项目中的实时编码方法)
3.NFC回调函数
NFC回调逻辑更为简单,仅涉及根据状态切换LED一个功能。这里不做展示。
- 一些细节
1.防止按键连按
由于物理按键在开关时无法像电路一样干净利落,按下过程中的轻微力道变化可能导致实际电路发生通断,进而触发按键操作。具体表现为,你轻轻的按下了按键,结果实际按键实际触发了多次。这种情况在服役时间较久的机械键键盘上经常发生(G613用户落泪)。对于扫描式的键盘来说,这通常与键盘的扫描频率、滤波算法有关,而对于GPIO这种被动案件,这里的做法是人为的增减按键等待时间。这便是上面按键回调函数最前面会对last_button_time做判断的原因。通过该变量限制按键回调最快每500ms执行一次,对于本项目的场景来说可以解决问题
2.WIFI msg encode
由于SDK没有给出WIFI报文的编码接口,这里手撸了一个WIFI报文的编码函数,即nfc_wifi_msg_encode。由于代码较长,这里不做展示,需要的可以去附件的源代码中获取。另外需要注意一点,IOS设备目前并不支持该类型的MIME Media Record报文,因此iPhone用户还无法使用NFC标签连接WIFI。
结语
嵌入式/IoT 一直是我比较感兴趣的一个方向,小小的芯片、板卡能够能够融入生活的方方面面,产生了不可估量的作用。作为一个快要步入社会的理工男,希望以后能够由更多机会接触这样的活动,愿活动越办越好!