这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【换取手持数字示波器】基于nRF7002Wi-Fi与MQTT协议的远程灯光与按键

共1条 1/1 1 跳转至

【换取手持数字示波器】基于nRF7002Wi-Fi与MQTT协议的远程灯光与按键控制

助工
2025-02-01 17:35:05     打赏

感谢EEPW推出的“换取手持示波器”活动,本次分享的主题是:基于Nordic的无线开发板nrf7002-dk通过MQTT协议远程控制led并获取按键信息。

目前Nordic最新的nrf54系列的开发板已经面向市场,感兴趣的朋友可以采购体验一下BLE 6.0的新特性!

一、

1.1 板卡介绍:

nRF7002-DK是用于nRF7002 Wi-Fi 6协同IC的开发套件,该开发套件采用nRF5340多协议片上系统 (SoC) 作为nRF7002的主处理器,在单一的电路板上包含了开发工作所需的一切,可让开发人员轻松开启基于nRF7002 的物联网项目。该 DK 包括 Arduino 连接器、两个可编程按钮、一个 Wi-Fi 双频段天线和一个低功耗蓝牙天线,以及电流测量引脚。

   FoDdwpy_jWmjC-4W6_yX6CRewYkx     FodLPVHD9UE_1Xz-oMP74QundOEn

这款DK支持低功耗 Wi-Fi 应用开发,并实现了多项 Wi-Fi 6 功能,比如 OFDMA、波束成型和 TWT。nRF7002 Wi-Fi 6配套IC为另一个主机添加了低功耗Wi-Fi 6功能,提供无缝连接和基于Wi-Fi的定位(本地Wi-Fi集线器的SSID嗅探)功能。该IC设计用于搭配Nordic现有的nRF52®和nRF53®系列多协议片上系统 (SoC) 和nRF91®系列蜂窝物联网系统级封装 (SiP) 使用。nRF7002 IC 还可与非nordic主机器件搭配使用。通过SPI或QSPI与主机通信,并带有额外的共存功能,可与其他协议如蓝牙、Thread或Zigbee无缝共存。

nRF7002在Nordic的nRF Connect SDK中提供集成和支持。

板卡特性

  • 用于nRF7002双频带Wi-Fi 6配套IC的开发套件

  • nRF5340 SoC主机器件

  • Wi-Fi 6 (IEEE 802.11 a/b/g/n/ac/ax)、蓝牙低功耗 (LE)、蓝牙网状网络、802.15.4、Thread、Zigbee®、ANT、2.4GHz专有和NFC无线协议支持

  • 2.4GHz、5GHz芯片和NFC天线

  • SWF射频连接器

  • SEGGER J-Link板载编程器/调试器

  • 用户可编程LED (2x) 和按钮 (2x)

  • 用于测量功耗的引脚

  • 来自USB、外部或锂聚合物电池的2.9V至5.0V电源

  • Arduino连接器

 

1.2 开发环境介绍:

在vscode中搭建nrf7002dk的开发环境是非常方便的,先安装ncs command line工具,再安装desktop工具,最后在vscode中设置。FjLLgSuzRldPhXOSmCYN2RdtOBPA

 

细节可以参考官方文档:https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-VS-Code

 

二、项目思路/流程图

Nordic的开发环境对我来说比较新,从未接触过。因此首先想到的是去官方找一下有没有相关的demo。入手点就是Wi-Fi的sta例程,提供了联网的入门代码。其次是MQTT,Zephyr有相关的MQTT库,熟悉后可以正常调用。Broker,我选择的是安卓手机上可以使用的MQTT DASHBOARD,比较方便的创建Broker以及客户端。

Fpj9j6u7g-MfjkdAlQvrHy3MutjT

三、连接Wi-Fi

nRF Connect SDK有一个开箱即用的Wi-Fi Station模式的例程:Wi-Fi: Station https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.3.0/nrf/samples/wifi/sta/README.html 

首先要进行相关回调函数的初始化,如下所示是Zephyr里的网络管理函数,官方链接:https://docs.zephyrproject.org/apidoc/latest/group__net__mgmt.html

Network Management event callback structure Used to register a callback into the network management event part, in order to let the owner of this struct to get network event notification based on given event mask.

 net_mgmt_init_event_callback(&wifi_shell_mgmt_cb,    wifi_mgmt_event_handler, WIFI_SHELL_MGMT_EVENTS);
 net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);
 net_mgmt_init_event_callback(&net_shell_mgmt_cb,     net_mgmt_event_handler, NET_EVENT_IPV4_DHCP_BOUND);

 net_mgmt_add_event_callback(&net_shell_mgmt_cb);

 LOG_INF("Starting %s with CPU frequency: %d MHz", CONFIG_BOARD, SystemCoreClock/MHZ(1));
 k_sleep(K_SECONDS(1));接下来在main函数中调用进行Wi-Fi连接:
 do {
  wifi_connect();

  for (i = 0; i < CONNECTION_TIMEOUT; i++) {
   k_sleep(K_MSEC(STATUS_POLLING_MS));
   cmd_wifi_status();
   if (context.connect_result) {
    break;
   }
  }
  if (context.connected) {
  } else if (!context.connect_result) {
   LOG_ERR("Connection Timed Out");
  }
 }while (0);


下面是wifi_connect函数的实现:

static int wifi_connect(void)
{
	struct net_if *iface = net_if_get_default();
	static struct wifi_connect_req_params cnx_params;

	context.connected = false;
	context.connect_result = false;
	__wifi_args_to_params(&cnx_params);

	if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
		     &cnx_params, sizeof(struct wifi_connect_req_params))) {
		LOG_ERR("Connection request failed");

		return -ENOEXEC;
	}
	LOG_INF("Connection requested");

	return 0;
}


其中比较重要的是net_mgmt这个宏,其定义如下,经过一些列展开后,它会调用Wi-Fi相关的底层驱动函数去连接网络。

#define  net_mgmt(_mgmt_request, _iface, _data, _len)    net_mgmt_##_mgmt_request(_mgmt_request, _iface, _data, _len)


串口输出如下,成功连接到了2.4G Wi-Fi网络。

[00:00:00.307,281] <inf> wifi_nrf: qspi_init: QSPI freq = 24 MHz
[00:00:00.307,312] <inf> wifi_nrf: qspi_init: QSPI latency = 1
[00:00:00.314,788] <inf> wifi_nrf: zep_shim_pr_info: wifi_nrf_fmac_fw_load: LMAC patches loaded
[00:00:00.325,653] <inf> wifi_nrf: zep_shim_pr_info: wifi_nrf_fmac_fw_load: LMAC boot check passed
[00:00:00.328,613] <inf> wifi_nrf: zep_shim_pr_info: wifi_nrf_fmac_fw_load: UMAC patches loaded
[00:00:00.339,385] <inf> wifi_nrf: zep_shim_pr_info: wifi_nrf_fmac_fw_load: UMAC boot check passed
[00:00:00.360,992] <inf> wifi_nrf: zep_shim_pr_info: RPU LPM type: HW

[00:00:00.479,034] <inf> fs_nvs: nvs_mount: 2 Sectors of 4096 bytes
[00:00:00.479,064] <inf> fs_nvs: nvs_mount: alloc wra: 0, fe8
[00:00:00.479,064] <inf> fs_nvs: nvs_mount: data wra: 0, 0
*** Booting Zephyr OS build v3.2.99-ncs2 ***
[00:00:00.479,125] <inf> net_config: net_config_init_by_iface: Initializing network
[00:00:00.479,156] <inf> net_config: check_interface: Waiting interface 1 (0x200013e8) to be up...
[00:00:00.479,949] <inf> net_config: setup_ipv4: IPv4 address: 192.168.0.104
[00:00:00.479,980] <inf> net_config: setup_dhcpv4: Running dhcpv4 client...
[00:00:00.480,987] <inf> MQTT_OVER_WIFI: main: Starting nrf7002dk_nrf5340_cpuapp with CPU frequency: 64 MHz
[00:00:00.481,231] <inf> wpa_supp: wpa_printf_impl: z_wpas_start: 385 Starting wpa_supplicant thread with debug level: 3

[00:00:00.481,475] <inf> wpa_supp: wpa_printf_impl: Successfully initialized wpa_supplicant
[00:00:01.481,048] <inf> MQTT_OVER_WIFI: main: QSPI Encryption disabled
[00:00:01.481,140] <inf> MQTT_OVER_WIFI: main: Static IP address (overridable): 192.168.0.104/255.255.255.0 -> 
[00:00:03.083,801] <inf> MQTT_OVER_WIFI: wifi_connect: Connection requested
[00:00:03.383,941] <inf> MQTT_OVER_WIFI: cmd_wifi_status: ==================
[00:00:03.383,972] <inf> MQTT_OVER_WIFI: cmd_wifi_status: State: SCANNING
[00:00:06.997,833] <inf> wpa_supp: wpa_printf_impl: wlan0: SME: Trying to authenticate with 20:6b:e7:bb:8c:3b (SSID='TP-LINK_168' freq=2437 MHz)
[00:00:07.001,098] <inf> wifi_nrf: wifi_nrf_wpa_supp_authenticate: wifi_nrf_wpa_supp_authenticate:Authentication request sent successfully

[00:00:07.258,483] <inf> wpa_supp: wpa_printf_impl: wlan0: Trying to associate with 20:6b:e7:bb:8c:3b (SSID='TP-LINK_168' freq=2437 MHz)
[00:00:07.267,700] <inf> wifi_nrf: wifi_nrf_wpa_supp_associate: wifi_nrf_wpa_supp_associate: Association request sent successfully

[00:00:07.283,569] <inf> wpa_supp: wpa_printf_impl: wlan0: Associated with 20:6b:e7:bb:8c:3b
[00:00:07.283,721] <inf> wpa_supp: wpa_printf_impl: wlan0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0
[00:00:07.292,633] <inf> MQTT_OVER_WIFI: cmd_wifi_status: ==================
[00:00:07.292,663] <inf> MQTT_OVER_WIFI: cmd_wifi_status: State: 4WAY_HANDSHAKE
[00:00:07.292,694] <inf> MQTT_OVER_WIFI: cmd_wifi_status: Interface Mode: STATION
[00:00:07.292,724] <inf> MQTT_OVER_WIFI: cmd_wifi_status: Link Mode: WIFI 4 (802.11n/HT)
[00:00:07.292,755] <inf> MQTT_OVER_WIFI: cmd_wifi_status: SSID: TP-LINK_168 
[00:00:07.292,785] <inf> MQTT_OVER_WIFI: cmd_wifi_status: BSSID: 20:6B:E7:BB:8C:3B
[00:00:07.292,785] <inf> MQTT_OVER_WIFI: cmd_wifi_status: Band: 2.4GHz
[00:00:07.292,816] <inf> MQTT_OVER_WIFI: cmd_wifi_status: Channel: 6
[00:00:07.292,816] <inf> MQTT_OVER_WIFI: cmd_wifi_status: Security: WPA2-PSK
[00:00:07.292,846] <inf> MQTT_OVER_WIFI: cmd_wifi_status: MFP: Optional
[00:00:07.292,846] <inf> MQTT_OVER_WIFI: cmd_wifi_status: RSSI: -40


四、添加MQTT功能

MQTT(消息队列遥测传输协议),是一种基于 发布/订阅 (publish/subscribe)模式的"轻量级"通讯协议, 该协议构建于TCP/IP协议上 。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

在项目配置文件中添加如下配置:

# MQTT
CONFIG_MQTT_LIB=y
CONFIG_MQTT_CLEAN_SESSION=y

# Application
CONFIG_MQTT_PUB_TOPIC="publish/buttonStatus"
CONFIG_MQTT_SUB_TOPIC="subscribe/ledAction"
CONFIG_MQTT_BROKER_HOSTNAME="broker.hivemq.com"

CONFIG_MQTT_CLIENT_ID="Shanghai-9989"
CONFIG_MQTT_BROKER_PORT=1883


连接Wi-Fi后,连接到MQTT Broker,在main函数中调用:static void connect_mqtt(void),连接成功后,会在循环中检查mqtt心跳。

  err = poll(&fds, 1, mqtt_keepalive_time_left(&client));
  if (err < 0) {
   LOG_ERR("Error in poll(): %d", errno);
   break;
  }


关于MQTT相关代码,自己在测试的时候也发现,有时候手机MQTT客户端发布的主题,nrf7002dk订阅该主题,有时候延迟比较明显。后来发现Nordic论坛有个类似的帖子:

https://devzone.nordicsemi.com/f/nordic-q-a/102064/nrf7002-dk-with-mqtt-os-usage-fault-os-stack-overflow-context-area-not-valid

其中有关于上述代码的相关说明,这里引用一下:

The first line in the while(1)-loop is a polling function. Lets have a look at it:

   err = poll(&fds, 1, mqtt_keepalive_time_left(&client));

 

So, if the fds get a flag (mqtt rx), it will loop through the code. If not, it will continue polling for mqtt_keepalive_time_left(&client). This will be the keepalive-value.

What you could do, is to use this as a "mqtt handle thread", which keeps the connection alive and does something with any data it receives. Then you could use a different thread to do any sending in any frequency you'd like.

 

主要的发布与订阅都是跟mqtt_evt_handler密切相关:

void mqtt_evt_handler(struct mqtt_client *const c,
   const struct mqtt_evt *evt)
{
 int err;

 switch (evt->type) {
 case MQTT_EVT_CONNACK:
  if (evt->result != 0) {
   LOG_ERR("MQTT connect failed: %d", evt->result);
   break;
  }

  LOG_INF("MQTT client connected");
  subscribe(c);
  break;

 case MQTT_EVT_DISCONNECT:
  LOG_INF("MQTT client disconnected: %d", evt->result);
  break;

 case MQTT_EVT_PUBLISH:
 {
  const struct mqtt_publish_param *p = &evt->param.publish;
  //Print the length of the recived message 
  LOG_INF("MQTT PUBLISH result=%d len=%d",
   evt->result, p->message.payload.len);

  //Extract the data of the recived message 
  err = get_received_payload(c, p->message.payload.len);
  
  //Send acknowledgment to the broker on receiving QoS1 publish message 
  if (p->message.topic.qos == MQTT_QOS_1_AT_LEAST_ONCE) {
   const struct mqtt_puback_param ack = {
    .message_id = p->message_id
   };

   /* Send acknowledgment. */
   mqtt_publish_qos1_ack(c, &ack);
  }

  if (err >= 0) {
   data_print("Received: ", payload_buf, p->message.payload.len);
   // Control LED1 and LED2 
   if(strncmp(payload_buf,CONFIG_TURN_LED1_ON_CMD,sizeof(CONFIG_TURN_LED1_ON_CMD)-1) == 0){
    dk_set_led_on(DK_LED1);
   }
   else if(strncmp(payload_buf,CONFIG_TURN_LED1_OFF_CMD,sizeof(CONFIG_TURN_LED1_OFF_CMD)-1) == 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){
    dk_set_led_on(DK_LED2);
   }
   else if(strncmp(payload_buf,CONFIG_TURN_LED2_OFF_CMD,sizeof(CONFIG_TURN_LED2_OFF_CMD)-1) == 0){
    dk_set_led_off(DK_LED2);
   }

  // Payload buffer is smaller than the received data 
  } else if (err == -EMSGSIZE) {
   LOG_ERR("Received payload (%d bytes) is larger than the payload buffer size (%d bytes).",
    p->message.payload.len, sizeof(payload_buf));
  // Failed to extract data, disconnect 
  } else {
   LOG_ERR("get_received_payload failed: %d", err);
   LOG_INF("Disconnecting MQTT client...");

   err = mqtt_disconnect(c);
   if (err) {
    LOG_ERR("Could not disconnect: %d", err);
   }
  }
 } break;

 case MQTT_EVT_PUBACK:
  if (evt->result != 0) {
   LOG_ERR("MQTT PUBACK error: %d", evt->result);
   break;
  }

  LOG_INF("PUBACK packet id: %u", evt->param.puback.message_id);
  break;

 case MQTT_EVT_SUBACK:
  if (evt->result != 0) {
   LOG_ERR("MQTT SUBACK error: %d", evt->result);
   break;
  }

  LOG_INF("SUBACK packet id: %u", evt->param.suback.message_id);
  break;

 case MQTT_EVT_PINGRESP:
  if (evt->result != 0) {
   LOG_ERR("MQTT PINGRESP error: %d", evt->result);
  }
  break;

 default:
  LOG_INF("Unhandled MQTT event type: %d", evt->type);
  break;
 }
}


上述MQTT_EVT_PUBLISH就是当前订阅的主题被别的MQTT Client发布后,Nrf7002dk这个客户端接收了该报文后产生的事件。对应到本项目就是LED1ON或者LEDOFF等当前客户端订阅的主题有了更新!因此可以根据payload的内容进一步判断是需要亮灭哪个小灯。

 

按键方面:按键按下后,会调用回调函数button_handler,进而判断是BTN1还是BTN2被按下,然后把相应的字符串打包到MQTT报文,这样别的MQTT客户端如果订阅了这个主题,也会收到更新。

static void button_handler(uint32_t button_state, uint32_t has_changed)
{
 switch (has_changed) {
 case DK_BTN1_MSK:
  if (button_state & DK_BTN1_MSK){ 
   int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
     CONFIG_BUTTON1_EVENT_PUBLISH_MSG, sizeof(CONFIG_BUTTON1_EVENT_PUBLISH_MSG)-1);
   if (err) {
    LOG_ERR("Failed to send message, %d", err);
    return; 
   }
  }
  break;
 case DK_BTN2_MSK:
  if (button_state & DK_BTN2_MSK){ 
   int err = data_publish(&client, MQTT_QOS_1_AT_LEAST_ONCE,
     CONFIG_BUTTON2_EVENT_PUBLISH_MSG, sizeof(CONFIG_BUTTON2_EVENT_PUBLISH_MSG)-1);
   if (err) {
    LOG_ERR("Failed to send message, %d", err);
    return; 
   }
  }
  break;
 }
}


五、配置MQTT Broker

本项目中使用的是MQTT DASHBOARD这个app。

Fm8hv7_6olypAadPsp3hgVgnaI1I

 

 

连接wifi后,连接到mqtt broker,串口输出如下。可以看到nrf7002会Publish按键的状态,会订阅LED打开/关闭的命令。

[00:00:13.613,647] <inf> MQTT_OVER_WIFI: main: Connecting to MQTT Broker...
[00:00:13.654,052] <inf> MQTT_OVER_WIFI: broker_init: IPv4 Address found 52.28.106.54
[00:00:13.922,821] <inf> net_mqtt: client_connect: Connect completed
[00:00:15.043,395] <inf> MQTT_OVER_WIFI: mqtt_evt_handler: MQTT client connected
[00:00:15.043,426] <inf> MQTT_OVER_WIFI: subscribe: Subscribing to: subscribe/ledAction len 27
[00:00:15.356,536] <inf> MQTT_OVER_WIFI: mqtt_evt_handler: SUBACK packet id: 1234
[00:00:46.666,534] <inf> MQTT_OVER_WIFI: mqtt_evt_handler: MQTT PUBLISH result=0 len=6
[00:00:46.666,625] <inf> MQTT_OVER_WIFI: data_print: Received: LED2ON
[00:00:47.486,114] <inf> MQTT_OVER_WIFI: mqtt_evt_handler: MQTT PUBLISH result=0 len=7
[00:00:47.486,175] <inf> MQTT_OVER_WIFI: data_print: Received: LED2OFF
[00:00:52.721,649] <inf> MQTT_OVER_WIFI: data_print: Publishing: Button 1 Pressed
[00:00:52.721,679] <inf> MQTT_OVER_WIFI: data_publish: to topic: publish/buttonStatus len: 28
[00:00:53.011,718] <inf> MQTT_OVER_WIFI: mqtt_evt_handler: PUBACK packet id: 51886
[00:00:54.529,937] <inf> MQTT_OVER_WIFI: data_print: Publishing: Button 2 Pressed
[00:00:54.529,968] <inf> MQTT_OVER_WIFI: data_publish: to topic: publish/buttonStatus len: 28


实物展示:请看LED2的亮灭,LED1灯用于代表网络连接,请忽略。右图按键按下后,手机上的MQTT客户端会显示:Button1Pressed或者Button2Pressed.

FleMg1iooqld1lt2ikHEqBwbZCNh     FrBU919a7yXZEFGMRpI50YzBu5LH


遇到的问题:

手机上使用的MQTT客户端软件是MQTT DashBoard,当时创建了一个client,测试过程中发现两者只有一个能够连接到Broker。手机客户端一连接,开发板这边就断开连接了。

FiiIow2ROiK-oP6T8F4v-4G6RQgd

后来在CSDN找到了类似的问题:MQTT client conflict 客户端ID冲突导致重复掉线问题 https://blog.csdn.net/Holy_Q/article/details/129519248?spm=1001.2014.3001.5502

所以问题的原因是当时使用的client id与nrf7002dk使用的是相同的id。

解决的办法就是在手机app创建客户端的时候,客户端id使用随机的一个id,必须保证与nrf7002dk用的是不同的id.

总结:

后续改进空间:

1. 可以使用单独的线程来处理MQTT相关任务,加入小屏幕进行显示等。

2. Zephyr这块Iot领域的专业RTOS,这次了解的比较浅,期待自己能够深入的去了解一下。


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]