打印
[活动]

【Nordic nRF7002开发板试用体验】微信蓝牙联网+MQTT控制

[复制链接]
1206|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 HonestQiao 于 2023-9-26 00:12 编辑

nRF7002DK开发板综合评测:微信蓝牙联网+MQTT控制
作者:乔楚|HonestQiao
日期:2023-09-24

一、前言
Nordic(北欧)半导体出的nRF7002-DK是一款相当不错的开发板,官方提供了基于Zephyr RTOS的nRF Connect SDK。

经过学习,发现nRF Connect SDK中,包含了蓝牙、蜂窝网络、加成、边缘计算、多核、网络、NFC、无线网络等17个分类180多个实例,每个实例包含源码以及详细的说明,可以让开发者快速了解各项功能的具体开发,真的是一个大宝库。

localfile://path?assets/16932438396589.jpg


另外,nRF Connect SDK基于Zephyr,而Zephyr本身也提供21个大类了400多个实例:

localfile://path?assets/16955503410208.jpg
通过对这些实例的研究学习,对于nRF7002-DK的各项功能的基本应用,有了快速的了解和入手。

二、测试计划
  • 基础功能评测:

    • WiFi连接测试
    • TCP/UDP客户端和服务端测试
  • 微信一键配网:

    • 启动nRF7002-DK进入无线配网模式
    • 手机微信扫码进入配网小程序
    • 通过小程序,给nRF7002-DK提供网络连接配置信息
    • nRF7002-DK通过小程序提供的配置信息,进行联网
  • MQTT远程控制:

    • 在微信一键配网的基础上,nRF7002-DK通过WiFi网络连接MQTT服务器
    • nRF7002-DK通过MQTT服务,发布板载LED状态的信息
    • 手机App设计图形化控制界面,通过MQTT服务,发布控制指令,控制板载LED的亮度
    • nRF7002-DK根据控制指令,控制板载LED

三、环境搭建


为了快速开展学习和研究,我使用了基于Docker的开发环境,基本操作步骤如下:
# 1. 环境参数设置
echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc
echo 'export sdk_nrf_revision=v2.4.1' >> ~/.bashrc
echo 'export docker_nrf_revision=v2.4-branch' >> ~/.bashrc
source ~/.bashrc

# 2. 源码下载:
# 建立工作目录
mkdir workdir && cd workdir

# 安装west
pip3 install --user west
west --version
# 此时应正确显示版本号

# 下载完整库,需下载 3G 以上大小数据
west init -m https://github.com/nrfconnect/sdk-nrf --mr $sdk_nrf_revision
west update

# 3. docker编译,省去环境构建步骤【难度:⭐️⭐️】
# 预拉取镜像【执行一次即可】
docker pull nordicplayground/nrfconnect-sdk:$docker_nrf_revision

# 4. 编译烧录测试
# 编译点灯源码:zephyr/samples/basic/blinky
docker run -it --rm \
-v ${PWD}:/workdir/project nordicplayground/nrfconnect-sdk:$docker_nrf_revision \
west build -b nrf7002dk_nrf5340_cpuapp zephyr/samples/basic/blinky

# 编译最终结果:
[168/168] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       23676 B         1 MB      2.26%
             RAM:        6256 B       448 KB      1.36%
        IDT_LIST:          0 GB         2 KB      0.00%

# 烧录工具使用nrfjprog
nrfjprog --verify --program ./build/zephyr/zephyr.hex –-chiperase -r

烧录成功后,开发板上额LED,就会欢快的闪动起来了。

四、基础功能评测

4.1 WiFi测试
nRF7002-DK作为一款支持2.4G和5G的开发板,环境调通了,上手第一件事情,就是测试WiFi连接。

