最近拿到了一块STM32L562 Discovery Kit开发板,这块板子搭载了基于Arm Cortex-M33内核的STM32L5系列MCU。作为ST最新推出的超低功耗安全微控制器系列,STM32L5在物联网终端设备领域有着独特的优势。我打算在这块开发板上移植一个轻量级GUI框架,为后续开发人机交互界面做准备。
选择STM32L5系列开发GUI应用主要基于以下几点考虑:首先,Cortex-M33内核支持TrustZone技术,可以方便地实现安全关键代码与非安全代码的隔离;其次,STM32L5在运行模式下的功耗仅为100μA/MHz,特别适合电池供电的便携式设备;再者,该系列芯片内置了丰富的图形加速外设,包括Chrom-ART加速器和硬件JPEG解码器。
在嵌入式领域,可供选择的GUI框架主要有以下几种:
考虑到STM32L562的资源限制(256KB SRAM,512KB Flash)和项目需求,我最终选择了LVGL作为移植对象。LVGL 7.x版本最低只需要16KB RAM和64KB Flash即可运行,且具有以下优势:
STM32L562 Discovery Kit开发板配备了以下与GUI相关的硬件资源:
在移植过程中,我们需要配置以下外设:
我使用的是STM32CubeIDE 1.7.0作为开发环境,这是ST官方推出的免费IDE,集成了STM32CubeMX配置工具和GCC编译工具链。具体安装步骤如下:
提示:建议同时安装STM32CubeProgrammer,用于后续的固件烧录和调试。
从GitHub获取最新稳定版LVGL(当前为v7.11.0):
bash复制git clone --branch v7.11.0 https://github.com/lvgl/lvgl.git
将以下目录复制到工程中:
在工程属性中添加包含路径:
在lv_conf.h中配置基本参数:
c复制#define LV_COLOR_DEPTH 16
#define LV_HOR_RES_MAX 240
#define LV_VER_RES_MAX 240
#define LV_USE_PERF_MONITOR 1
实现显示驱动接口(lv_port_disp.c):
c复制static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
LCD_SetWindow(area->x1, area->y1, w, h);
LCD_WriteData((uint8_t *)color_p, w * h * 2);
lv_disp_flush_ready(disp_drv);
}
在lv_port_indev.c中配置触摸输入:
c复制static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) {
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
if(TOUCH_GetState(&touch) == TOUCH_STATE_PRESSED) {
TOUCH_GetXY(&touch, &last_x, &last_y);
data->point.x = last_x;
data->point.y = last_y;
data->state = LV_INDEV_STATE_PR;
} else {
data->point.x = last_x;
data->point.y = last_y;
data->state = LV_INDEV_STATE_REL;
}
}
由于STM32L562内存有限,需要特别优化LVGL的内存使用:
c复制#define LV_MEM_SIZE (48*1024) // 分配48KB内存给LVGL
#define LV_DISP_DEF_REFR_PERIOD 30 // 刷新周期30ms
#define LV_ATTRIBUTE_FAST_MEM __attribute__((section(".fast_mem")))
将LVGL的快速内存区域分配到DTCM RAM(64KB):
ld复制MEMORY
{
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
...
}
SECTIONS
{
.fast_mem :
{
. = ALIGN(4);
_sfast = .;
*(.fast_mem)
*(.fast_mem*)
. = ALIGN(4);
_efast = .;
} >DTCMRAM
}
在main.c中创建测试界面:
c复制void create_test_ui(void) {
lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);
lv_obj_set_size(btn, 100, 50);
lv_obj_align(btn, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_t * label = lv_label_create(btn, NULL);
lv_label_set_text(label, "Click Me!");
lv_obj_set_event_cb(btn, btn_event_cb);
}
static void btn_event_cb(lv_obj_t * btn, lv_event_t event) {
if(event == LV_EVENT_CLICKED) {
static uint8_t cnt = 0;
cnt++;
lv_obj_t * label = lv_obj_get_child(btn, NULL);
lv_label_set_text_fmt(label, "Clicked: %d", cnt);
}
}
使用LVGL内置的性能监控工具,我们得到以下数据:
| 测试项 | 数值 | 说明 |
|---|---|---|
| CPU占用率 | 15-20% | 界面空闲状态 |
| 刷新帧率 | 33 FPS | 无动画时 |
| 内存使用 | 42KB/48KB | 含一个简单界面 |
| 触摸响应延迟 | <50ms | 从触摸到界面反馈 |
添加一个简单的动画示例:
c复制void create_animation_demo(void) {
lv_obj_t * obj = lv_obj_create(lv_scr_act(), NULL);
lv_obj_set_size(obj, 50, 50);
lv_obj_set_style_local_bg_color(obj, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, obj);
lv_anim_set_values(&a, 0, 200);
lv_anim_set_time(&a, 1000);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_create(&a);
}
初期测试时发现屏幕有明显闪烁现象,通过以下步骤解决:
disp_flush回调结束时才通知LVGL刷新完成c复制#define LV_DISP_DOUBLE_BUF 1
发现触摸坐标不准确,实现校准流程:
c复制void touch_calibrate(void) {
lv_point_t points[3] = {{20,20}, {200,120}, {120,200}};
lv_indev_set_cal_points(touch_indev, points);
}
当创建复杂界面时出现内存不足,采取以下措施:
c复制#define LV_USE_OBJ_REBUILD 1
c复制#define LV_MEMCPY_MEMSET_OPTIMIZE 1
将图片资源存储到QSPI Flash中:
c复制void qspi_read(uint32_t addr, void *buf, uint32_t len) {
BSP_QSPI_Read(buf, addr, len);
}
c复制lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv);
fs_drv.letter = 'Q';
fs_drv.open_cb = qspi_open;
fs_drv.read_cb = qspi_read;
lv_fs_drv_register(&fs_drv);
利用STM32L5的TrustZone特性,将触摸数据处理放在安全区:
c复制void SECURE_Touch_GetXY(int32_t *x, int32_t *y) {
*x = touch.x;
*y = touch.y;
}
c复制int32_t x, y;
SECURE_Touch_GetXY(&x, &y);
c复制void lv_task_handler(void) {
static uint32_t last_touch = 0;
if(lv_disp_get_inactive_time(NULL) > 2000) {
// 无操作2秒后进入低功耗模式
__WFI();
return;
}
...
}
经过一周的开发和调试,我们成功在STM32L562 Discovery Kit上移植了LVGL图形库,并实现了以下功能:
实测表明,STM32L5完全有能力驱动240x240分辨率的GUI应用,同时保持较低的功耗。在后续开发中,可以考虑:
整个移植过程中最大的收获是对STM32L5的图形加速特性有了更深入的理解,特别是如何平衡性能与功耗。对于资源受限的嵌入式GUI开发,提前做好内存规划和性能优化至关重要。