Nordic是在无线领域非常知名的一家北欧厂商,这次非常感谢Funpack活动提供的宝贵的活动机会,得以体验Noridic第一款Wi-Fi 6产品!本次活动完成的目标任务是:任务二:
一、
1.1 板卡介绍:
nRF7002-DK是用于nRF7002 Wi-Fi 6协同IC的开发套件,该开发套件采用nRF5340多协议片上系统 (SoC) 作为nRF7002的主处理器,在单一的电路板上包含了开发工作所需的一切,可让开发人员轻松开启基于nRF7002 的物联网项目。该 DK 包括 Arduino 连接器、两个可编程按钮、一个 Wi-Fi 双频段天线和一个低功耗蓝牙天线,以及电流测量引脚。
这款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中设置。
细节可以参考官方文档:https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-VS-Code
二、项目思路/流程图
Nordic的开发环境对我来说比较新,从未接触过。因此首先想到的是去官方找一下有没有相关的demo。入手点就是Wi-Fi的sta例程,提供了联网的入门代码。其次是MQTT,Zephyr有相关的MQTT库,熟悉后可以正常调用。Broker,我选择的是安卓手机上可以使用的MQTT DASHBOARD,比较方便的创建Broker以及客户端。
三、连接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论坛有个类似的帖子:
其中有关于上述代码的相关说明,这里引用一下:
The first line in the while(1)-loop is a polling function. Lets have a look at it:
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。
[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.
同时MQTT DASHBOARD也有相关的log:
遇到的问题:
手机上使用的MQTT客户端软件是MQTT DashBoard,当时创建了一个client,测试过程中发现两者只有一个能够连接到Broker。手机客户端一连接,开发板这边就断开连接了。
后来在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,这次了解的比较浅,期待自己能够深入的去了解一下。
最后还是感谢硬禾学堂,得捷电子提供的这次体验机会。