Funpack2-6 NRF7002-DK 远程控制板卡LED和读取按键信息
基于NRF7002-DK,实现WiFi连接功能,连接网络,并实现远程控制板卡LED和读取按键信息
标签
Funpack活动
物联网
Wi-Fi 6
Sanyaa
更新2023-10-12
967

            基于NRF7002-DK实现WiFi连接功能并远程控制板卡LED和读取按键信息

一.项目介绍

本项目依托Funpack第二季第六期,基于NRF7002-DK平台,实现WiFi连接功能并远程控制板卡LED和读取按键信息。本项目通过7002WiFi芯片、nrf5340 Soc搭建WiFi-Station模式,实现WiFi连接并创建TCP Server,支持client端连接,远程控制Server端LED和读取Server端按键信息

项目任务:

  1. NRF7002-DK板WiFi连接,实现网络通信
  2. 创建TCP Server,支持cilent客户端连接
  3. 通过client端,远程控制板卡上的LED和读取按键信息

二. 设计思路

本项目设计围绕WIFI芯片nRF7002,搭载主控芯片nRF5340 SOC,双核处理器协同工作,实现WiFi连接,建立网络通信,远端控制外围设备LED和读取按键信息。

  1. nRF5340与nRF7002建立通信,使用QSPI通信协议
  2. 创建WiFi连接,连接路由器,建立通信
  3. 创建TCP Server,监听Client端连接
  4. Client端远程控制板卡LED和读取按键信息

三. 开发流程

1.搭建开发环境

