Allwinner R128 application development case – SPI driver ST7789V1.3-inch LCD

SPI driver ST7789V1.3-inch LCD

The R128 platform provides the SPI TFT interface of SPI DBI, which has the following features:

  • Supports DBI Type C 3 Line/4 Line Interface Mode
  • Supports 2 Data Lane Interface Mode
  • Supports data source from CPU or DMA
  • Supports RGB111/444/565/666/888 video format
  • Maximum resolution of RGB666 240 x 320@30Hz with single data lane
  • Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
  • Supports tearing effect
  • Supports software flexible control video frame rate

At the same time, the SPILCD driver framework is provided for use by SPI screens.

The SPI screen adapted this time is ZJY130S0800TG01, which is driven by SPI.

The pin configuration is as follows:

R128 Devkit TFT module
PA12 CS
PA13 SCL
PA18 SDA
PA9 BLK
PA20 RES
PA19 DC
3V3 VCC
GND GND

Loading plan

The development board we use is R128-Devkit and we need to develop C906 core applications, so the loading solution is r128s2_module_c906

$ source envsetup.sh
$ lunch_rtos 1

Set up SPI driver

The screen uses the SPI driver, so you need to check the SPI driver and run mrtos_menuconfig to enter the configuration page. Go to the address below to find SPI Devices

Drivers Options --->
    soc related device drivers --->
        SPI Devices --->
        -*- enable spi driver

Configure SPI pins

Open your favorite editor and modify the file: board/r128s2/module/configs/sys_config.fex. We don’t need to use the SPI HOLD and SPI WP pins here, just comment them out.

;------------------------------------------------- ----------------------------------
;SPI controller configuration
;------------------------------------------------- ----------------------------------
;Please configure spi in dts
[spi1]
spi1_used = 1
spi1_cs_number = 1
spi1_cs_bitmap = 1
spi1_cs0 = port:PA12<6><0><3><default>
spi1_sclk = port:PA13<6><0><3><default>
spi1_mosi = port:PA18<6><0><3><default>
spi1_miso = port:PA21<6><0><3><default>
;spi1_hold = port:PA19<6><0><2><default>
;spi1_wp = port:PA20<6><0><2><default>

Set PWM driver

The screen backlight uses a PWM driver, so you need to check the PWM driver and run mrtos_menuconfig to enter the configuration page. Go to the following address to find PWM Devices

Drivers Options --->
    soc related device drivers --->
        PWM Devices --->
        -*- enable pwm driver

Configure PWM pins

Open your favorite editor, modify the file: board/r128s2/module/configs/sys_config.fex, and add the PWM1 node

[pwm1]
pwm_used=1
pwm_positive = port:PA9<4><0><3><default>

Set up SPI LCD driver

SPI LCD is managed by a dedicated driver. Run mrtos_menuconfig to enter the configuration page. Go to the following address to find SPILCD Devices, and be sure to check spilcd hal APIs test at the same time to facilitate testing.

Drivers Options --->
    soc related device drivers --->
        [*] DISP Driver Support(spi_lcd)
        [*] spilcd hal APIs test

Writing SPI LCD display driver

Get screen initialization sequence

First ask the screen manufacturer to provide driver source code

Find the LCD’s initialization sequence code

Find the source code of screen initialization

The organized initialization code is as follows:

LCD_WR_REG(0x11); // Sleep out
delay_ms(120); // Delay 120ms
//*********** Start Initial Sequence **********//
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);

LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);

LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);

LCD_WR_REG(0xB7);
LCD_WR_DATA8(0x35);

LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x20); // 2b

LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2C);

LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);

LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x01);

LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x18); // VDV, 0x20:0v

LCD_WR_REG(0xC6);
LCD_WR_DATA8(0x13); // 0x13:60Hz

LCD_WR_REG(0xD0);
LCD_WR_DATA8(0xA4);
LCD_WR_DATA8(0xA1);

LCD_WR_REG(0xD6);
LCD_WR_DATA8(0xA1); //After sleep in, the gate output is GND

LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x36);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x29);
LCD_WR_DATA8(0x30);

LCD_WR_REG(0xE1);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x21);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x38);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x31);

LCD_WR_REG(0xE4);
LCD_WR_DATA8(0x1D); //Use 240 gates (N + 1)*8
LCD_WR_DATA8(0x00); //Set the gate starting position
LCD_WR_DATA8(0x00); // When gate is not used up, bit4(TMG) is set to 0

