本帖最后由 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的各项功能的基本应用,有了快速的了解和入手。
二、测试计划基础功能评测:
微信一键配网:
- 启动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(北欧)半导体提供本次测试的机会,后续将会进一步深入研究,了解和测试更多的功能。
|