前言

本篇文章使用STM32CubeIDE进行开发,移植LVGL到STM32F103C8T6。F103C8T6刚好卡在LVGL能用的范围。

**开源地址:**https://gitee.com/wei-yuliu/stm32-f103-c8-t6-lvgl-stm32-cube-ide.git

一年前尝试过移植LVGL到STM32F103C8T6,各种问题,一直失败,放弃了。最近突然想起这件事,发现其实不难(兴许是当时能力还不够)。文章可能会有很多问题讲述不是很清楚,也可能有很多错误,欢迎指正!

挖个坑:

移植到Linux-x86:文章还没写

移植到Linux-ARM:文章还没写

学到什么

  • 使用STM32CubeIDE/CubeMX配置单片机的时钟,GPIO,外设,中断等。
  • 使用STM32Hal库代码编写,函数调用。
  • STM32CubeIDE/CubeMX项目配置,会合理工具使用会让编程事半功倍。
  • 屏幕厂商提供的示例,移植驱动到自己开发板。
  • 懂得LVGL移植的那点东西:
    • 裁剪&配置(lv_conf.h);
    • 提供时钟心lv_tick_inc(1)跳给lvgl;
    • 提供绘图函数LCD_DrawArea()给lvgl;
    • 时不时问问lvgl是否要刷新了lv_task_handler();
    • 写lvgl应用(main.c的例子);
  • 为后续Linux驱动开发,应用开发提供思维。我认为linux驱动和应用本质是硬件驱动提供接口给应用调用。你能移植设备厂商的代码了,跟应用开发人员(也还是你- -)规定一下接口需要什么参数,剩下的就不需要驱动工程师考虑的问题了,到应用开发人员头疼了(也还是你- -)。linux一堆规定,规定驱动要怎么写(mmp),规定提供给应用开发人员的接口(读写文件),这里就不深究了,后面移植到linux的时候再细细品味。

LVGL

下载

https://github.com/lvgl/lvgl/releases

stm32-lvgl-1


裁剪

stm32-lvgl-2

stm32-lvgl-3


STM32Cube IDE配置

项目配置

stm32-lvgl-4

stm32-lvgl-5

stm32-lvgl-6

stm32-lvgl-7

stm32-lvgl-8

stm32-lvgl-9

生成的目录结构

stm32-lvgl-10

配置LVGL目录

stm32-lvgl-11

stm32-lvgl-12

stm32-lvgl-13

移植屏幕厂商驱动测试

  • 移植屏幕驱动最重要的一点是:给LVGL提供绘图函数。

  • 由于每个人使用的屏幕不同,这里不写代码了,可以前往开原仓库中查看参考,地址:

画矩形函数:

void LCD_DrawArea(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *colors);
  • 在Core文件夹新增Hardware文件夹
  • 新增lcd.h和lcd.c存放移植屏幕厂商代码

LVGL移植代码编写

裁剪lvgl功能

修改后的lv_conf.h文件可以前往:https://gitee.com/wei-yuliu/stm32-f103-c8-t6-lvgl-stm32-cube-ide.git,直接改一下LV_HOR_RES_MAX,LV_VER_RES_MAX就能用了。

  • 详细说明

在lv_conf.h文件(原lv_conf_template.h)做如下修改:

  1. #if 0改成#if 1:这个是开启lvgl,如果为0为不使用lvgl,为0整个.h文件失效

  2. 屏幕大小:LV_HOR_RES_MAX,LV_VER_RES_MAX设置成自己屏幕大小

  3. 颜色翻转:如果是SPI屏要设置LV_COLOR_16_SWAP为1

  4. 屏幕旋转(0,90°,180°,270°)缓冲区:LV_DISP_ROT_MAX_BUF调小一点,10u变成1u,测试没有用到屏幕旋转功能,LV_DISP_ROT_MAX_BUF很小都没事。

  5. LV_MEM_SIZE:内存池,32 * 1024(32KB)改成2 * 1024(2KB),RAM占用2KB,C8T6只有20KB,所以我们这里调小一点。这里就不得不说LVGL内存的两种分配方式了:

    1. 手动分配LV_MEM_CUSTOM为0时,一个大小固定的数组(2KB)
    2. 自动分配LV_MEM_CUSTOM为1时,由malloc分配,单片机不怎么使用。移植LVGL到linux这种带操作系统才会用。
  6. 功能裁剪

