仪表盘基本界面显示
221.3.20 仪表盘基本界面显示
本节只在 SquareLine Studio 中制作静态仪表盘、导出自动生成的 UI 文件,并把它们移植到 STM32 工程;没有编写新的动态业务代码。课堂只修改了
lv_conf.h的内置字体开关,并删除了上一课遗留的 DHT11 显示代码。按照课程边界,这里不把 SquareLine 自动生成的整份ui_Screen1.c、字体库源码或后续课程已有的 RTC、指针动画、图标改色代码放到标题下。下一节才开始通过业务代码修改界面。
一、仪表盘基本界面显示
本节从上一组温湿度风扇 Demo 切换到汽车仪表盘项目,完整走了一遍:
新建 SquareLine 工程 → 导入图片素材 → 摆放并命名控件 → 校准指针旋转中心 → 导出 UI → 替换 STM32 工程中的旧 UI → 解决编译错误 → 烧写静态界面
课堂开头展示的是这个系列后面才会完成的目标效果,包括动态日期时间、状态图标和可变化的仪表指针;本节烧写得到的仍是静态界面。必须区分“开头的最终目标演示”和“本节实际完成状态”,不能提前把后续功能算入第 221 节。
1.1 仪表盘界面显示流程
前面的温湿度风扇 Demo 已经验证了 SquareLine 的基本工作流:设计界面、导出 UI、加入 Keil 工程,再从业务代码访问 UI 对象。本仪表盘项目被老师划分为四个阶段:
- 创建并完成 SquareLine 工程;
- 把静态界面显示到开发板;
- 通过代码修改日期、时间、指针与图标状态;
- 继续整理和优化代码。
本节只完成前两项。原笔记中的 “Squirrel”“S 量工程”均是语音识别错误,正确名称是 SquareLine Studio 和 SquareLine 工程。
SquareLine 负责把可视化设计转换成 LVGL 对象创建、图片数据和样式设置代码,但不会自动完成 RTC 读取、指针数值映射或车辆状态采集。这些动态行为仍需在 STM32 业务代码中实现。
1.2 最终实验效果
课堂开头展示的系列目标界面包含:
- 左侧仪表盘背景及一根可旋转指针;
- 右上角日期和时间;
- 下方灯光、转向、水温、安全带等状态图标;
- 通过 RTC 提供的日历时间;
- 通过业务变量改变指针角度和图标颜色。
RTC 是 Real-Time Clock(实时时钟)。它负责维护年、月、日、时、分、秒,但不会自动改变 LVGL Label。完整数据链路应当是:
RTC 外设 → 程序读取日期/时间 → 格式化成字符串 → lv_label_set_text() → LCD 刷新
本节没有配置或讲解这条业务链路,日期 2025-1-10、时间 23:00 只是 SquareLine 中的占位文本。烧写后它们不会走动;指针也固定在设计时设置的角度,图标保持素材或设计时的颜色。动态效果留到后续课程。
1.3 创建SquareLine工程
配套工程信息表明,本节实际使用:
| 项目 | 设置 |
|---|---|
| SquareLine Studio | 1.5.0 |
| LVGL | 8.3.11 |
| 工程目录 | 23SQL_Dashboard |
| 工程文件 | 23DashBoard.spj |
| 分辨率 | 480×320 |
| 色深 | 16 bit swap |
| 导出目录 | 23SQL_Dashboard/ui |
这里的 23 号工程是 PC 上的界面设计工程;随后复制第 22 号 STM32 工程并建立 24Lvgl_Dashboard,作为单片机端运行工程。两者作用不同,不能混用路径。
1.3.1 创建新文件夹
在 unit3code 下新建 23SQL_Dashboard,用于保存本次 SquareLine 工程。课程使用递增编号区分阶段:
- 22:上一节可运行的 LVGL+DHT11 工程;
- 23:新的 SquareLine 仪表盘设计工程;
- 24:从 22 复制出来、用于承载新仪表盘 UI 的 STM32 工程。
给工程目录加编号的价值是保留每个阶段的可运行快照,方便出现问题时回退比较。编号后面的功能名应能说明用途,例如 SQL_Dashboard 表示 SquareLine 仪表盘设计,Lvgl_Dashboard 表示单片机端 LVGL 仪表盘工程。
1.3.2 双击SquareLine Studio软件
视频从桌面启动 SquareLine Studio 1.5.0。软件版本很重要:不同版本的项目格式、控件属性、导出目录和 LVGL 版本支持可能不同。本课界面与配套工程都以 1.5.0 和 LVGL 8.3.11 为准。
1.3.3 创建项目
在启动器中选择 Create,课堂使用 Desktop 分类下的 Eclipse with SDL for development on PC 模板,然后设置:
- Project path:
D:\uint3code\23SQL_Dashboard\; - Project name:
23DashBoard.spj; - Resolution:
480 × 320; - Rotation:
0 degree; - Shape:
Rectangle; - Color depth:
16 bit swap; - LVGL version:
8.3.11。
虽然模板名称中带有 PC 和 SDL,本节并不是把整个 Eclipse/SDL 工程搬进 STM32。这里主要借它建立符合 LVGL 8.3 的 SquareLine 工程并导出通用 UI C 文件,LCD、触摸、时钟和 LVGL 移植层仍来自原有 STM32 工程。
“16 bit swap”是 颜色深度及字节顺序,不是“比特率”:
- 16 bit 表示本工程使用 16 位颜色,通常对应 RGB565;
- swap 表示每个 16 位像素的两个字节按目标显示链路要求交换顺序;
- 它必须与 STM32 工程的
LV_COLOR_DEPTH 16和LV_COLOR_16_SWAP 1匹配,否则可能出现红蓝互换或颜色明显异常。
1.3.4 找到图片文件夹
素材位于 unit3code/images,共使用 6 张 PNG:
| 素材 | 用途 |
|---|---|
gauge-bg.png | 仪表盘背景 |
gauge-needle.png | 仪表指针 |
Light.png | 灯光状态图标 |
turn_light.png | 转向状态图标 |
temp_gray.png | 水温状态图标 |
safety_belt.png | 安全带状态图标 |
原笔记写成 imagines,实际目录名是 images。PNG 的透明通道使指针和小图标能够叠加在黑色背景上,不会出现矩形底色。
1.3.5 复制图片到工程assets
选中上述 6 张图片,复制到 23SQL_Dashboard/assets。SquareLine 会监视 Assets 目录,文件复制完成后会自动显示在下方 Assets 面板中,也可以使用软件的 Add file into Assets 功能导入。
需要区分两个概念:
- Asset 文件名决定使用哪张图片以及导出的图片数据;
- Image 控件名决定导出后在 C 代码中怎样引用该控件。
把图片放进 Assets 只代表资源可用;将它拖到 Screen 上,才会创建一个实际显示该资源的 Image 控件。
1.3.6 修改屏幕背景
选中 Screen1,在 Style Settings → Background → BG Color 中启用背景颜色并设为 #000000。只有勾选/启用该样式属性,输入的颜色值才会写入控件样式。
#000000 是 RGB 三通道全 0,即黑色;与仪表盘图片本身的深色背景一致。导出后对应的是给 ui_Screen1 设置黑色背景,而不是修改 LCD 背光亮度。LCD 背光属于硬件供电或 PWM 亮度控制,与 LVGL 背景颜色是两回事。
1.3.7 拖动图片到界面
先拖入仪表盘背景,再拖入指针,最后拖入四个小图标。创建顺序也会影响默认绘制层级:后创建的指针需要显示在仪表盘背景上方。
指针在旋转角度为 0 时先按素材方向摆放:
- 指针底部与仪表盘的 0 刻度竖线对齐;
- 指针顶部靠近仪表盘中央圆环的下沿;
- 此时不要先追求旋转效果,先确定基准位置,后面才能计算 Pivot。
四个状态图标的最终 SquareLine 坐标参数为:
| 控件 | X | Y |
|---|---|---|
ImageLight | 4 | 93 |
ImageTurnlight | 54 | 93 |
ImageWatertemp | 104 | 93 |
ImageSfatyBelt | 154 | 93 |
它们的 Y 都是 93,所以视觉上处于同一水平线;X 每次增加 50,所以中心间距相同。课堂把单位设为 px,适合固定的 480×320 屏幕。
这些 X/Y 不是以屏幕左上角为原点的绝对坐标。本节控件使用 Align = CENTER,因此 X/Y 是相对屏幕中心的偏移量。例如 X=4 表示控件中心位于屏幕水平中心右侧 4 像素;若误按左上角绝对坐标理解,就无法解释导出代码中的位置。
1.3.8 查看效果
通过 SquareLine 的 Play/预览功能检查布局,可以快速发现:
- 图片是否被其他控件遮挡;
- 指针是否与 0 刻度及中央圆环对齐;
- 四个图标的高度和间距是否一致;
- 日期、时间是否超出 480×320 画布。
预览只能验证视觉效果,不等价于真实 STM32 运行。PC 预览不会暴露单片机 Flash/RAM 容量、LCD 字节顺序、字体宏未启用或 Keil 工程漏加源文件等问题,所以最后仍必须编译并烧写验证。
1.3.9 修改图片名字
不要保留 Image2、Image3 这类无意义名称。控件名会生成到 ui.h 和 ui_Screen1.c 中,后续业务代码需要靠它找到对应对象。本节最终名称为:
| 界面对象 | 导出的对象名 | 说明 |
|---|---|---|
| 仪表盘 | ui_ImageDashboard | Dashboard 背景 |
| 指针 | ui_ImageNeedle | Needle 指针 |
| 灯光 | ui_ImageLight | Light 图标 |
| 安全带 | ui_ImageSfatyBelt | 课程实际拼写 |
| 水温 | ui_ImageWatertemp | Water temperature 图标 |
| 转向 | ui_ImageTurnlight | Turn light 图标 |
正确英文是 SafetyBelt,但课堂最终写成了 SfatyBelt,少了字母 e。SquareLine 会原样把这个拼写导出成 ui_ImageSfatyBelt;后续使用配套源码时必须按实际标识符拼写,不能只改笔记而导致代码找不到对象。
名称只是代码标识,不会改变图片内容。若项目已经导出并被业务代码引用,再改控件名会使旧标识符失效,需要同步修改所有引用。
1.3.10 添加时间戳
这里实际添加的是两个静态 Label:一个显示日期,一个显示时间。“时间戳”在软件开发中通常指自某个纪元开始累计的数值,本节屏幕显示的是格式化日期时间文本,因此称为“日期和时间标签”更准确。
1.3.10.1 修改label的字体颜色
选中 Label,在 Style Settings → Text → Text Color 中启用并输入 FFFFFF,即 #FFFFFF 纯白色。白色文字与黑色背景形成高对比度。
颜色选择器拖到角落未必能得到完全相同的数值;需要精确颜色时直接输入 6 位十六进制值更可靠。这里的 255/#FFFFFF 描述的是文字颜色或不透明度,不是屏幕亮度。
1.3.10.2 修改label的内容和字体大小
第一个 Label 设置为:
- Name:
LabelDate; - Text:
2025-1-10; - Text color:
#FFFFFF; - Font:Montserrat 24;
- Align:Center;
- X/Y:
125/-68。
课堂先试过 18、20 等字号,最终配套工程和导出代码使用 lv_font_montserrat_24。日期内容只是设计占位值,不表示单片机 RTC 已经设置为该日期。
1.3.10.3 添加并设置第二个label
第二个 Label 用于时间:
- Name:
LabelTime; - Text:
23:00; - Text color:
#FFFFFF; - Font:Montserrat 40;
- Align:Center;
- X/Y:
125/-34。
课堂曾试过 48 和 44,随后觉得过大而调整为 40;最终值应以导出工程的 lv_font_montserrat_40 为准。23:00 同样只是静态占位字符串。
1.3.10.4 修改label的名称和位置
LabelDate 和 LabelTime 比 Label1、Label2 更容易在代码中识别。原转录中的 dirt 是对 date 的误识别,正确含义是日期。
两个标签均以屏幕中心对齐,再用 X/Y 偏移到右上区域:日期位于 (125, -68),时间位于 (125, -34)。因为字体大小不同,即使 X 相同,文本左边缘也不一定相同;最终应以整体视觉对齐为准,而不是只比较 X 数字。
1.3.11 修改指针转动角度
指针图片尺寸为 22×86 像素,控件位置为 X=-86、Y=71、Align=Center。要让它像真实仪表针一样绕仪表盘中心旋转,必须同时处理:
- Image 控件在屏幕上的位置;
- Pivot X/Y,即图片局部坐标系中的旋转中心;
- Rotation,即绕 Pivot 旋转的角度。
三者含义不同。移动控件不会自动改变 Pivot,修改 Pivot 也不会把控件本身搬到另一个屏幕坐标。
1.3.11.1 指针转动角度的修改方法
选中 ImageNeedle,在 Image 属性中修改 Rotation。LVGL 8.3 的图片角度以 0.1° 为单位:
| Rotation 数值 | 实际角度 |
|---|---|
| 100 | 10.0° |
| 1000 | 100.0° |
| 2000 | 200.0° |
| 2500 | 250.0° |
| 3600 | 360.0°,一整圈 |
所以原笔记“0~3600 对应 0~360°”是正确方向,但还应明确单位换算:
实际角度(°) = Rotation ÷ 10
正值按 LVGL 8.3 的约定顺时针旋转。Angle 只是图像旋转量,不是仪表盘刻度值;以后要让 0~10 的仪表数值驱动指针,还需要业务代码做数值到角度的映射。
1.3.11.2 指针转动圆心的调整方法
Pivot 是相对于图片左上角的局部坐标,而且可以位于图片矩形之外。课堂用一个直观方法估算 Pivot Y:
- 先把 Rotation 设为 0,让指针回到基准方向;
- 记住指针原来的 Y≈71;
- 临时向上拖动指针,使预期旋转中心落到仪表盘圆心附近;
- 此时 Y≈40,相差约 30~31 像素;
- 把指针恢复原位置,并把 Pivot Y 设为
-30。
负的 Pivot Y 表示旋转点位于指针图片顶部上方 30 像素。该点恰好接近仪表盘中央圆环,所以旋转后指针会绕表盘中心扫动,而不是绕自身矩形中心“原地打转”。
1.3.11.3 指针转动效果优化
指针宽度为 22 像素,水平中心约在 11 像素处,课堂把 Pivot X 设置为 10;结合 Pivot Y=-30,最终参数为:
| 属性 | 最终值 | 含义 |
|---|---|---|
| Pivot X | 10 | 旋转中心接近指针的水平中线 |
| Pivot Y | -30 | 旋转中心位于图片顶边上方 30 像素 |
| Rotation | 2500 | 初始旋转 250.0° |
| Scale | 256 | LVGL 图片原始大小,即 100% |
配套导出代码对应 lv_img_set_pivot(ui_ImageNeedle, 10, -30) 和 lv_img_set_angle(ui_ImageNeedle, 2500)。这两行由 SquareLine 自动生成,本节没有在业务代码中手写,所以不放入标题下的源码区。
(10, -30) 只适用于本节这张 22×86 指针素材及当前摆放方式。若图片尺寸、透明边距、指针初始位置或仪表盘位置改变,就必须重新计算和预览,不能把这组数字当成通用公式。
完成后的静态界面由 6 张图片和 2 个 Label 组成。当前 Rotation=2500 时,指针在视觉上指向仪表盘的 10 附近。
1.3.12 选择路径并导出文件
使用 Export → Export UI Files,把导出目录设为 23SQL_Dashboard/ui。看到导出成功提示后,再按 Ctrl+S 保存 SquareLine 工程本身。
“导出 UI”和“保存工程”不是同一操作:
- 保存工程更新
.spj,下次可以继续编辑布局; - 导出 UI 生成给 LVGL/Keil 编译的
.c、.h和图片数组文件。
本项目导出的业务相关源文件清单为:
ui.cui_helpers.cscreens/ui_Screen1.ccomponents/ui_comp_hook.c- 6 个
images/ui_img_*.c
本界面没有配置事件,所以导出目录只有 ui_events.h,没有需要加入 Keil 的 ui_events.c。头文件不单独编译,但其所在目录必须能被编译器搜索到。
1.3.13 创建新工程并移除之前文件
复制第 22 节使用的 22STM32_Lvgl_DHT11 工程,重命名为 24Lvgl_Dashboard。这样可以复用已经调通的 STM32 时钟、LCD、触摸、LVGL 移植层和 Keil 配置。
接着只替换 24Lvgl_Dashboard/midware/ui:
- 删除该目录中旧的温湿度 UI 文件;
- 把
23SQL_Dashboard/ui的全部导出内容复制进去; - 回到 Keil,外部文件改变提示选择 Reload;
- 关闭仍打开的旧 UI 编辑页时,如果 Keil 询问是否保存旧缓冲区,应选择“否”。
最后一步非常重要:若把编辑器内尚未刷新的旧内容再次保存,可能覆盖刚复制进来的新文件。删除范围也只能是明确的 midware/ui 内容,不能删除整个 midware,因为 LVGL 本体和显示移植代码也在其中。
1.3.14 添加之前文件
磁盘中存在 .c 文件,并不代表 Keil 会自动编译它。课堂采用较稳妥的做法:先从 Keil 的 midware/ui 分组中 Remove 旧 UI 文件条目,再把新工程中的下列文件加入:
- 根目录的
ui.c、ui_helpers.c; screens/ui_Screen1.c;images下的 6 个图片数组.c;components/ui_comp_hook.c。
Keil 的 Remove 通常只是移除工程条目,不等于删除磁盘文件;文件系统中的旧 UI 已在上一步明确替换。应确认选择的是 24 号工程路径,不能误把 23 号设计工程或 22 号旧工程的源文件加入。
旧工程已经配置过 midware/ui 等 Include Paths,所以本节通常不需要重复添加头文件路径。但若出现 ui.h: No such file,仍应检查实际路径是否与工程设置一致。
1.3.15 编译并修改错误
第一次编译主要出现两类问题。
第一类是内置字体未启用:
lv_font_montserrat_24undefined;lv_font_montserrat_40undefined。
SquareLine 生成代码引用了这两个字体,但 LVGL 为节省 Flash,不会默认编译所有字号。课堂在 lv_conf.h 中把 LV_FONT_MONTSERRAT_24 和 LV_FONT_MONTSERRAT_40 从 0 改为 1。这里修改的是 LVGL 配置宏,不是新增业务代码。
第二类是 main.c 仍保留上一节的温湿度业务代码:
- 新 UI 已不存在
ui_Arc1、ui_Arc2、ui_Label2、ui_Label4,所以出现 undefined; DHT_Read()、Data[]、buff和对应sprintf()不再属于当前界面;- 老的临时标题修改同样引用了已不存在的
ui_Label1。
课堂删除这些旧 UI 更新代码,使主循环恢复为只执行:
lv_timer_handler() → HAL_Delay(5) → 下一轮
这不是在否定 DHT11 驱动,而是新界面已经换了一套对象接口;继续保留旧对象名必然编译失败。本节也没有加入新的 RTC、指针或图标业务代码。
老师还修正了 Keil Options for Target → User → Run #1 中的绝对路径:复制 22 号工程后,它仍指向旧目录下的 Keil5_disp_size_bar.exe。旧目录存在时命令可能仍能运行,所以不一定立即报错;但项目不再自洽。应改为 24 号工程自己的:
D:\uint3code\24Lvgl_Dashboard\Keil5_disp_size_bar.exe
这个程序只是构建后的容量显示工具,不参与 MCU 固件功能。改路径不会改变仪表盘逻辑,但能避免删除或移动旧工程后容量统计命令失效。
视频最终构建窗口显示约:
| 存储区 | 已用 | 占用率 | 剩余 |
|---|---|---|---|
| RAM | 61.71 KB / 64 KB | 96.42% | 2344 B |
| Flash | 460.67 KB / 512 KB | 89.97% | 52560 B |
图片会显著增加 Flash。以 233×233 的仪表盘背景为例,配套导出文件使用 LV_IMG_CF_TRUE_COLOR_ALPHA:在 16 位颜色下,每像素通常包含 2 字节颜色和 1 字节 Alpha,原始数组约为:
233 × 233 × 3 = 162867 字节
图片数组声明为常量,主要占 Flash,不会把同样大小全部复制到 RAM。RAM 的高占用更多来自原有 LVGL 内存池、显示缓冲区、栈和全局数据。本节的容量余量已经很紧,后续新增大图片或缓存前必须继续观察 .map 文件和链接结果。
烧写后的本节实际效果是:仪表盘、静态指针、四个状态图标、日期 2025-1-10 和时间 23:00 正常显示;日期时间不变化,指针不运动,图标也没有由业务状态动态改色。这正是本节应达到的边界,下一节才开始修改图标颜色等界面状态。
常见排查顺序如下:
| 现象 | 优先检查 |
|---|---|
| 屏幕颜色异常 | SquareLine 是否选 16 bit swap,LV_COLOR_DEPTH/LV_COLOR_16_SWAP 是否匹配 |
| 图片不显示或链接失败 | 6 个 ui_img_*.c 是否全部加入 Keil |
| UI 对象 undefined | ui_Screen1.c、ui.c 是否加入,新 ui.h 是否覆盖成功 |
| 字体 undefined | lv_conf.h 中 Montserrat 24、40 是否启用 |
仍报 ui_Arc1 等错误 | main.c 是否还保留旧温湿度界面代码 |
| 修改了磁盘文件却仍看到旧内容 | Keil 是否 Reload,是否误把旧编辑缓冲区保存回磁盘 |
| 指针绕自身旋转 | Pivot 是否为当前素材重新校准,而不只是修改 Rotation |
| 构建后容量工具失效 | Run #1 是否仍指向复制前的绝对路径 |
| 下载后时间不走 | 本节只有占位文本,动态 RTC 更新尚未实现 |
二、内容总结
本节完成了仪表盘的静态设计和移植:
- SquareLine 工程使用 480×320、LVGL 8.3.11 和 16 bit swap;
- 画面由仪表盘背景、指针、四个状态图标以及日期/时间两个 Label 组成;
- 图标使用统一 Y=93,并以 50 像素的 X 偏移间隔排列;
- 控件名尽量表达用途,便于后续代码访问;实际导出名称中的
ImageSfatyBelt保留了课堂拼写错误; - 指针最终 Pivot=
(10, -30)、Rotation=2500,其中 2500 表示 250.0°; (10, -30)只适用于当前图片尺寸和摆放位置;- 导出 UI 后要同时替换磁盘文件和 Keil 工程条目,并启用 Montserrat 24、40;
- 编译时删除上一节遗留的 DHT11 界面更新代码;
- 烧写结果仅为静态界面,RTC 刷新、指针变化和图标改色属于后续课程。
原笔记总结中的“十负三十二五零零”应拆开理解为三个独立参数:Pivot X=10、Pivot Y=-30、Rotation=2500。不能把它们连成一个数,也不能把 Pivot 当成屏幕坐标。