STM32 + RT-Thread + LVGL

news/2024/10/6 8:27:53

一、基本信息

  • 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 软件包

  1. 添加软件包

  2. LVGL 文件目录

  3. 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_OPSdevice.ops     = &lcd_ops;
#elsedevice.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*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/43064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

分布式链路跟踪 Jaeger

分布式应用环境下,事务的完成需要由多个不同的组件协调完成,调用链路比较复杂,问题的定位也不再像原来单体应用这么复杂。 我们采用分布式应用链路跟踪工具完成对事务的跟踪和问题的定位。 Jaeger,jaeger在BIG-IP Next的AS3 实现中有用到。 本质上讲,像Jaeger这样的跟踪工…

LLM大模型: llama源码要点解读(一)

transformer火了之后,基于transformer架构的llama也火了,可能的原因:来自meta,一线互联网大厂,质量有保证;自称70b参数的表现比chatGPT3还好(Llama 2:Open Foundation and Fine-Tuned Chat Models)!可能会成为大模型界的Android:各种基于llama的微调和应用会越来越多…

[快速阅读七] Halcon里emphasize函数相关资料.

时不时有人问我我的SSE优化Demo里emphasize(边缘强调)的原理是啥,有没有写博客,其实不是我不愿意写博客,而是那个东西太过于简单,我不想写博客。但是耐不住问的人多了,我就干脆复制点资料放在博客里吧,省的每次我还要去找点资料复制给人家。时不时有人问我我的SSE优化D…

如何通过加密U盘 实现数据传输闭环管控?

加密U盘是用来保护存储在其中数据的安全的。通过加密技术,用户可以将其敏感文件和信息存储在U盘中,并设置密码或使用其他加密方法来防止未经授权的访问。这种安全措施可以防止数据泄露或盗窃,特别是在丢失或被盗的情况下,确保数据不会落入他人手中。许多不同类型的企业和组…

[Cloud Networking] Layer 2

目录1. 什么是Mac Address?2. 如何查找MAC地址?3. 二层数据交换 1. 什么是Mac Address? MAC 地址是计算机的唯一48位硬件编码,嵌入到网卡中。MAC地址也称为网络设备的物理地址,在IEEE 802中规定,数据链路层分为 逻辑链路控制(LLC)子层 和 媒体控制访问(MAC)子层。 MA…

跨国大文件传输需要哪些方面?怎么实现数据快速传输?

跨国大文件传输涉及到许多方面,包括网络速度、安全性、可靠性和法律合规性等。以下是跨国大文件传输时需要考虑的一些重要方面: 高速稳定的网络连接:确保有足够的带宽和稳定的网络连接以支持大文件的快速传输。这可能需要考虑到跨国网络的延迟和带宽限制。 1、数据加密:为了…

华为云短信服务教你用C++实现Smgp协议

本文简单对SGIP协议进行了介绍,并尝试用C++实现协议栈,但实际商用发送短信往往更加复杂,可以选择华为云消息&短信服务通过HTTP协议接入。本文分享自华为云社区《华为云短信服务教你用C++实现Smgp协议》,作者:张俭。 引言&协议概述 中国联合网络通信有限公司短消息…

老生常谈!程序员为什么要阅读源代码?

面试造航母,入职拧螺丝。相信大家对这句话的精髓都深有体会,大家好,我是码农先森。 阅读源码这是一个老生常谈的话题了,但又是很多人想做又没有付出行动的事情。前段时间我研究了 Swoole 的源代码,并且输出了系列的源码分析文章「感兴趣的朋友可以翻阅以前的文章」。虽然这…