【Funpack2-6】使用nRF7002-DK实现一个多功能NFC卡片
本项目中,我们以活动方给出的——“任务三: 使用板卡的NFC功能,模拟出一个自定义功能的卡片,使用手机靠近并能读取卡片信息”这个题目为基础,实现一个可以通过板载按键进行切换的多功能NFC卡片,并通过板载LED灯来显示卡片状态。
标签
嵌入式系统
Funpack活动
开发板
Jacobian今天没有coredump
更新2023-10-16
484

引言

非常高兴能够参加本次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标准可提供的超快速率、更广 的覆盖范围和更高的可靠性,从而提供了具有高性价比的低功耗解决方案。

FhzsGCGr4-YNRX8WdkhgmVPzEXc2

nRF7002-DK开发板提开发板提供了多种外设和接口,最常用的包括Ardurin连接器、两颗可编程按键以及两颗LED。 更重要的是,nRF7002-DK开发板板载了J-Link,开发时仅通过一条线即可实现对板卡的供电、烧写、调试,可以说是在单一的电路板上包含了开发工作所需的一切。唯一美中不足的是,都三二零二年了,竟然还在使用micro-USB接口。

FodLPVHD9UE_1Xz-oMP74QundOEn

在软件方面,nRF7002-DK开发板使用了nRF Connect SDK作为软件开发套件,该套件集成了Zephyr RTOS、协议栈、应用示例和硬件驱动程序。nRF Connect SDK自带了工具链管理工具,配合官方提供的VS-code插件,可以高效、畅快的进行开发工作。

FgJM8Ja4jjwicr3z2v9iSOUHYru0

项目需求

本项目中,我们以活动方给出的——“任务三: 使用板卡的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 一直是我比较感兴趣的一个方向,小小的芯片、板卡能够能够融入生活的方方面面,产生了不可估量的作用。作为一个快要步入社会的理工男,希望以后能够由更多机会接触这样的活动,愿活动越办越好!

附件下载
Funpack.zip
源代码
团队介绍
个人
团队成员
Jacobian今天没有coredump
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号