分析lvgl的代码启动过程,对比esp32,stm32,linux
lvgl是gui层负责绘制gui并根据输入设备的事件来响应重绘 ,然后把绘制的缓冲区发送给显示驱动去实际显示。
以下代码参考lvgl arduino官方例程,gui guider模拟器例程,,零知 stm32 fsmc lvgl例程
第0步 时钟
时钟是lvgl绘制gui的节拍器。获取时钟 在这个文件里 ..\lvgl\src\hal\lv_hal_tick.c
在arduino 中时钟是从 millis()这个函数获得的
#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
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
#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核心初始化 完成各种结构体与函数的初始化
lv_init();
第二步 硬件抽象层
硬件虚拟层需要 完成 display, input devices, tick 也就是与驱动的对接,与输入设备的对接 完成tick (节拍)?
1 先看display
先实例化底层显示驱动
SDL驱动 monitor_init();
TFT espi驱动 tft.begin(); /* TFT init TFT初始化*/
tft.setRotation( 1 ); /* Landscape orientation, flipped 设置方向*/
创造缓冲区
/*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);
1 | lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10); |
完成display到实际底层驱动的填充函数
/* 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样式
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
/*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 这样 触摸屏
uint16_t calData[5] = { 187, 3596, 387, 3256, 5 }; tft.setTouch( calData ); // espi如果设置了touch cs 自动完成触摸的初始化
独立的xpt2046驱动 触摸屏
XPT2046_Touchscreen ts(CS_PIN, irq_pin);
模拟器的鼠标键盘
mouse_init();//在模拟器里也完成了触摸回调函数
其他的滚轮键盘编码器也同样原理
对接触摸驱动 完成触摸回调函数
espi
/*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
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输入驱动与硬件驱动的对接
触摸样式
/*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 );
模拟器样式 使用鼠标
/* 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 自动完成此步骤
模拟器
/* 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
/*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默认不带事件。除非用函数强制开启
/*Create a Label on the currently active screen在当前屏幕上生成一个label*/
/*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++特性就很好用
{ lv_event_code_t code = lv_event_get_code(e);
}
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*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具