[GUI]stm32搭载3.5寸SPI-TFT屏移植LittleVGL

唠几句,记录下移植笔记
新项目用到LVGL,也是首次接触GUI库,所以Emmmm,,,学呗!!!
之前都是直接在LCD屏上画点、画线、画圆、画个矩形、画个多边形、显示个字符串、显示张图片而已,没有用过GUI库,在网上找了点学习资料,然后把LVGL库的用法整体过了一遍。
寻思着搞个屏练习一下,然后趁着周末把SPI屏(IFI9488/ 480*320)驱动起来,甚至还优化了绘图部分的程序,下面是部分优化记录

方式一:移植淘宝卖家提供的Demo
串口日志:刷整屏用时3s
system Init complate!
RUNNING time: 3032

方式二:优化1(库函数)
串口日志:刷整屏用时1.2s
system Init complate!
RUNNING time: 1221

方式三:优化2(寄存器:spi写)
串口日志:刷整屏用时0.33s
system Init complate!
RUNNING time: 333

最终版
串口日志:刷整屏用时0.24s
system Init complate!
RUNNING time: 239

还有优化的空间,据说用ESP32的SPI能达到80M,那肯定很爽,但是不折腾了,重点还是LVGL部分。
把画点部分代码贴出来吧,这里没有上DMA哦

#define RGB_R(n)	(n>>8)&0xF8
#define RGB_G(n)	(n>>3)&0xFC
#define RGB_B(n)	n<<3

#define LCD_PUSH_GRAM(n)								\
do{														\
	*((__IO uint8_t*)&hspi2.Instance->DR) = RGB_R(n);	\
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));	\
	*((__IO uint8_t*)&hspi2.Instance->DR) = RGB_G(n);	\
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));	\
	*((__IO uint8_t*)&hspi2.Instance->DR) = RGB_B(n);	\
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));	\
}while(0U)


//在指定位置写入一个指定像素的点数据
static inline void _LCD_send_pixel(uint16_t x, uint16_t y, uint16_t color){
	//制定位置
	LCD_CS_L;
	LCD_DC_L;
	*((__IO uint8_t*)&hspi2.Instance->DR) = 0x2A;	//setx-cmd
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));

	LCD_DC_H;
	*((__IO uint8_t*)&hspi2.Instance->DR) = x>>8;	//x-start
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = x&0x00FF;
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = x>>8;	//x-end
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = x&0x00FF;
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));

	LCD_DC_L;
	*((__IO uint8_t*)&hspi2.Instance->DR) = 0x2B;	//sety-cmd
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));

	LCD_DC_H;
	*((__IO uint8_t*)&hspi2.Instance->DR) = y>>8;	//y-start
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = y&0x00FF;
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = y>>8;	//y-end
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));
	*((__IO uint8_t*)&hspi2.Instance->DR) = y&0x00FF;
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));

	LCD_DC_L;
	*((__IO uint8_t*)&hspi2.Instance->DR) = 0x2C;	//start write gram
	while(!__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE));

	LCD_DC_H;
	LCD_PUSH_GRAM(color);
	LCD_CS_H;
}
void LCD_send_pixel(uint16_t x, uint16_t y, uint16_t color){
	_LCD_send_pixel(x, y, color);
}

