机器人设计与应用综合实训——ESP32开发技术分享

本文为机器人设计与应用综合实训中,基于ESP32的开发技术分享帖,主要记录实训过程中的ESP32开发要点、问题排查及实践总结,适配实训报告补充、技术复盘及同学间交流使用。

核心方向:ESP32芯片基础应用、机器人控制模块开发、传感器数据交互、代码调试与优化,贴合实训核心需求,兼顾技术细节与实操记录。

一、实训基础信息

实训项目名称

ESP32 WIFI联网与天气信息显示开发

实训时间

本次实训当日

ESP32开发板型号

ESP32-S3

开发环境

VSCode+ESP-IDF v5.4.2

实训小组人数

1人

二、ESP32开发核心知识点梳理

知识点类别

具体知识点

核心原理简述

实训应用场景<br/>(机器人相关)

备注<br/>(易错点/重点)

引脚配置

WIFI外设引脚复用

ESP32-S3内置WIFI模块,芯片已规划专用引脚实现WIFI射频通信,无需手动配置物理引脚,仅需软件使能

机器人联网通信、远程数据传输与控制

不可随意修改WIFI专用引脚配置,避免射频功能失效

外设驱动

WIFI STA模式驱动

STA模式下ESP32作为客户端连接无线路由器等AP设备,通过驱动程序实现网络扫描、连接、数据收发

机器人获取网络时间、访问网络天气接口

需正确配置WIFI账号密码,确保与AP的2.4G频段匹配

通信协议

HTTP客户端协议

基于TCP/IP协议栈,通过HTTP GET/POST请求向网络服务器发送数据并获取响应结果

机器人从天气服务器请求气象数据

需替换为自己的天气接口密钥,请求格式需匹配服务器要求

代码开发

NVS闪存初始化

NVS(非易失性存储)用于保存WIFI连接信息等配置,初始化后可实现数据掉电保存

WIFI连接信息持久化,避免重复配置

初始化失败时需执行擦除操作后重新初始化

代码开发

FreeRTOS任务调度与CPU释放

通过vTaskDelay实现延时,释放CPU资源,避免看门狗因主线程阻塞触发复位

WIFI联网、数据请求过程中的延时处理

网络操作处必须添加CPU释放操作,防止程序卡死

其他(自定义)

网络时间同步

通过网络协议从时间服务器获取当前UTC时间,转换为本地时间

机器人时钟模块显示实时时间

同步时添加延时,避免网络卡顿导致程序异常

三、ESP32机器人开发实操步骤

步骤序号

实操内容<br/>(ESP32相关)

操作步骤细节

使用工具/代码片段

操作结果<br/>(成功/失败及原因)

1

开发环境搭建

确认VSCode中ESP-IDF v5.4.2配置正常,打开已有工程,检查工程结构完整性

VSCode、ESP-IDF插件

成功,工程可正常编译,无环境配置报错

2

ESP32开发板调试

连接ESP32-S3开发板与电脑Type-C口,确认设备管理器识别串口,测试开发板供电与复位功能

Type-C数据线、电脑设备管理器

成功,串口识别正常,开发板复位无异常

3

外设与ESP32连接

无需物理外设连接,仅在工程中添加WIFI组件文件夹,包含wifi.c和wifi.h驱动文件

文件资源管理器

成功,WIFI组件文件添加至工程指定目录

4

代码编写与烧录

1. 在CMakeLists.txt中添加WIFI头文件访问路径;<br/>2. 在main.c中引入wifi.h,调用Wifi_STA_Init()初始化WIFI;<br/>3. 修改wifi.c中WIFI账号密码(电脑共享2.4G热点);<br/>4. 替换天气接口密钥,解除http_post_request()注释;<br/>5. 添加网络时间初始化ESP_Network_Init(),编写时间显示代码;<br/>6. 编译并通过UART方式烧录程序

#include "wifi.h"<br/>Wifi_STA_Init();<br/>ESP_Network_Init();<br/>ESP-IDF编译、烧录按钮

成功,程序无编译错误,烧录过程无失败提示

5

机器人功能调试

1. 开启电脑2.4G共享热点,确保开发板联网;<br/>2. 打开ESP-IDF监视器,查看WIFI连接日志;<br/>3. 检查LCD屏是否显示实时网络时间;<br/>4. 验证天气数据是否正常请求并解析

