【Funpack2-6】使用nRF7002-DK开发板完成WIFI控制和检测功能
nRF7002-DK提供WIFI连接网络功能,使用MQTT服务器远程连接板卡,实现操控LED、检测Button信息
标签
嵌入式系统
Funpack活动
开发板
hkhll
更新2023-10-09
1047

一、基本描述

首先介绍下nRF7002-DK开发板,这款包含了Nordic公司最新推出的基于低功耗双频WIFI 6协议的nRF7002芯片,以及作为主处理器的nRF5340芯片,两者通过QSPI总线通信连接。板上还支持蓝牙、NFCThread等无线协议。官方提供的产品特性如下:

  • 用于nRF7002双频带Wi-Fi 6配套IC的开发套件
  • nRF5340 SoC主机器件
  • Wi-Fi 6 (IEEE 802.11 a/b/g/n/ac/ax)、蓝牙低功耗 (LE)、蓝牙网状网络、802.15.4ThreadZigbee®ANT2.4GHz专有和NFC无线协议支持
  • 2.4GHz5GHz芯片和NFC天线
  • SWF射频连接器
  • SEGGER J-Link板载编程器/调试器
  • 用户可编程LED (2x) 和按钮 (2x)
  • 用于测量功耗的引脚
  • 来自USB、外部或锂聚合物电池的2.9V5.0V电源
  • Arduino连接器

再介绍一下本项目采用的软件开发平台,由Nordic官方推出的nRF Connect SDK,搭配安装在Visual Studio Code的官方插件组合构成。Visual Studio Code插件集工具链管理、代码维护、固件烧录、Debug调试为一体,很是方便。通过下载nRF Connect for Desktop软件,打开后下载工具链管理工具,在工具链管理工具里下载对应的nRF Connect SDK,这里采用的是2.4.1版本。详细安装方法见Installation — nRF Connect SDK 2.4.99 documentation (nordicsemi.com)

FgEUnRIYq34ljVHEkxSc9g36D_GC

Fjh5CxUibUGYmtyzkf60856puGFd

nRF Connect SDK的软件代码是基于开源的Zephyr实时操作系统和Nordic官方提供的代码,并采用Zyphyr的west工具进行代码管理。这样很多Zyphyr的代码案例,同样也适用于Nordic的开发板。可以理解成nRF Connect SDK是在Zyphyr软件代码的基础上,集成了Nordic自身维护开发的模块。配置好的SDK代码根目录以及nrf目录信息如下:

Fhpq3jWVwYWyjAIyjuoPutw-Pmf6Fi4zkOkekQQm4HnYScLr3knGlb-Z

本项目通过WIFI连接开发板,通过MQTT协议,实现远程控制LED灯,和获取按键信息。MQTT本身是开源协议,也有很多开源服务器软件、客户端软件。本次使用的是Mosquitto软件Eclipse Mosquitto,消息服务器使用的是官方提供的版本,也可自行搭建Mosquitto服务器。

mqtt-fidge-2.svg

nRF7002-DK开发板分别提供可编程的两个LED和两个按键,远程操作可选择点亮或者关闭LED1或LED2;在板卡上按下按键1或按键2时,在远程能收到消息,说明当面按下的是哪个按键。具体分解下本次任务:远程控制LED是远程将控制信令从MQTT服务端发出,到达开发板所在的MQTT客户端,这里开发板使用的是MQTT的订阅模式;而远程获取按键信息,是开发板作为MQTT服务端,通过按下对应按键,触发MQTT的发布模式,在远端做好对应的订阅设置,从而实现在远程接收传递的按键信息,这里开发板使用的是MQTT的发布模式。

Mosquitto提供的服务器地址是公开可用的,别的设备也会连接使用,也可能会使用相同的发送或订阅主题名称,这时就要确定唯一的设备标识来区分不同设备发出或者接收的消息。设置MQTT代理服务器地址为test.mosquitto.org,设备标识也可以不填,默认是开发板的MAC地址。另外可以定义发布和订阅的主题,这里用默认的就行。

