找回密码
 立即注册
首页 业界区 业界 ESP32掌控终端项目(详细+长篇+源码)

ESP32掌控终端项目(详细+长篇+源码)

钱艷芳 2025-6-3 00:36:01
ESP32掌控终端项目(详细+长篇+源码)

项目涉及技术栈:
LVGL,MQTT,HTTP,FreeRTOS,摄像头,蓝牙,SD卡读取,ESP-ADF音频框架,网路获取天气,网路获取实时时间
lvgl基础函数可看我另一篇随笔
LVGL 8.3.0开发实战:高频函数速查与移植避坑指南 - 沁拒离 - 博客园
逐步实现整体框架

1、显示文本标签
  1.     // 创建页面容器
  2.     page1 = lv_obj_create(lv_scr_act());  
  3.     lv_obj_add_style(page1, &style, 0);       
  4.     lv_obj_add_flag(page1, LV_OBJ_FLAG_HIDDEN); // 初始隐藏   
  5.        
  6.   // 创建一个标签对象,并将其添加到页面1中
  7.     lv_obj_t *title = lv_label_create(page1);
  8.     // 设置标签的文本为“应用”
  9.     lv_label_set_text(title, "应用");
  10.     // 为标签对象添加样式
  11.     lv_obj_add_style(title, &style, LV_PART_MAIN);
  12.     // 将标签对象对齐到页面的顶部中间位置,并设置偏移量为0和10
  13.     lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
复制代码
简单图解:
1.png
2.png
流程:
先创建页面容器(page1),在页面容器(page1)中再创建标题容器(title),文本在标题容器中显示
2、显示中文字符

一、生成中文字体文件


  • 使用在线转换工具
    LVGL 官方提供了 在线字体转换工具,支持将 TTF/WOFF 字体文件转换为 LVGL 可用的 .c 格式字体文件125。

    • 参数设置

      • 字体大小:建议 16px 及以上,避免显示模糊68。
      • BPP(抗锯齿):推荐选择 4 位,提升显示效果46。
      • 字符范围:选择所需汉字范围(如 0x4E00-0x9FFF 表示常用汉字),或手动输入特定字符810。
      • 压缩选项:勾选 Compressed 可减小字体体积36。

    二、配置项目与代码

  • 添加字体文件到工程

    • 将生成的 .c 文件(如 font_alipuhui20.c)放入 工程
      3.png

    在CMakeLists.txt中添加进编译
    4.png


menuconfig配置

在 lv_conf.h 中声明字体并启用多字体字符支持:
  1. LV_FONT_FMT_TXT_LARGE
复制代码
5.png
        选项的中文解释翻译:
6.png
启用字体字号

7.png
设置字体样式

1、全局样式设置(推荐        ):
  1. // 创建页面样式
  2.     static lv_style_t style;   
  3.     lv_style_init(&style);
  4.     lv_style_set_text_font(&style, &font_alipuhui20);  // 设置全局字体样式
复制代码
2、单句样式设置:
  1.     // 创建扫描状态标签
  2.     lv_obj_t *page1_1 = lv_label_create(page1);
  3.     lv_label_set_text(page1_1, "WLAN 扫描了...");
  4.     lv_obj_set_style_text_color(page1_1, lv_color_hex(0x000000), 0); // 设置文本颜色为黑色
  5.     lv_obj_set_style_text_font(page1_1, &font_alipuhui20, 0);
复制代码
3、插入图片

图片转化网址
图像转换器 — LVGL --- Image Converter — LVGL
8.png

