第一次写文章,以记录esp32的学习过程,做为学习笔记,因能力有限仅供参考。esp32单片机主要用物联网开发,学习它必然绕不过wifi的tcp和udp协议,ble的gatt规范等。学习过程中也遇到了很多困扰了几天的问题,现在把它记录下来,供网友参考。

在这个项目中,目的实现三块esp-wroom32分别作tcp服务器a、客户端b、客户端c,还有手机或电脑做客户端,实现sock通信,手机或电脑控制服务器a和客户端b、c的led灯状态。在局域网中单片机与手机都由DHCP自动分配ip地址,单片机所创建的tcp服务器ip也被绑定为本地ip地址,也就是说tcp服务器ip地址是随机的,客户端也就无法连接服务器。有的解决办法是将服务器设置为静态IP地址,但如果出现同一个ip地址可能无法连接AP网络。此篇文章中采用另一种办法,tcp服务器中在创建一个udp广播,向局域网中广播自己tcp服务器IP地址和端口号,客户端在指定端口接收广播并解析出服务器的ip和端口,连接服务器进行tcp的sock通信。

做这个小项目时主要遇到的问题是udp广播地址是什么,接收方如何接收,如何将本地IP地址转化为广播地址。现在有了答案,通常的广播地址为ipv4局域网中最后一段的地址改为255,比如192.168.1.255。广播套接字所设置的端口为接收方的目标端口号,如果发送方发送的数据的目标端口号与接收方绑定的本地端口号匹配,那么接收方就可以接收到该数据。下面为具体的相关代码:

代码中参考了官方例程wifi_sta和tcp_server等,

首先定义tcp服务器的端口号,udp目标接收方的端口号,一个全局变量以保存本地IP地址:

#define UDP_SERVER_PORT             8848
#define BROADCAST_PORT              8080
#define BROADCAST_INTERVAL_MS       5000
#define DEVICE_NAME             "ESP32_SERVER"

esp_ip4_addr_t ip_addr;

在自定义的wifi事件回调函数中获取并保存本地IP地址:

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < WIFI_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");

    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));

        ip_addr = event->ip_info.ip;    //保存本地IP地址

        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

接下来就是udp的广播任务:

/* UDP广播任务 */
void udp_broadcast_task()
{  
    // 将IPv4地址转换为字符串格式的IP地址
    char ip_addr_str[INET_ADDRSTRLEN];
    inet_ntoa_r(ip_addr.addr, ip_addr_str, sizeof(ip_addr_str));

    // 设置广播地址和接收方的端口号
    struct sockaddr_in broadcast_addr = {
        .sin_addr.s_addr = ip_addr.addr | htonl(0xFF), //广播地址,其类型为uint32_t
        .sin_family = AF_INET,
        .sin_port = htons(BROADCAST_PORT),
    };
    // 将广播地址转换为字符串
    ESP_LOGI(TAG, "broadcast_addr IP: %s", inet_ntoa(broadcast_addr.sin_addr.s_addr));

    // Create a socket for UDP broadcast
    int broadcast_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (broadcast_socket < 0) {
        ESP_LOGE(TAG, "Failed to create socket: %d", broadcast_socket);
        goto error;
    }


    // 设置套接字选项以启用地址重用
    int reuseEnable = 1;
    setsockopt(broadcast_socket, SOL_SOCKET, SO_REUSEADDR, &reuseEnable, sizeof(reuseEnable));

    // 使能广播
    int broadcast_enable = 1;
    if (setsockopt(broadcast_socket, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {
        ESP_LOGE(TAG, "Failed to enable broadcasting");
        close(broadcast_socket);
        goto error;
    }

    // Send the broadcast message every BROADCAST_INTERVAL_MS milliseconds
    while (1) {
        // 创建一个 JSON 对象,并添加三个属性
        cJSON *json = cJSON_CreateObject();
        cJSON_AddStringToObject(json, "device", DEVICE_NAME);
        cJSON_AddStringToObject(json, "ip", ip_addr_str);
        cJSON_AddNumberToObject(json, "port", UDP_SERVER_PORT);

        // 将 JSON 数据转换为字符串
        char *json_str = cJSON_Print(json);

        ESP_LOGI(TAG, "Broadcasting message: %s", json_str);
        int ret = sendto(broadcast_socket, json_str, strlen(json_str), 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
        if (ret < 0) {
            ESP_LOGE(TAG, "Failed to send broadcast message: %d", ret);
        }
        cJSON_Delete(json);
        free(json_str);
        vTaskDelay(BROADCAST_INTERVAL_MS / portTICK_PERIOD_MS);
    }

error:
    vTaskDelete(NULL);
}

此项目代码应还包括设置sta连接wifi,和tcp服务器的任务,参考官方例程即可解决,这里不再给出。最后其广播效果:

esp32 UDP发送数据例程 esp32 udp广播_mcu

 客户端及手机便可以根据广播内容,连接tcp服务器,进行通信操作等。在调试过程中还发现包含5GHZ频段wifi的路由器,esp32无法在局域网通信,即使用的2.4GHZ的频段,只知道esp32无5GHZ的协议,具体原因尚不明白,更换AP就好了。

路虽远,行则将至。

事虽难,做则必成。