基本启动流程

![img](3. LVGL模拟器NXP GUI Guider使用/2109578-20220821111715583-1999413560.png)

lvgl是gui层负责绘制gui并根据输入设备的事件来响应重绘 ,然后把绘制的缓冲区发送给显示驱动去实际显示。

以下代码参考lvgl arduino官方例程,gui guider模拟器例程,例如 stm32 fsmc lvgl例程

第一步 时钟

时钟是lvgl绘制gui的节拍器。获取时钟在这个文件里 ..\lvgl\src\hal\lv_hal_tick.c

在arduino 中时钟是从 millis()这个函数获得的

1
2
3
4
5
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/

stm32使用硬件timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HardwareTimer *MyTim;
/*Initialize the graphics library's tick*/
MyTim = new HardwareTimer(TIM2);
MyTim->setMode(2, TIMER_OUTPUT_COMPARE); // In our case, channekFalling is configured but not really used. Nevertheless it would be possible to attach a callback to channel compare match.
MyTim->setOverflow(1000/LVGL_TICK_PERIOD, HERTZ_FORMAT); // period in Hz
MyTim->attachInterrupt(lv_tick_handler);
MyTim->resume();



static void lv_tick_handler(HardwareTimer*)
{

lv_tick_inc(LVGL_TICK_PERIOD);
}

linux等使用 mingw标准库 <stddef.h> 里的宏定义 sys_time

1
2
3
4
5
6
7
8
9
10
11
#if !LV_TICK_CUSTOM
/**
* You have to call this function periodically
* @param tick_period the call period of this function in milliseconds
*/
LV_ATTRIBUTE_TICK_INC void lv_tick_inc(uint32_t tick_period)
{
tick_irq_flag = 0;
sys_time += tick_period;
}
#endif

第二步 核心初始化

lv核心初始化 完成各种结构体与函数的初始化

1
lv_init();

第三步 硬件抽象层

硬件虚拟层需要 完成 display, input devices, tick 也就是与驱动的对接,与输入设备的对接 完成tick (节拍)?

1 先看display

  • 先实例化底层显示驱动

    1
    2
    3
    4
    SDL驱动      monitor_init();

    TFT espi驱动 tft.begin(); /* TFT init TFT初始化*/
    tft.setRotation( 1 ); /* Landscape orientation, flipped 设置方向*/
  • 创造缓冲区

1
2
3
4
5
6
/*Create a display buffer  SDL样式的*/  
static lv_disp_draw_buf_t disp_buf1;
static lv_color_t buf1_1[480 * 10];
lv_disp_draw_buf_init(&disp_buf1, buf1_1, NULL, 480 * 10);

lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);
  • 完成display到实际底层驱动的填充函数
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Display flushing 显示填充  espi样式  */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
uint32_t w = ( area->x2 - area->x1 + 1 );
uint32_t h = ( area->y2 - area->y1 + 1 );

tft.startWrite();
tft.setAddrWindow( area->x1, area->y1, w, h );
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();

lv_disp_flush_ready( disp );
}
  • 直接写GRAM样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{

u16 height,width;
u16 i,j;
width=area->x2 - area->x1+1; //得到填充的宽度
height=area->y2 - area->y1+1; //高度
for(i=0;i<height;i++)
{
LCD_SetCursor(area->x1,area->y1+i); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(j=0;j<width;j++)
{
LCD_TYPE->LCD_RAM=color_p->full;//写入数据
color_p++;
}
}

lv_disp_flush_ready(disp); /* tell lvgl that flushing is done */
}
  • SDL样式

monitor_flush

创造display

1
2
3
4
5
6
7
8
/*Initialize the display初始化display*/
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
disp_drv.flush_cb = my_disp_flush;// 这里就用到了填充函数
disp_drv.buffer = &disp_buf;
lv_disp_drv_register(&disp_drv);

2 再看输入层

  • 先初始化硬件接口

TFT-esp 这样 触摸屏

1
2
uint16_t calData[5] = { 187, 3596, 387, 3256, 5 };
tft.setTouch( calData ); // espi如果设置了touch cs 自动完成触摸的初始化

独立的xpt2046驱动 触摸屏

1
XPT2046_Touchscreen ts(CS_PIN, irq_pin);

模拟器的鼠标键盘

1
mouse_init();//在模拟器里也完成了触摸回调函数

其他的滚轮键盘编码器也同样原理

对接触摸驱动 完成触摸回调函数

espi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*Read the touchpad*/
/*读取触摸板*/
void my_touchpad_read( lv_indev_drv_t * indev_driver, lv_indev_data_t * data )
{
uint16_t touchX, touchY;

bool touched = tft.getTouch( &touchX, &touchY, 600 );

if( !touched )
{
data->state = LV_INDEV_STATE_REL;
}
else
{
data->state = LV_INDEV_STATE_PR;

/*Set the coordinates*/
data->point.x = touchX;
data->point.y = touchY;

Serial.print( "Data x " );
Serial.println( touchX );

Serial.print( "Data y " );
Serial.println( touchY );
}
}