使用官方提供的软件nRF Connect SDK(https://www.nordicsemi.cn/tools/nrfconnectsdk/),也可以通过下载官方软件nRF Connect for Desktop搭建,如下:Fg53aumGu-ntTENycalpa0NW0cXF

open:

Fgsv3OXBuqyDKDJhTt8giyqlJE9e

我安装的是:v2.4.0,之后打开就可以在vscode上创建项目了

Fl-rHlx0Li3sCyPoakkRnjV5HXNO

2.设计开发流程

程序流程:

Ftq4Mj1RjpgAQdQR399TcqD6y3Nn

3.创建项目,编写代码

  • 硬件LED和Button初始化,主要配置gpio信息,LED-gpio配置为输出模式,button-gpio配置为输入模式,同button加入中断,当按键被按下时,触发中断,发送按下事件
static void button_isr(const struct device *port, struct gpio_callback *cb, uint32_t pin_msk)
{
	int ret;
	struct button_msg msg;

	if (debounce_is_ongoing) {
		LOG_DBG("Btn debounce in action");
		return;
	}

	uint32_t btn_pin = 0;
	uint32_t btn_idx = 0;

	ret = pin_msk_to_pin(pin_msk, &btn_pin);
	ERR_CHK(ret);

	ret = pin_to_btn_idx(btn_pin, &btn_idx);
	ERR_CHK(ret);

	LOG_INF("Pushed button idx: %d pin: %d name: %s", btn_idx, btn_pin,
		btn_cfg[btn_idx].btn_name);

	msg.button_pin = btn_pin;
	msg.button_action = BUTTON_PRESS;

	ret = k_msgq_put(&button_queue, (void *)&msg, K_NO_WAIT);
	if (ret == -EAGAIN) {
		LOG_WRN("Btn msg queue full");
	}

	debounce_is_ongoing = true;
	k_timer_start(&button_debounce_timer, K_MSEC(CONFIG_BUTTON_DEBOUNCE_MS), K_NO_WAIT);
}
int button_handler_init(void)
{
	int ret;

	if (ARRAY_SIZE(btn_cfg) == 0) {
		LOG_WRN("No buttons assigned");
		return -EINVAL;
	}

	gpio_53_dev = DEVICE_DT_GET(DT_NODELABEL(gpio1));

	if (!device_is_ready(gpio_53_dev)) {
		LOG_ERR("Device driver not ready.");
		return -ENODEV;
	}

	for (uint8_t i = 0; i < ARRAY_SIZE(btn_cfg); i++) {
		ret = gpio_pin_configure(gpio_53_dev, btn_cfg[i].btn_pin,
					 GPIO_INPUT | btn_cfg[i].btn_cfg_mask);
		if (ret) {
			return ret;
		}

		gpio_init_callback(&btn_callback[i], button_isr, BIT(btn_cfg[i].btn_pin));

		ret = gpio_add_callback(gpio_53_dev, &btn_callback[i]);
		if (ret) {
			return ret;
		}

		ret = gpio_pin_interrupt_configure(gpio_53_dev, btn_cfg[i].btn_pin,
						   GPIO_INT_EDGE_TO_INACTIVE);
		if (ret) {
			return ret;
		}
	}

	return 0;
}
void led_init(void)
{
	int ret = -1;
	if (led_0.port && !device_is_ready(led_0.port)) {
		LOG_ERR("Error %d: LED device %s is not ready; ignoring it",
		       ret, led_0.port->name);
		led_0.port = NULL;
	}
	if (led_0.port) {
		ret = gpio_pin_configure_dt(&led_0, GPIO_OUTPUT);
		if (ret != 0) {
			LOG_ERR("Error %d: failed to configure LED device %s pin %d",
			       ret, led_0.port->name, led_0.pin);
			led_0.port = NULL;
		} else {
			LOG_INF("Set up LED at %s pin %d", led_0.port->name, led_0.pin);
		}
	}


	if (led_1.port && !device_is_ready(led_1.port)) {
		LOG_ERR("Error %d: LED device %s is not ready; ignoring it",
		       ret, led_1.port->name);
		led_1.port = NULL;
	}
	if (led_1.port) {
		ret = gpio_pin_configure_dt(&led_1, GPIO_OUTPUT);
		if (ret != 0) {
			LOG_ERR("Error %d: failed to configure LED device %s pin %d",
			       ret, led_1.port->name, led_1.pin);
			led_1.port = NULL;
		} else {
			LOG_INF("Set up LED at %s pin %d", led_1.port->name, led_1.pin);
		}
	}
}
  • 创建WiFi connect线程,循环等待,直到WiFi连接路由器,采用AP模式。
  • Wifi_Configuration_Reader函数主要是配置路由器信息,比如SSID,password,WPA2等等
  • net_mgmt函数建立路由器连接。静态DHCP,配置IP192.168.1.101
/*! Wifi_Configuration_Reader reads the configuration arguments recorded on
* the configuration file, and stores them on a struct.
* @param[in] struct wifi_connect_req_params *parameters struct containing 
*                   the WiFi configuration info.
* @return int, 0 when the function is executed successfully.
*/
static int Wifi_Configuration_Reader(                                       \
							struct wifi_connect_req_params *parameters ){
	parameters -> timeout = SYS_FOREVER_MS;

	/* SSID */
	parameters -> ssid = CONFIG_STA_SAMPLE_SSID;
	parameters -> ssid_length = strlen( parameters -> ssid );

#if defined( CONFIG_STA_KEY_MGMT_WPA2 )
	parameters -> security = 1;
#elif defined( CONFIG_STA_KEY_MGMT_WPA2_256 )
	parameters -> security = 2;
#elif defined( CONFIG_STA_KEY_MGMT_WPA3 )
	parameters -> security = 3;
#else
	parameters -> security = 0;
#endif

#if !defined( CONFIG_STA_KEY_MGMT_NONE )
	parameters -> psk = CONFIG_STA_SAMPLE_PASSWORD;
	parameters -> psk_length = strlen( parameters -> psk );
#endif
	parameters -> channel = WIFI_CHANNEL_ANY;

	/* MFP (optional) */
	parameters -> mfp = WIFI_MFP_OPTIONAL;

	return 0;
}
/*! Wifi_Connect runs the connection with the WiFi provider. 
* @param[in] void
* @return int, 0 when the function is executed successfully.
*/
static int Wifi_Connect( void ) {
	struct net_if *interface = net_if_get_default();
	static struct wifi_connect_req_params connectionParameters;

	context.connected = false;
	context.connect_result = false;
	Wifi_Configuration_Reader ( &connectionParameters );

	if (net_mgmt(                                                           \
                NET_REQUEST_WIFI_CONNECT,                                   \
				interface,                                                  \
		     	&connectionParameters,                                      \
				sizeof( struct wifi_connect_req_params ))) {
		LOG_ERR( "Connection request failed" );
		return -ENOEXEC;
	}

	LOG_INF( "Connection requested" );

	return 0;
}
/*! Wifi_Stationing implements the task WiFi Stationing.
* 
* @brief Wifi_Stationing makes the complete connection process, while 
*       printing by LOG commands the connection status.
*       This function is used on an independent thread.
*/
void Wifi_Stationing( void ){
    int i;
	memset( &context, 0, sizeof( context ));
	
	// net_config_init();
	net_mgmt_init_event_callback(                                           \
                                &wifiEventsCallback,                        \
                                Handle_Wifi_Events,                         \
                                SHELL_EVENTS_FLAG );

	net_mgmt_add_event_callback( &wifiEventsCallback );

	while ( 1 ) {
		Wifi_Connect();

		for ( i = 0; i < TIMEOUT_MS; i++ ) {
			k_sleep( K_MSEC( STATUS_POLLING_MS ));
			Cmd_Wifi_Status();
			if ( context.connect_result ) {
				break;
			}
		}
		if ( context.connected ) {
			LOG_INF( "============" );
			k_sleep( K_FOREVER );
		}
		else if ( !context.connect_result ) {
			LOG_ERR( "Connection Timed Out" );
		}
	}
}
/*! Task_Wifi_Stationing_Init initializes the task Wifi Stationing.
*
* @brief Wifi Stationing initialization
*/
void Task_Wifi_Stationing_Init( void ){
    

	k_thread_create	(														\
					&wifiThread,										    \
					WIFI_STACK,										        \
					WIFI_STACK_SIZE,									    \
					(k_thread_entry_t)Wifi_Stationing,						\
					NULL,													\
					NULL,													\
					NULL,													\
					WIFI_PRIORITY,									        \
					0,														\
					K_NO_WAIT);	

	 k_thread_name_set(&wifiThread, "wifiStationing");
	 k_thread_start(&wifiThread);
}
  • 创建TCP server 主线程。主要是一些socket的建立,绑定和监听。
  • accept函数等待客户端建立连接,一旦建立连接,会创建两个线程。
void Tcp_Server_Socket_Create()
{
    struct sockaddr_in server_addr;

    server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_sock < 0) {
        LOG_ERR("Failed to create socket");
        return;
    }

	server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(TCP_SERVER_PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        LOG_ERR("Failed to bind");
        close(server_sock);
        return;
    }

    if (listen(server_sock, MAX_CLIENTS) < 0) {
        LOG_ERR("Failed to listen");
        close(server_sock);
        return;
    }

    LOG_INF("TCP server started, listening on port %d",TCP_SERVER_PORT);
}

