1616
1717## 写在前面
1818
19- 如果您对LVGL十分感兴趣,我非常建议自己尝试一下移植LVGL,本文将以通俗易懂的方式,向大家介绍LVGL移植的过程,希望读者能在本文的辅助下,独自完成LVGL的移植。在本文的最后部分会向大家介绍有关性能优化的细节。
19+ 如果您对LVGL十分感兴趣,我非常建议自己尝试一下移植LVGL,本文将以通俗易懂的方式,向大家介绍LVGL移植的过程,希望读者能在本文的辅助下,独自完成LVGL的移植。 在本文的最后部分会向大家介绍有关性能优化的细节。
2020
21- 事实上,LVGL的每个大版本,driver部分的接口都有较大的变化,所以我们选择了两个目前最新的release版本讲述移植过程,分别是 ` v8.3 ` 和 ` v9 ` 。
21+ 事实上,LVGL的每个大版本,driver部分的接口都有较大的变化,所以我们选择了两个目前最新的release版本讲述移植过程,分别是 ` v8.4 ` 和 ` v9 ` 。
22+
23+ ** 你可以在 ` lvgl ` 工程源码的 ` examples/porting ` 文件夹下找到当前版本的移植模板文件**
2224
2325## 准备工作
2426
@@ -132,6 +134,8 @@ static int ili9488_set_addr_win(struct ili9488_priv *priv, int xs, int ys, int x
132134
133135发送颜色数据一般跟在 ` 0x2C ` 命令之后,如果你选择局部数据更新,则还应该在这之前设置绘制区域 ` 0x2A ` , ` 0x2B ` 。
134136
137+ 如下所示的是一个窗口绘制函数,接受一个矩形区域的参数和一个给定长度的像素数据buffer
138+
135139``` c
136140static void ili9488_video_sync (struct ili9488_priv * priv, int xs, int ys, int xe, int ye, void * vmem16, size_t len)
137141{
@@ -141,13 +145,213 @@ static void ili9488_video_sync(struct ili9488_priv *priv, int xs, int ys, int xe
141145}
142146```
143147
148+ 在本工程中,`write_buf_rs` 是一个宏定义,会根据另一个宏`DISP_OVER_PIO`来决定是否通过`PIO`外设模拟I8080协议发送数据
149+
150+ ```c
151+ /* rs=0 means writing register, rs=1 means writing data */
152+ #if DISP_OVER_PIO
153+ #define write_buf_rs(p, b, l, r) i80_write_buf_rs(b, l, r)
154+ #else
155+ #define write_buf_rs(p, b, l, r) fbtft_write_gpio16_wr_rs(p, b, l, r)
156+ #endif
157+ ```
158+
159+ 下面是两个函数的原型,执行的逻辑是先设置RS引脚,再发送给定buffer中的数据到I8080端口。
160+
161+ ``` c
162+ static void fbtft_write_gpio16_wr_rs (struct ili9488_priv * priv, void * buf, size_t len, bool rs)
163+ void i80_write_buf_rs(void * buf, size_t len, bool rs);
164+ ```
165+
144166#### 全屏刷新
145167
146- 待添加
168+ 如果你需要全屏刷新,则必须先分配一个全屏的buffer,在本工程中,一块全屏buffer的大小为 `307200` 字节,
169+ 这超出了RP2040上的 256KB SRAM 限制,不过在RP2350上,有512KB SRAM,可以使用全屏刷新。
147170
148- ## LVGL 8.3
171+ 在发送完初始化序列后,设置一次绘制区域为整屏大小,然后发送命令`0x2C`,下面是一个示例
149172
150- 待添加
173+ ```
174+ priv->tftops->set_addr_win(priv, 0, 0,
175+ priv->display->xres - 1,
176+ priv->display->yres - 1);
177+ ```
178+
179+ 然后就可以循环发送全屏buffer,驱动IC中的行列指针会根据发送的数据自动移动的复位。
180+
181+ 全屏刷新一般用于内部有 LCD 控制器的 MCU 或 MPU,例如在 F1C200s 上,可以将 TCON 设置成I8080模式,
182+ 在初始化TCON之前,先通过GPIO模拟I8080端口,初始化显示屏,然后再初始化 TCON, BE, FE 等,根据framebuffer
183+ 中的数据输出I8080时序,就可以实现 LCD 控制器全屏刷新的效果。
184+
185+
186+ ## LVGL 8.4
187+
188+ ### 显示驱动
189+
190+ 可以参考`pico_dm_qd3503728_noos/porting/lv_port_disp_template.c`
191+
192+ #### 1. 分配 LVGL 显示 buffer
193+ ```c
194+
195+ /* 你可以在这里调用 LCD 的初始化函数 */
196+ disp_init();
197+
198+ #ifndef MY_DISP_BUF_SIZE
199+ #warning '"MY_DISP_BUF_SIZE" is not defined, defaulting to (HOR_RES * VER_RES / 2)'
200+ #define MY_DISP_BUF_SIZE (MY_DISP_HOR_RES * MY_DISP_VER_RES / 2)
201+ #endif
202+
203+ static lv_disp_draw_buf_t draw_buf_dsc_1;
204+ static lv_color_t buf_1[MY_DISP_BUF_SIZE];
205+
206+ /* 调用lvgl接口初始化显示buffer */
207+ lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_BUF_SIZE);
208+ ```
209+
210+ #### 2. 分配、注册 LVGL 显示驱动
211+ ``` c
212+ static lv_disp_drv_t disp_drv; /* Descriptor of a display driver*/
213+ lv_disp_drv_init (&disp_drv); /* Basic initialization* /
214+
215+ /* 你的屏幕的宽高分辨率 */
216+ disp_drv.hor_res = MY_DISP_HOR_RES;
217+ disp_drv.ver_res = MY_DISP_VER_RES;
218+
219+ /* 这个是需要你实现的显示 buffer 刷新的回调函数 */
220+ disp_drv.flush_cb = ili9488_flush;
221+
222+ /* 将第一步分配的 LVGL 显示 buffer 注册到显示驱动中 */
223+ disp_drv.draw_buf = &draw_buf_dsc_1;
224+
225+ /* 注册显示驱动 */
226+ lv_disp_drv_register(&disp_drv);
227+ ```
228+
229+ 下面是 `ili9488_flush` 相关函数的实现
230+
231+ ```c
232+ static inline void ili9488_write_cmd(uint16_t cmd)
233+ {
234+ write_buf_rs(&g_priv, &cmd, sizeof(cmd), 0);
235+ }
236+ #define write_cmd ili9488_write_cmd
237+ static inline void ili9488_write_data(uint16_t data)
238+ {
239+ write_buf_rs(&g_priv, &data, sizeof(data), 1);
240+ }
241+ #define write_data ili9488_write_data
242+
243+ #include "lvgl/lvgl.h"
244+ void ili9488_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
245+ {
246+ #if 1
247+ write_cmd(0x2A);
248+ write_data(area->x1 >> 8);
249+ write_data(area->x1);
250+ write_data(area->x2 >> 8);
251+ write_data(area->x2);
252+
253+ /* set row address */
254+ write_cmd(0x2B);
255+ write_data(area->y1 >> 8);
256+ write_data(area->y1);
257+ write_data(area->y2 >> 8);
258+ write_data(area->y2);
259+
260+ /* write start */
261+ write_cmd(0x2C);
262+ write_buf_rs(&g_priv, (void *)color_p, lv_area_get_size(area) * 2, 1);
263+ #else
264+ struct ili9488_priv *priv = &g_priv;
265+ priv->tftops->set_addr_win(priv, area->x1, area->y1, area->x2, area->y2);
266+ write_buf_rs(priv, (void *)px_map, lv_area_get_size(area) * 2, 1);
267+ #endif
268+ lv_disp_flush_ready(disp_drv);
269+ }
270+ ```
271+
272+ ### 输入驱动
273+
274+ 可以参考` pico_dm_qd3503728_noos/porting/lv_port_indev_template.c `
275+
276+ #### 1. 分配、注册 LVGL Touchpad 驱动
277+
278+ ``` c
279+ static lv_indev_drv_t indev_drv;
280+
281+ /* ------------------
282+ * Touchpad
283+ * -----------------*/
284+
285+ /* 你可以在这里初始化你的触摸屏 */
286+ touchpad_init ();
287+
288+ /* 初始化 LVGL 触摸屏驱动 */
289+ lv_indev_drv_init (&indev_drv);
290+
291+ /* 设置驱动类型为点类型 */
292+ indev_drv.type = LV_INDEV_TYPE_POINTER;
293+
294+ /* 设置触摸屏驱动的读取回调函数,这个需要你自己实现 */
295+ indev_drv.read_cb = touchpad_read;
296+
297+ /* 注册触摸屏驱动 */
298+ indev_touchpad = lv_indev_drv_register(&indev_drv);
299+ ```
300+
301+ 下面是 `touchpad_read` 相关函数的实现
302+
303+ ```c
304+ static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
305+ {
306+ static lv_coord_t last_x = 0;
307+ static lv_coord_t last_y = 0;
308+
309+ /*Save the pressed coordinates and the state*/
310+ if(touchpad_is_pressed()) {
311+ touchpad_get_xy(&last_x, &last_y);
312+ // printf("touchpad is pressed, x: %d, y: %d\n", last_x, last_y);
313+ data->state = LV_INDEV_STATE_PR;
314+ }
315+ else {
316+ data->state = LV_INDEV_STATE_REL;
317+ }
318+
319+ /*Set the last pressed coordinates*/
320+ data->point.x = last_x;
321+ data->point.y = last_y;
322+ }
323+
324+ static bool touchpad_is_pressed(void)
325+ {
326+ /*Your code comes here*/
327+ return ft6236_is_pressed();
328+ // return false;
329+ }
330+
331+ static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
332+ {
333+ /*Your code comes here*/
334+ (*x) = ft6236_read_x();
335+ (*y) = ft6236_read_y();
336+ }
337+ ```
338+
339+ ### Tick 驱动
340+
341+ v8.4 版本的 LVGL 允许在 lv_conf.h 中设置 tick 回调函数
342+
343+ ``` c
344+ #define LV_TICK_CUSTOM 1
345+ #if LV_TICK_CUSTOM
346+ #define LV_TICK_CUSTOM_INCLUDE "pico/time.h" /* Header for the system time function* /
347+ #define LV_TICK_CUSTOM_SYS_TIME_EXPR (time_us_32() / 1000LL) /* Expression evaluating to current system time in ms* /
348+ /* If using lvgl as ESP32 component* /
349+ // #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
350+ // #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
351+ #endif /* LV_TICK_CUSTOM* /
352+ ```
353+
354+ `time_us_32()` 是 Pico SDK 中的一个函数,用于获取系统启动后经过的时间,单位为微秒,因为 LVGL 的tick以毫秒为单位,所以需要除1000
151355
152356## LVGL 9
153357
0 commit comments