ESP-IDF监视器、LCD显示屏

成功,WIFI正常连接热点,LCD实时显示时间,天气信息可正常获取

6

功能优化与完善

在WIFI连接、网络时间同步、天气请求代码处添加vTaskDelay(10)释放CPU资源;关闭无用的天气数据冗余获取代码

vTaskDelay(10);

成功,程序运行稳定,无看门狗复位、卡死现象

四、ESP32开发常见问题及解决方案

问题序号

问题描述(ESP32相关)

排查过程

解决方案

问题总结(避免方法)

1

WIFI初始化失败,提示NVS相关错误

查看监视器报错信息,定位为nvs_flash_init()返回错误码

在Wifi_STA_Init()中添加NVS擦除逻辑:<br/>if (ret== ESP_ERR_NVS_NO_FREE_PAGES

2

烧录后开发板无法连接WIFI,提示连接超时

1. 检查热点频段是否为2.4G(ESP32-S3部分版本不支持5G);<br/>2. 核对wifi.c中账号密码是否与热点一致

1. 将电脑共享热点切换为2.4G频段;<br/>2. 准确修改WIFI账号密码,无空格、大小写错误

确保WIFI热点为2.4G,配置信息与热点完全一致,避免因频段、密码问题导致连接失败

3

程序运行中出现看门狗复位,串口提示任务阻塞

排查代码,发现网络时间同步、WIFI数据请求处无CPU释放操作,主线程长期占用CPU

在while循环和网络操作相关代码处添加vTaskDelay(10),定期释放CPU资源

所有耗时操作(网络、外设通信)处必须添加延时释放CPU,避免触发看门狗机制

4

天气数据无法获取,提示请求失败

检查天气接口密钥是否过期,http_post_request()函数是否被注释

1. 替换为自己的有效天气接口密钥;<br/>2. 解除wifi.c中http_post_request()的注释,确保请求函数正常调用

使用专属的接口密钥,确认功能函数未被注释,保证网络请求代码正常执行

5

LCD屏无时间显示,程序无其他报错

检查main.c中是否调用ESP_Network_Init(),时间解析与显示代码是否完整

1. 添加网络时间初始化代码ESP_Network_Init();<br/>2. 完善时间解析代码:timeinfo = *localtime(timer_sec);,确保LCD标签赋值正常

确认功能初始化代码完整,数据解析与外设显示的代码逻辑无缺失

五、实训总结与ESP32开发心得

项目完成情况

本次实训目标为实现ESP32-S3的WIFI联网、网络时间同步与天气信息显示,所有功能均成功实现,程序运行稳定,LCD屏可实时显示时间,能正常从网络接口获取天气数据,无卡顿、复位等异常现象

ESP32开发重点收获

1. 掌握了ESP32-S3 WIFI STA模式的开发流程,包括驱动移植、配置与初始化;<br/>2. 理解了NVS闪存的作用与初始化方法,解决了NVS相关的初始化异常问题;<br/>3. 学会了基于ESP-IDF的HTTP客户端开发,能通过网络请求获取服务器数据;<br/>4. 掌握了FreeRTOS中CPU资源释放的技巧,避免看门狗复位问题;<br/>5. 理解了工程中CMakeLists.txt的配置规则,学会添加头文件路径与源文件;<br/>6. 掌握了网络时间同步的实现方法,能将网络时间解析并显示在LCD屏

存在的不足

1. 对HTTP协议的细节理解不够深入,仅能调用现成函数实现请求,无法自主编写复杂的网络请求代码;<br/>2. 对ESP32-S3的WIFI底层驱动原理了解较少,仅能完成上层应用开发;<br/>3. 天气数据仅实现了基础获取,未进行数据解析与格式化显示,功能较为基础;<br/>4. 程序的异常处理不够完善,未考虑热点断开、网络卡顿等极端情况的应对

后续改进计划

1. 学习HTTP协议的详细原理,尝试自主编写GET/POST请求代码,实现更灵活的网络通信;<br/>2. 深入学习ESP32 WIFI底层驱动,了解射频通信、网络协议栈的相关知识;<br/>3. 完善天气数据的解析与格式化,将温度、湿度、天气状况等信息分区域显示在LCD屏;<br/>4. 添加网络异常处理代码,实现WIFI断连重连、请求超时提示等功能;<br/>5. 尝试将WIFI功能与机器人其他模块结合,实现远程控制、数据上传等更复杂的功能

六、代码附录

1. CMakeLists.txt添加WIFI头文件路径关键代码

file(GLOB_RECURSE SOURCES ./*.c hello_world_main.c)

set(INCLUDE_DIRS "." 
            "LCD"
            "Timer"
            "batch"
            "ui/generated"
            "ui/custom"
            "ui/generated/guider_customer_fonts"
            "WIFI"
)

idf_component_register(SRCS ${SOURCES} 
                INCLUDE_DIRS ${INCLUDE_DIRS}
                )

2. main.cWIFI与网络时间初始化关键代码

/*
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "LCD/LCD.h"
#include "tig.h"
#include "Timer.h"
#include "demos/lv_demos.h"
#include "gui_guider.h"
#include "custom.h"
#include "wifi.h"

lv_ui guider_ui;
void app_main(void)
{
    bsp_i2c_init();         // IIC接口初始化
    pca9557_init();         // 扩展口初始化
    // bsp_lcd_init();      // LCD初始化
    ESP_Timer_Init();       // 定时器初始化
    bsp_lvgl_start();       // LVGL初始化
    Wifi_STA_Init();        // WIFI初始化
    ESP_Network_Init();

    setup_ui(&guider_ui);
    custom_init(&guider_ui);
    uint32_t sec = timer_sec;
    while(1){
        if(sec != timer_sec){       // 每秒进入一次
            sec = timer_sec;
            char str[50]={0};
            sprintf(str,"%02d:%02d:%02d",timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec);
            lv_label_set_text(guider_ui.screen_label_1, str);
            printf("天气: %s\n",weather[0].rain);
            printf("温度: %s\n",weather[0].temp);
        }
        vTaskDelay(10);      // 释放CPU资源1ms
    }
}

3. wifi.cNVS初始化与天气请求关键代码

#include <stdio.h>
#include "wifi.h"

EventGroupHandle_t wifi_event_group = NULL;
static uint8_t WiFi_retry_num = 0;

// 今天~后天的数据
WEATHER_Type weather[3];

static bool gl_get_finish = false;
char send_buf[256] = {0};

/**
 * wifi事件回调函数
 */
void  wifi_event_handler (void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    if (event_base == WIFI_EVENT){	//WIFI事件
        switch (event_id){
            case WIFI_EVENT_STA_START://WIFI开始连接
                ESP_LOGI(TAG,"WIFI正在连接");
                esp_wifi_connect();//重新连接
            break;
            case WIFI_EVENT_STA_CONNECTED://WIFI已连接
                ESP_LOGI(TAG,"WIFI连接成功");
            break;
            case WIFI_EVENT_STA_DISCONNECTED://WIFI已断开
                vTaskDelay(2000 / portTICK_PERIOD_MS);
                ESP_LOGI(TAG,"WIFI正在连接");
                esp_wifi_connect();//重新连接
            break;
            default:break;
        }
    }
    if (event_base == IP_EVENT){	//IP事件
        switch (event_id){
            case IP_EVENT_STA_GOT_IP:
		    ip_event_got_ip_t *ip_event = (ip_event_got_ip_t *)event_data;
			ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&ip_event->ip_info.ip));
            xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);//设置WIFI已连接
            break; 
        }
    }
}

