【微雪RP2040双核开发板】LVGL移植
<div class='showpostmsg'><h1> </h1><h1><b>准备</b></h1>
<p >基于样例的c工程进行</p>
<p >git clone <a href="https://github.com/lvgl/lvgl.git"><u>https://github.com/lvgl/lvgl.git</u></a></p>
<p >下载代码到RP2040-LCD-1.28\c\lib目录下</p>
<p > </p>
<h1 ><b>移植过程</b></h1>
<h2 ><b>配置文件lv_conf.h</b></h2>
<p >复制</p>
<p >lvgl/lv_conf_template.h</p>
<p >改名字为lv_conf.h</p>
<p >将#if 0改为1,即直接#include “lv_conf.h”</p>
<p >将lv_conf.h添加到工程头文件包含路径。</p>
<h2 ><b>底层驱动模板</b></h2>
<p >lvgl/examples/porting</p>
<p >下有对应的模板文件,分别是显示,文件系统和输入设备的驱动模板。</p>
<p >lv_port_disp_template.c/h</p>
<p >lv_port_fs_template.c/h</p>
<p >lv_port_indev_template.c/h</p>
<p >暂时只移植显示,所以复制lv_port_disp_template.c/h到RP2040-LCD-1.28\c</p>
<p >改名字为</p>
<p >lv_port_disp.c/h</p>
<p >将.c和.h里面的#if 0改为1</p>
<p >.c中#include "lv_port_disp_template.h"改为#include "lv_port_disp.h"</p>
<h2 ><b>HAL层模板</b></h2>
<p >lvgl/src/hal</p>
<p >下</p>
<p >我们直接使用不修改</p>
<p >lv_hal_disp.c/h</p>
<p >lv_hal_indev.c/h</p>
<p >lv_hal_tick.c/h</p>
<p >lv_hal.h</p>
<h2 ><b>添加文件</b></h2>
<p >如下</p>
<p > </p>
<p >RP2040-LCD-1.28/c/CMakeLists.txt中</p>
<p >add_subdirectory(./lib/lvgl)</p>
<p >添加lvgl的源码</p>
<p > </p>
<p >然后调用RP2040-LCD-1.28/c/lib/lvgl/CMakeLists.txt</p>
<p >最终调用</p>
<p >RP2040-LCD-1.28/c/lib/lvgl/env_support/cmake/custom.cmake</p>
<h2 ><b>需要移植的代码</b></h2>
<p >移植比较简单,直接使用底层驱动模板根据实际实现修改lv_port_disp.c,并配置lv_conf.h即可。</p>
<h3 ><b>头文件包含模式</b></h3>
<p >需要在工程中定义宏</p>
<p >#define LV_LVGL_H_INCLUDE_SIMPLE 1</p>
<p >这样需要将lvgl.h所在路径配置为工程头文件包含路径。</p>
<p >代码中直接#include "lvgl.h"即可</p>
<p >否则是#include "../../lvgl.h"</p>
<p > </p>
<p >lib/lvgl/env_support/cmake/custom.cmake中</p>
<p >该宏默认是使能的</p>
<p >option(LV_LVGL_H_INCLUDE_SIMPLE</p>
<p > "Use #include \"lvgl.h\" instead of #include \"../../lvgl.h\"" ON)</p>
<h3 ><b>分辨率配置</b></h3>
<p >lv_port_disp.h中</p>
<p >#define MY_DISP_HOR_RES 240</p>
<p >#define MY_DISP_VER_RES 240</p>
<h3 ><b>初始化</b></h3>
<p >lv_port_disp.c中</p>
<p >实现disp_init</p>
<p >即调用自己的初始化函数,如果在其他地方初始化了,该函数实现为空函数体即可。</p>
<h3 ><b>缓冲区</b></h3>
<p >lv_port_disp.c中</p>
<p >lv_port_disp_init</p>
<p >注释掉/* Example for 2) */</p>
<p >/* Example for 3) also set disp_drv.full_refresh = 1 below*/对应的代码</p>
<p >使用/* Example for 1) */</p>
<p >lv_port_disp_init</p>
<p >该函数调用disp_init实现初始化</p>
<h3 ><b>刷新显示</b></h3>
<p >lv_port_disp.c中包含#include "LCD_1in28.h"</p>
<p >disp_flush</p>
<p >/*put_px(x, y, color_p)/改为</p>
<p >lcd_draw_point(x,y,color_p->full);</p>
<p >并调用LcdPush();</p>
<p > </p>
<div class="parsedown-markdown">
<p>static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)</p>
<p >{</p>
<p > if(disp_flush_enabled) {</p>
<p > /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/</p>
<p > </p>
<p > volatile int32_t x;</p>
<p > volatile int32_t y;</p>
<p > printf("%d %d %d %d\r\n",area->y1,area->y2,area->x1,area->x2);</p>
<p > for(y = area->y1; y <= area->y2; y++) {</p>
<p > for(x = area->x1; x <= area->x2; x++) {</p>
<p > /*Put a pixel to the display. For example:*/</p>
<p > /*put_px(x, y, *color_p)*/</p>
<p > lcd_draw_point(x,y,color_p->full);</p>
<p > color_p++;</p>
<p > }</p>
<p > }</p>
<p > LcdPush();</p>
<p > }</p>
<p > </p>
<p > /*IMPORTANT!!!</p>
<p > *Inform the graphics library that you are ready with the flushing*/</p>
<p > lv_disp_flush_ready(disp_drv);</p>
<p >}</p>
</div>
<p > </p>
<p >lib/LCD/LCD_1in28.c中实现</p>
<div class="parsedown-markdown">
<p>static uint16_t s_disbuffer_au16;</p>
<p > </p>
<p >void lcd_draw_point(int32_t x,int32_t y,uint16_t color)</p>
<p >{</p>
<p > s_disbuffer_au16 = (color>>8) | ((color&0xFF)<<8);</p>
<p >}</p>
<p > </p>
<p >void LcdPush(void)</p>
<p >{</p>
<p > LCD_1IN28_SetWindows(0, 0, LCD_1IN28_WIDTH, LCD_1IN28_HEIGHT);</p>
<p > DEV_Digital_Write(LCD_DC_PIN, 1);</p>
<p > spi_write_blocking(SPI_PORT, (uint8_t *)&s_disbuffer_au16, LCD_1IN28_HEIGHT*LCD_1IN28_WIDTH*2);</p>
<p > ///UWORD j;</p>
<p > ///for(j = 0; j < LCD_1IN28_HEIGHT; j++){</p>
<p > // DEV_SPI_Write_nByte((uint8_t *)&s_disbuffer_au16, LCD_1IN28_WIDTH*2);</p>
<p > //}</p>
<p >}</p>
</div>
<p > </p>
<p > </p>
<p >lib/LCD/LCD_1in28.h中申明</p>
<div class="parsedown-markdown">
<p>void lcd_draw_point(int32_t x,int32_t y,uint16_t color);</p>
<p >void LcdPush(void);</p>
</div>
<p > </p>
<h3 ><b>颜色深度</b></h3>
<p >lv_conf.h中</p>
<p >#define LV_COLOR_DEPTH 16</p>
<p >typedef LV_CONCAT3(lv_color, LV_COLOR_DEPTH, _t) lv_color_t;</p>
<p >则lv_color_t类型为lv_color_16_t</p>
<p> </p>
<p> </p>
<h3 ><b>堆大小配置</b></h3>
<p >lv_conf.h中</p>
<p >#define LV_MEM_SIZE (10U * 1024U) /* */</p>
<p >按需提供堆大小,过大可能编译不过,过小可能影响创建对象。</p>
<h3 ><b>时间滴答</b></h3>
<p >如果在lv_conf.h中指定LV_TICK_CUSTOM为1则需要用户提供相关接口</p>
<p >LV_TICK_CUSTOM_SYS_TIME_EXPR用于获取当前毫秒值</p>
<p >和头文件LV_TICK_CUSTOM_INCLUDE</p>
<p > </p>
<p >否则使用lvgl/src/hal/lv_hal_tick.c的实现</p>
<p >每隔x毫秒调用lv_tick_inc(x),使用内部计数器定时。</p>
<p >周期调用lv_tick_inc更新时间滴答,比如专门在某个定时线程中</p>
<div class="parsedown-markdown">
<p>bool timer_callback(repeating_timer_t *rt)</p>
<p >{</p>
<p > lv_tick_inc(5);</p>
<p >}</p>
<p >add_repeating_timer_ms(5,timer_callback,0,0);</p>
</div>
<p > </p>
<h3 ><b>日志</b></h3>
<p >lv_conf.h中</p>
<p >#define LV_USE_LOG 0</p>
<p >改为</p>
<p >#define LV_USE_LOG 1使能日志</p>
<p >#define LV_LOG_LEVEL LV_LOG_LEVEL_TRACE设置日志,等级</p>
<p >LV_LOG_LEVEL_TRACE表示所有信息都打印</p>
<p >如果#define LV_LOG_PRINTF 0</p>
<p >则需要调用设置lv_log_register_print_cb()打印函数</p>
<p >否则使用printf</p>
<p >#define LV_LOG_USE_TIMESTAMP 1</p>
<p >使能打印时间</p>
<p >其他的模块打印使能</p>
<h3 ><b>字体配置</b></h3>
<p >lv_conf.h</p>
<p >中按需使能对应的字体,如果有对应编译错误信息根据提示使能</p>
<p >#define LV_FONT_MONTSERRAT_12 1</p>
<p >#define LV_FONT_MONTSERRAT_14 1</p>
<p >#define LV_FONT_MONTSERRAT_16 1</p>
<h3 ><b>工程配置</b></h3>
<p >RP2040-LCD-1.28\c\CMakeLists.txt中</p>
<p >添加</p>
<div class="parsedown-markdown">
<p>add_subdirectory(./lib/lvgl)</p>
<p >include_directories(./lib/lvgl)</p>
<p >include_directories(.)</p>
<p >add_definitions(-DLV_LVGL_H_INCLUDE_SIMPLE=1)</p>
<p >add_executable(main</p>
<p >main.c lv_port_disp.c</p>
<p >)</p>
<p > </p>
<p > </p>
<p >target_link_libraries(main examples lvgl LCD QMI8658 GUI Fonts Config pico_stdlib hardware_spi hardware_i2c )</p>
<p > </p>
<p >examples/CMakeLists.txt中</p>
<p >include_directories(../lib/lvgl)</p>
<p >add_definitions(-DLV_LVGL_H_INCLUDE_SIMPLE=1)</p>
</div>
<p > </p>
<h2 ><b>测试</b></h2>
<p >创建一个按钮</p>
<p >examples/LCD_1in28_test.c</p>
<div class="parsedown-markdown">
<p>#include "LCD_Test.h"</p>
<p >#include "LCD_1in28.h"</p>
<p >#include "QMI8658.h"</p>
<p >#include <stdio.h></p>
<p >#include "pico/stdlib.h"</p>
<p >#include "hardware/adc.h"</p>
<p >#include "draw.h"</p>
<p >#include "lvgl.h"</p>
<p >#include "lv_port_disp.h"</p>
<p > </p>
<p >bool timer_callback(repeating_timer_t *rt)</p>
<p >{</p>
<p > lv_tick_inc(5);</p>
<p > ///printf("%lld\r\n",time_us_64());</p>
<p >}</p>
<p > </p>
<p >static void btn_event_cb(lv_event_t * e)</p>
<p >{</p>
<p > lv_event_code_t code = lv_event_get_code(e);</p>
<p > lv_obj_t * btn = lv_event_get_target(e);</p>
<p > if(code == LV_EVENT_CLICKED) {</p>
<p > static uint8_t cnt = 0;</p>
<p > cnt++;</p>
<p > </p>
<p > /*Get the first child of the button which is the label and change its text*/</p>
<p > lv_obj_t * label = lv_obj_get_child(btn, 0);</p>
<p > lv_label_set_text_fmt(label, "Button: %d", cnt);</p>
<p > }</p>
<p >}</p>
<p > </p>
<p >/**</p>
<p > * Create a button with a label and react on click event.</p>
<p > */</p>
<p >void lv_example_get_started_1(void)</p>
<p >{</p>
<p > lv_color_t color;</p>
<p > color.full=(uint16_t)0xF800;</p>
<p > lv_obj_t * btn = lv_btn_create(lv_scr_act()); /*Add a button the current screen*/</p>
<p > lv_obj_set_style_text_font(btn, &lv_font_montserrat_24 ,0);</p>
<p > lv_obj_set_style_text_color(btn,color,0);</p>
<p > lv_obj_set_pos(btn, 120-100, 120-50); /*Set its position*/</p>
<p > lv_obj_set_size(btn, 200, 100); /*Set its size*/</p>
<p > lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /*Assign a callback to the button*/</p>
<p > </p>
<p > lv_obj_t * label = lv_label_create(btn); /*Add a label to the button*/</p>
<p > lv_label_set_text(label, "Hello LVGL"); /*Set the labels text*/</p>
<p > lv_obj_center(label);</p>
<p >}</p>
<p > </p>
<p >struct repeating_timer timer;</p>
<p > </p>
<p >int LCD_1in28_test(void)</p>
<p >{</p>
<p > if (DEV_Module_Init() != 0)</p>
<p > {</p>
<p > return -1;</p>
<p > }</p>
<p > adc_init();</p>
<p > adc_gpio_init(29);</p>
<p > adc_select_input(3);</p>
<p > LCD_1IN28_Init(VERTICAL);</p>
<p > LCD_1IN28_Clear(WHITE);</p>
<p > DEV_SET_PWM(60);</p>
<p > add_repeating_timer_ms(5,timer_callback,0,&timer);</p>
<p > ///sleep_ms(5000);</p>
<p > lv_init();</p>
<p > lv_port_disp_init();</p>
<p > lv_example_get_started_1();</p>
<p > while(1)</p>
<p > {</p>
<p > sleep_us(1000); /* 必须又sleep 否则定时器不会回调 */</p>
<p > lv_task_handler(); </p>
<p > }</p>
<p > </p>
<p > DEV_Module_Exit();</p>
<p > return 0;</p>
<p >}</p>
</div>
<p > </p>
<p >效果如下</p>
<p > </p>
<p > </p>
<h3 ><b>镜像处理</b></h3>
<p >写点函数交换xy即可</p>
<p > </p>
<h3 ><b>颜色处理</b></h3>
<p >16位565模式,高低字节需要交换下</p>
<div class="parsedown-markdown">
<p>void lcd_draw_point(int32_t x,int32_t y,uint16_t color)</p>
<p >{</p>
<p > s_disbuffer_au16 = (color>>8) | ((color&0xFF)<<8);</p>
<p >}</p>
</div>
<p > </p>
<h2 ><b>编译</b></h2>
<p >cd build</p>
<p >export PICO_SDK_PATH="/home/lhj/pico-setup/pico/pico-sdk" && cmake ..</p>
<p >Make</p>
<p >不编译demo,example</p>
<p >export PICO_SDK_PATH="/home/lhj/pico-setup/pico/pico-sdk" && cmake -DLV_CONF_BUILD_DISABLE_EXAMPLES=1 -DLV_CONF_BUILD_DISABLE_DEMOS=1 ..</p>
<p > </p>
<h2 ><b>坑</b></h2>
<p >不要用dor循环书信屏幕,直接调用spi_write_blocking</p>
<p >for循环编译器会优化导致逻辑错误,可能并不会按照循环次数循环,而是卡死。</p>
<div class="parsedown-markdown">
<p>void LcdPush(void)</p>
<p >{</p>
<p > LCD_1IN28_SetWindows(0, 0, LCD_1IN28_WIDTH, LCD_1IN28_HEIGHT);</p>
<p > DEV_Digital_Write(LCD_DC_PIN, 1);</p>
<p > spi_write_blocking(SPI_PORT, (uint8_t *)&s_disbuffer_au16, LCD_1IN28_HEIGHT*LCD_1IN28_WIDTH*2);</p>
<p > ///UWORD j;</p>
<p > ///for(j = 0; j < LCD_1IN28_HEIGHT; j++){</p>
<p > // DEV_SPI_Write_nByte((uint8_t *)&s_disbuffer_au16, LCD_1IN28_WIDTH*2);</p>
<p > //}</p>
<p >}</p>
</div>
<p > </p>
<p > </p>
<h1 ><b>参考</b></h1>
<p ><a href="https://lvgl.io/"><u>https://lvgl.io/</u></a></p>
<p > </p>
<p > </p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>源码</p>
<div></div>
<p>感谢分享。</p>
<p>谢谢大佬的教程!</p>
<p>mark</p>
页:
[1]