嵌入式UI
未读移植LVGL到STM32官方英文移植教程:
Getting LVGL(获取 LVGL) — LVGL 文档
韦东山中文移植教程:
Porting(移植) — LVGL 文档
源码获取然后从github上下载源码:https://github.com/lvgl/lvgl
官方移植方法:https://docs.lvgl.io/master/details/integration/adding-lvgl-to-your-project/getting_lvgl.html
这是LVGL v9.3版本
移植该笔记基于lvgl v9.x往上的,如果使用版本较低,就可能与本笔记中的内容不同
我们的只需要关注源码中需要移植的部分:
以keil工程为例,我们找到项目根目录下Middlewares(没有就创建一个),然后再其中创建一个lvgl文件夹用于存放我们移植lvgl相关的内容
然后在该lvgl目录下新建src和port文件夹
src文件夹:lvgl源码
port文件夹:存放我们自己的移植代码,比如显示驱动移植、输入设备(触摸/按键)接口移植等等
src源码将下载下来的lvg ...
裁剪LVGLLVGL主打的就是轻量且多功能,其官网介绍中有这两句话:
RAM: 4kB + 150byte / widget (~48kB for a UI with a few screens)
Flash:~1OOkB for LVGL (depends on the enabled features)
这里的意思是,经过裁剪之后能达到这样的效果,并不是我们拿到手默认的配置就能达到这样效果。LVGL的裁剪在lv_conf.h文件中进行。
实际项目过程中,我们的Flash和SRAM可能不够用,所以需要进行裁剪优化LVGL占用的内存!
Flash优化lvgl的flash占用主要是以下内容:
控件与核心代码(lv_conf.h中控制,使能的控件越多,占用越多)
字体点阵数组(如ui_font_opensasnsitalic100.o里的点阵数据)。
图片/图标等资源(如你用LVGL/SquareLine导出的图片c文件)。
音频、logo、动画帧等。
这些都是由const修饰的数组,只读区域在Flash之中
字体裁剪在使用SquareLine ...
前言在做项目的过程中使用到了LVGL,遇到了一些问题,于LVGL的调度有关,所以这里写一篇笔记记录LVGL调度相关内容
LVGL任务调度任务处理介绍LVGL通过定时器轮询机制周期性检查并执行所有任务(动画、事件、刷新等),由主循环中持续调用lv_task_handler() 处理任务。
所有动画、控件刷新、事件分发等任务操作,都是在该函数内执行,并且最终都是通过定时器机制驱动。
我们必须在循环中间隔5~20ms持续调用lv_task_handler() 来处理任务,
在使用LVGL时,通常伴随RTOS的使用,所以我们都是将lvgl任务处理单独由一个任务执行,保证lv_task_handler()周期性的被调用处理所有的事件(UI刷新,输入检测等)
12345678910/* LVGL Handler task,驱动LVGL运行 */void LvglHandlerTask(void *argument){ while(1) { lv_task_handler(); // 启动lvgl的事务处理 osDelay(5); }}
定时 ...
前言对于LVGL相关的任务,出现异常时会进入lv_assert_handler,为了准确定位是哪里出了问题,是出了什么问题导致的死机等,我们需要知道以下调试的方法
日志输出默认情况下,LVGL的日志输出关闭,如果想要开启,在lv_conf.h中,将LV_USE_LOG配置为1
自定义日志输出自定义的日志回调函数通过注册回调函数的方式,将日志内容输出到串口、文件等。
1.设置LV_LOG_PRINTF = 0
2.然后注册回调函数即可,之后LVGL内部所有通过 LV_LOG_INFO、LV_LOG_WARN、LV_LOG_ERROR、LV_LOG_TRACE 等宏产生的日志,都会被发送到你注册的回调函数输出日志
函数原型:
1void (*lv_log_print_g_cb_t)(lv_log_level_t level, const char * buf);
参数:
level:表示输出日志的等级,有以下的等级
LV_LOG_LEVEL_TRACE 跟踪(最详细)
LV_LOG_LEVEL_INFO 信息
LV_LOG_LEVEL_WARN 警告
LV_LOG_LEVEL ...
LVGL介绍LVGL是什么?LVGL(Light and Versatile Graphics Library)- 轻便是且多功能的图形库,一个开源、跨平台的轻量级嵌入式图形界面库(GUI),专为资源有限的MCU/MPU设备设计(无操作系统)。它广泛应用于手表、家电、仪表、工控、物联网等场景。
最初LVGL叫做LittlevGL
官网:https://lvgl.io/英文文档:LVGL 9.3 documentation
中文文档:Introduction(介绍) — LVGL 文档
LVGL软硬件所需资源(推荐内存分配)
处理器要求
16、32 或 64 位微控制器或处理器
推荐主频 > 16MHz
存储资源
Flash/ROM:
最低 > 64 KB(仅包含最基本组件)
推荐 > 180 KB(功能更全面)
RAM:
静态RAM占用约 2KB(视使用的控件和功能而定)
处理任务堆栈(stack):最低 > 2 KB,推荐 > 8 KB (lv_task_handler任务)
动态内存池(heap):最低 > ...
软件层次架构在嵌入式系统开发中,常见的软件架构会把代码分为多个”层”(Layer),以便于管理、移植和维护。不同项目可能会有不同的划分,但主流嵌入式系统常见的层主要有:
应用层、中间件层、操作系统层、驱动层/硬件抽象层、硬件层
以下是对各层的详细介绍
应用层介绍:应用层(Application layer),有时也叫业务层,一般是实现产品的业务逻辑和最终功能,由开发者根据具体需求编写,比如界面、控制逻辑、协议处理等。
包含内容:
主程序入口(如main函数、app_main)
各种业务模块(如任务管理、UI界面、页面管理、控制流程)
设备/算法应用(如温度采集、数据上传、蓝牙通信等)
通常只通过中间件或驱动API访问下层,不直接操作硬件。
典型文件/模块
main.c、app.c、tasks.c、ui_app.c、page_manager.c等
中间件层介绍:中间件(Middleware),中间件层是为应用层提供通用功能的支撑库或协议栈,起到“工具箱”作用,帮助应用层快速实现复杂功能。常见的中间件为为协议栈、文件系统、GUI库、音频库等。
包含内容:
RT ...
嵌入式软件
未读volatilevolatile关键字作用
禁止编译器优化告知编译器该变量可能被“外部因素”修改(如硬件、中断、多线程等),每次访问必须直接读写内存,而非依赖寄存器缓存或优化掉“冗余”操作。
确保内存可见性强制程序按代码顺序执行对变量的读写操作,避免因编译器重排指令导致的意外行为。
volatile使用场景
直接访问硬件寄存器(某个外设等)
12345volatile uint32_t *status_reg = (volatile uint32_t*)0x40021000; while (*status_reg & 0x01) { // 必须每次从内存读取状态位 // 等待硬件完成操作 }
如果不适用volatile编译器的优化:
缓存到寄存器编译器发现 status_reg 的值在循环中没有被修改(代码中未显式修改),于是将 *status_reg 第一次读取的值缓存到寄存器中,后续循环直接复用寄存器值,不再从内存读取。
优化结果:生成的汇编代码可能如下:
1234567; 第一次读取 *status_reg 到寄存器 eax ...
嵌入式软件
未读背景在嵌入式图形界面开发中,STM32微控制器常用来驱动各类显示屏(如 TFT LCD、OLED),实现丰富的用户交互。随着用户体验要求提升,显示界面的刷新帧率成为评价系统性能的重要指标之一。特别是在使用LVGL这类高性能 GUI 库时,全屏或局部刷新数据量大,对数据搬运速度提出了更高的要求。
最简单的SPI屏幕驱动方式是:CPU逐个将像素将数据通过 SPI 总线写入屏幕。这种方式在低分辨率或静态界面下尚可满足需求,但在高分辨率、频繁刷新的场景下,CPU负载会迅速升高,导致帧率下降,界面卡顿,影响用户体验。
所以我们就需要在原有通讯协议(I2C/SPI)协议的DMA来帮助我们搬运数据
为什么使用DMA?不使用DMA:MCU在整个数据搬运过程中都需要亲自参与,CPU会被大量堵塞在像素数据的发送上,导致刷新期间其它任务处理变慢,严重影响系统的实时性和整体性能。
使用DMA:由于CPU和DMA是一个并行的关系,CPU发起DMA请求后,DMA硬件能批量、高速搬运数据到外设(SPI)。
在DMA传输期间,CPU可以专注于准备下一帧的像素数据,或处理其他任务。等DMA搬运完成后,CPU只需在中断中 ...
嵌入式软件
未读软件模拟I2C在通过软件模拟I2C时,我们是选定任意引脚作为I2C的SDA和SCL,这个时候需要配置GPIO使其既能输入又能输出,这,推挽输出和开漏输出都是可以的,以下将会对两者的配置进行详细介绍
开漏输出引脚配置这是最推荐的做法,也是I2C协议的要求将SDA和SCL引脚配置为上拉电阻(弱) + 开漏输出的模式,这样可以使主机只能输出低电平,避免了总线上一高一低造成的短路问题。
1.开漏输出:
保证IO只具备输出低电平的能力,完全避免电源短路的发生
2.上拉电阻:
通常使用外接4.7K上拉电阻,I2C通信的需要输出高电平的能力,由于开漏模式下不具备输出高电平的能力,需要外部加一个上拉电阻,使其具备输出高电平的能力
这样设计电路的好处:
1.避免电源短路,安全
2.避免推挽模式下的引脚频繁切换,同时具备输入和输出功能
3.这样的硬件设计,使开漏模式具备线与特性:任意设备输出低电平,总线就是低电平,所有设备高电平,总线才是高电平。利用这个特性执行多主机下的总线仲裁机制,始终同步
硬件原理开漏模式的特性是由于硬件上决定的STM32的手册上对开漏输出的描述:
开漏输出:
GPIO写入’ ...