在 nrf/samples/wifi/sta 目录中,就提供了一个用于测试的实例。
在使用该实例前,需要先进行SSID的配置,打开其中的项目配置文件prj.conf,修改如下的部分:
# file: nrf/samples/wifi/sta/prj.conf
# Below configs need to be modified based on security
# CONFIG_STA_KEY_MGMT_NONE=y
CONFIG_STA_KEY_MGMT_WPA2=y
# CONFIG_STA_KEY_MGMT_WPA2_256=y
# CONFIG_STA_KEY_MGMT_WPA3=y
CONFIG_STA_SAMPLE_SSID="WiFi名称"
CONFIG_STA_SAMPLE_PASSWORD="WiFi密码"



具体修改:
  • CONFIG_STA_KEY_MGMT_WPA2:WiFi连接认证方式
  • CONFIG_STA_SAMPLE_SSID: WiFi的SSID
  • CONFIG_STA_SAMPLE_PASSWORD: WiFi的密码
也可以使用 west build -b menuconfig 在配置界面进行修改。


修改完成后,基于以上的docker编译环境,使用如下的命令进行编译:
docker run -it --rm \
-v ${PWD}:/workdir/project nordicplayground/nrfconnect-sdk:$docker_nrf_revision \
west build -b nrf7002dk_nrf5340_cpuapp nrf/samples/wifi/sta


将生成的固件 build/zephyr/zephyr.hex 烧录到开发板以后,开发板重新启动,然后输出如下:

localfile://path?assets/16955520325683.jpg


片刻之后,成功连接到设置的无线网络:

localfile://path?assets/16955520720641.jpg


在无线路由器的管理界面,也可以看到新的设备连接信息:

localfile://path?assets/16955522956411.jpg
在 nrf/samples/wifi/sta 实例中,默认使用的是2.4G的无线网络连接,如果要连接到5G无线网络,则需要进一步做如下的修改:
  • 项目配置文件修改:
  • # file: nrf/samples/wifi/sta/prj.conf

    CONFIG_STA_SAMPLE_SSID="此处应为支持5G网络的WiFi名称"



  • 源码文件添加配置:
    # file: nrf/samples/wifi/sta/src/main.c

            params->channel = WIFI_CHANNEL_ANY;

        # 此处根据连接到的WiFi,添加对应的网络连接频率设置
            // params->band = WIFI_FREQ_BAND_2_4_GHZ;
            params->band = WIFI_FREQ_BAND_5_GHZ;

            /* MFP (optional) */
            params->mfp = WIFI_MFP_OPTIONAL;



在上述代码中,params->band 就表示要连接2.4G还是5G。

修改设置完成以后,重新编译烧录,开发板重启后,最终就会连接到5G网络了:

localfile://path?assets/16955526599576.jpg

4.2 TCP/UDP客户端和服务端测试
在zephyr/samples/net/socket中,提供了 tcp、udp等多种测试的实例,简单配置即可运行,所以这里就不详细写了,重点放在第二部分和第三部分的评测中。
  • TCP/UDP客户端测试:zephyr/samples/net/socket/echo_client,其中包含了开发板作为tcp和udp连接到服务端进行数据读取读写的演示
  • TCP/UDP服务端测试:zephyr/samples/net/socket/echo_server,其中包含了开发板作为tcp和udp服务端,启动服务供其他客户端连接进行数据读取读写的演示
其他还有coap、http、websocket等的演示,感兴趣的同学,可以仔细研究。

五、微信一键配网

要实现微信一键配网,需要以下的条件:
  • 专用的微信小程序,用于连接到开发,然后进行配网信息的发送
  • 开发板上,运行蓝牙服务,切提供数据写入的权限;一旦受到了正确的配置信息,则自动进行WiFi的连接。
WiFi连接的部分,在 WiFi连接测试 中已经进行。

经过研究提供的实例和阅读资料,数据写入的部分,参考 nrf/samples/bluetooth/peripheral_uart 来实现。

