基于NRF7002-DK实现WiFi连接功能并远程控制板卡LED和读取按键信息
一.项目介绍
本项目依托Funpack第二季第六期,基于NRF7002-DK平台,实现WiFi连接功能并远程控制板卡LED和读取按键信息。本项目通过7002WiFi芯片、nrf5340 Soc搭建WiFi-Station模式,实现WiFi连接并创建TCP Server,支持client端连接,远程控制Server端LED和读取Server端按键信息
项目任务:
- NRF7002-DK板WiFi连接,实现网络通信
- 创建TCP Server,支持cilent客户端连接
- 通过client端,远程控制板卡上的LED和读取按键信息
二. 设计思路
本项目设计围绕WIFI芯片nRF7002,搭载主控芯片nRF5340 SOC,双核处理器协同工作,实现WiFi连接,建立网络通信,远端控制外围设备LED和读取按键信息。
- nRF5340与nRF7002建立通信,使用QSPI通信协议
- 创建WiFi连接,连接路由器,建立通信
- 创建TCP Server,监听Client端连接
- Client端远程控制板卡LED和读取按键信息
三. 开发流程
1.搭建开发环境
使用官方提供的软件nRF Connect SDK(https://www.nordicsemi.cn/tools/nrfconnectsdk/),也可以通过下载官方软件nRF Connect for Desktop搭建,如下:
open:
我安装的是:v2.4.0,之后打开就可以在vscode上创建项目了
2.设计开发流程
程序流程:
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到自己开发版。如下图
程序烧写完成后,串口出现以下信息,表示WiFi连接成功。
第一步在手机端创建client端,建立连接,显示连接成功。
第二步,远程控制LED,通过客户端发送指令,远程控制LED亮灭。
发送0:ON--控制LED0--亮
发送1:ON--控制LED1--亮
发送0:OFF--控制LED0--灭
发送1:OFF--控制LED1--灭
以下主要展示LED0的亮和灭,LED1操作类似。
第三步发送按键信息,按下按键,服务端会向客户端远程发送按键信息
按下button0信息:Pushed button idx: 0 pin: 8 name: BUTTON_0
按下button1信息:Pushed button idx: 1 pin: 9 name: BUTTON_1,如下所示:
五.活动总结与心得体会
首先有幸参加这次活动,感谢举办方,可以接触到这款芯片,此芯片具有高性能,低功耗,WiFi 6协同IC开发套件,在物联网市场,智能家居市场有广泛的应用。通过学习这款芯片以及运用官方提供开发套件,为后续物联网的开发,蓝牙技术的运用,以及TCP网络的构建都有巨大的提升。在学习与探索的过程中遇到问题,并通过多方面,多渠道的共同努力,得到问题的解决方法。在探索的过程中不仅学习到了知识,还积累了经验。
其次,虽然完成了这次任务,但也不能局限于这次,nRF7002-DK,官方中提供很多例子,在蓝牙领域,尤其是mesh,还有很多的未知领域有待提升。nRF Connect SDK是nRF5340 SoC的软件开发套件,集成了Zephyr RTOS、协议栈、应用示例和硬件驱动程序。各个方面都非常值得学习,深耕。
最后,让我们撸起袖子,干起来。