My post [Luckfox RV1106 Linux Development Board Review] link:
1. Unboxing and testing
2. SDK acquisition and image compilation
3. GPIO Lighting
4. Access the Internet through PC shared network and light up Python in Ubuntu
5. Compile the Buildroot system and test the camera
6. PWM control - command and C mode
7. PWM Control - Python and Device Tree Method
8. ADC and UART test
9. Python controls I2C to drive OLED
10. C program controls I2C to drive OLED
11. SPI drives LCD
This review is based on the SPI0 interface enabled in the previous review. By further modifying the device tree file <SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-luckfox-pico-pro-max.dts, the 0.96-inch Hezhou LCD (ST7735) driven by Luckfox Pro Max (SPI0 port) is registered as a FrameBuffer device. In this way, LVGL can be used as a display interface based on FB.
This experiment mainly refers to the LVGL User Guide of Luckfox Wiki ( https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-LVGL ).
1. Device tree file modification
The LVGL.zip provided in the official document contains the system image, lvgl_demo project and device files, but unfortunately it is based on the Weixue Pico-LCD-1.3 display (ST7789 driver), which is different from the Hezhou LCD in my hand. Fortunately, both LCD drivers are products of Sitronix, and both have drivers in the Linux system.
The Weixue 1.3-inch screen comes with a five-way switch and ABXY four-button, so the device tree file in LVGL.zip has not only the SPI0 configuration but also the IO configuration of these buttons. Because many IOs are used, interfaces such as I2C3 are disabled. The Hezhou 0.96-inch screen I use only comes with a five-way switch, so I did not copy the device tree file directly, but only modified the SPI0 part. The complete rv1106g-luckfox-pico-pro-max.dts file code is as follows:
// 基于官方案例提供设备树源码,剔除注释部分和未使用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>;
};
};
};
In addition to the device tree file, you also need to modify <SDK directory>/sysdrv/source/kernel/arch/arm/configs/luckfox_rv1106_linux_defconfig to add FB file support. Editing the file directly can save the step of make menuconfig.
Figure 12-1 FB device support additions
After modifying the device tree and configuration files, you can compile the kernel again and flash the development board. After updating the system image, restart the development board and you will see the device file: /dev/fb0.
Figure 12-2 The development board has fb0
2. lvgl_demo project modification
The official LVGL.zip contains the test project lvgl_demo. Copy the project folder to the virtual machine. Because the project is based on FB's LVGL porting case, no other modifications are required. You only need to update the CC variable in Makefile to the Luckfox SDK path.
In addition, when the Hezhou screen is used in the case, the console outputs an error message: ioctl(FBIOBLANK): Invalid argument. Refer to a technical post ( https://blog.csdn.net/klp1358484518/article/details/130032766 ), and you need to comment out several lines of code in lv_drivers/display/fbdev.c:
// Located in fbdev_init() function
// Make sure that the display is on.
// if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) {
// perror("ioctl(FBIOBLANK)");
// return;
// }
Of course, since I use a 0.96-inch screen, I have modified main.c - the original case shows pictures adapted to a 1.3-inch screen, and here it is changed to display a line of strings in the center of the screen. The code of main.c is as follows:
#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;
}
Figure 12-3 Example effect - the icons on the screen will move when the five-way switch is turned
3. Description of the problem in debugging
The project seemed simple, but it took the next day to debug. The main problem was screen adaptation. The screen was originally displayed vertically (i.e. the font in the above picture was rotated 90° to the left). I tried to adjust the screen direction by modifying the program code, using the lv_disp_set_rotation() function, but it didn't work. I thought that the underlying driver did not support screen rotation, so I changed my mind and tried to modify the device file. Note: I later found that the LVGL document mentioned that if there is no hardware support, the lv_draw_sw_rotate() function can be used to rotate the screen, but I didn't try it myself and I don't know if it works.
Figure 12-4 Screenshot of the Rotation section of the LVGL online documentation
It is not easy to find information about the device tree node description of ST7735. I also searched several technical blogs and analyzed the setting methods of "rotation, width and height" properties. However, after verification, if the screen size is set according to the actual parameters of 0.98 screen, part of the screen will not be usable, so I simply set only rotate.
As for the reason why the problem occurs after setting the width and height, I personally guess that it may be that the ST7735r driver provided in the Linux source code is adapted to the size of 160*128 and is not compatible with other sizes. This is also analyzed by checking the corresponding documentation in the Linux source code Documentation - <Luckfox SDK>/sysdrv/source/kernel/Documentation/devicetree/bindings/display/sitronix,st7735r.yaml.
Figure 12-5 Device tree nodes for Hezhou 0.96-inch screen
Figure 12-6 Effect of setting the width and height attributes—part of the screen is unusable
Figure 12-7 Screenshot of sitronix,st7735r.yaml document content
After the above system image is burned, the fbset command in the development board shows that the current screen resolution is 160x128. I find it quite magical that the actual screen parameter is 160x80 but the system can recognize it as 160x128. In view of this, I also set the screen size to 160x128 in the program, and I didn't expect it to be displayed accurately.
Figure 12-8 fbset command to check screen resolution
Figure 12-9 LVGL sets width and height based on system resolution