5.1 NUS服务
peripheral_uart 这个实例,使用了 Nordic UART Service (NUS) 服务,通过低功耗 Bluetooth® LE的GATT,来提供蓝牙串口服务。
需要注意的是,BLE默认限制数据传输长度为20个字节,因为ATT的默认MTU为23个bytes,除去ATT的opcode一个字节以及ATT的handle 2个字节之后,剩下的20个字节便是留给GATT的了。可以通过对外提供接口,在连接后,修改这个值的大小。

5.2 配置信息规定
为了方便微信小程序发送配网信息到NUS服务,提前做了如下的规定:
  • 发送SSID信息:ssid:SSID名称
  • 发送passwd信息:passwd:密码
  • 发送type信息:type:连接类型,0表示2.4G网络,1表示5G网络
  • 发送action信息:action:connect,表示启动无线联网
根据上面的规划,微信小程序使用蓝牙连接到开发板以后,将依次发送上面的信息给开发板,开发板接收到action切为connect后,就会使用接收到的信息,进行无线联网。

5.3 配置数据存储
在代码中,添加了一个变量,用于存储配置信息:
typedef struct {
    char ssid[21];  // 定义一个长度为20的字符数组用于存储ssid
    char passwd[21];  // 定义一个长度为20的字符数组用于存储passwd
        int type;                //  定义一个int用于存2.4/5G
} wifi_config_t;  // 定义一个结构体类型,名为wifi_config_t

wifi_config_t wifi_config;



在代码中,在 main 函数石,启动 NUS 服务:

 err = bt_nus_init(&nus_cb);
        if (err) {
                LOG_ERR("Failed to initialize UART service (err: %d)", err);
                return 0;
        }
        printk("Starting Nordic BLE WiFi-Pairing service\r\n");


5.4 蓝牙回调解析配置
然后,在 蓝牙接收数据的回调 bt_receive_cb 中,进行配置信息解析处理:

static void bt_receive_cb(struct bt_conn *conn, const uint8_t *const data,
                          uint16_t len)
{
        // int err;
        char addr[BT_ADDR_LE_STR_LEN] = {0};
        uint8_t buf[CONFIG_BT_NUS_UART_BUFFER_SIZE] = {0};

        bt_addr_le_to_str(bt_conn_get_dst(conn), addr, ARRAY_SIZE(addr));
        strncpy(buf, data, len);
        printk("Received data[len=%d]:%s from: %s\n", len, buf, addr);

    char *cfg_name, *cfg_value, *ptr;
    cfg_name = strtok_r(buf, ":", &ptr);
    cfg_value = strtok_r(NULL, ":", &ptr);

        if(cfg_name         == NULL || cfg_value == NULL) {
                printk("parse fail\n");
        } else {
                printk("parse ok: name=%s value=%s\n", cfg_name, cfg_value);
                if(strcmp(cfg_name, "ssid")==0) {
                        strcpy(wifi_config.ssid, cfg_value);
                } else if(strcmp(cfg_name, "passwd")==0) {
                        strcpy(wifi_config.passwd, cfg_value);
                } else if(strcmp(cfg_name, "type")==0) {
                        wifi_config.type = (cfg_value[0] - '0') >0 ? 1 :0;
                } else if(strcmp(cfg_name, "action")==0 && strcmp(cfg_value, "connect")==0) {
                        printk("connect to ssid=%s passwd=%s type=%d\n", wifi_config.ssid, wifi_config.passwd, wifi_config.type);
                        wifi_connect();
                } else {
                        printk("unknown config name\n");
                }
        }
}

static struct bt_nus_cb nus_cb = {
        .received = bt_receive_cb,
};

5.5 联网处理
收到了配置信息,就可以进行联网的处理了,具体逻辑如下:


static void set_wifi_credential(void *cb_arg)
{
        struct wifi_credentials_personal config;

        memcpy(config.header.ssid, wifi_config.ssid, sizeof(wifi_config.ssid));
        config.header.ssid_len = strlen(config.header.ssid);
        config.header.type = WIFI_SECURITY_TYPE_PSK;
        memcpy(config.password, wifi_config.passwd, sizeof(wifi_config.passwd));
        config.password_len = strlen(config.password);

        if(!wifi_config.type) {
                config.header.flags |= WIFI_CREDENTIALS_FLAG_2_4GHz;
        } else {
                config.header.flags |= WIFI_CREDENTIALS_FLAG_5GHz;
        }

        memcpy((struct wifi_credentials_personal *)cb_arg, &config, sizeof(config));
}

static void wifi_connect(void)
{
        int rc;
        struct wifi_credentials_personal config = { 0 };
        struct net_if *iface = net_if_get_default();
        struct wifi_connect_req_params cnx_params = { 0 };

        set_wifi_credential(&config);
        if (config.header.ssid_len > 0) {
                LOG_INF("Configuration found. Try to apply.\n");

                cnx_params.ssid = config.header.ssid;
                cnx_params.ssid_length = config.header.ssid_len;
                cnx_params.security = config.header.type;

                cnx_params.psk = NULL;
                cnx_params.psk_length = 0;
                cnx_params.sae_password = NULL;
                cnx_params.sae_password_length = 0;

                if (config.header.type != WIFI_SECURITY_TYPE_NONE) {
                        cnx_params.psk = config.password;
                        cnx_params.psk_length = config.password_len;
                }

                cnx_params.channel = WIFI_CHANNEL_ANY;
                cnx_params.band = config.header.flags & WIFI_CREDENTIALS_FLAG_5GHz ?
                                WIFI_FREQ_BAND_5_GHZ : WIFI_FREQ_BAND_2_4_GHZ;
                cnx_params.mfp = WIFI_MFP_OPTIONAL;
                rc = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
                        &cnx_params, sizeof(struct wifi_connect_req_params));
                if (rc < 0) {
                        LOG_ERR("Cannot apply saved Wi-Fi configuration, err = %d.\n", rc);
                } else {
                        LOG_INF("Configuration applied.\n");
                }
        }
}

最后,在代码中,检测网络连接是否完成:


static int get_wifi_status(void)
{
        struct net_if *iface = net_if_get_default();
        struct wifi_iface_status status = { 0 };

        if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status,
                                sizeof(struct wifi_iface_status))) {
                LOG_INF("Status request failed");

                return -ENOEXEC;
        }

        LOG_INF("==================");
        LOG_INF("State: %s", wifi_state_txt(status.state));

        if (status.state >= WIFI_STATE_COMPLETED) {
                return 1;
        }
        return 0;
}

void main(void)
{
    // ...
        while(!get_wifi_status()) {
                printk("Wait for wifi connected\n");
                k_sleep(K_SECONDS(3));
        }
        // ...
}



5.6 功能测试
5.6.1 开发板启动,进入BLE配网状态
对应的代码编译烧录后,开发板重新启动输出如下:

localfile://path?assets/16955553047698.jpg
提示信息为等待通过BLE进行蓝牙配对。

5.6.2 微信小程序连接
此时,启动微信蓝牙调试小程序,扫描蓝牙设备:

localfile://path?assets/16955556722984.jpg

5.6.3 微信小程序发送配网信息
点击上图中的Nordic_BLE_WiFi_Pairing,即可进入进入配网界面:


localfile://path?assets/16955557102058.jpg
在界面中,输入要连接的SSID和密码,并勾选是否5G网络,然后点蓝牙配网,将自动通过BLE给开发板发送配置信息:



localfile://path?assets/16955557451185.jpg


5.6.4 开发板接受并完成网络连接
发送完成后,开发板收到发送的配网信息,会自动进行WiFi连接:

localfile://path?assets/16955557912133.jpg
连接成功后,将通过DHCP,自动获取IP地址等信息。

补充:
上述使用的蓝牙配网程序,参考
https://github.com/wyx-dev/bluetooth-serial 实现。

