STM32 + RT-Thread + LVGL
一、基本信息
- MCU:STM32F103ZET6
- RT-Thread:5.0.2
- LVGL:8.3.11
- LCD:ST7735s
- 编译环境:RTThread studio
二、LVGL 移植要求
- 16、32或64位微控制器或处理器
- 建议速度大于16 MHz
- 闪存/ROM: > 64 kB(建议180 kB)
- 内存:8 kB(建议24 kB)
- 1个帧缓冲器:在MCU、外部RAM或显示控制器中
- LVGL的图形缓冲:>“水平分辨率”像素(推荐1/10“屏幕尺寸”)
- C99或更新的编译器
- 基本的C(或C++)知识:指针、结构、回调
三、添加 LVGL 软件包
-
添加软件包
-
LVGL 文件目录
-
lv_rt_thread_port.c 文件
由上图可知:文件已经帮我们完成了三个函数的调用,只需要在对函数进行实例即可,由于我没用到触摸屏,所以将 lv_port_indev_init() 的调用屏蔽了
四、lv_user_gui_init() 函数
此函数的主要作用是 LVGL 启动的初始化界面,相当于开机界面,主要是消除初始化启动功能时导致屏幕出现长时间的白屏的现象,程序如下
点击查看代码
#include <lvgl.h> void lv_user_gui_init(void) { /* 获取默认显示器的活动屏幕 */ lv_obj_t *scr = lv_scr_act(); lv_obj_clean(scr); /* 清屏 */ /* 创建界面启动界面 */ lv_obj_t *page = lv_obj_create(scr); lv_obj_set_size(page, LV_HOR_RES, LV_VER_RES); lv_obj_set_style_bg_color(page, lv_color_black(), LV_PART_MAIN); /* 设置背景颜色 */ lv_obj_set_style_radius(page, 0, LV_PART_MAIN | LV_STATE_DEFAULT); /* 设置导角为0 */ lv_obj_set_style_border_width(page, 0, LV_PART_MAIN | LV_STATE_DEFAULT); /* 设置边框为0 */ /* 添加标签 */ lv_obj_t *label = lv_label_create(page); lv_label_set_text(label, "Loading"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); }
五、lv_port_disp_init() 函数
此函数主要的作用是,初始化显示屏,并将显示屏的图像刷新函数与 flush_cb 函数进行绑定,程序如下
点击查看代码
#include <lvgl.h> #include <rtthread.h> #include <board.h> //#define DRV_DEBUG #define LOG_TAG "LVGL.port.disp" #include <drv_log.h> static rt_device_t lcd_device = RT_NULL; static struct rt_device_graphic_info lcd_info; static lv_disp_drv_t disp_drv; /* 显示驱动程序的描述符 */ /* 用于存储缓冲区的静态或全局变量 */ static lv_disp_draw_buf_t disp_buf; void lv_port_disp_init(void) { rt_err_t result; void *lv_disp_buf1 = RT_NULL; void *lv_disp_buf2 = RT_NULL; /* 查找 LCD 设备 */ lcd_device = rt_device_find("lcd"); if (lcd_device == 0) { LOG_E("lcd_device error!"); return; } result = rt_device_open(lcd_device, RT_DEVICE_FLAG_RDWR); if(result != RT_EOK) { LOG_E("open lcd device failed"); return; } /* get framebuffer address */ result = rt_device_control(lcd_device, RTGRAPHIC_CTRL_GET_INFO, &lcd_info); if (result != RT_EOK) { LOG_E("error!"); /* get device information failed */ return; } RT_ASSERT (lcd_info.bits_per_pixel == 8 || lcd_info.bits_per_pixel == 16 || lcd_info.bits_per_pixel == 24 || lcd_info.bits_per_pixel == 32); lv_disp_buf1 = rt_malloc(lcd_info.smem_len * sizeof(lv_color_t)); rt_memset(lv_disp_buf1, 0, lcd_info.smem_len * sizeof(lv_color_t)); RT_ASSERT(lv_disp_buf1 != RT_NULL); lv_disp_buf2 = rt_malloc(lcd_info.smem_len * sizeof(lv_color_t)); rt_memset(lv_disp_buf2, 0, lcd_info.smem_len * sizeof(lv_color_t)); RT_ASSERT(lv_disp_buf2 != RT_NULL); /* 使用缓冲区初始化 disp_buf */ lv_disp_draw_buf_init(&disp_buf, lv_disp_buf1, lv_disp_buf2, lcd_info.smem_len); lv_disp_drv_init(&disp_drv); /* 设置显示器的分辨率 */ disp_drv.hor_res = lcd_info.width; disp_drv.ver_res = lcd_info.height; /* 设置显示缓冲区 */ disp_drv.draw_buf = &disp_buf; /* 用于将缓冲区的内容复制到显示器 */ disp_drv.flush_cb = lcd_device->user_data; /* 注册驱动程序 */ lv_disp_drv_register(&disp_drv); }
六、ST7735s 驱动程序
这里不要局限于 ST7735s 这个显示屏,主要是介绍 LCD 与 LVGL 对接的 bsp 的编写过程。程序中的其他函数主要都是初始化 lcd 的工作,主要关注 lcd_fb_flush 函数,此函数会在 LVGL 中界面更新的时候调用,从而刷新屏幕的显示。
点击查看代码
/** * @brief LCD 驱动的操作函数 * @param device LCD 设备结构体 * @param cmd 操作命令 * @param args 传入的参数 * @retval None */ static rt_err_t drv_lcd_control(struct rt_device *device, int cmd, void *args) { LOG_D("drv_lcd_control cmd is: %d\n", cmd); switch (cmd) { case RTGRAPHIC_CTRL_RECT_UPDATE: break; case RTGRAPHIC_CTRL_POWERON: { /* LCD 退出睡眠模式 */ lcd_display_on(); lcd_exit_sleep(); } break; case RTGRAPHIC_CTRL_POWEROFF: { /* LCD 进入睡眠模式 */ lcd_display_off(); lcd_enter_sleep(); } break; case RTGRAPHIC_CTRL_GET_INFO: { /* 获取 LCD 参数 */ memcpy(args, &lcd_info, sizeof(lcd_info)); } break; default: return -RT_EINVAL; } return RT_EOK; } static void lcd_fb_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { rt_uint32_t px_size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); /* 设置屏幕刷新区域 */ lcd_draw_area_set(area->x1, area->y1, area->x2, area->y2); /* 这里直接通过 SPI 发送数据,所以需要单独将数据引脚拉高 */ rt_pin_write(LCD_DC_PIN, PIN_HIGH); /* SPI 发送时是 uint_8, 而像素是 uint_16 */ rt_spi_send(spi_dev_lcd, color_p, px_size * 2); lv_disp_flush_ready(disp_drv); } /* 驱动函数实现的结构体 */ #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops lcd_ops = { drv_lcd_init, RT_NULL, RT_NULL, RT_NULL, RT_NULL, drv_lcd_control }; #endif /** * @brief LCD 设备注册 * * @param None * @retval int 注册结果 */ int drv_lcd_hw_init(void) { rt_err_t result = RT_EOK; rt_uint32_t lcd_buff_size = LCD_HEIGHT * LCD_WIDTH * 2; device.user_data = lcd_fb_flush; /* 设置 LCD 设备信息 */ lcd_info.height = LCD_HEIGHT; lcd_info.width = LCD_WIDTH; lcd_info.bits_per_pixel = LCD_BITS_PER_PIXEL; lcd_info.pixel_format = RTGRAPHIC_PIXEL_FORMAT_RGB565; // 图像的格式(RGB:565) /* LCD 显示缓冲区,大小为显示一帧图像所需空间 */ lcd_info.smem_len = lcd_buff_size; #ifdef RT_USING_DEVICE_OPS device.ops = &lcd_ops; #else device.init = drv_lcd_init; device.control = drv_lcd_control; #endif /* 注册 LCD 设备 */ result = rt_device_register(&device, "lcd", RT_DEVICE_FLAG_RDWR); return result; } INIT_DEVICE_EXPORT(drv_lcd_hw_init);
七、总结
从上面的过程可以看出,移植 LVGL 的过程很简单,最主要的是 lcd_fb_flush 函数的实现。需要注意的便是 lv_disp_flush_ready(disp_drv) 这个函数一定要添加,后面的界面可能不刷新,或者刷新不正常等现象。最后还需要添加一个头文件,如下所示
点击查看代码
#ifndef LV_CONF_H #define LV_CONF_H #define LV_COLOR_DEPTH 16 #endif /*LV_CONF_H*/
本文来自博客园,作者:浇筑菜鸟,转载请注明原文链接:https://www.cnblogs.com/jzcn/p/18241447
如本博客的内容侵犯了你的权益,请与以下地址联系,本人获知后,马上删除。同时本人深表歉意,并致以崇高的谢意! cn_jiaozhu@qq.com
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器