LCD_WR_REG(0x21);

LCD_WR_REG(0x29);

Rewrite the SPI LCD driver with a ready-made driver

Just choose a ready-made SPI LCD to modify. Here, select the nv3029s.c driver to modify.

Copy these two drivers and rename them to st7789v.c

First edit st7789v.h and change nv3029s to st7789v

#ifndef _ST7789V_H
#define _ST7789V_H

#include "panels.h"

struct __lcd_panel st7789v_panel;

#endif /*End of file*/

Edit st7789v.c and change nv3029s to st7789v

Write initialization sequence

First delete the initialization function in static void LCD_panel_init(unsigned int sel).

Then copy the initialization sequence provided by the screen manufacturer

Then rewrite the driver interface according to the interface of the spi_lcd framework. The specific interface is as follows

Screen factory function SPILCD framework interface
LCD_WR_REG sunxi_lcd_cmd_write
LCD_WR_DATA8 sunxi_lcd_para_write
delay_ms sunxi_lcd_delay_ms

Can be replaced directly

After completion, it is as follows

Then modify the address function according to the driver provided by the screen manufacturer

Make the following changes

static void address(unsigned int sel, int x, int y, int width, int height)
{<!-- -->
sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
sunxi_lcd_para_write(sel, (y >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, y & amp; 0xff);
sunxi_lcd_para_write(sel, (height >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, height & amp; 0xff);
sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
sunxi_lcd_para_write(sel, (x >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, x & amp; 0xff);
sunxi_lcd_para_write(sel, (width >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, width & amp; 0xff);
sunxi_lcd_cmd_write(sel, 0x2c);
}

Complete the driver as follows

#include "st7789v.h"

static void LCD_power_on(u32 sel);
static void LCD_power_off(u32 sel);
static void LCD_bl_open(u32 sel);
static void LCD_bl_close(u32 sel);
static void LCD_panel_init(u32 sel);
static void LCD_panel_exit(u32 sel);
#define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v)
#define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val)

static struct disp_panel_para info[LCD_FB_MAX];

static void address(unsigned int sel, int x, int y, int width, int height)
{<!-- -->
sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
sunxi_lcd_para_write(sel, (y >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, y & amp; 0xff);
sunxi_lcd_para_write(sel, (height >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, height & amp; 0xff);
sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
sunxi_lcd_para_write(sel, (x >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, x & amp; 0xff);
sunxi_lcd_para_write(sel, (width >> 8) & amp; 0xff);
sunxi_lcd_para_write(sel, width & amp; 0xff);
sunxi_lcd_cmd_write(sel, 0x2c);
}

static void LCD_panel_init(unsigned int sel)
{
if (bsp_disp_get_panel_info(sel, & amp;info[sel])) {
lcd_fb_wrn("get panel info fail!\\
");
return;
}

sunxi_lcd_cmd_write(sel, 0x11); // Sleep out
sunxi_lcd_delay_ms(120); // Delay 120ms
//*********** Start Initial Sequence **********//
sunxi_lcd_cmd_write(sel, 0x36);
sunxi_lcd_para_write(sel, 0x00);

sunxi_lcd_cmd_write(sel, 0x3A);
sunxi_lcd_para_write(sel, 0x05);

sunxi_lcd_cmd_write(sel, 0xB2);
sunxi_lcd_para_write(sel, 0x1F);
sunxi_lcd_para_write(sel, 0x1F);
sunxi_lcd_para_write(sel, 0x00);
sunxi_lcd_para_write(sel, 0x33);
sunxi_lcd_para_write(sel, 0x33);

sunxi_lcd_cmd_write(sel, 0xB7);
sunxi_lcd_para_write(sel, 0x35);

sunxi_lcd_cmd_write(sel, 0xBB);
sunxi_lcd_para_write(sel, 0x20); // 2b

sunxi_lcd_cmd_write(sel, 0xC0);
sunxi_lcd_para_write(sel, 0x2C);

sunxi_lcd_cmd_write(sel, 0xC2);
sunxi_lcd_para_write(sel, 0x01);

sunxi_lcd_cmd_write(sel, 0xC3);
sunxi_lcd_para_write(sel, 0x01);

sunxi_lcd_cmd_write(sel, 0xC4);
sunxi_lcd_para_write(sel, 0x18); // VDV, 0x20:0v

sunxi_lcd_cmd_write(sel, 0xC6);
sunxi_lcd_para_write(sel, 0x13); // 0x13:60Hz

sunxi_lcd_cmd_write(sel, 0xD0);
sunxi_lcd_para_write(sel, 0xA4);
sunxi_lcd_para_write(sel, 0xA1);

sunxi_lcd_cmd_write(sel, 0xD6);
sunxi_lcd_para_write(sel, 0xA1); // After sleep in, the gate output is GND

sunxi_lcd_cmd_write(sel, 0xE0);
sunxi_lcd_para_write(sel, 0xF0);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x07);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x25);
sunxi_lcd_para_write(sel, 0x33);
sunxi_lcd_para_write(sel, 0x3C);
sunxi_lcd_para_write(sel, 0x36);
sunxi_lcd_para_write(sel, 0x14);
sunxi_lcd_para_write(sel, 0x12);
sunxi_lcd_para_write(sel, 0x29);
sunxi_lcd_para_write(sel, 0x30);

sunxi_lcd_cmd_write(sel, 0xE1);
sunxi_lcd_para_write(sel, 0xF0);
sunxi_lcd_para_write(sel, 0x02);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x05);
sunxi_lcd_para_write(sel, 0x05);
sunxi_lcd_para_write(sel, 0x21);
sunxi_lcd_para_write(sel, 0x25);
sunxi_lcd_para_write(sel, 0x32);
sunxi_lcd_para_write(sel, 0x3B);
sunxi_lcd_para_write(sel, 0x38);
sunxi_lcd_para_write(sel, 0x12);
sunxi_lcd_para_write(sel, 0x14);
sunxi_lcd_para_write(sel, 0x27);
sunxi_lcd_para_write(sel, 0x31);

sunxi_lcd_cmd_write(sel, 0xE4);
sunxi_lcd_para_write(sel, 0x1D); // Use 240 gates (N + 1)*8
sunxi_lcd_para_write(sel, 0x00); //Set the starting point of gate
sunxi_lcd_para_write(sel, 0x00); // When gate is not used up, bit4(TMG) is set to 0

sunxi_lcd_cmd_write(sel, 0x21);

sunxi_lcd_cmd_write(sel, 0x29);

if (info[sel].lcd_x < info[sel].lcd_y)
address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1);
else
address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1);
}

static void LCD_panel_exit(unsigned int sel)
{
sunxi_lcd_cmd_write(sel, 0x28);
sunxi_lcd_delay_ms(20);
sunxi_lcd_cmd_write(sel, 0x10);
sunxi_lcd_delay_ms(20);
sunxi_lcd_pin_cfg(sel, 0);
}

static s32 LCD_open_flow(u32 sel)
{
lcd_fb_here;
/* open lcd power, and delay 50ms */
LCD_OPEN_FUNC(sel, LCD_power_on, 50);
/* open lcd power, than delay 200ms */
LCD_OPEN_FUNC(sel, LCD_panel_init, 200);

LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50);
/* open lcd backlight, and delay 0ms */
LCD_OPEN_FUNC(sel, LCD_bl_open, 0);

return 0;
}

static s32 LCD_close_flow(u32 sel)
{
lcd_fb_here;
/* close lcd backlight, and delay 0ms */
LCD_CLOSE_FUNC(sel, LCD_bl_close, 50);
/* open lcd power, than delay 200ms */
LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
/* close lcd power, and delay 500ms */
LCD_CLOSE_FUNC(sel, LCD_power_off, 10);

return 0;
}

static void LCD_power_on(u32 sel)
{
/* config lcd_power pin to open lcd power0 */
lcd_fb_here;
power_en(sel, 1);

sunxi_lcd_power_enable(sel, 0);

sunxi_lcd_pin_cfg(sel, 1);
RESET(sel, 1);
sunxi_lcd_delay_ms(100);
RESET(sel, 0);
sunxi_lcd_delay_ms(100);
RESET(sel, 1);
}

static void LCD_power_off(u32 sel)
{
lcd_fb_here;
/* config lcd_power pin to close lcd power0 */
sunxi_lcd_power_disable(sel, 0);
power_en(sel, 0);
}

static void LCD_bl_open(u32 sel)
{
sunxi_lcd_pwm_enable(sel);
/* config lcd_bl_en pin to open lcd backlight */
sunxi_lcd_backlight_enable(sel);
lcd_fb_here;
}

static void LCD_bl_close(u32 sel)
{
/* config lcd_bl_en pin to close lcd backlight */
sunxi_lcd_backlight_disable(sel);
sunxi_lcd_pwm_disable(sel);
lcd_fb_here;
}


/* sel: 0:lcd0; 1:lcd1 */
static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3)
{
lcd_fb_here;
return 0;
}

static int lcd_set_var(unsigned int sel, struct fb_info *p_info)
{
return 0;
}

static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height)
{
address(sel, x, y, width, height);
return 0;
}

