项目介绍
这里是我参加Funpack第二季第六期活动的任务总结报告,我所完成的是任务二,使用WiFi连接功能,连接网络,并实现远程控制板卡LED和读取按键信息。
我将按照以下步骤进行开发板的控制开发:
-
初始化硬件部分,包括初始化GPIO、UART等。
-
进行WiFi连接。首先,我们配置WiFi SSID和密码,并在连接成功后进行下一步操作。
-
连接MQTT。我们需要配置MQTT代理地址、端口和主题等参数,并在连接成功后进行下一步操作。
-
使用MQTT进行信息的收发。在MQTT连接成功后,我们可以发布和订阅主题,通过MQTT发送和接收信息。
主要硬件介绍
nRF7002 DK是nRF7002 Wi-Fi 6协同IC的开发评估板,它包含了在单板上开发所需要的所有元器件。开发板采用nRF5340多协议SoC作为主处理器,配合nRF7002 Wi-Fi协同芯片。可以同时支持低功耗蓝牙和Wi-Fi 应用的开发,并实现如 OFDMA、波束成形和目标唤醒时间等多项 Wi-Fi 6 功能。
nRF7002是Nordic的Wi-Fi产品系列中的首款器件,符合802.11ax标准,可提供双频段(2.4和5GHz)连接,支持Matter中使用的全部无线协议,可以为产品中添加最新的Wi-Fi 6技术,该芯片还具有帮助保护用户数据的先进安全功能。并与Nordic现有的超低功率技术无缝结合,可延长电池使用寿命。它提供快速、可靠的连接,具有先进的安全功能,并且方便集成到各个应用当中。
主要软件介绍
在软件开发上,我使用的是官方的NRF connect SDK的方式进行开发,官方貌似好像还有另一种SDK,但已经不加入新功能了,所以用这个即可。这个SDK有根据这个NRF7002-DK的各个驱动的例程程序,如wifi连接,mqtt连接部分等,涵盖了对开发板上各种外设的使用,并添加了一些流行的第三方组件这为我们的二次开发提供了极大的便利,无需从底层开始实现,而是可以直接构建在这些示例程序的基础上进行开发。
通过使用官方示例程序包括wifi,mqtt,GPIO等。通过借鉴示例代码,我们可以了解如何正确地使用这些外设及功能,并且直接在我们的项目中应用,而无需自己从头编写底层驱动程序。
SDK可以直接从官方的exe工具的此处打开vscode,进行快读编程开发。
int i;
memset(&context, 0, sizeof(context));
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));
一开始则是wifi连接的一些时间绑定,对于wifi连接过程中出现的各个不同的状态进行不同处理,在WiFi连接过程中可能会出现以下几种不同的状态:
-
连接中:此状态表示正在尝试连接到WiFi网络。
-
连接成功:当WiFi连接成功时,可以执行相应的操作。
-
连接失败:如果连接失败,可能是由于密码错误、网络不可用等问题。可以针对连接失败的情况执行相应的处理。
对于WiFi断开的情况,当连接丢失时,可以采取以下措施:
-
处理断开事件:如果检测到WiFi连接已断开,可以根据实际需求执行相应的操作。可以更新UI,显示连接失败的消息或者切换到重新连接状态。
-
重新连接尝试:在断开事件发生后,可以尝试重新连接WiFi网络。这可能涉及到重新输入密码、扫描附近的WiFi网络等操作。
void bsp_init(){
int ret;
if (!gpio_is_ready_dt(&led0)) {
return 0;
}
ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
if (!gpio_is_ready_dt(&led1)) {
return 0;
}
ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
/*88888888888888888888888888888888888888888888888888888888888888*/
if (!gpio_is_ready_dt(&button0)) {
printk("Error: button device %s is not ready\n",
button0.port->name);
return 0;
}
ret = gpio_pin_configure_dt(&button0, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button0.port->name, button0.pin);
return 0;
}
ret = gpio_pin_interrupt_configure_dt(&button0,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button0.port->name, button0.pin);
return 0;
}
gpio_init_callback(&button0_cb_data, button0_pressed, BIT(button0.pin));
gpio_add_callback(button0.port, &button0_cb_data);
printk("Set up button at %s pin %d\n", button0.port->name, button0.pin);
////////////////////////////////////////////////////////////////////////
if (!gpio_is_ready_dt(&button1)) {
printk("Error: button device %s is not ready\n",
button1.port->name);
return 0;
}
ret = gpio_pin_configure_dt(&button1, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button1.port->name, button1.pin);
return 0;
}
ret = gpio_pin_interrupt_configure_dt(&button1,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button1.port->name, button1.pin);
return 0;
}
gpio_init_callback(&button1_cb_data, button1_pressed, BIT(button1.pin));
gpio_add_callback(button1.port, &button1_cb_data);
printk("Set up button at %s pin %d\n", button1.port->name, button1.pin);
}
再之后,则是板子上的按键即led灯进行初始化,为后续变化做准备。
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;
}
这是wifi连接的开始,直接调用的官方例程。
void connect_mqtt(){
int err;
uint32_t connect_attempt = 0
err = client_init(&client);
if (err) {
LOG_ERR("Failed to initialize MQTT client: %d", err);
return;
}
do_connect:
if (connect_attempt++ > 0) {
LOG_INF("Reconnecting in %d seconds...",
20);
k_sleep(K_SECONDS(20));
}
err = mqtt_connect(&client);
if (err) {
LOG_ERR("Error in mqtt_connect1: %d", err);
goto do_connect;
}
err = fds_init(&client,&fds);
if (err) {
LOG_ERR("Error in fds_init: %d", err);
return;
}
while (1) {
err = poll(&fds, 1, mqtt_keepalive_time_left(&client));
if (err < 0) {
LOG_ERR("Error in poll(): %d", errno);
break;
}
err = mqtt_live(&client);
if ((err != 0) && (err != -EAGAIN)) {
LOG_ERR("Error in mqtt_live: %d", err);
break;
}
if ((fds.revents & POLLIN) == POLLIN) {
err = mqtt_input(&client);
if (err != 0) {
LOG_ERR("Error in mqtt_input: %d", err);
break;
}
}
if ((fds.revents & POLLERR) == POLLERR) {
LOG_ERR("POLLERR");
break;
}
if ((fds.revents & POLLNVAL) == POLLNVAL) {
LOG_ERR("POLLNVAL");
break;
}
}
while(1){
k_sleep(K_MSEC(2000));
LOG_INF("3333333");
}
}
在这段MQTT连接部分的代码中,会包含以下功能:
- 连接到MQTT服务器:
根据用户提供的地址、账号、密码和端口等信息,通过调用MQTT客户端库的连接函数,建立与MQTT服务器的连接。可以使用提供的用户名和密码进行身份验证。
- 订阅相关的Topic:
连接成功后,通过调用MQTT客户端库的订阅函数来订阅用户感兴趣的Topic。这样,客户端就可以接收到来自服务器发布的消息。
- 保持MQTT心跳连接:
为了保持与服务器的连接,使用一个无限循环(while循环),周期性地发送心跳信号。这通常通过调用MQTT客户端库的心跳发送函数来实现。这样,客户端会定期发送心跳包给服务器,以保持连接的活跃状态。
- 处理接收的消息:
在循环的每个迭代中,使用MQTT客户端库的接收函数来接收从服务器发布的消息。你可以解析这些消息,并针对不同的Topic进行相应的处理。例如,你可以将消息显示在终端上,将其存储到数据库中,或者执行其他操作。
- 判断连接状态:
在循环中,通过调用MQTT客户端库的连接状态检查函数,判断与服务器的连接是否断开。如果连接断开,则跳出循环。
- 断开连接后的处理:
如果检测到与MQTT服务器的连接已断开,则在循环外部执行特定的断开连接后的处理操作。例如,输出一条断开连接的提示信息,或者尝试重新连接服务器。
整体而言,该段代码包括了连接到MQTT服务器、订阅Topic、保持心跳连接的while循环部分,以及在连接断开后的提示和处理部分。这样可以确保客户端始终与服务器保持连接,并能够持续接收和处理消息。具体实现方式和函数调用会根据所使用的MQTT客户端库和编程语言而有所不同。
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);
printk("Button pressed at1\n");
gpio_pin_set_dt(&led0,1);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED1_OFF_CMD,sizeof(CONFIG_TURN_LED1_OFF_CMD)-1) == 0){
//dk_set_led_off(DK_LED1);
printk("Button pressed at2\n");
gpio_pin_set_dt(&led0,0);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED2_ON_CMD,sizeof(CONFIG_TURN_LED2_ON_CMD)-1) == 0){
//dk_set_led_on(DK_LED2);
printk("Button pressed at3\n");
gpio_pin_set_dt(&led1,1);
}
else if(strncmp(payload_buf,CONFIG_TURN_LED2_OFF_CMD,sizeof(CONFIG_TURN_LED2_OFF_CMD)-1) == 0){
//dk_set_led_off(DK_LED2);
printk("Button pressed at4\n");
gpio_pin_set_dt(&led1,0);
}
这是另外一段比较重要的地方,mqtt自身的事件中,根据收到的消息进行比较,若与用户的led开关信息一致,则做出相应的变化。
效果
总结
在本次活动中,学习了如何NRF家的相关芯片。在过程中遇到的问题,通过百度搜索都能找到适合的答案,使自我得到了提升,感谢硬禾学堂平台。