CONFIG_MQTT_SAMPLE_TRANSPORT_BROKER_HOSTNAME="test.mosquitto.org"
CONFIG_MQTT_SAMPLE_TRANSPORT_CLIENT_ID="There_is_another_nrf7002dk"
CONFIG_MQTT_SAMPLE_TRANSPORT_PUBLISH_TOPIC="my/publish/topic"
CONFIG_MQTT_SAMPLE_TRANSPORT_SUBSCRIBE_TOPIC="my/subscribe/topic"

 

开发板连接的WIFI信息,同样也是通过配置文件设置,需要后续写入到编译生成的固件之中。如下第一个配置表示是否启用静态WIFI,后面两个配置是设置静态WIFI的名称和密码。

CONFIG_WIFI_CREDENTIALS_STATIC=y
CONFIG_WIFI_CREDENTIALS_STATIC_SSID="xxx"
CONFIG_WIFI_CREDENTIALS_STATIC_PASSWORD="password"

二、软件流程

这里主要参考MQTT — nRF Connect SDK 2.4.99 documentation (nordicsemi.com)的案例实现。代码上将功能分成多个模块来实现,Trigger模块用于定时器触发或者按键触发信号发送;Sampler模块用于订阅Trigger发出的消息,进行过滤转发给Payload通道;Transport用于管理MQTT网络,接收MQTT订阅消息,或者发送Payload通道提供的MQTT发布消息;Network模块用于根据配置维护开机后的联网状态。

Architecture

Trigger模块的主要代码为trigger_task函数里面开机初始化LED以及按键信息,为按键设置回调触发函数button_hander,在检测到按键状态信息发生改变时,通过message_send函数给Trigger通道发送消息。

static void message_send(void)
{
	int not_used = -1;
	int err;

	err = zbus_chan_pub(&TRIGGER_CHAN, &not_used, K_SECONDS(1));
	if (err) {
		LOG_ERR("zbus_chan_pub, error: %d", err);
		SEND_FATAL_ERROR();
	}
}

static void button_handler(uint32_t button_states, uint32_t has_changed)
{
	if (has_changed & button_states) {
		message_send();
	}
}

static void trigger_task(void)
{
	if (dk_leds_init() != 0) {
		LOG_ERR("Failed to initialize the LED library");
	}
	
	int err = dk_buttons_init(button_handler);

	if (err) {
		LOG_ERR("dk_buttons_init, error: %d", err);
		SEND_FATAL_ERROR();
		return;
	}
}

Sampler模块订阅Trigger通道上发出的消息,对收到的进行过滤、组装,再将组装后的消息发送给Payload通道。

#define FORMAT_STRING "Current pressed button is: %d!"
static void sample(void)
{
	struct payload payload = { 0 };
	uint32_t uptime = k_uptime_get_32();
	uint32_t buttonpin = dk_get_buttons();
	int err, len;
	len = snprintk(payload.string, sizeof(payload.string), FORMAT_STRING, buttonpin);
	if ((len < 0) || (len >= sizeof(payload))) {
		LOG_ERR("Failed to construct message, error: %d", len);
		SEND_FATAL_ERROR();
		return;
	}
	err = zbus_chan_pub(&PAYLOAD_CHAN, &payload, K_SECONDS(1));
	if (err) {
		LOG_ERR("zbus_chan_pub, error:%d", err);
		SEND_FATAL_ERROR();
	}
}

Transport模块主要有两个功能,一个是订阅Payload通道的消息,作为MQTT服务端,将组装后的消息发布出去,具体是将消息发送给MQTT代理服务器;另外是作为MQTT的消息订阅端,订阅从MQTT代理服务器发送的消息,接收后进行处理。on_mqtt_publish函数是接收MQTT订阅消息处理的地方,在这里根据字符串匹配控制LED的状态。on_mqtt_publish函数是作为回调函数在transport_task函数中使用,在transport_task函数内还订阅接收来自Payload通道的消息,组装后发送给MQTT代理服务器。

#define CONFIG_TURN_LED1_ON "Turn LED1 on"
#define CONFIG_TURN_LED1_OFF "Turn LED1 off"
#define CONFIG_TURN_LED2_ON "Turn LED2 on"
#define CONFIG_TURN_LED2_OFF "Turn LED2 off"
static void on_mqtt_publish(struct mqtt_helper_buf topic, struct mqtt_helper_buf payload)
{
	LOG_INF("Received payload: %.*s on topic: %.*s", payload.size, payload.ptr, topic.size, topic.ptr);

	if(strncmp(payload.ptr, CONFIG_TURN_LED1_ON, payload.size) == 0){
		dk_set_led_on(DK_LED1);
	}
	else if(strncmp(payload.ptr, CONFIG_TURN_LED1_OFF, payload.size) == 0){
		dk_set_led_off(DK_LED1);
	}
	else if(strncmp(payload.ptr, CONFIG_TURN_LED2_ON, payload.size) == 0){
		dk_set_led_on(DK_LED2);
	}
	else if(strncmp(payload.ptr, CONFIG_TURN_LED2_OFF, payload.size) == 0){
		dk_set_led_off(DK_LED2);
	}
}

static void transport_task(void)
{
	int err;
	const struct zbus_channel *chan;
	enum network_status status;
	struct payload payload;
	struct mqtt_helper_cfg cfg = {
		.cb = {
			.on_connack = on_mqtt_connack,
			.on_disconnect = on_mqtt_disconnect,
			.on_publish = on_mqtt_publish,
			.on_suback = on_mqtt_suback,
		},
	};

	/* Initialize and start application workqueue.
	 * This workqueue can be used to offload tasks and/or as a timer when wanting to
	 * schedule functionality using the 'k_work' API.
	 */
	k_work_queue_init(&transport_queue);
	k_work_queue_start(&transport_queue, stack_area,
			   K_THREAD_STACK_SIZEOF(stack_area),
			   K_HIGHEST_APPLICATION_THREAD_PRIO,
			   NULL);

	err = mqtt_helper_init(&cfg);
	if (err) {
		LOG_ERR("mqtt_helper_init, error: %d", err);
		SEND_FATAL_ERROR();
		return;
	}

	/* Set initial state */
	smf_set_initial(SMF_CTX(&s_obj), &state[MQTT_DISCONNECTED]);

	while (!zbus_sub_wait(&transport, &chan, K_FOREVER)) {

		s_obj.chan = chan;

		if (&PAYLOAD_CHAN == chan) {

			err = zbus_chan_read(&PAYLOAD_CHAN, &payload, K_SECONDS(1));
			if (err) {
				LOG_ERR("zbus_chan_read, error: %d", err);
				SEND_FATAL_ERROR();
				return;
			}

			s_obj.payload = payload;
			LOG_INF("Current payload: %.*s", sizeof(payload.string), payload.string);
			err = smf_run_state(SMF_CTX(&s_obj));
			if (err) {
				LOG_ERR("smf_run_state, error: %d", err);
				SEND_FATAL_ERROR();
				return;
			}
		}
	}
}

三、功能展示

1、分别按下开发板的按键1和按键2,能通过mosquitto_sub.exe接收到订阅信息,显示当前按下的是哪个按钮。

FrtmEERslLnAuSfdq6xWJTH9ahLm

2、使用mosquitto_pub.exe向MQTT代理服务器发送点亮LED1消息,LED1随后被点亮。

FpNpB0du8O1PunT0xZZjE5ZgK5dd

3、使用mosquitto_pub.exe向MQTT代理服务器发送点亮LED2消息,LED2随后被点亮。

FooqjDK-1JfU5rmBFhmT5Iy-i9c4

4、使用mosquitto_pub.exe向MQTT代理服务器发送关闭LED1消息,LED1随后被关闭。

FuITN4QfE7k3jo6ojU9hzoqOghSa

四、心得体会

近期也在折腾其他arm板卡,深度感受到了设备树概念的方便之处,只需要有原本的设计原理图,新增的外设也仅需要编辑设备树,实现硬件到软件上的支持。这一点也在nRF7002-DK开发板上有体现,开发板是基于Zephyr RTOS软件系统上运行,结构功能上是类似linux内核。平台自身提供了丰富的案例,方便对功能进行裁剪、整合。

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