static int lcd_blank(unsigned int sel, unsigned int en)
{
return 0;
}

struct __lcd_panel st7789v_panel = {
    /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex
       */
.name = "st7789v",
.func = {
.cfg_open_flow = LCD_open_flow,
.cfg_close_flow = LCD_close_flow,
.lcd_user_defined_func = LCD_user_defined_func,
.blank = lcd_blank,
.set_var = lcd_set_var,
.set_addr_win = lcd_set_addr_win,
},
};

Docking driver framework

After completing the writing of the screen driver, we need to connect it to the SPILCD driver framework. First edit Kconfig

Add configuration of st7789v

config LCD_SUPPORT_ST7789V
    bool "LCD support st7789v panel"
    default n
    ---help---
        If you want to support st7789v panel for display driver, select it.

Then edit panels.c and add a reference to the st7789 driver in panel_array

As shown below

#ifdef CONFIG_LCD_SUPPORT_ST7789V
     &st7789v_panel,
#endif

Then edit panels.h and add the reference as well

As shown below

#ifdef CONFIG_LCD_SUPPORT_ST7789V
extern struct __lcd_panel st7789v_panel;
#endif

Finally edit the outer Makefile to add compilation options

As follows

obj-${<!-- -->CONFIG_LCD_SUPPORT_ST7789V} + = panels/st7789v.o