void Tcp_Server_Tsk(void *p1, void *p2, void *p3)
{
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
   
   memset(&clients,0,sizeof(struct client_info));

   Tcp_Server_Socket_Create();

	while (1) {
       
        int new_client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
        if (new_client_sock < 0) {
            LOG_ERR("Failed to accept new client");
			continue;
        }

        /*如果有一个客户端已经连接,不在建立,直到客户端断开才创建新的连接*/
        if(!clients.connected)	
        {
            clients.sockfd = new_client_sock;
            clients.connected = true;
            LOG_INF("New client connected");	

            k_thread_create(&clients.tx_thread, clients.tx_stack,
                            K_THREAD_STACK_SIZEOF(clients.tx_stack), tx_thread_func,
                            &clients, NULL, NULL, TCP_SEND_PRIORITY, 0, K_NO_WAIT);
            k_thread_name_set(&clients.tx_thread, "tx_thread");
            k_thread_start(&clients.tx_thread);
            

            k_thread_create(&clients.rx_thread, clients.rx_stack,
                            K_THREAD_STACK_SIZEOF(clients.rx_stack), rx_thread_func,
                            &clients, NULL, NULL, TCP_RECV_PRIORITY, 0, K_NO_WAIT);
            k_thread_name_set(&clients.rx_thread, "rx_thread");
            k_thread_start(&clients.rx_thread);		
        } 
	}
    // 关闭服务器套接字
    close(server_sock);
}
/*! Task_TCP_Server_Init initializes the task TCP Server
*
* @brief TCP Server initialization
*/
void Task_TCP_Server_Init( void )
{
    k_thread_create	(													\
					&TcpServerThread,									\
					TCP_SERVER_STACK,									\
					TCP_SERVER_STACK_SIZE,								\
					(k_thread_entry_t)Tcp_Server_Tsk,					\
					NULL,												\
					NULL,												\
					NULL,												\
					TCP_SERVER_PRIORITY,								\
					0,													\
					K_MSEC(20));	

	k_thread_name_set(&TcpServerThread, "TCP Server Tsk");
	k_thread_start(&TcpServerThread);
    
}
  • 与客户端建立连接后,实现远程通信。
  • 一个发送线程用于发送按键信息,一个接收线程用于接收远程控制LED指令