void WifiConfig(void)
{
    wifi_event_group = xEventGroupCreate();//创建事件组
    esp_netif_init();//初始化网络
    esp_event_loop_create_default();//创建默认事件组
    esp_netif_t *interface = esp_netif_create_default_wifi_sta();//创建默认WIFI STA
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,  wifi_event_handler, NULL);//注册WIFI事件
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL); //注册IP事件
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//初始化WIFI
	ESP_ERROR_CHECK(esp_wifi_init(&cfg));//初始化WIFI
	ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//设置WIFI模式为STA

    uint8_t mac[8];
    esp_efuse_mac_get_default(mac);
    char hostname[32] = {0};
    sprintf(hostname, "ESP32-S3-%02X%02X", mac[4], mac[5]);
    esp_netif_set_hostname(interface, hostname);

	wifi_config_t wifi_config = {
		.sta = {
			.ssid = WiFi_STA_SSID,
			.password = WiFi_STA_PASSWORD,
			.bssid_set = 0,
		}};

	ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
	ESP_ERROR_CHECK(esp_wifi_start());
	ESP_LOGI(TAG, "wifi_init_sta finished.");
    ESP_LOGI(TAG, "wifi start connect to AP.");
    while (true){ //等待WIFI连接成功
        EventBits_t bits = xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
        if (bits & WIFI_CONNECTED_BIT){
            break;
        }
    }
    ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",WiFi_STA_SSID, WiFi_STA_PASSWORD);
}