六、MQTT远程控制
在 官方的实例中,也提供了MQTT的实例可以学习,具体可以查看 nrf/samples/net/mqtt
另外,在Nordic的技术博客上,有一篇文章Implementing MQTT over Wi-Fi on the nRF7002 Development Kit,提供了一个实例: mqtt_over_wifi_nrf7002DK,该实例使用了Wi-Fi: Provisioning Service服务,通过 Nordic官方提供的 nRF Wi-Fi Provisioner 手机App,来完成网络配置,然后进行MQTT连接的处理。
经过一番研究,将MQTT的实例和微信一键配网的程序,最终整合到一起,实现通过微信一键配网,配网成功后,开发板连接到MQTT,然后在手机App上,通过MQTT发送信息,来控制开发板上的LED。

6.1 配置处理
需要注意的是,mqtt_over_wifi_nrf7002DK 实例是的 sdk-nrf-2.3 版本上开发的,在 sdk-nrf-2.4.x版本中,不能直接使用,经过测试,修改如下配置即可:


# file: child_image/hci_rpmsg.conf

CONFIG_BT_BUF_ACL_TX_SIZE=151
CONFIG_BT_BUF_ACL_RX_SIZE=151
CONFIG_BT_CTLR_DATA_LENGTH_MAX=151
# CONFIG_BT_MAX_CONN=2

实际使用时,还需要进行MQTT服务器的配置,具体配置如下:
# file: prj.conf

# Application
CONFIG_MQTT_PUB_TOPIC="hq/nrf7002/pub"
CONFIG_MQTT_SUB_TOPIC="hq/nrf7002/sub"
#Note If you notice that the test.mosquitto.org is unresponsive,
#there are several other public MQTT brokers that you can use (Ex: broker.hivemq.com at port 1883)
CONFIG_MQTT_BROKER_HOSTNAME="192.168.1.15"

CONFIG_MQTT_CLIENT_ID="nRF7002DK_Test_Client"
CONFIG_MQTT_BROKER_PORT=1883

为了更好的进行测试,我在本地假设了MQTT服务,使用的是 EMQX,可以从如下地址下载安装 https://www.emqx.io/zh/downloads

在上述配置中,对应的MQTT 主题如下:
  • 消息订阅:hq/nrf7002/sub
  • 消息发布:hq/nrf7002/pub

为了更好的对LED进行控制,还对配置文件做了修改添加:
# file: Kconfig

config TURN_LED2_ON_CMD
        string "Command to turn on LED"
        default "LED2ON"

config TURN_LED2_OFF_CMD
        string "Command to turn off LED"
        default "LED2OFF"

config TURN_LEDALL_ON_CMD
        string "Command to turn on all LED"
        default "LEDALLON"

config TURN_LEDALL_OFF_CMD
        string "Command to turn off a;; LED"
        default "LEDALLOFF"



添加了打开所有灯和熄灭所有灯的配置。

6.2 代码处理
然后,将之前的微信配网部分的代码,和mqtt_over_wifi_nrf7002DK的代码进行整合,主要修改如下:
  • 把 Wi-Fi: Provisioning Service 相关的代码删除或者注释掉
  • 将NUS服务相关的代码拷贝过来,并在main函数中,检测到网络连接后,再连接MQTT
  • while(!get_wifi_status()) {
            printk("Wait for wifi connected\n");
            k_sleep(K_SECONDS(3));
    }

    LOG_INF("Connecting to MQTT Broker...");
    /* Connect to MQTT Broker */

    timer_init();  //初始化定时器

    connect_mqtt();

同时,因为之前添加了同时控制所有灯的配置,所以代码方面,也需要做一些修改:


# file: src/mqtt_connection.c

                        // Control LED1 and LED2
                        if(strncmp(payload_buf,CONFIG_TURN_LED1_ON_CMD,sizeof(CONFIG_TURN_LED1_ON_CMD)-1) == 0){
                                status1 = 1;
                                dk_set_led_on(DK_LED1);
                        }
                        else if(strncmp(payload_buf,CONFIG_TURN_LED1_OFF_CMD,sizeof(CONFIG_TURN_LED1_OFF_CMD)-1) == 0){
                                status1 = 0;
                                dk_set_led_off(DK_LED1);
                        }
                        else if(strncmp(payload_buf,CONFIG_TURN_LED2_ON_CMD,sizeof(CONFIG_TURN_LED2_ON_CMD)-1) == 0){
                                status2 = 1;
                                dk_set_led_on(DK_LED2);
                        }
                        else if(strncmp(payload_buf,CONFIG_TURN_LED2_OFF_CMD,sizeof(CONFIG_TURN_LED2_OFF_CMD)-1) == 0){
                                status2 = 0;
                                dk_set_led_off(DK_LED2);
                        }
                        else if(strncmp(payload_buf,CONFIG_TURN_LEDALL_ON_CMD,sizeof(CONFIG_TURN_LEDALL_ON_CMD)-1) == 0){
                                status1 = 1;
                                status2 = 1;
                                dk_set_led_on(DK_LED1);
                                dk_set_led_on(DK_LED2);
                        }
                        else if(strncmp(payload_buf,CONFIG_TURN_LEDALL_OFF_CMD,sizeof(CONFIG_TURN_LEDALL_OFF_CMD)-1) == 0){
                                status1 = 0;
                                status2 = 0;
                                dk_set_led_off(DK_LED1);
                                dk_set_led_off(DK_LED2);
                        }


6.3 功能测试
将修改后的代码编译烧录后,开发板重启运行。

6.3.1 BLE配网
首先进入BLE配网模式:

localfile://path?assets/16955577359759.jpg
使用微信小程序配网以后,自动连接网络,然后启动MQTT连接:
localfile://path?assets/16955577112991.jpg
在上述信息中,开发板已经成功连接到了本地的MQTT服务。

6.3.2 手机App控制
下面,就可以使用手机端的App进行测试了。
在手机端,有多种App可以连接MQTT进行测试,其中较为好用的是 Mqtt Dashboard,可以从如下地址下载:
https://play.google.com/store/apps/details?id=com.app.vetru.mqttdashboard

下载安装以后,首先进行MQTT服务器设置:

localfile://path?assets/16955595796566.jpg
然后,添加标签,进行控制设置:

localfile://path?assets/16955595967263.jpg
具体的配置如下:
  • 控制LED1打开:hq/nrf7002/sub LED1ON
  • 控制LED1关闭:hq/nrf7002/sub LED1OFF
  • 控制LED2打开:hq/nrf7002/sub LED2ON
  • 控制LED2关闭:hq/nrf7002/sub LED2OFF
  • 控制LED全部打开:hq/nrf7002/sub LEDALLON
  • 控制LED全部关闭:hq/nrf7002/sub LEDALLOFF
配置完成后,控制界面如下:

localfile://path?assets/16955596869809.jpg
在控制界面上,依次点击开灯1、关灯1、开灯2、关灯2、开灯(所有)、关灯(所有),开发板就会收到控制信息,并进行LED的处理:

localfile://path?assets/16955597605561.jpg

七、总结
进行到这里,这次测评就接近尾声了。
通过这次测试,到Nordic(北欧)半导体为开发者提供了全方位的支持,从软件到硬件到环境,都能够顺利的上手,并快速进入状态。另外,本次测试的nRF7002-DK开发板,在nRF Connect SDK的完善支持,能够提供丰富的功能,和强大的网络连接能力,真的是非常的棒!
非常感谢21ic和Nordic(北欧)半导体提供本次测试的机会,后续将会进一步深入研究,了解和测试更多的功能。



使用特权

评论回复

相关帖子

沙发
EPTmachine| | 2023-10-16 08:58 | 只看该作者
感谢分享

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

41

主题

99

帖子

2

粉丝