void tx_thread_func(void *p1, void *p2, void *p3) {
    struct client_info *client = (struct client_info *)p1;
    int client_sock = client->sockfd;
	int sentBytes;
	
    struct button_msg msg;
    uint32_t btn_idx = 0;
	
    while (client->connected) 
    {
        k_msgq_get(&button_queue,&msg,K_FOREVER);
        pin_to_btn_idx(msg.button_pin, &btn_idx);

        sprintf(MsgBuff,"Pushed button idx: %d pin: %d name: %s", btn_idx, msg.button_pin,
		    btn_idx == 0 ? "BUTTON_0" : "BUTTON_1");

		send(client_sock,MsgBuff,strlen(MsgBuff),0);
    }
}


void rx_thread_func(void *p1, void *p2, void *p3)
{
    struct client_info *client = (struct client_info *)p1;
    int client_sock = client->sockfd;
    char buff[128];

    while (client->connected)
    {
        // 处理客户端连接上的事件,即客户端发送的数据,有且只有一个客户端同时连接
        int len = recv(client_sock, buff, sizeof(buff), 0);
        if (len > 0) {
            LOG_INF("Received data from client : %s", buff);
            // 处理接收到的数据
            Led_Process(buff);
        } else if (len == 0) {
            LOG_ERR("Client disconnected");
            close(client_sock);
            client->connected = false;
            break;
        } else {
            LOG_ERR("Receive error on client, errno :%d",errno);
            close(client_sock);
            client->connected = false;
            break;
        }     
    }
    
}
  • 工程配置文件prj.conf,主要使能WiFi,开启静态DHCP,配置路由器信息SSID,password
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_WIFI=y
CONFIG_WIFI_NRF700X=y

# WPA supplicant
CONFIG_WPA_SUPP=y

# System settings
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_NANO=n

# Networking
CONFIG_NET_CONFIG_AUTO_INIT=y
CONFIG_NETWORKING=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_LOG=y
CONFIG_NET_IPV6=n
CONFIG_NET_IPV4=y
CONFIG_NET_UDP=n
CONFIG_NET_TCP=y
CONFIG_NET_DHCPV4=n


CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16

# Below section is the primary contributor to SRAM and is currently
# tuned for performance, but this will be revisited in the future.
CONFIG_NET_BUF_RX_COUNT=8
CONFIG_NET_BUF_TX_COUNT=8
CONFIG_NET_BUF_DATA_SIZE=1280
CONFIG_HEAP_MEM_POOL_SIZE=133120


CONFIG_NET_TC_RX_COUNT=1
CONFIG_NET_TC_TX_COUNT=0
CONFIG_NET_MAX_CONTEXTS=10
CONFIG_POSIX_MAX_FDS=20
CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1
CONFIG_NET_IF_MAX_IPV4_COUNT=1