功能模块修改后说明
LV_USE_ANIMATION0禁用动画
LV_USE_SHADOW0禁用阴影绘制
LV_USE_PATTERN0禁用矩形图案填充
LV_USE_VALUE_STR0禁用矩形上的数值字符串绘制
LV_USE_BLEND_MODES0禁用混合模式(仅正常混合)
LV_USE_OPA_SCALE0禁用整体透明度缩放
LV_USE_IMG_TRANSFORM0禁用图像旋转和缩放
LV_USE_API_EXTENSION_V60禁用 v6 API 兼容
LV_USE_API_EXTENSION_V70禁用 v7 API 兼容
LV_USE_DEBUG0完全禁用调试断言
  1. 对象裁剪

    1. 保留对象:

      • LV_USE_BTN → 1
      • LV_USE_CONT → 1
      • LV_USE_LABEL → 1
    2. 裁剪掉的对象:

      对象修改后控件名称
      LV_USE_ARC0Arc(圆弧)
      LV_USE_BAR0Bar(进度条)
      LV_USE_BTNMATRIX0Button Matrix(按钮矩阵)
      LV_USE_CALENDAR0Calendar(日历)
      LV_USE_CANVAS0Canvas(画布)
      LV_USE_CHECKBOX0Checkbox(复选框)
      LV_USE_CHART0Chart(图表)
      LV_USE_CPICKER0Color Picker(颜色选择器)
      LV_USE_DROPDOWN0Dropdown List(下拉列表)
      LV_USE_GAUGE0Gauge(仪表盘)
      LV_USE_IMG0Image(图像)
      LV_USE_IMGBTN0Image Button(图像按钮)
      LV_USE_KEYBOARD0Keyboard(虚拟键盘)
      LV_USE_LED0LED(指示灯)
      LV_USE_LINE0Line(线条)
      LV_USE_LIST0List(列表)
      LV_USE_LINEMETER0Line Meter(线性仪表)
      LV_USE_OBJMASK0Object Mask(对象遮罩)
      LV_USE_MSGBOX0Message Box(消息框)
      LV_USE_PAGE0Page(页面)
      LV_USE_SPINNER0Spinner(加载动画)
      LV_USE_ROLLER0Roller(滚轮选择器)
      LV_USE_SLIDER0Slider(滑动条)
      LV_USE_SPINBOX0Spinbox(数值调节框)
      LV_USE_SWITCH0Switch(开关)
      LV_USE_TEXTAREA0Textarea(文本区域)
      LV_USE_TABLE0Table(表格)
      LV_USE_TABVIEW0Tabview(选项卡视图)
      LV_USE_TILEVIEW0Tileview(磁贴视图)
      LV_USE_WIN0Window(窗口)

配置LVGL

  • 在Core/Hardware文件夹下新增lvglc.h和lvglc.c
/*
 * lvglc.h
 */
#ifndef HARDWARE_LVGLC_H_
#define HARDWARE_LVGLC_H_
#include "lcd.h" //引入lcd驱动
#include "lvgl.h" //引入lvgl

void lvgl_init(void);
void lvgl_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
#endif
/*
 * lvglc.c
 */
#include "lvglc.h"

#define LV_BUF_SIZE (LCD_WIDTH * 5) // 缓冲区大小(行数)
static lv_disp_buf_t draw_buf;     // LVGL绘制缓冲区
static lv_color_t buf1[LV_BUF_SIZE];    // 第一缓冲区
/**
 * lvgl初始化函数
 *
 */
void lvgl_init() {
  lv_init();
  lv_disp_buf_init(&draw_buf, buf1, NULL, LV_BUF_SIZE);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = LCD_WIDTH; //屏幕宽度
  disp_drv.ver_res = LCD_HEIGHT; //屏幕高度
  disp_drv.flush_cb = lvgl_disp_flush;//刷新回调
  disp_drv.buffer = &draw_buf; //缓冲区
  lv_disp_drv_register(&disp_drv);
}

/* 显示刷新回调函数 */
void lvgl_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {

  //绘图矩形这里调用的就是lcd驱动,也就是说,lcd只需要给lvgl提供一个绘图函数就可以了。这也就是为什么lvgl能移植到各种地方的原因之一
  LCD_DrawArea(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);

  lv_disp_flush_ready(disp); // 通知LVGL刷新完成
}

给lvgl提供时钟

  • stm32f103c8t6需要配置时钟和定时器为lvgl提供时钟
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时中断,在main.c中直接定义这个函数,按需修改
{
	if(htim->Instance==TIM2)//如果是定时器2
		lv_tick_inc(1); //给lvgl提供时钟
	}
}

使用lvgl!

int main(void)
{
	//其他代码
	//....
	//....
	
	//启动定时器
	HAL_TIM_Base_Start_IT(&htim2);
	LCD_Init();//lcd初始化
  	lvgl_init();//lvgl初始化,lvglc.c中的函数
  	
  	lv_obj_t *btn = lv_btn_create(lv_scr_act(), NULL);  //创建按钮
  	lv_obj_set_size(btn, 120, 50);               // 宽度120像素,高度50像素
  	lv_obj_align(btn, NULL, LV_ALIGN_CENTER, 0, 0); // 居中显示
  	//设置按钮背景颜色
  	lv_obj_set_style_local_bg_color(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0000FF));
    //在while中调用lv_task_handler()
    while(1)
    {
        lv_task_handler();
        HAL_Delay(5);
    }
}

代码优化

  • 不开启代码优化是绝对超出flash的!

properties -> c/c++build -> settings -> optimization level设置成**-Os**

stm32-lvgl-14

编译烧录

stm32-lvgl-16