// JSON解析函数
static void cJSON_parse_task(char *text)
{
	char *date, *Temp_High = NULL, *Temp_Low, *humidity, *Rainfall;

	cJSON *root, *arrayItem, *subArray;
	cJSON *arr_item, *sub_array_item;
	cJSON *JsonDate, *JsonTemp_High, *JsonTemp_Low, *JsonHumidity, *JsonText_day;
	root = cJSON_Parse(text);
	if (root != NULL){
		arrayItem = cJSON_GetObjectItem(root, "results");	//获取数组大小
		int arr_size = cJSON_GetArraySize(arrayItem);		//获取数组大小
		ESP_LOGI(HTTP_TAG, "root_arr_size: %d \n", arr_size);

		cJSON *first_result  = cJSON_GetArrayItem(arrayItem, 0);
		cJSON *location = cJSON_GetObjectItem(first_result, "location");
		cJSON *name = cJSON_GetObjectItem(location, "name");
		if (cJSON_IsString(name)){
			ESP_LOGI(RESULT_TAG, "%s ", name->valuestring);
		}

		arr_item = arrayItem->child;//获取数组第一个元素
		for (int i = 0; i < arr_size; i++){
			subArray = cJSON_GetObjectItem(arr_item, "daily");
			int sub_array_size = cJSON_GetArraySize(subArray);
			sub_array_item = subArray->child;

			ESP_LOGI(HTTP_TAG, "sub_arr_size: %d \n", sub_array_size);
			for (int j = 0; j < sub_array_size; j++){	
				printf("\t %d组数据解析开始 \n", j);
				// ESP_LOGE(HTTP_TAG, "Data IS json");
				if (sub_array_item->type == cJSON_Object){
					JsonDate = cJSON_GetObjectItem(sub_array_item, "date");
					if (cJSON_IsString(JsonDate)){
						date = JsonDate->valuestring;//获取时间
						sprintf(weather[j].date, " %s ", date);
						ESP_LOGI(RESULT_TAG, "%s ", weather[j].date);			
					}
					
					JsonTemp_High = cJSON_GetObjectItem(sub_array_item, "high");
					if (cJSON_IsString(JsonTemp_High))
						Temp_High = JsonTemp_High->valuestring;

					JsonTemp_Low = cJSON_GetObjectItem(sub_array_item, "low");
					if (cJSON_IsString(JsonTemp_Low)){
						Temp_Low = JsonTemp_Low->valuestring;
						sprintf(weather[j].temp, " %s-%s ", Temp_Low, Temp_High);
						ESP_LOGI(RESULT_TAG, "低-高温度:%s", weather[j].temp);
					}
					
					JsonHumidity = cJSON_GetObjectItem(sub_array_item, "humidity");
					if (cJSON_IsString(JsonHumidity)){
						humidity = JsonHumidity->valuestring;
						sprintf(weather[j].humi, " %s ", humidity);						
						ESP_LOGI(RESULT_TAG, "湿度:%s", weather[j].humi);
					}
					
					JsonText_day = cJSON_GetObjectItem(sub_array_item, "text_day");
					if (cJSON_IsString(JsonText_day)){
						Rainfall = JsonText_day->valuestring;
						sprintf(weather[j].rain, " %s ", Rainfall);					
						ESP_LOGI(RESULT_TAG, "%s", weather[j].rain);
					}

					JsonText_day = cJSON_GetObjectItem(sub_array_item, "code_day");
					if (cJSON_IsString(JsonText_day)){
						Rainfall = JsonText_day->valuestring;
						sscanf(Rainfall, "%d", &weather[j].code);
						ESP_LOGI(RESULT_TAG, "%d", weather[j].code);
					}
					vTaskDelay(2000 / portTICK_PERIOD_MS);
				}
				sub_array_item = sub_array_item->next;
			}
			arr_item = arr_item->next;
		}
		printf("\n");
		ESP_LOGI(HTTP_TAG, "Finish");
		gl_get_finish = true;
	}
	cJSON_Delete(root);
}