xpt2046

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool my_touchpad_read(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;

/*Save the state and save the pressed coordinate*/
data->state = ts.touched() ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
if(data->state == LV_INDEV_STATE_PR){
TS_Point p = ts.getPoint();

//convert to lcd position
last_x = LV_HOR_RES-(p.x *LV_HOR_RES)/4095; /*TODO save the current X coordinate*/
last_y = LV_VER_RES-(p.y *LV_VER_RES)/4095; /*TODO save the current Y coordinate*/

Serial.print("touched:");
Serial.print(last_x);Serial.print(",");Serial.println(last_y);
}

/*Set the coordinates (if released use the last pressed coordinates)*/
data->point.x = last_x;
data->point.y = last_y;

return false; /*Return `false` because we are not buffering and no more data to read*/
}

完成lvgl输入驱动与硬件驱动的对接

触摸样式

1
2
3
4
5
6
7
/*Initialize the (dummy) input device driver*/
/*初始化(虚拟)输入设备驱动程序*/
static lv_indev_drv_t indev_drv;
lv_indev_drv_init( &indev_drv );
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;//上一步的在这用到了
lv_indev_drv_register( &indev_drv );

模拟器样式 使用鼠标

1
2
3
4
5
6
7
8
/* Add the mouse as input device
* Use the 'mouse' driver which reads the PC's mouse*/
mouse_init();
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); /*Basic initialization*/
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = mouse_read; /*This function will be called periodically (by the library) to get the mouse position and state*/
lv_indev_t * mouse_indev = lv_indev_drv_register(&indev_drv);

3 初始化节拍

arduino 自动完成此步骤

模拟器

1
2
3
4
/* Tick init.
* You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about how much time were elapsed
* Create an SDL thread to do this*/
SDL_CreateThread(tick_thread, "tick", NULL);

stm32

1
2
3
4
5
6
/*Initialize the graphics library's tick*/
MyTim = new HardwareTimer(TIM2);
MyTim->setMode(2, TIMER_OUTPUT_COMPARE); // In our case, channekFalling is configured but not really used. Nevertheless it would be possible to attach a callback to channel compare match.
MyTim->setOverflow(1000/LVGL_TICK_PERIOD, HERTZ_FORMAT); // period in Hz
MyTim->attachInterrupt(lv_tick_handler);
MyTim->resume();

说实话我没怎么看懂

第三步 开始绘制gui

硬件抽象层完成初始化 就可以开始绘制ui

第一步是初始化sceen

从sceen里创建各种widget 给widget添加事件回调 然后在无线循环里调用lv_timer_handler(),它会按LVGL_TICK_PERIOD 设置的事件重复的响应输入事件来重绘ui

这里简单绘制个label,注意label默认不带事件。除非用函数强制开启

1
/*Create a Label on the currently active screen在当前屏幕上生成一个label*/

  static lv_obj_t * label1= lv_label_create(lv_scr_act());// lv_sce_act()可以获得当前屏幕 使用静态指针是避免setup函数结束后指针被释放找不到对象自己

1
2
3
4
5
6
7
8
9
/*Modify the Label's text设置文本*/
lv_label_set_text(label1, "Hello world!");

lv_obj_set_pos(label1, 0, 2);

/* Align the Label to the center
* NULL means align on parent (which is the screen now)
* 0, 0 at the end means an x, y offset after alignment*/
//

添加一个带事件的按钮

这里我一开始尝试用模拟器,但是guiguider跟lvgl是是纯c语言的 不支持 c++的String,但是c想实现字符串的动态增长可是无比蛋疼的 这时候arduino的C++特性就很好用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

static void btn_event_cb(lv_event_t * e)//回调函数都是静态的
{
lv_event_code_t code = lv_event_get_code(e);


if(code == LV_EVENT_CLICKED) {

static String temp = lv_label_get_text(label);//临时变量也是静态的

​ temp = temp + "!";

​ lv_label_set_text(label1, temp.c_str());

​ }


}
1
2
3
4
lv_obj_t * btn = lv_btn_create(lv_scr_act());     /*Add a button the current screen*/
lv_obj_set_pos(btn, 50, 50); /*Set its position*/
lv_obj_set_size(btn, 120, 50); /*Set its size*/
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /*Assign a callback to the button*/

相关链接(侵删)

  1. 分析lvgl的代码启动过程,对比esp32,stm32,linux

=================我是分割线=================

欢迎到公众号来唠嗑: