基于nRF7002 Wi-Fi与MQTT协议的远程灯光与按键控制
本项目实现的效果是:使用WiFi连接功能,连接网络,并实现远程控制板卡LED和读取按键信息 主控板卡:Nordic推出的nRF7002DK 网络协议:MQTT
标签
MQTT
Wi-Fi
Funpack2-6
nRF7002
Zephyr
RTOS
littlestudent
更新2023-10-07
744

Nordic是在无线领域非常知名的一家北欧厂商,这次非常感谢Funpack活动提供的宝贵的活动机会,得以体验Noridic第一款Wi-Fi 6产品!本次活动完成的目标任务是:任务二:

• 使用WiFi连接功能,连接网络,并实现远程控制板卡LED和读取按键信息

一、

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="funpack/publish/buttonStatus"
CONFIG_MQTT_SUB_TOPIC="funpack/subscribe/ledAction"
CONFIG_MQTT_BROKER_HOSTNAME="broker.hivemq.com"

CONFIG_MQTT_CLIENT_ID="Shanghai-EETree-2461"
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: funpack/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: funpack/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: funpack/publish/buttonStatus len: 28

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

FleMg1iooqld1lt2ikHEqBwbZCNh      FrBU919a7yXZEFGMRpI50YzBu5LH

同时MQTT DASHBOARD也有相关的log:

Flac4ejlZ4j35TFsr2laYioqWQ25

遇到的问题:

手机上使用的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.

总结:

这次参加Noridic nRF7002DK 活动收获非常大,这是一个非常新的wifi开发板,也是自己体验的第一款Nordic开发板。初步完成了任务的目标,但是也有不少改进的空间,比如使用单独的线程来处理MQTT相关任务,加入小屏幕进行显示等。

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

最后还是感谢硬禾学堂,得捷电子提供的这次体验机会。

 

附件下载
mqtt_over_wifi_nrf7002DK-v2.zip
团队介绍
电子爱好者
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号