Select ST7789V driver

In the SPILCD driver selection interface, you can see LCD_FB panels select to select the SPI screen driver.

Enter the LCD_FB panels select option

Select and check [*] LCD support st7789v panel

Configure SPI LCD pins

Open your favorite editor and modify the file: board/r128s2/module/configs/sys_config.fex

[lcd_fb0]
lcd_used = 1
lcd_model_name = "spilcd"
lcd_driver_name = "st7789v"
; Screen specification configuration
lcd_x = 240
lcd_y = 240
lcd_width = 32
lcd_height = 32
;SPI rate
lcd_data_speed = 50
; PWM backlight configuration item
lcd_pwm_used = 1
lcd_pwm_ch = 1
lcd_pwm_freq = 5000
lcd_pwm_pol = 0
lcd_backlight = 100
; Configure lcd_if = 1 for SPI mode, double buffering
lcd_if = 0
fb_buffer_num = 2
; The following configuration is invalid in SPI mode
lcd_pixel_fmt = 11
lcd_dbi_fmt = 2
lcd_dbi_clk_mode = 1
lcd_dbi_te = 1
lcd_dbi_if = 4
lcd_rgb_order = 0
lcd_fps = 60
; Use SPI1 as communication interface
lcd_spi_bus_num = 1
lcd_frm = 2
lcd_gamma_en = 1

lcd_power_num = 0
lcd_gpio_regu_num = 0
lcd_bl_percent_num = 0

lcd_spi_dc_pin = port:PA19<1><0><3><0>
;RESET Pin
lcd_gpio_0 = port:PA20<1><0><2><0>

Compile and package

Run the command mp to compile and package. You can see that st7789v.o is compiled.

Test

After the burning starts, the screen backlight starts, but the screen is completely black.

Enter test_spilcd and the screen will display yellow.


Enter lv_examples 1 to display the lvgl interface

FAQ

LVGL appears DMA Over Size

This is because LVGL is configured with LV_COLOR_DEPTH as 32, but the SPI screen is configured as 16 bits. Please modify lv_conf.h

Part of the screen appears

  • Check if the address function is correct
  • Check whether the sys_config.fex screen configuration resolution is correct