static void http_post_request(void)
{
    char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; // Buffer to store response of http request
	int content_length = 0;
	static const char *URL = "http://" HOST "/v3/weather/daily.json?"
							 "key=" UserKey "&location=" Location
							 "&language=" Language
							 "&unit=c&start=" Strat "&days=" Days;
	/*https://api.seniverse.com/v3/weather/daily.json?key=So18Lkv4F4hn2d3aT&location=nanning&language=zh-Hans&unit=c&start=0&days=5*/
	esp_http_client_config_t config = {
		.url = URL,
	};

	esp_http_client_handle_t client = esp_http_client_init(&config);

	// GET Request
	esp_http_client_set_method(client, HTTP_METHOD_GET);

	esp_err_t err = esp_http_client_open(client, 0);// 打开HTTP连接
	if (err != ESP_OK){
		ESP_LOGE(HTTP_TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
	}else{
		content_length = esp_http_client_fetch_headers(client);
		if (content_length < 0){
			ESP_LOGE(HTTP_TAG, "HTTP client fetch headers failed");
		}else{
			int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
			if (data_read >= 0){
				ESP_LOGI(HTTP_TAG, "HTTP GET Status = %d, content_length = %lld",
						 esp_http_client_get_status_code(client),
						 esp_http_client_get_content_length(client));
				// 打印天气信息
				ESP_LOGI(WiFi_TAG, "\n\n\n %s \n", output_buffer);
				// JSON解析
				cJSON_parse_task(output_buffer);
			}else{
				ESP_LOGE(HTTP_TAG, "Failed to read response");
			}
		}
	}
	esp_http_client_close(client);
}

void Wifi_STA_Init(void)
{
    // 初始化NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );
    WifiConfig();
    http_post_request();		// 请求天气数据
}


static void http_post_GLM(void)
{
    char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};

    esp_http_client_config_t config = {
        .url = "https://open.bigmodel.cn/api/paas/v4/chat/completions",
        // .event_handler = http_event_handler, // 添加事件处理
        // .timeout_ms = 80000,                // 增加超时到80秒
        // .skip_cert_common_name_check = true, // 跳过证书CN检查
		// .auth_type = HTTP_AUTH_TYPE_NONE,
        // .cert_pem = NULL,                   // 不指定证书
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    const char *json_data =
    "{"
        "\"model\":\"GLM-4-Flash-250414\","
        "\"messages\":["
            "{"
                "\"role\": \"user\","
                "\"content\": \"你好\"" // 填入问题内容
            "}"
        "]"
    "}";

    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_header(client, "Authorization", "76b5d293386f4a88b7d44e0cbc5f97fd.jCCXsqlgsHvJ9NyO");//key_API
    esp_err_t err = esp_http_client_open(client, strlen(json_data));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "连接失败: %s", esp_err_to_name(err));
        esp_http_client_cleanup(client);
        esp_http_client_close(client);
    }

    int wlen = esp_http_client_write(client, json_data, strlen(json_data));
    if (wlen < 0) {
        ESP_LOGE(TAG, "数据写入失败");
        esp_http_client_cleanup(client);
        esp_http_client_close(client);
    }

    int content_length = esp_http_client_fetch_headers(client);
    if (content_length < 0) {
        ESP_LOGE(TAG, "获取头部信息失败");
        esp_http_client_cleanup(client);
        esp_http_client_close(client);
    }

    int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
    if (data_read >= 0) {
        ESP_LOGI(TAG, "HTTP状态码 = %d, 内容长度 = %"PRIu64,
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
        ESP_LOGI(TAG, "响应内容: %s", output_buffer);
    } else {
        ESP_LOGE(TAG, "读取响应失败");
    }

    esp_http_client_cleanup(client);
    esp_http_client_close(client);
}
static void http_task(void *pvParameters)
{
    http_post_GLM();
    ESP_LOGI(TAG, "HTTP示例完成");
    vTaskDelete(NULL);
}
void Wifi_GLM_Init(void)
{
    // 初始化NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );
    WifiConfig();
     // 创建HTTP任务
    xTaskCreate(&http_task, "http_task", 8192, NULL, 5, NULL); 
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