[原创] 【Luckfox幸狐 RV1106 Linux 开发板测评】十二、实现FrameBuffer设备及LVGL应用

sonicfirr   2024-2-18 19:38 楼主

本人【Luckfox幸狐 RV1106 Linux 开发板测评】帖子链接:

一、开箱及测试

二、SDK获取与编译镜像

三、GPIO点灯

四、通过PC机共享网络接入Internet和Ubuntu下Python点灯

五、编译Buildroot系统并测试摄像头

六、PWM控制——命令和C方式

七、PWM控制——Python和设备树方式

八、ADC和UART测试

九、Python控制I2C驱动OLED

十、C程序控制I2C驱动OLED

十一、SPI驱动LCD

 

本篇测评基于上一测评中开启的SPI0接口,进一步通过修改设备树文件<SDK目录>/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-luckfox-pico-pro-max.dts,使得Luckfox Pro Max驱动(SPI0口)的0.96寸合宙LCD(ST7735)注册为FrameBuffer设备。这样,LVGL就可以基于FB作为显示接口。

本次实验主要参考Luckfox Wiki之LVGL使用指南篇(https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-LVGL)。

1、设备树文件修改

官方文档中提供的LVGL.zip包含系统镜像、lvgl_demo工程和设备文件,可惜是基于微雪Pico-LCD-1.3显示屏(ST7789驱动),与本人手中的合宙LCD不同。好在两款LCD驱动器都是Sitronix公司的产品,而且Linux系统中都有驱动。

微雪1.3寸屏附带五向开关和ABXY四按键,所以LVGL.zip中的设备树文件除了SPI0的配置还有这些按键控制IO的配置,因为用到IO多,所以I2C3等接口都被禁用了。本人使用的合宙0.96寸屏只附带五向开关,所以没有直接拷贝设备树文件,只是修改了SPI0部分。完整的rv1106g-luckfox-pico-pro-max.dts文件代码如下:

// 基于官方案例提供设备树源码,剔除注释部分和未使用IO部分
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2022 Rockchip Electronics Co., Ltd.
 */

/dts-v1/;

#include "rv1106.dtsi"
#include "rv1106-evb.dtsi"
#include "rv1106-luckfox-pico-pro-max-ipc.dtsi"

/ {
	model = "Luckfox Pico Max";
	compatible = "rockchip,rv1103g-38x38-ipc-v10", "rockchip,rv1106";

	/*KEY*/
	/*KEY-DOWN*/
	gpio2pa0:gpio2pa0 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio2_pa0>;
		regulator-name = "gpio2_pa0";
		regulator-always-on;
	};

	/*KEY-RIGHT*/
	gpio2pa2:gpio2pa2 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio2_pa2>;
		regulator-name = "gpio2_pa2";
		regulator-always-on;
	};

	/*KEY-LEFT*/
	gpio2pa4:gpio2pa4 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio2_pa4>;
		regulator-name = "gpio2_pa4";
		regulator-always-on;
	};

	/*KEY-CENTER*/
	gpio1pc6:gpio1pc6 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio1_pc6>;
		regulator-name = "gpio1_pc6";
		regulator-always-on;
	};

	/*KEY-UP*/
	gpio1pc7:gpio1pc7 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio1_pc7>;
		regulator-name = "gpio1_pc7";
		regulator-always-on;
	};

	/*LCD_RES*/
	gpio1pc3:gpio1pc3 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio1_pc3>;
		regulator-name = "gpio1_pc3";
		regulator-always-on;
	};

	/*LCD_BL*/
	gpio2pb0:gpio2pb0 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio2_pb0>;
		regulator-name = "gpio2_pb0";
		regulator-always-on;
	};

	/*LCD_DC*/
    gpio2pb1:gpio2pb1 {
		compatible = "regulator-fixed";
		pinctrl-names = "default";
		pinctrl-0 = <&gpio2_pb1>;
		regulator-name = "gpio2_pb1";
		regulator-always-on;
	};
};