//清屏
void LCD_Clear(uint16_t Color){
	uint32_t i,m;  
	LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);

	uint32_t tickstart = HAL_GetTick();

	LCD_CS_L;
	LCD_DC_H;
	for(i=0;i

下载 LVGL 库

这种事情最好是找 LVGL官网 或 LVGL GitHub库,不过最好用的还是国内源 LVGL Gitee库
git checkout v7.8.0这里用的V7.8.0版本,V8.1.0版本把握不住哈哈哈

将LVGL移植到你的工程中

重点关注这几个文件或文件夹即可
[GUI]stm32搭载3.5寸SPI-TFT屏移植LittleVGL_第1张图片将 lvgl 库添加到 keil 中,记得添加路径哦

port 为接口文件,其中lv_port_disp/lv_port_indev/lv_port_fs 分别对应 LCD 屏绘制,触摸、鼠标、编码器等输入设备,还有文件系统,这里只使能lv_port_disp

[GUI]stm32搭载3.5寸SPI-TFT屏移植LittleVGL_第2张图片
将画点函数,放到对应的接口函数中
[GUI]stm32搭载3.5寸SPI-TFT屏移植LittleVGL_第3张图片可将LCD 初始化放到 lvgl 控件初始化函数中
[GUI]stm32搭载3.5寸SPI-TFT屏移植LittleVGL_第4张图片

在 main 函数中,初始化LVGL和 lvgl 的心跳程序

#define USE_LVGL	1

// main函数
int main(void){
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_TIM6_Init();
	MX_TIM7_Init();
	MX_USART1_UART_Init();
	MX_IWDG_Init();
	MX_SPI1_Init();
	MX_SPI2_Init();
	MX_SPI3_Init();
#if USE_LVGL
	lv_init();
    lv_port_disp_init();        // 显示器初始化
//    lv_port_indev_init();       // 输入设备初始化(如果没有实现就注释掉)
//    lv_port_fs_init();          // 文件系统设备初始化(如果没有实现就注释掉)

	slim_index();		//小控件测试
//	slim_index2();		//仪表盘控件测试
#else
	LCD_Init();
#endif
	printf("system Init complate!\n");
	while (1) {
#if USE_LVGL
			lv_tick_inc(50);
			lv_task_handler();
#else
			for(uint16_t i=0;i<320;i++){
			LCD_send_pixel(i, i, RED);
			}
			for(uint16_t i=0;i<320;i++){
			LCD_send_pixel(i, 320-i, RED);
			}
			HAL_Delay(1000);
			HAL_IWDG_Refresh(&hiwdg);
#endif  
		}
}

测试函数

lv_obj_t *bar1;
lv_obj_t *btn2;
lv_obj_t *label_bar1;
lv_obj_t *slider1;
lv_obj_t *led1;
static void slim_index(void){
	lv_obj_t *scr = lv_scr_act(); //获取屏幕对象
	
	//彩色标签
#if 1
	lv_obj_t * label1 = lv_label_create(scr, NULL);
    lv_label_set_recolor(label1, true);
    lv_label_set_long_mode(label1, LV_LABEL_LONG_SROLL_CIRC); /*Circular scroll*/
    lv_obj_set_width(label1, 120);
    lv_label_set_text(label1, "#ff0000 Hello# #00ff00 world ! slim su.#");
    lv_obj_align(label1, NULL, LV_ALIGN_IN_TOP_LEFT, 20, 5);
	lv_label_set_anim_speed(label1, 80);
#endif
	
	//圆弧
#if 1
	static lv_style_t style_arc1_bg, style_arc1_indic;
	lv_style_init(&style_arc1_bg);
	lv_style_set_line_width(&style_arc1_bg, LV_STATE_DEFAULT, 10);
	lv_style_set_line_color(&style_arc1_bg, LV_STATE_DEFAULT, LV_COLOR_MAKE(0, 255, 0));
	lv_style_init(&style_arc1_indic);
	lv_style_set_line_width(&style_arc1_indic, LV_STATE_DEFAULT, 10);
	lv_style_set_line_color(&style_arc1_indic, LV_STATE_DEFAULT, LV_COLOR_MAKE(255, 0, 0));
	
	lv_obj_t *arc1 = lv_arc_create(scr, NULL);
	lv_obj_set_size(arc1, 100, 100);
	lv_obj_set_pos(arc1, 5, 50);
	lv_arc_set_bg_angles(arc1, 0, 360);
	lv_arc_set_angles(arc1, 0, 90);
	lv_obj_add_style(arc1, LV_ARC_PART_BG, &style_arc1_bg);
	lv_obj_add_style(arc1, LV_ARC_PART_INDIC, &style_arc1_indic);
	
	static lv_style_t style_arc2_bg, style_arc2_indic;
	lv_style_init(&style_arc2_bg);
	lv_style_set_bg_opa(&style_arc2_bg, LV_STATE_DEFAULT, 0);		//设置透明度
	lv_style_set_border_width(&style_arc2_bg, LV_STATE_DEFAULT, 0);	//边框线宽
	lv_style_set_line_width(&style_arc2_bg, LV_STATE_DEFAULT, 15);
	lv_style_set_line_color(&style_arc2_bg, LV_STATE_DEFAULT, LV_COLOR_SILVER);
	lv_style_init(&style_arc2_indic);
	lv_style_set_line_width(&style_arc2_indic, LV_STATE_DEFAULT, 15);
	lv_style_set_line_color(&style_arc2_indic, LV_STATE_DEFAULT, LV_COLOR_PURPLE);
	
	lv_obj_t *arc2 = lv_arc_create(scr, NULL);
	lv_obj_set_size(arc2, 100, 100);
	lv_obj_set_pos(arc2, 120, 50);
	lv_arc_set_bg_angles(arc2, 0, 360);
	lv_arc_set_angles(arc2, 90, 270);
	lv_obj_add_style(arc2, LV_ARC_PART_BG, &style_arc2_bg);
	lv_obj_add_style(arc2, LV_ARC_PART_INDIC, &style_arc2_indic);
	lv_obj_t *label2 = lv_label_create(arc2, NULL);	 // 在Arc控件上创建一个标签
	lv_obj_align(label2, arc2, LV_ALIGN_CENTER, 20, 0); // 对齐到Arc控件中心
	lv_label_set_text(label2, "2");				 // 设置标签文本
//	lv_obj_set_event_cb(arc2, arc_event_handler);
#endif

	//进度条
#if 1
	static lv_style_t style_bar_label1;
	lv_style_init(&style_bar_label1);
	lv_style_set_text_font(&style_bar_label1, LV_STATE_DEFAULT, &lv_font_montserrat_18);
	
	bar1 = lv_bar_create(scr, NULL);
	lv_obj_set_size(bar1, 100, 15);
	lv_bar_set_range(bar1, 0, 100);
	lv_obj_set_style_local_bg_color(bar1, LV_BAR_PART_BG, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
    lv_obj_set_style_local_bg_color(bar1, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_GREEN);
	lv_bar_set_anim_time(bar1, 200);
	lv_bar_set_value(bar1, 80, LV_ANIM_ON);
	lv_obj_align(bar1, scr, LV_ALIGN_IN_TOP_MID, 40, 20);
	
	label_bar1 = lv_label_create(scr, NULL);
	lv_obj_add_style(label_bar1, LV_LABEL_PART_MAIN, &style_bar_label1);
	lv_obj_align(label_bar1, bar1, LV_ALIGN_OUT_RIGHT_MID, 10, 0); 
	lv_label_set_text(label_bar1, "80%");
#endif

	//复选框
#if 1
	static lv_style_t style_checkbox1;
	lv_style_init(&style_checkbox1);
	lv_style_set_text_font(&style_checkbox1, LV_STATE_DEFAULT, &lv_font_montserrat_18);
	lv_style_set_bg_opa(&style_checkbox1, LV_STATE_DEFAULT, LV_OPA_100);
	lv_style_set_bg_color(&style_checkbox1, LV_STATE_DEFAULT, LV_COLOR_YELLOW);

	lv_obj_t *checkbox1 = lv_checkbox_create(scr, NULL);
	lv_obj_set_width(checkbox1, 120);
	lv_obj_set_pos(checkbox1, 240, 150);
	lv_obj_add_style(checkbox1, LV_CHECKBOX_PART_BG, &style_checkbox1);
	lv_checkbox_set_text(checkbox1, "CheckBox");
#endif

	//滑块
#if 0
	slider1 = lv_slider_create(scr, NULL);
	lv_obj_set_size(slider1, 100, 10);
	lv_slider_set_range(slider1, 0, 100);
	lv_slider_set_anim_time(slider1, 200);
	lv_bar_set_value(slider1, 20, LV_ANIM_ON);
	lv_obj_align(slider1, scr, LV_ALIGN_IN_TOP_RIGHT, -20, 20); 
//	lv_obj_set_event_cb(slider1, arc_event_handler);
#endif

	//按钮
#if 1
	lv_obj_t *btn1 = lv_btn_create(scr, NULL);
	lv_obj_set_pos(btn1, 240, 80);
	lv_obj_set_size(btn1, 100, 40);
	lv_obj_t *label_btn1 = lv_label_create(scr, NULL);
	lv_obj_align(label_btn1, btn1, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn1, "key1");
//	lv_obj_set_event_cb(btn1, arc_event_handler);
	
	btn2 = lv_btn_create(scr, NULL);
	lv_obj_set_pos(btn2, 220, 220);
	lv_obj_set_size(btn2, 100, 40);
	lv_btn_set_checkable(btn2, true);
	lv_btn_set_state(btn2, true);
	lv_obj_t *label_btn2 = lv_label_create(scr, NULL);
	lv_obj_align(label_btn2, btn2, LV_ALIGN_CENTER, 0, 0); 
	lv_label_set_text(label_btn2, "key2");
//	lv_obj_set_event_cb(btn2, arc_event_handler);
#endif 

	//预加载
#if 1
	static lv_style_t style_spinner1_bg, style_spinner1_indic;
	lv_style_init(&style_spinner1_bg);
	lv_style_set_line_width(&style_spinner1_bg, LV_STATE_DEFAULT, 15);
	lv_style_set_line_color(&style_spinner1_bg, LV_STATE_DEFAULT, LV_COLOR_MAGENTA);
	lv_style_init(&style_spinner1_indic);
	lv_style_set_line_width(&style_spinner1_indic, LV_STATE_DEFAULT, 15);
	lv_style_set_line_color(&style_spinner1_indic, LV_STATE_DEFAULT, LV_COLOR_NAVY);
	
	lv_obj_t *spinner1 = lv_spinner_create(scr, NULL);
	lv_obj_set_size(spinner1, 80, 80);
	lv_obj_set_pos(spinner1, 10, 180);
	lv_obj_add_style(spinner1, LV_SPINNER_PART_BG, &style_spinner1_bg);
	lv_obj_add_style(spinner1, LV_SPINNER_PART_INDIC, &style_spinner1_indic);
	
	static lv_style_t style_spinner2_bg, style_spinner2_indic;
	lv_style_init(&style_spinner2_bg);
	lv_style_set_line_width(&style_spinner2_bg, LV_STATE_DEFAULT, 0);
	lv_style_init(&style_spinner2_indic);
	lv_style_set_line_width(&style_spinner2_indic, LV_STATE_DEFAULT, 8);
	lv_style_set_line_color(&style_spinner2_indic, LV_STATE_DEFAULT, LV_COLOR_MAROON);
	
	lv_obj_t *spinner2 = lv_spinner_create(scr, NULL);
	lv_obj_set_size(spinner2, 60, 60);
	lv_obj_set_pos(spinner2, 120, 180);
	lv_spinner_set_type(spinner2, LV_SPINNER_TYPE_FILLSPIN_ARC);
	lv_obj_add_style(spinner2, LV_SPINNER_PART_BG, &style_spinner2_bg);
	lv_obj_add_style(spinner2, LV_SPINNER_PART_INDIC, &style_spinner2_indic);
#endif	
	
	//线条
#if 1
	static lv_style_t style_line1;
	lv_style_init(&style_line1);
	lv_style_set_line_width(&style_line1, LV_STATE_DEFAULT, 10);
	lv_style_set_line_color(&style_line1, LV_STATE_DEFAULT, LV_COLOR_MAKE(255, 100, 100));
	lv_style_set_line_rounded(&style_line1, LV_STATE_DEFAULT, true); //圆角
		
	static lv_point_t line_points[] = {{360, 60},{450,300}};
	lv_obj_t *line1 = lv_line_create(scr, NULL);
	lv_obj_add_style(line1, LV_LINE_PART_MAIN, &style_line1);
	lv_line_set_points(line1, line_points, 2);
	lv_obj_set_pos(line1, 0, 0);	//点的坐标以此为原点
#endif
	
	//led
#if 1
	static lv_style_t style_led1;
	lv_style_init(&style_led1);
	lv_style_set_bg_color(&style_led1, LV_STATE_DEFAULT, LV_COLOR_YELLOW);
	
	led1 = lv_led_create(scr, NULL);
	lv_obj_set_pos(led1, 330, 215);
	lv_obj_set_size(led1, 50, 50);
	lv_led_off(led1);
	lv_obj_add_style(led1, LV_LED_PART_MAIN, &style_led1);
#endif
}
static void slim_index2(void){
	lv_obj_t *scr = lv_scr_act(); //获取屏幕对象
	
	//仪表盘,这里就不再过多的设置样式了,反正样式设计函数调用都差不多
	lv_obj_t *gauge1 = lv_gauge_create(scr, NULL);
	lv_gauge_set_range(gauge1, 0, 80);
	lv_obj_set_size(gauge1, 220, 220);
	lv_gauge_set_scale(gauge1, 270, 21, 5);
	lv_obj_align(gauge1, scr, LV_ALIGN_CENTER, 0, 0); 
}

//没用调试触摸,所以一些控件用定时器做设置咯
extern lv_obj_t *arc;
extern lv_obj_t *bar1;
extern lv_obj_t *btn2;
extern lv_obj_t *label_bar1;
extern lv_obj_t *led1;
extern TIM_HandleTypeDef htim6;
extern TIM_HandleTypeDef htim7;
extern IWDG_HandleTypeDef hiwdg;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	static uint8_t i = 0;
	char str[10] = {0};
    if (htim == (&htim6)){	//5s
		HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
		i += 10;
		i = (i>100)?0:i;
		if((bar1 != NULL) && (btn2 != NULL)){
			lv_bar_set_value(bar1, i, LV_ANIM_ON);
			lv_btn_toggle(btn2);
			lv_led_toggle(led1);
			sprintf(str, "%d%%",i);
			lv_label_set_text(label_bar1, str);
		}
    }
	if (htim == (&htim7)){	//1s
        HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_3);
		HAL_IWDG_Refresh(&hiwdg);
    }
}

测试一下咯

PS:单个控件动画还是挺流畅的,多个控件同时动画的时候确实有些卡顿,但是没有展示的这么严重,应该是视频转GIF,丢帧有些严重。不过功能倒是实现了,嗨森!!!下一步移植触摸驱动,然后搞到ESP32上试试能不能跑个80M的spi

你可能感兴趣的:(GUI,嵌入式,gui,littlevgl,LVGL,spi-lcd,lvgl移植)