将生成图片.c源文件添加进工程
9.png
  1.     /**************** Logo显示逻辑 ****************/
  2.     // 创建独立Logo容器(直接放在根屏幕)
  3.     logo_container = lv_obj_create(lv_scr_act());
  4.     lv_obj_remove_style_all(logo_container); // 移除logo_container的所有样式
  5.     lv_obj_set_size(logo_container, LV_PCT(100), LV_PCT(100)); // 全屏
  6.     lv_obj_set_layout(logo_container, LV_LAYOUT_FLEX);    // 设置logo_container的布局为flex布局
  7.     // 设置logo_container的flex对齐方式为居中对齐
  8.     lv_obj_set_flex_align(logo_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
  9.     // 创建Logo图片
  10.     LV_IMG_DECLARE(Pic2);//图片源文件(.c)
  11.     lv_obj_t *logo_img = lv_img_create(logo_container);// 创建lvgl图片对象,在容器内
  12.     lv_img_set_src(logo_img, &Pic2);//设置图片对象图像源
复制代码
10.png
4、开机旋转logo制作
  1. void lv_gui_animation_start() // 显示开机界面
  2. {
  3. /****************开机logo ****************/
  4.     // 创建全屏临时容器(确保覆盖整个屏幕)
  5.     lv_obj_t *logo_pag_pre = lv_obj_create(lv_scr_act());
  6.     lv_obj_remove_style_all(logo_pag_pre);       // 清除默认样式
  7.     lv_obj_set_size(logo_pag_pre, LV_PCT(100), LV_PCT(100)); // 全屏尺寸
  8.     lv_obj_set_layout(logo_pag_pre, LV_LAYOUT_FLEX); // 启用弹性布局
  9.     lv_obj_set_flex_align(logo_pag_pre, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); // 内容居中
  10.     // // 声明并创建Logo图片
  11.     LV_IMG_DECLARE(Pic2);
  12.     lv_obj_t *logo_img = lv_img_create(logo_pag_pre);
  13.     lv_img_set_src(logo_img, &Pic2);
  14.     // 精确居中控制(双重保障)
  15.     lv_obj_center(logo_img);                    // 弹性布局居中
  16.     lv_obj_align(logo_img, LV_ALIGN_CENTER, 0, 0); // 绝对坐标居中
  17.     // lv_img_set_pivot(logo_img, 60, 60); // 设置图片旋转中心   
  18.     lv_img_set_pivot(logo_img, 100, 100); // 设置图片旋转中心   
  19.     /****************开机logo动画****************/
  20.     // // // 设置旋转动画
  21.     lv_anim_t a; // 创建动画变量
  22.     lv_anim_init(&a); // 初始化动画变量
  23.     lv_anim_set_var(&a, logo_img); // 动画变量赋值为logo图片
  24.     lv_anim_set_exec_cb(&a, set_angle); // 创建设置角度的回调函数
  25.     lv_anim_set_values(&a, 0, 3600); // 设置动画旋转角度的开始值和结尾值
  26.     lv_anim_set_time(&a, 200); // 设置转一圈的周期是200毫秒
  27.     lv_anim_set_repeat_count(&a, 5); // 设置旋转5次
  28.     lv_anim_start(&a); // 动画开始
  29.     // 延迟2秒后删除logo_pag_pre对象
  30.     lv_obj_del_delayed(logo_pag_pre, 2000);
  31.     // 给logo_pag_pre对象添加删除事件回调函数
  32.     lv_obj_add_event_cb(logo_pag_pre, logo_del_cb, LV_EVENT_DELETE, NULL);
  33. ///////////////////////////////
  34. }
复制代码
流程:创建全屏动画容器页面(logo_pag_pre),在lvgl中声明并创建Logo图片,为图片添加动画,动画结束删除容器页面
效果
11.png
12.png
13.png
14.png
5、制作应用

5.1、制作应用图标

例如原神”应用“的制作
首页应用图标点击进入应用效果
15.png
16.png
简要说明:“应用”为按键+图片实现。在按键上覆盖图片(应用图标),按键按下触发对应按键事件(进入应用)
  1.         /****************应用4,原神 ****************/
  2.     // 创建第4个应用的按键
  3.     lv_obj_t *icon4 = lv_btn_create(page1);//创建按钮
  4.     lv_obj_add_style(icon4, &btn_style, 0);
  5.     lv_obj_set_style_bg_color(icon4, lv_color_hex(0xd8b010), 0);
  6.     lv_obj_set_pos(icon4, 15, 147);
  7.     lv_obj_add_event_cb(icon4, yuanshen_event_handler, LV_EVENT_CLICKED, NULL);//按键回调(应用触发)函数为yuanshen_event_handler(),需用户编写实际功能
  8.     // 为按钮添加图片(使其看起来像应用)
  9.     lv_obj_t * img4 = lv_img_create(icon4);
  10.     // 声明一个图像
  11.     LV_IMG_DECLARE(yuanshen);
  12.     // 设置图像对象显示的图像
  13.     lv_img_set_src(img4, &yuanshen);
  14.     lv_obj_align(img4, LV_ALIGN_CENTER, 0, 0);
复制代码
其中图标的通用样式(btn_style):
  1.            // 设置应用图标通用样式
  2.     static lv_style_t btn_style;
  3.     // 初始化按钮样式
  4.     lv_style_init(&btn_style);
  5.     // 设置按钮圆角半径为16
  6.     lv_style_set_radius(&btn_style, 16);  
  7.     // 设置按钮背景透明度为100%
  8.     lv_style_set_bg_opa( &btn_style, LV_OPA_COVER );
  9.     // 设置按钮文字颜色为白色
  10.     lv_style_set_text_color(&btn_style, lv_color_hex(0xffffff));
  11.     // 设置按钮边框宽度为0
  12.     lv_style_set_border_width(&btn_style, 0);
  13.     // 设置按钮内边距为5
  14.     lv_style_set_pad_all(&btn_style, 5);
  15.     // 设置按钮宽度为80
  16.     lv_style_set_width(&btn_style, 80);  
  17.     // 设置按钮高度为80
  18.     lv_style_set_height(&btn_style, 80);
复制代码
5.2、应用触发回调编写

以应用4,原神为例,设计应用点击进入应用:原神加载界面(用图片实现)
首先按键回调(应用触发)函数为yuanshen_event_handler()
  1. /*********************** 应用4原神回调 ****************************/
  2. void yuanshen_event_handler(lv_event_t * e)
  3. {
  4.     // 创建一个界面
  5.     static lv_style_t style;
  6.     lv_style_init(&style);
  7.     lv_style_set_radius(&style, 10);  
  8.     lv_style_set_bg_opa( &style, LV_OPA_COVER );
  9.     lv_style_set_bg_color(&style, lv_color_hex(0xffffff));
  10.     lv_style_set_border_width(&style, 0);
  11.     lv_style_set_pad_all(&style, 0);
  12.     lv_style_set_width(&style, 320);  
  13.     lv_style_set_height(&style, 240);
  14.     // 创建一个图标对象,并将其添加到当前活动屏幕上
  15.     app_page = lv_obj_create(lv_scr_act());
  16.     // 将样式添加到图标对象上
  17.     lv_obj_add_style(app_page, &style, 0);
  18.    
  19.     // // 声明并创建图片
  20.     LV_IMG_DECLARE(yuanshenqidong);
  21.     // lv_obj_t *logo_img = lv_img_create(logo_pag_pre);
  22.     lv_obj_t *logo_img = lv_img_create(app_page);   
  23.     lv_img_set_src(logo_img, &yuanshenqidong);
  24.     // 精确居中控制(双重保障)
  25.     lv_obj_center(logo_img);                    // 弹性布局居中
  26.     lv_obj_align(logo_img, LV_ALIGN_CENTER, 0, 0); // 绝对坐标居中
  27.     //退出应用部分暂略,后续添加(5.3添加应用退出按钮)
  28. }
复制代码
进入应用效果:
17.png
5.3添加应用退出按钮

18.png
  1.     // 创建标题背景
  2.     // 创建一个对象,用于显示标题
  3.     lv_obj_t *att_title = lv_obj_create(app_page);
  4.     // 设置对象的大小
  5.     lv_obj_set_size(att_title, 40, 40);
  6.     // 设置对象间隙
  7.     lv_obj_set_style_pad_all(att_title, 0, 0);  // 设置间隙
  8.     // 对齐对象
  9.     lv_obj_align(att_title, LV_ALIGN_TOP_LEFT, 0, 0);
  10.     // 设置对象背景颜色
  11.     lv_obj_set_style_bg_color(att_title, lv_color_hex(0xffffff), 0);
  12.    // 创建后退按钮
  13.     btn_bk_back = lv_btn_create(att_title);
  14.     lv_obj_align(btn_bk_back, LV_ALIGN_LEFT_MID, 0, 0);
  15.     lv_obj_set_size(btn_bk_back, 60, 30);
  16.     lv_obj_set_style_border_width(btn_bk_back, 0, 0); // 设置边框宽度
  17.     lv_obj_set_style_pad_all(btn_bk_back, 0, 0);  // 设置间隙
  18.     lv_obj_set_style_bg_opa(btn_bk_back, LV_OPA_TRANSP, LV_PART_MAIN); // 背景透明
  19.     lv_obj_set_style_shadow_opa(btn_bk_back, LV_OPA_TRANSP, LV_PART_MAIN); // 阴影透明
  20.     lv_obj_add_event_cb(btn_bk_back, btn_home_back_cb, LV_EVENT_CLICKED, NULL); // 添加按键处理函数
  21.     // 创建后退按钮符号
  22.     lv_obj_t *label_back = lv_label_create(btn_bk_back);  // 创建一个标签对象,并将其与btn_bk_back关联
  23.     lv_label_set_text(label_back, LV_SYMBOL_LEFT);  // 按键上显示左箭头符号
  24.     lv_obj_set_style_text_font(label_back, &lv_font_montserrat_20, 0); // 设置标签的字体为lv_font_montserrat_20
  25.     lv_obj_set_style_text_color(label_back, lv_color_hex(0xA9A9A9), 0);  // 设置标签的字体颜色为深灰
  26.     lv_obj_align(label_back, LV_ALIGN_CENTER, -10, 0); // 将标签对象居中对齐,并设置x轴偏移量为-10
复制代码
5.4退出按钮回调函数
  1. // 应用返回主界面按钮事件处理函数
  2. void btn_home_back_cb(lv_event_t * e)
  3. {
  4.         //按应用区别添加相应操作(释放内存,删除应用运行中的任务等......)
  5.     lv_obj_del(app_page); // 删除画布
  6.     icon_flag = 0;//应用标识,0为主页面
  7. }
复制代码
由于应用回调函数是在容器(画布)app_page中创建,删除容器,则会显示在其底层的容器(page1主界面),以此实现退出应用,按应用的情况在退出回调中应该添加相应操作如(释放内存,删除应用运行中的任务等......)。
6、wifi连接
  1. #include "esp_wifi.h"   
  2. #include "protocol_examples_common.h"
  3. // 添加网络接口相关头文件
  4. #include "esp_netif.h"
  5. // 确保事件循环头文件存在
  6. #include "esp_event.h"
  7. // 包含示例连接功能的头文件
  8. #include "nvs_flash.h"
  9. #include <esp_system.h>
  10. //// 连接WiFi(menuconfig中配置)
  11. void wifi_connect(void)
  12. {
  13.     ESP_ERROR_CHECK(nvs_flash_init());       // 初始化非易失存储
  14.     ESP_ERROR_CHECK(esp_netif_init());       // 初始化网络堆栈
  15.     ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建事件循环
  16.     // 检查example_connect函数的返回值,确保连接成功
  17.     ESP_ERROR_CHECK(example_connect());      // 连接WiFi
  18. }
复制代码
wifi连接使用库函数快速实现。
在menuconfig中配置wifi账号与密码(在文章后面menuconfig部分有详细记录)
7、MQTT应用

该应用控制MQTT设备云设备,
需要添加protocol_examples_common组件库
protocol_examples_common介绍:
"protocol_examples_common.h"//用于存放与协议(Protocol)相关的通用示例代码或共享组件,为开发者提供可复用的模块或模板,帮助快速理解和使用项目中涉及的通信协议(如 HTTP、TCP、MQTT 等)。
工程位置添加内容
19.png
20.png
先看现象:
点击亮灯图标点击灭灯图标掌机终端打印:
21.png
掌机终端打印:
22.png
23.png
24.png
这里使用掌机终端推送MQTT主题,另一块esp32核心板订阅相同主题,并解析数据,做出对应指令操作(亮/灭灯)
详细完整代码在文章结尾,这里不做详细的讲解,仅介绍代码关键的部分。
推送主题
  1. //推送主题
  2. void put_led_on() {
  3.     // if (mqtt_connected) {
  4.         const char *status = "led_on";//推送主题
  5.         esp_mqtt_client_publish(mqtt_client, SUB_TOPIC, status, strlen(status), 0, 0);
  6.         // esp_mqtt_client_publish(mqtt_client, "ledstate", status, strlen(status), 0, 0);
  7.         ESP_LOGI(TAG3, "led_on");
  8.     // }
  9. }
复制代码
绑定绑定推送主题事件。由应用MQTT内的点击事件触发。
25.png

订阅主题
  1. //----------------MQTT订阅-----------------------//
  2. esp_mqtt_client_subscribe(mqtt_client, SUB_TOPIC, 0);//订阅主题ledctrl
复制代码
26.png

处理MQTT接收事件(部分)
  1. static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
  2.                               int32_t event_id, void *event_data) {
  3.     esp_mqtt_event_handle_t event = event_data;
  4.     switch (event->event_id) {
  5. case MQTT_EVENT_DATA: {//<MQTT接收数据事件>
  6.             // 处理接收数据
  7.             //----------------判断接收主题-----------------------------------------------------------------//
  8.             char topic[event->topic_len + 1];
  9.             memcpy(topic, event->topic, event->topic_len);
  10.             topic[event->topic_len] = '\0';
  11.             //----------------判断接收数据-----------------------------------------------------------------//
  12.             char data[event->data_len + 1];
  13.             memcpy(data, event->data, event->data_len);
  14.             data[event->data_len] = '\0';
  15.             //----------------打印接收主题+数据-----------------------------------------------------------------//
  16.             ESP_LOGI(TAG3, "Received: Topic=%s, Data=%s", topic, data);
  17.             
  18.             lv_label_set_text_fmt(label_topic, "Topic: %s", topic);
  19.             lv_label_set_text_fmt(label_data, "Data: %s", data);
  20.             if (strcmp(topic, SUB_TOPIC) == 0) {//判断接收主题(ledctrl)数据——>控制led灯->推送led状态信息
  21.                 if (strcmp(data, "on") == 0) {
  22.                     gpio_set_level(OUTPUT_PIN, 0);
  23.                     //推送led状态信息到主题ledstate
  24.                     esp_mqtt_client_publish(mqtt_client, PUB_TOPIC3, "led设备已开启", 0, 0, 0);//同时推送状态到主题ledstate
  25.                 } else if (strcmp(data, "off") == 0) {
  26.                     gpio_set_level(OUTPUT_PIN, 1);
  27.                     esp_mqtt_client_publish(mqtt_client, PUB_TOPIC3, "led设备已关闭", 0, 0, 0);
  28.                 }
  29.             }
  30.             break;
  31. }
复制代码
接收到订阅的主题数据,可以判断内容后执行相应的用户操作(例如:点灯,调节屏幕亮度,启动/关闭外设......)
12、HTTP综合应用

12.1、HTTP获取实时时间

使用淘宝的时间戳获取API
  1. #define WEB_SERVER "acs.m.taobao.com"
  2. // 定义WEB服务器端口
  3. #define WEB_PORT "80"
  4. // 定义WEB服务器路径
  5. #define WEB_PATH "/gw/mtop.common.getTimestamp/"
复制代码
通过更改ESP32官方http get exampl ,实现获取该网址时间戳,获取成功后通过CJson解析出时间戳
编写时间戳转化函数,转化成时间
注意获取到的时间戳是单位:毫秒
时区是UT8?需要转化为东八市区(北京时间)
将获取的时间转化为本地时间保存(好处是不用再次使用网络获取,仅需从本地获取就可得到准确的时间,节省算力和网路带宽)
我将其显示到主页面顶部实时刷新
27.png
  1. void show_time_task(void *pvParameter)
  2. {
  3.         // 设置时区为中国标准时间(东八区)
  4.     setenv("TZ", "CST-8", 1);
  5.     tzset();
  6.     // 已经从网络或HTTP中获取了时间戳(单位:毫秒)
  7.     time_t base_time = g_timestamp_ms / 1000;  // 修正:毫秒转秒
  8.     int64_t base_us = esp_timer_get_time(); // 获取时的微秒数(系统运行时间)
  9.     while (1) {
  10.         int64_t now_us = esp_timer_get_time();
  11.         time_t now = base_time + (now_us - base_us) / 1000000;  // 当前时间戳(秒)
  12.         
  13.         struct tm timeinfo;
  14.         // 将now时间转换为本地时间,并存储在timeinfo中
  15.         localtime_r(&now, &timeinfo);
  16.         // char time_save_buf[64];
  17.         strftime(time_save_buf, sizeof(time_save_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
  18.         printf("现在时间:%s\n", time_save_buf);
  19.         vTaskDelay(1000 / portTICK_PERIOD_MS); // 每秒打印一次
  20.     }
  21. }
复制代码
LVGL的刷新显示

需注意,想要实时显示时间,需要刷新显示时间的容器
这里使用使用LVGL定时器回调函数进行刷新显示(1s)
创建定时器:
  1.     //使用LVGL(Light and Versatile Graphics Library)创建一个定时器
  2.     lv_timer_create(update_time_cb, 1000, NULL);  // 每 1000ms 更新一次
复制代码
定时器回调
  1. //更新日期显示(1s回调)
  2. void update_time_cb(lv_timer_t *timer)
  3. {
  4.     if (time_label) {//time_label为显示时间的容器
  5.         lv_label_set_text(time_label, time_save_buf);//将时间重新写入容器
  6.     }
  7. }
复制代码
12.2、HTTP获取天气信息

与上文获取时间流程基本一致,源码过长不做展示
效果:
28.png
流程:
通过http发送获取本地位置信息(城市),再将城市信息添加进请求URL中向心知天气API请求,获取到天气信息通过CJson解析出城市和气温保存到字符数组,将字符数组显示到界面上
idf_component.yml配置
  1. ## IDF Component Manager Manifest File
  2. dependencies:
  3.   espressif/esp32-camera: "^2.0.10" # 摄像头驱动
  4.   lvgl/lvgl: "~8.3.0"
  5.   espressif/esp_lvgl_port: "~1.4.0" # LVGL接口
  6.   espressif/esp_lcd_touch_ft5x06: "~1.0.6" # 触摸屏驱动
  7.   protocol_examples_common: # MQTT
  8.     path: ${IDF_PATH}/examples/common_components/protocol_examples_common
复制代码
更改完idf_component.yml配置需要重新选择芯片,才能下载组件,选完芯片需要重新配置menuconfig
menuconfig配置

配置Flash大小
Flash size
29.png
配置使用分区表
Partition Table

30.png
分区表
partitions.csv
  1. # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
  2. # Name,   Type, SubType, Offset,  Size, Flags
  3. nvs,      data, nvs,     0x9000,  24k
  4. phy_init, data, phy,     0xf000,  4k
  5. factory,  app,  factory, ,        8M
  6. storage,  data, spiffs,  ,        3M
复制代码
wifi配置
配置账号密码
31.png
lvgl配置
选择14,24号字体
32.png
Font:
选择大字体
33.png
若是遇到色彩失真:
设置LVGL色彩:
34.png
Component config

LVGL configuration

Color settings

35.png
结束语(源码):

博主目前在广州,深圳找实习。
如有大佬(HR)能推荐实习,恳请私信我(主页侧边栏有wx二维码),帮帮小弟,万分感谢
由于添加的组件和图片过多,上传不了github(限制25MB),就不上传了,我放网盘
代码运行需先配置wifi联网,配置MQTT连接参数,心知天气秘钥
否则得先注释网络部分
36.png
wifi扫描连接(博主代码填入配置连接自动),音乐播放器,蓝牙HID还未移植,可自行移植,或移植立创开发板实战派s3掌机例程相关部分
链接:https://caiyun.139.com/w/i/2nFZ6kCfsGdhz
提取码:gu6c
复制内容打开移动云盘PC客户端,操作更方便哦
37.png
如果文章对你有所帮助,可以帮我点一下左下角推荐该文,万分感谢
博主目前在广州,深圳找实习。
如有大佬(HR,BOSS)能推荐实习,恳请私信小弟给个机会,帮帮小弟,万分感谢

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册