/**********GPIO**********/
&pinctrl {
	/*KEY*/
	gpio2-pa0 {
		gpio2_pa0:gpio2-pa0 {
			rockchip,pins =	<2 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	gpio2-pa2 {
		gpio2_pa2:gpio2-pa2 {
			rockchip,pins =	<2 RK_PA2 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	gpio2-pa4 {
		gpio2_pa4:gpio2-pa4 {
			rockchip,pins =	<2 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	gpio1-pc6 {
		gpio1_pc6:gpio1-pc6 {
			rockchip,pins =	<1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	gpio1-pc7 {
		gpio1_pc7:gpio1-pc7 {
			rockchip,pins =	<1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

	/*RESET*/
	gpio1-pc3 {
		gpio1_pc3:gpio1-pc3 {
			rockchip,pins =	<1 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	/*BL*/
	gpio2-pb0 {
		gpio2_pb0:gpio2-pb0 {
			rockchip,pins =	<2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};

	/*LCD_DC*/
  gpio2-pb1 {
		gpio2_pb1:gpio2-pb1 {
			rockchip,pins =	<2 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
};

/**********FLASH**********/
&sfc {
	status = "okay";
	flash@0 {
		compatible = "spi-nand";
		reg = <0>;
		spi-max-frequency = <75000000>;
		spi-rx-bus-width = <4>;
		spi-tx-bus-width = <1>;
	};
};

/**********ETH**********/
&gmac {
	status = "okay";
};

/**********USB**********/
&usbdrd_dwc3 {
	status = "okay";
	dr_mode = "peripheral";
};

/**********I2C**********/
&i2c3 {
	status = "okay";
	pinctrl-0 = <&i2c3m1_xfer>;
	clock-frequency = <100000>;
};

/**********SPI**********/
&spi0 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0m0_cs0 &spi0m0_pins>;

	st7735s@0{
		status = "okay";
		compatible = "sitronix,st7735r";
		reg = <0>;
		spi-max-frequency = <48000000>;
//		width = <80>;
//		height = <160>;
		rotate = <270>;
		fps = <30>;
		buswidth = <8>;
		debug = <0x7>;
		led-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_HIGH>;//BL
		dc = <&gpio2 RK_PB1 GPIO_ACTIVE_HIGH>;       //DC
		reset = <&gpio1 RK_PC3 GPIO_ACTIVE_LOW>;     //RES
	};
};

&pinctrl {
    spi0 {
        /omit-if-no-ref/
        spi0m0_pins: spi0m0-pins {
            rockchip,pins =
                /* spi0_clk_m0 */
                <1 RK_PC1 4 &pcfg_pull_none>,
                /* spie_miso_m0 */
                /* <1 RK_PC3 6 &pcfg_pull_none>, */
                /* spi_mosi_m0 */
                <1 RK_PC2 6 &pcfg_pull_none>;
        };
    };
};

 

除了设备树文件,还要修改<SDK目录>/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig,以添加FB文件支持。直接编辑文件可以省去make menuconfig的步骤。

 

image-20240218193520-1.png  

图12-1 FB设备支持添加项

 

设备树和配置文件修改后,就可以再次编译内核并烧写开发板了。更新系统镜像后再次启动开发板,就可以看到设备文件:/dev/fb0。

 

image-20240218193520-2.png  

图12-2 开发板具备了fb0

 

2、lvgl_demo项目修改

官方提供的LVGL.zip中包含测试项目lvgl_demo,将项目文件夹拷贝到虚拟机,因为项目是基于FB的LVGL移植案例,所以不用再做其它修改,只需要更新Makefile中CC变量的赋值为Luckfox SDK路径。

另外,合宙屏在使用案例时一上来控制台输出报错信息:ioctl(FBIOBLANK): Invalid argument,参考一篇技术贴(https://blog.csdn.net/klp1358484518/article/details/130032766),需要注释掉lv_drivers/display/fbdev.c中的几行代码:

    // 位于fbdev_init()函数

    // Make sure that the display is on.

    // if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) {

    //     perror("ioctl(FBIOBLANK)");

    //     return;

    // }

 

当然,由于本人使用的是0.96寸屏,所以main.c做了修改——原案例显示图片适配1.3寸屏,这里改为屏幕中心显示一行字符串。main.c的代码如下:

#include "lvgl/lvgl.h"
#include "DEV_Config.h"
#include "lv_drivers/display/fbdev.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>

#define DISP_BUF_SIZE (160 * 128)

void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p);

int main(void)
{
    /*LittlevGL init*/
    lv_init();

    /*Linux frame buffer device init*/
    fbdev_init();

    /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);

    /*Initialize and register a display driver*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.draw_buf   = &disp_buf;
    disp_drv.flush_cb   = fbdev_flush;
    disp_drv.hor_res    = 160;
    disp_drv.ver_res    = 128;
    lv_disp_drv_register(&disp_drv);

    /*Initialize pin*/
    DEV_ModuleInit();

    lv_obj_t *scr = lv_disp_get_scr_act(NULL);
    // 显示字符串
    lv_obj_t *label = lv_label_create(scr);
    lv_label_set_text(label, "Luckfox LVGL FB!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

    /*Create a cursor*/
    lv_obj_t *cursor = lv_img_create(scr);
    lv_img_set_src(cursor, LV_SYMBOL_GPS);
    lv_obj_set_pos(cursor, 70, 40);
    int x=70,y=40,move=0;

    /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_timer_handler();
        usleep(5000);

        /*Joystick*/
        if(GET_KEY_RIGHT == 0){
            x += 1;
            if(x > 226)x = 226;
            move =1;
        }
        else if(GET_KEY_LEFT == 0){
            x -= 1;
            if(x < 0)x = 0;
            move =1;
        }
       else if(GET_KEY_UP == 0){
            y -= 1;
            if(y < 0)y = 0;
            move =1;
        }
        else if(GET_KEY_DOWN == 0){
            y += 1;
            if(y > 224)y = 224;
            move =1;
        }
       else if(GET_KEY_PRESS == 0){
            x = 80;
            y = 64;
            move =1;
        }
        if(move == 1){
            lv_obj_set_pos(cursor, x, y);
            move = 0;
        }
    }

    return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}

 

image-20240218193520-3.png  

图12-3 案例效果——五向开关拨动屏幕上的图标也会移动

 

3、调试中的问题说明

项目看似简单但也耗费了在下一天的时间进行调试,主要是屏幕适配的问题,最初屏幕是纵向显示的(即上图字体左旋90°)。而本人又将调整屏幕方向的思路放在修改程序代码上,使用lv_disp_set_rotation()函数,但是没有成功,判断就是底层驱动不支持旋转屏幕,于是转思路尝试修改设备文件。注:后续发现LVGL文档中提到如果没有硬件支持,可以使用lv_draw_sw_rotate()函数进行转屏,不过自己没有进行尝试,也不清楚是否有效。

 

image-20240218193520-4.png  

图12-4 LVGL在线文档Rotation部分页面截图

 

关于ST7735设备树节点描述的资料不太好找,本人也是搜索了几篇技术博客,从中分析到“旋转、宽高”属性的设置方法。不过经过验证,屏幕尺寸如果依照0.98屏的真实参数设置会出现部分屏幕不能使用的问题,索性只设置了rotate。

而关于宽高设置后出现问题的原因,个人猜测可能是Linux源码中提供的ST7735r驱动就是适配160*128尺寸的,不能兼容其它尺寸。这一点也是查看了Linux源码 Documentation中的相应说明文档分析出来的——<Luckfox SDK>/sysdrv/source/kernel/Documentation/devicetree/bindings/display/sitronix,st7735r.yaml。

 

image-20240218193520-5.png  

图12-5 合宙0.96寸屏的设备树节点

 

image-20240218193520-6.png  

图12-6 设置了宽高属性的效果——屏幕一部分无法使用

 

image-20240218193520-7.png  

图12-7 sitronix,st7735r.yaml文档内容截图

 

上述系统镜像烧写后,开发板中通过命令fbset,可以看到当前屏幕分辨率就是160x128——关于真实屏幕参数为160x80系统却可以识别为160x128在下也是觉得蛮魔性的。有鉴于此,在下也是在程序中设置屏幕尺寸为160x128,没想到还真能准确显示了。

 

image-20240218193520-8.png  

图12-8 fbset命令查看屏幕分辨率

 

image-20240218193520-9.png  

图12-9 LVGL设置宽高依据系统分辨率

 

 

回复评论 (3)

这个屏幕什么价格,不贵的话我也搞一个,显示起来还挺方便

点赞  2024-2-19 09:34
引用: LitchiCheng 发表于 2024-2-19 09:34 这个屏幕什么价格,不贵的话我也搞一个,显示起来还挺方便

合宙的,10几块钱吧,其实还不如买微雪的,官方案例可以适配,尺寸也大点。

点赞  2024-2-19 10:33
引用: sonicfirr 发表于 2024-2-19 10:33 合宙的,10几块钱吧,其实还不如买微雪的,官方案例可以适配,尺寸也大点。

可以可以,我去看看微雪的

点赞  2024-2-19 11:53
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复