#CONFIG_NET_CONTEXT_NET_PKT_POOL=y
CONFIG_NET_MAX_CONN=5
CONFIG_NET_CONTEXT_SYNC_RECV=y
CONFIG_INIT_STACKS=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_STATISTICS_MLD=n


# Memories
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_NET_RX_STACK_SIZE=8192
CONFIG_NET_TX_STACK_SIZE=8192
CONFIG_NET_TCP_WORKQ_STACK_SIZE=8192

# Debugging
CONFIG_STACK_SENTINEL=y
CONFIG_DEBUG_COREDUMP=y
CONFIG_DEBUG_COREDUMP_BACKEND_LOGGING=y
CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN=y

# Kernel options
CONFIG_ENTROPY_GENERATOR=y
CONFIG_PM=y
CONFIG_PM_DEVICE=y

# Logging
CONFIG_LOG=y

# printing of scan results puts pressure on queues in new locking
# design in net_mgmt. So, use a higher timeout for a crowded
# environment.
CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000

# 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=""
CONFIG_STA_SAMPLE_PASSWORD=""

#client connect tcp server port
CONFIG_TCP_SERVER_PORT=5242

CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1"
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.101"
  • 设备树配置,主要是按键和LED的GPIO相关属性
buttons {
		compatible = "gpio-keys";
		label = "buttons";
		button0: button_0 {
			gpios = <&gpio1 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button 1";
		};
		button1: button_1 {
			gpios = <&gpio1 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button 2";
		};
};
leds {
		compatible = "gpio-leds";
		led0: led_0 {
			gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
			label = "Green LED 0";
		};
		led1: led_1 {
			gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
			label = "Green LED 1";
		};
};

四.功能介绍

当我们在prj.conf把自己的路由器账号和密码配置好使,build没有出错,就可以flash到自己开发版。如下图FmVRYWE-27AcUjAA_8bRPrk-cMWR

程序烧写完成后,串口出现以下信息,表示WiFi连接成功。FuZDmva99rxSiADpsSLxF0Io9dN7

第一步在手机端创建client端,建立连接,显示连接成功。

FnUXRqAIbIypqI6cW0tf3Oe43M21FtRlhykGOnn-gh5oK5OQAryjn9iu

FoshzmNm2B3XEwTXrgQb9H38vReU第二步,远程控制LED,通过客户端发送指令,远程控制LED亮灭。

发送0:ON--控制LED0--亮

发送1:ON--控制LED1--亮

发送0:OFF--控制LED0--灭

发送1:OFF--控制LED1--灭

以下主要展示LED0的亮和灭,LED1操作类似。

Fhp81CKnO6FkuNZDV7lzzKlNkClv

FtY0OnajEX6-SslE0Xvxnb6KHokO

第三步发送按键信息,按下按键,服务端会向客户端远程发送按键信息

按下button0信息:Pushed button idx: 0 pin: 8 name: BUTTON_0

按下button1信息:Pushed button idx: 1 pin: 9 name: BUTTON_1,如下所示:

FrcnelIfn2BvEHy02SGK4XJ92D_7

五.活动总结与心得体会

首先有幸参加这次活动,感谢举办方,可以接触到这款芯片,此芯片具有高性能,低功耗,WiFi 6协同IC开发套件,在物联网市场,智能家居市场有广泛的应用。通过学习这款芯片以及运用官方提供开发套件,为后续物联网的开发,蓝牙技术的运用,以及TCP网络的构建都有巨大的提升。在学习与探索的过程中遇到问题,并通过多方面,多渠道的共同努力,得到问题的解决方法。在探索的过程中不仅学习到了知识,还积累了经验。

其次,虽然完成了这次任务,但也不能局限于这次,nRF7002-DK,官方中提供很多例子,在蓝牙领域,尤其是mesh,还有很多的未知领域有待提升。nRF Connect SDK是nRF5340 SoC的软件开发套件,集成了Zephyr RTOS、协议栈、应用示例和硬件驱动程序。各个方面都非常值得学习,深耕。

最后,让我们撸起袖子,干起来。

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