ESP32掌控终端项目(详细+长篇+源码)
项目涉及技术栈:
LVGL,MQTT,HTTP,FreeRTOS,摄像头,蓝牙,SD卡读取,ESP-ADF音频框架,网路获取天气,网路获取实时时间
lvgl基础函数可看我另一篇随笔
LVGL 8.3.0开发实战:高频函数速查与移植避坑指南 - 沁拒离 - 博客园
逐步实现整体框架
1、显示文本标签
c
// 创建页面容器
page1 = lv_obj_create(lv_scr_act());
lv_obj_add_style(page1, &style, 0);
lv_obj_add_flag(page1, LV_OBJ_FLAG_HIDDEN); // 初始隐藏
// 创建一个标签对象,并将其添加到页面1中
lv_obj_t *title = lv_label_create(page1);
// 设置标签的文本为"应用"
lv_label_set_text(title, "应用");
// 为标签对象添加样式
lv_obj_add_style(title, &style, LV_PART_MAIN);
// 将标签对象对齐到页面的顶部中间位置,并设置偏移量为0和10
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
简单图解:
![]() |
![]() |
---|---|
流程:
先创建页面容器(page1),在页面容器(page1)中再创建标题容器(title),文本在标题容器中显示
2、显示中文字符
一、生成中文字体文件
-
使用在线转换工具
LVGL 官方提供了 在线字体转换工具,支持将 TTF/WOFF 字体文件转换为 LVGL 可用的
.c
格式字体文件125。- 参数设置 :
- 字体大小:建议 16px 及以上,避免显示模糊68。
- BPP(抗锯齿):推荐选择 4 位,提升显示效果46。
- 字符范围 :选择所需汉字范围(如
0x4E00-0x9FFF
表示常用汉字),或手动输入特定字符810。 - 压缩选项 :勾选 Compressed 可减小字体体积36。
二、配置项目与代码
- 参数设置 :
-
添加字体文件到工程
-
将生成的
.c
文件(如 font_alipuhui20.c)放入 工程
在CMakeLists.txt中添加进编译
-
menuconfig配置
在 lv_conf.h
中声明字体并启用多字体字符支持:
LV_FONT_FMT_TXT_LARGE

选项的中文解释翻译:
启用字体字号

设置字体样式
1、全局样式设置(推荐 ):
c
// 创建页面样式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_text_font(&style, &font_alipuhui20); // 设置全局字体样式
2、单句样式设置:
c
// 创建扫描状态标签
lv_obj_t *page1_1 = lv_label_create(page1);
lv_label_set_text(page1_1, "WLAN 扫描了...");
lv_obj_set_style_text_color(page1_1, lv_color_hex(0x000000), 0); // 设置文本颜色为黑色
lv_obj_set_style_text_font(page1_1, &font_alipuhui20, 0);
3、插入图片
图片转化网址
图像转换器 --- LVGL --- Image Converter --- LVGL

将生成图片.c源文件添加进工程

