一、GUI_GUIDER
1.是NXP的一款用于LVGL设计的工具,图形化设计,移植到LVGL非常简单。
2.注意每个版本的GUI_GUIDER只支持两个版本的LVGL
******下面给出的这个网盘的GUI_GUIDER支持的是V8.2.0和V7.10.1两个版本
二、GUI_GUIDER下载
1.官网(需要NXP账号)
2.ARM论坛老哥网盘
3.安装
正常安装就行,路径别太深,不要有中文。
三、创建一个工程
四、完成一个UI界面
五、移植到KEIL
1.打开编译后生成文件的位置
2.复制custon和generated文件夹到keil工程中,将其存放进入一个lvgl_app文件夹
3.在keil中在LVGL_APP中将刚才拷贝的文件夹中的所有C文件加入
4.将路径加入
5.加入以下代码编译下载
#include "system.h"
#include "SysTick.h"
#include "usart.h"
#include "tftlcd.h"
#include "touch.h"
#include "time.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
//#include "lv_demo_keypad_encoder.h"
#include "gui_guider.h"
#include "events_init.h"
lv_ui guider_ui;
int main()
{
u8 i=0;
u8 key;
u16 penColor = BLUE;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组
USART1_Init(115200);
TFTLCD_Init(); //LCD初始化
LCD_Display_Dir(1);
TP_Init();
// tp_dev.init();
TIM4_Init(200,360-1); //定时1ms
lv_init();
lv_port_disp_init(); // lvgl显示接口初始化,放在lv_init()的后面
lv_port_indev_init(); // lvgl输入接口初始化,放在lv_init()的后面
// lv_demo_keypad_encoder();
setup_ui(&guider_ui);
// events_init(&guider_ui);
while(1)
{
lv_task_handler(); // lvgl的事务处理
tp_dev.scan(0);
delay_ms(100);
}
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update))
{
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
lv_tick_inc(1);//lvgl的1ms中断
}
}
六、事件触发(实现交互)(已证实无法在MCU运行,直接看七)
1.参考博客
2.创建界面
拖入一个按钮和两个标签,实现一个三个任务:
(1)按钮短按其中一个标签显示数值加1
(2)按钮长按标签显示数值开始减
(3)按钮按下时另外一个标签显示按钮状态
注意按钮默认的名字为btn_1 ,这个名字应该是可以更改的,后面程序只需要改一下就可以
标签也向上述方式命名label_1和label_2
3.代码修改
主要修改custom.c
主要逻辑就是添加一个按钮事件,判断按钮事件类型为什么再做出对应的操作
//按键回调函数
static void btn_cb(lv_event_t* e)
{
static int cnt = 0; //静态计数值
lv_event_code_t code = lv_event_get_code(e); //获取按键值
switch (code)
{
case LV_EVENT_SHORT_CLICKED: //单击加计数
cnt++;
lv_label_set_text_fmt(guider_ui.screen_label_2, "Button Status: %s","CLICKED"); //设置文本标签
break;
case LV_EVENT_LONG_PRESSED:
case LV_EVENT_LONG_PRESSED_REPEAT: //长按减计数
lv_label_set_text_fmt(guider_ui.screen_label_2, "Button Status: %s", "LONG PRESSED"); //设置文本标签
cnt--;
break;
default:
break;
}
lv_label_set_text_fmt(guider_ui.screen_label_1, "%d", cnt); //设置文本标签
}
void custom_init(lv_ui *ui)
{
/* Add your codes here */
lv_obj_add_event_cb(guider_ui.screen_btn_1, btn_cb, LV_EVENT_ALL, NULL); //绑定回调函数
}
修改代码找到生成的文件就行,修改custom.c就行,修改完成之后再进入到GUI GUIDER进行模拟运行看效果。
4.运行效果
七、事件触发更新(7月28日)(正确的事件交互)
1.之前的方式有问题
上面“六”说的是交互的功能写在custom.c里面就行,但是后面发现这个custom.c只能加入第一个screen里面的事件,其他页面screen的事件无法加入,运行仿真会出错。
2.解决办法(其他屏幕的事件)
(1)选中控件添加事件
选择触发方式和作用对象,再自定义代码,在code中添加代码,比如我这里的逻辑就是screen_1中的sw_1值改变时,screen_1中的led_1电平翻转。
(2)在code中添加的代码
lv_led_toggle(guider_ui.screen_1_led_1);
(3)具体API参考网址:
如果说能够加入在custom.c中,那么代码应该为
// SPDX-License-Identifier: MIT
// Copyright 2020 NXP
/**
* @file custom.c
*
*/
/*********************
* INCLUDES
*********************/
#include <stdio.h>
#include "lvgl.h"
#include "custom.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**
* Create a demo application
*/
//按键回调函数
//按键回调函数
static void btn_cb(lv_event_t* e)
{
static int cnt = 0; //静态计数值
lv_event_code_t code = lv_event_get_code(e); //获取按键值
switch (code)
{
case LV_EVENT_SHORT_CLICKED: //单击加计数
cnt++;
lv_label_set_text_fmt(guider_ui.screen_label_2, "Button Status: %s","CLICKED"); //设置文本标签
break;
case LV_EVENT_LONG_PRESSED:
case LV_EVENT_LONG_PRESSED_REPEAT: //长按减计数
lv_label_set_text_fmt(guider_ui.screen_label_2, "Button Status: %s", "LONG PRESSED"); //设置文本标签
cnt--;
break;
default:
break;
}
lv_label_set_text_fmt(guider_ui.screen_label_1, "%d", cnt); //设置文本标签
}
/*界面:screen_1
*控件: sw_1
*受控件: led_1
*事件:通过sw_1拨动开关led_1灯
*/
static void sw1_led1_cb(lv_event_t* e)
{
lv_event_code_t code = lv_event_get_code(e);
if(code==LV_EVENT_VALUE_CHANGED)
lv_led_toggle(guider_ui.screen_1_led_1);
}
void custom_init(lv_ui *ui)
{
/* Add your codes here */
lv_obj_add_event_cb(guider_ui.screen_btn_1, btn_cb, LV_EVENT_ALL, NULL); //绑定回调函数
lv_obj_add_event_cb(guider_ui.screen_1_sw_1,sw1_led1_cb,LV_EVENT_VALUE_CHANGED,NULL); //screen_1中的sw_1切换触发事件
}
(4)按这种方法生成的代码
在generated/event_init.c中有这个事件处理函数,所以也给了我们一个启示:如果我们向让单片机与其交互,可以在这里加入自己的代码(需要遵守编写规范)
static void screen_1_sw_1_event_handler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code)
{
case LV_EVENT_VALUE_CHANGED:
{
lv_led_toggle(guider_ui.screen_1_led_1);
}
break;
default:
break;
}
}
(5)规范写法
上面虽然说可以在event_init.c中加入自己的代码,但是用户主要的函数建议还是写到custom.c中,所以custom.c主要用于处理其他函数和第一个页面的事件
八、一些记录
1.读取SW的状态来决定LED的亮与灭
//SW_1开就打开led_1,关就关闭led_1
if(lv_obj_has_state(guider_ui.screen_1_sw_1,LV_STATE_CHECKED))
lv_led_on(guider_ui.screen_1_led_1);
else
lv_led_off(guider_ui.screen_1_led_1);
2.读取滑动条的值显示在标签上面
//获取滑块值
uint32_t value=lv_slider_get_value(guider_ui.screen_2_slider_1);
lv_label_set_text_fmt(guider_ui.screen_2_label_1, "%d", value); //设置文本标签
3. 读取spinbox的值显示在标签上面
uint8_t value=lv_spinbox_get_value(guider_ui.screen_spinbox_1);
lv_label_set_text_fmt(guider_ui.screen_label_1, "%d",value);
这里spinbox如果设置为有小数位读取出来的值完全不对,当设置没有小数位时读出来的数据则正确。
4.图表绘制折线图
uint8_t data[10]={0};
static lv_chart_series_t * chart_clean_ser; //用于记录序列号
if(chart_clean_ser!=NULL) //不是初次创建
lv_chart_remove_series(guider_ui.screen_chart_1,chart_clean_ser); //移除之前的连续折线
lv_chart_set_point_count(guider_ui.screen_chart_1, 6);
lv_chart_series_t * screen_chart_1_0 = lv_chart_add_series(guider_ui.screen_chart_1, lv_color_make(0x2c, 0xc9, 0x53), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_point_count(guider_ui.screen_chart_1, 10); //设置折线点数
chart_clean_ser=screen_chart_1_0; //记住序列号(方便后面删除进行下一次绘制图形)
for(uint8_t i=0;i<10;i++)
{
data[i]=rand()%100; //余100是因为设置的值范围是(0~100)
lv_chart_set_next_value(guider_ui.screen_chart_1, screen_chart_1_0, data[i]); //像链表一样插入数据点
}
这里用随机数当作数据绘制折线图 ,第六句代码是到GUIGUIDER生成的代码中复制的,主要是为了保持格式色彩一致性。(在GUIGUIDER屏幕初始化函数中去找,先图形界面手动添加一条数据线,看它的添加连续折线的代码)
运行效果:
5.图表绘制折线图修正
上述4.的图表绘制折线图的方式有一个问题:如果绘制过折线,切换屏幕后,回到本屏幕再次打点会出错,主要是因为屏幕切换回来初始化之后,屏幕上没有折线,而下面这句代码会根据
if(chart_clean_ser!=NULL) //不是初次创建
lv_chart_remove_series(guider_ui.screen_chart_1,chart_clean_ser); //移除之前的连续折线
chart_clean_ser是否为空来进行移除,这个变量定义为static类型,屏幕切换回来后虽然没有折线,但是chart_clean_ser变量不为NULL,就会触发移除函数,这样就会出错,我们用一个全局变量来记录是否有切换屏幕的记录,如果切换了屏幕则不要触发移除函数。这个全局变量为了方便管理定义在custom.c中。(同时如果我们想要知道某一个事件是否触发也可以在custom.c中定义一个全局变量,然后在触发事件的代码中对全局变量进行赋值,再在主函数中检查这个全局变量的值即可知道事件有没有触发。)下面是修改后的代码:
uint8_t data[10]={0};
static lv_chart_series_t * chart_clean_ser; //用于记录序列号
extern bool avoid_screenchange_to_chartclean_error;
if((chart_clean_ser!=NULL)&&(avoid_screenchange_to_chartclean_error==0)) //不是初次创建并且不是处于刚初始化屏幕阶段
lv_chart_remove_series(guider_ui.screen_chart_1,chart_clean_ser);
else if(avoid_screenchange_to_chartclean_error==1) //有切换屏幕,屏幕上面没有内容,这次不能清屏幕上的折线,否则出错
avoid_screenchange_to_chartclean_error=0; //屏幕完成初始化清空标志位
lv_chart_set_point_count(guider_ui.screen_chart_1, 6);
lv_chart_series_t * screen_chart_1_0 = lv_chart_add_series(guider_ui.screen_chart_1, lv_color_make(0x2c, 0xc9, 0x53), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_point_count(guider_ui.screen_chart_1, 10); //设置折线点数
chart_clean_ser=screen_chart_1_0; //记住序列号(方便后面删除进行下一次绘制图形)
for(uint8_t i=0;i<10;i++)
{
data[i]=rand()%100; //余100是因为设置的值范围是(0~100)
lv_chart_set_next_value(guider_ui.screen_chart_1, screen_chart_1_0, data[i]); //像链表一样插入数据点
}
定义在custom.c中 :
bool avoid_screenchange_to_chartclean_error=0; //防止屏幕初始化而chart_clean_ser不为NULL
6.下拉列表(下拉框)选中文本显示在标签上
char buf[5]={0};
lv_dropdown_get_selected_str(guider_ui.screen_3_ddlist_1,buf,sizeof(buf));
lv_label_set_text_fmt(guider_ui.screen_3_label_1, "%s",buf);
九、补坑
1.经过实践发现如果将事件函数添加到custom.c中,切换屏幕时删除屏幕会将事件删除,再次回到这个屏幕就不会再触发这个页面的事件,所以事件函数不要写在custom.c中,按“七”的方法来。(只运行在PC模拟器时,后面发现运行在MCU上根本不能正常运行)
2.下载到MCU中后发现第六种办法根本无法在MCU上正常运行,而第七种可以。
十、关于GUI-GUIDER移植到KEIL的一些问题
1.编码
在一个例子中移植之后出现以下报错
lvgl_app\generated\setup_scr_screen.c(442): error: #8: missing closing quote
按这篇博客的说法应该是UTF-8编码导致的编码错误,在如下框中加入以下内容可解决问题
--no-multibyte-chars