c
/**************** Logo显示逻辑 ****************/
// 创建独立Logo容器(直接放在根屏幕)
logo_container = lv_obj_create(lv_scr_act());
lv_obj_remove_style_all(logo_container); // 移除logo_container的所有样式
lv_obj_set_size(logo_container, LV_PCT(100), LV_PCT(100)); // 全屏
lv_obj_set_layout(logo_container, LV_LAYOUT_FLEX); // 设置logo_container的布局为flex布局
// 设置logo_container的flex对齐方式为居中对齐
lv_obj_set_flex_align(logo_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
// 创建Logo图片
LV_IMG_DECLARE(Pic2);//图片源文件(.c)
lv_obj_t *logo_img = lv_img_create(logo_container);// 创建lvgl图片对象,在容器内
lv_img_set_src(logo_img, &Pic2);//设置图片对象图像源

4、开机旋转logo制作
c
void lv_gui_animation_start() // 显示开机界面
{
/****************开机logo ****************/
// 创建全屏临时容器(确保覆盖整个屏幕)
lv_obj_t *logo_pag_pre = lv_obj_create(lv_scr_act());
lv_obj_remove_style_all(logo_pag_pre); // 清除默认样式
lv_obj_set_size(logo_pag_pre, LV_PCT(100), LV_PCT(100)); // 全屏尺寸
lv_obj_set_layout(logo_pag_pre, LV_LAYOUT_FLEX); // 启用弹性布局
lv_obj_set_flex_align(logo_pag_pre, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); // 内容居中
// // 声明并创建Logo图片
LV_IMG_DECLARE(Pic2);
lv_obj_t *logo_img = lv_img_create(logo_pag_pre);
lv_img_set_src(logo_img, &Pic2);
// 精确居中控制(双重保障)
lv_obj_center(logo_img); // 弹性布局居中
lv_obj_align(logo_img, LV_ALIGN_CENTER, 0, 0); // 绝对坐标居中
// lv_img_set_pivot(logo_img, 60, 60); // 设置图片旋转中心
lv_img_set_pivot(logo_img, 100, 100); // 设置图片旋转中心
/****************开机logo动画****************/
// // // 设置旋转动画
lv_anim_t a; // 创建动画变量
lv_anim_init(&a); // 初始化动画变量
lv_anim_set_var(&a, logo_img); // 动画变量赋值为logo图片
lv_anim_set_exec_cb(&a, set_angle); // 创建设置角度的回调函数
lv_anim_set_values(&a, 0, 3600); // 设置动画旋转角度的开始值和结尾值
lv_anim_set_time(&a, 200); // 设置转一圈的周期是200毫秒
lv_anim_set_repeat_count(&a, 5); // 设置旋转5次
lv_anim_start(&a); // 动画开始
// 延迟2秒后删除logo_pag_pre对象
lv_obj_del_delayed(logo_pag_pre, 2000);
// 给logo_pag_pre对象添加删除事件回调函数
lv_obj_add_event_cb(logo_pag_pre, logo_del_cb, LV_EVENT_DELETE, NULL);
///////////////////////////////
}
流程:创建全屏动画容器页面(logo_pag_pre),在lvgl中声明并创建Logo图片,为图片添加动画,动画结束删除容器页面
效果
![]() |
![]() |
---|---|
![]() |
![]() |
5、制作应用
5.1、制作应用图标
例如原神"应用"的制作
首页应用图标 | 点击进入应用效果 |
---|---|
![]() |
![]() |
简要说明:"应用"为按键+图片实现。在按键上覆盖图片(应用图标),按键按下触发对应按键事件(进入应用)
c
/****************应用4,原神 ****************/
// 创建第4个应用的按键
lv_obj_t *icon4 = lv_btn_create(page1);//创建按钮
lv_obj_add_style(icon4, &btn_style, 0);
lv_obj_set_style_bg_color(icon4, lv_color_hex(0xd8b010), 0);
lv_obj_set_pos(icon4, 15, 147);
lv_obj_add_event_cb(icon4, yuanshen_event_handler, LV_EVENT_CLICKED, NULL);//按键回调(应用触发)函数为yuanshen_event_handler(),需用户编写实际功能
// 为按钮添加图片(使其看起来像应用)
lv_obj_t * img4 = lv_img_create(icon4);
// 声明一个图像
LV_IMG_DECLARE(yuanshen);
// 设置图像对象显示的图像
lv_img_set_src(img4, &yuanshen);
lv_obj_align(img4, LV_ALIGN_CENTER, 0, 0);
其中图标的通用样式(btn_style):
c
// 设置应用图标通用样式
static lv_style_t btn_style;
// 初始化按钮样式
lv_style_init(&btn_style);
// 设置按钮圆角半径为16
lv_style_set_radius(&btn_style, 16);
// 设置按钮背景透明度为100%
lv_style_set_bg_opa( &btn_style, LV_OPA_COVER );
// 设置按钮文字颜色为白色
lv_style_set_text_color(&btn_style, lv_color_hex(0xffffff));
// 设置按钮边框宽度为0
lv_style_set_border_width(&btn_style, 0);
// 设置按钮内边距为5
lv_style_set_pad_all(&btn_style, 5);
// 设置按钮宽度为80
lv_style_set_width(&btn_style, 80);
// 设置按钮高度为80
lv_style_set_height(&btn_style, 80);
5.2、应用触发回调编写
以应用4,原神为例,设计应用点击进入应用:原神加载界面(用图片实现)
首先按键回调(应用触发)函数为yuanshen_event_handler()
c
/*********************** 应用4原神回调 ****************************/
void yuanshen_event_handler(lv_event_t * e)
{
// 创建一个界面
static lv_style_t style;
lv_style_init(&style);
lv_style_set_radius(&style, 10);
lv_style_set_bg_opa( &style, LV_OPA_COVER );
lv_style_set_bg_color(&style, lv_color_hex(0xffffff));
lv_style_set_border_width(&style, 0);
lv_style_set_pad_all(&style, 0);
lv_style_set_width(&style, 320);
lv_style_set_height(&style, 240);
// 创建一个图标对象,并将其添加到当前活动屏幕上
app_page = lv_obj_create(lv_scr_act());
// 将样式添加到图标对象上
lv_obj_add_style(app_page, &style, 0);
// // 声明并创建图片
LV_IMG_DECLARE(yuanshenqidong);
// lv_obj_t *logo_img = lv_img_create(logo_pag_pre);
lv_obj_t *logo_img = lv_img_create(app_page);
lv_img_set_src(logo_img, &yuanshenqidong);
// 精确居中控制(双重保障)
lv_obj_center(logo_img); // 弹性布局居中
lv_obj_align(logo_img, LV_ALIGN_CENTER, 0, 0); // 绝对坐标居中
//退出应用部分暂略,后续添加(5.3添加应用退出按钮)
}
进入应用效果:
5.3添加应用退出按钮

c
// 创建标题背景
// 创建一个对象,用于显示标题
lv_obj_t *att_title = lv_obj_create(app_page);
// 设置对象的大小
lv_obj_set_size(att_title, 40, 40);
// 设置对象间隙
lv_obj_set_style_pad_all(att_title, 0, 0); // 设置间隙
// 对齐对象
lv_obj_align(att_title, LV_ALIGN_TOP_LEFT, 0, 0);
// 设置对象背景颜色
lv_obj_set_style_bg_color(att_title, lv_color_hex(0xffffff), 0);
// 创建后退按钮
btn_bk_back = lv_btn_create(att_title);
lv_obj_align(btn_bk_back, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_set_size(btn_bk_back, 60, 30);
lv_obj_set_style_border_width(btn_bk_back, 0, 0); // 设置边框宽度
lv_obj_set_style_pad_all(btn_bk_back, 0, 0); // 设置间隙
lv_obj_set_style_bg_opa(btn_bk_back, LV_OPA_TRANSP, LV_PART_MAIN); // 背景透明
lv_obj_set_style_shadow_opa(btn_bk_back, LV_OPA_TRANSP, LV_PART_MAIN); // 阴影透明
lv_obj_add_event_cb(btn_bk_back, btn_home_back_cb, LV_EVENT_CLICKED, NULL); // 添加按键处理函数
// 创建后退按钮符号
lv_obj_t *label_back = lv_label_create(btn_bk_back); // 创建一个标签对象,并将其与btn_bk_back关联
lv_label_set_text(label_back, LV_SYMBOL_LEFT); // 按键上显示左箭头符号
lv_obj_set_style_text_font(label_back, &lv_font_montserrat_20, 0); // 设置标签的字体为lv_font_montserrat_20
lv_obj_set_style_text_color(label_back, lv_color_hex(0xA9A9A9), 0); // 设置标签的字体颜色为深灰
lv_obj_align(label_back, LV_ALIGN_CENTER, -10, 0); // 将标签对象居中对齐,并设置x轴偏移量为-10
5.4退出按钮回调函数
c
// 应用返回主界面按钮事件处理函数
void btn_home_back_cb(lv_event_t * e)
{
//按应用区别添加相应操作(释放内存,删除应用运行中的任务等......)
lv_obj_del(app_page); // 删除画布
icon_flag = 0;//应用标识,0为主页面
}
由于应用回调函数是在容器(画布)app_page中创建,删除容器,则会显示在其底层的容器(page1主界面),以此实现退出应用,按应用的情况在退出回调中应该添加相应操作如(释放内存,删除应用运行中的任务等......)。
6、wifi连接
c
#include "esp_wifi.h"
#include "protocol_examples_common.h"
// 添加网络接口相关头文件
#include "esp_netif.h"
// 确保事件循环头文件存在
#include "esp_event.h"
// 包含示例连接功能的头文件
#include "nvs_flash.h"
#include <esp_system.h>
//// 连接WiFi(menuconfig中配置)
void wifi_connect(void)
{
ESP_ERROR_CHECK(nvs_flash_init()); // 初始化非易失存储
ESP_ERROR_CHECK(esp_netif_init()); // 初始化网络堆栈
ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建事件循环
// 检查example_connect函数的返回值,确保连接成功
ESP_ERROR_CHECK(example_connect()); // 连接WiFi
}
wifi连接使用库函数快速实现。
在menuconfig中配置wifi账号与密码(在文章后面menuconfig部分有详细记录)
7、MQTT应用
该应用控制MQTT设备云设备,
需要添加protocol_examples_common组件库
protocol_examples_common介绍:
"protocol_examples_common.h"//用于存放与协议(Protocol)相关的通用示例代码或共享组件,为开发者提供可复用的模块或模板,帮助快速理解和使用项目中涉及的通信协议(如 HTTP、TCP、MQTT 等)。
工程位置 | 添加内容 |
---|---|
![]() |
![]() |
先看现象:
点击亮灯图标 | 点击灭灯图标 |
---|---|
掌机终端打印:![]() |
掌机终端打印:![]() |
![]() |
![]() |
这里使用掌机终端推送MQTT主题,另一块esp32核心板订阅相同主题,并解析数据,做出对应指令操作(亮/灭灯)
详细完整代码在文章结尾,这里不做详细的讲解,仅介绍代码关键的部分。
推送主题
c
//推送主题
void put_led_on() {
// if (mqtt_connected) {
const char *status = "led_on";//推送主题
esp_mqtt_client_publish(mqtt_client, SUB_TOPIC, status, strlen(status), 0, 0);
// esp_mqtt_client_publish(mqtt_client, "ledstate", status, strlen(status), 0, 0);
ESP_LOGI(TAG3, "led_on");
// }
}
绑定绑定推送主题事件。由应用MQTT内的点击事件触发。

订阅主题
c
//----------------MQTT订阅-----------------------//
esp_mqtt_client_subscribe(mqtt_client, SUB_TOPIC, 0);//订阅主题ledctrl

处理MQTT接收事件(部分)
c
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
switch (event->event_id) {
case MQTT_EVENT_DATA: {//<MQTT接收数据事件>
// 处理接收数据
//----------------判断接收主题-----------------------------------------------------------------//
char topic[event->topic_len + 1];
memcpy(topic, event->topic, event->topic_len);
topic[event->topic_len] = '\0';
//----------------判断接收数据-----------------------------------------------------------------//
char data[event->data_len + 1];
memcpy(data, event->data, event->data_len);
data[event->data_len] = '\0';
//----------------打印接收主题+数据-----------------------------------------------------------------//
ESP_LOGI(TAG3, "Received: Topic=%s, Data=%s", topic, data);
lv_label_set_text_fmt(label_topic, "Topic: %s", topic);
lv_label_set_text_fmt(label_data, "Data: %s", data);
if (strcmp(topic, SUB_TOPIC) == 0) {//判断接收主题(ledctrl)数据------>控制led灯->推送led状态信息
if (strcmp(data, "on") == 0) {
gpio_set_level(OUTPUT_PIN, 0);
//推送led状态信息到主题ledstate
esp_mqtt_client_publish(mqtt_client, PUB_TOPIC3, "led设备已开启", 0, 0, 0);//同时推送状态到主题ledstate
} else if (strcmp(data, "off") == 0) {
gpio_set_level(OUTPUT_PIN, 1);
esp_mqtt_client_publish(mqtt_client, PUB_TOPIC3, "led设备已关闭", 0, 0, 0);
}
}
break;
}
接收到订阅的主题数据,可以判断内容后执行相应的用户操作(例如:点灯,调节屏幕亮度,启动/关闭外设......)
12、HTTP综合应用
12.1、HTTP获取实时时间
使用淘宝的时间戳获取API
c
#define WEB_SERVER "acs.m.taobao.com"
// 定义WEB服务器端口
#define WEB_PORT "80"
// 定义WEB服务器路径
#define WEB_PATH "/gw/mtop.common.getTimestamp/"
通过更改ESP32官方http get exampl ,实现获取该网址时间戳,获取成功后通过CJson解析出时间戳
编写时间戳转化函数,转化成时间
注意获取到的时间戳是单位:毫秒
时区是UT8?需要转化为东八市区(北京时间)
将获取的时间转化为本地时间保存(好处是不用再次使用网络获取,仅需从本地获取就可得到准确的时间,节省算力和网路带宽)
我将其显示到主页面顶部实时刷新

c
void show_time_task(void *pvParameter)
{
// 设置时区为中国标准时间(东八区)
setenv("TZ", "CST-8", 1);
tzset();
// 已经从网络或HTTP中获取了时间戳(单位:毫秒)
time_t base_time = g_timestamp_ms / 1000; // 修正:毫秒转秒
int64_t base_us = esp_timer_get_time(); // 获取时的微秒数(系统运行时间)
while (1) {
int64_t now_us = esp_timer_get_time();
time_t now = base_time + (now_us - base_us) / 1000000; // 当前时间戳(秒)
struct tm timeinfo;
// 将now时间转换为本地时间,并存储在timeinfo中
localtime_r(&now, &timeinfo);
// char time_save_buf[64];
strftime(time_save_buf, sizeof(time_save_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
printf("现在时间:%s\n", time_save_buf);
vTaskDelay(1000 / portTICK_PERIOD_MS); // 每秒打印一次
}
}
LVGL的刷新显示
需注意,想要实时显示时间,需要刷新显示时间的容器
这里使用使用LVGL定时器回调函数进行刷新显示(1s)
创建定时器:
c
//使用LVGL(Light and Versatile Graphics Library)创建一个定时器
lv_timer_create(update_time_cb, 1000, NULL); // 每 1000ms 更新一次
定时器回调
c
//更新日期显示(1s回调)
void update_time_cb(lv_timer_t *timer)
{
if (time_label) {//time_label为显示时间的容器
lv_label_set_text(time_label, time_save_buf);//将时间重新写入容器
}
}
12.2、HTTP获取天气信息
与上文获取时间流程基本一致,源码过长不做展示
效果:
流程:
通过http发送获取本地位置信息(城市),再将城市信息添加进请求URL中向心知天气API请求,获取到天气信息通过CJson解析出城市和气温保存到字符数组,将字符数组显示到界面上
idf_component.yml配置
yml
## IDF Component Manager Manifest File
dependencies:
espressif/esp32-camera: "^2.0.10" # 摄像头驱动
lvgl/lvgl: "~8.3.0"
espressif/esp_lvgl_port: "~1.4.0" # LVGL接口
espressif/esp_lcd_touch_ft5x06: "~1.0.6" # 触摸屏驱动
protocol_examples_common: # MQTT
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
更改完idf_component.yml配置需要重新选择芯片,才能下载组件,选完芯片需要重新配置menuconfig
menuconfig配置
配置Flash大小
Flash size
配置使用分区表
Partition Table

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

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