Porting the st7789 screen driver to Allwinner XR806

Foreword

I am very glad to have the opportunity to participate in the “Free Trial” Allwinner XR806 Wi-Fi + BLE development board trial event equipped with ARM Technology STAR-MC1 held by Jishu Community.
I became interested in Quanzhi’s MCU chips last year, but I never had the opportunity to get in touch with them. When I saw the Quanzhi wifi + BLE development board trial provided by the Jishu community, I immediately participated. After getting the board, I quickly set up the environment. Due to my own schedule, I never had time to do it, so I hurriedly did it in the past two days.

SDK download and environment construction
git clone https://sdk.aw-ol.com/git_repo/XR806/xr806_sdk/xr806_sdk.git -b xr806_sdk

If prompted Username for https://sdk.aw-ol.com’: Please enter the username and password of Allwinner Online Developer Forum. (Note: Only users with level LV2 or above in Quanzhi Online Developer Forum have the permission to pull the SDK. Just register an account and you’re done)

Since SDKs are generally large, pulling may take some time.

Next, install the environment dependencies (I use Buildroot’s docker container and have already installed it, so there is no need to do it again)

sudo apt-get install build-essential subversion git libncurses5-dev zlib1g-dev gawk flex bison quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip lsof kconfig-frontends android-tools-mkbootimg python2 libpython3-dev gcc-multilib libc6:i386 libstdc++6:i386 lib32z1

Then configure the tool chain, directly download the gcc-arm-none-eabi-8-2019-q3-update-linux.tar.bz2 compressed package and extract it to the ~/.bin directory, and modify the gcc.mk file

# ----------------------------------------------- ----------------------------------
# cross compiler
#------------------------------------------------ --------------------------
CC_DIR := /home/vuser/.bin/gcc-arm-8.3/bin/
CC_PREFIX := ccache $(CC_DIR)/arm-none-eabi-

This is all configured. After looking at the project/example directory, there is a spi project that is suitable for modification. This is it.

Porting the st7789 driver

ST7789 is a highly integrated color TFT LCD display controller chip, usually used to drive small to medium-sized LCD screens. For example, 1.4-inch, 1.47-inch, 1.69-inch screens, etc. are common on Taobao.
Let’s start now, first create new st7789.c and st7789.h files. Then create a command sequence list for initializing the st7789 chip.

static lcd_init_cmd_t st7789_init_cmds[] = {
    {0x01, {0}, 0x80, 120},
    /* Sleep Out */
    {0x11, {0}, 0x80, 120},
    /* Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 */
    {0x36, {0x00}, 1},
    /* Interface Pixel Format, 16bits/pixel for RGB/MCU interface */
    {0x3A, {0x05}, 1},
#if 0
      {0x30, {0x00,0x50,0x01,0x3F}, 4},
      {0x12, {0x00}, 0},
#endif
    /* Porch Setting */
    {0xB2, {0x0c, 0x0c, 0x00, 0x33, 0x33}, 5},
    /* Gate Control, Vgh=13.65V, Vgl=-10.43V */
    {0xB7, {0x35}, 1},
    /* VCOM Setting, VCOM=1.35V */
    {0xBB, {0x32}, 1},
    // /* LCM Control, XOR: BGR, MX, MH */
    // {0xC0, {0x2C}, 1},
    /* VDV and VRH Command Enable, enable=1 */
    {0xC2, {0x01, 0xFF}, 2},
    /* VRH Set, Vap=4.4 + ... */
    {0xC3, {0x15}, 1},
    /* VDV Set, VDV=0 */
    {0xC4, {0x20}, 1},
    /* Frame Rate Control, 60Hz, inversion=0 */
    {0xC6, {0x0F}, 1},
    /* Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V */
    {0xD0, {0xA4, 0xA1}, 1},
    /* Positive Voltage Gamma Control */
    {0xE0,
     {0xD0, 0x08, 0x0E, 0x09, 0x09, 0x05, 0x31, 0x33, 0x48, 0x17, 0x14, 0x15,
      0x31, 0x34},
     14},
    /* Negative Voltage Gamma Control */
    {0xE1,
     {0xD0, 0x08, 0x0E, 0x09, 0x09, 0x15, 0x31, 0x33, 0x48, 0x17, 0x14, 0x15,
      0x31, 0x34},
     14},
    /* Display On */
    {0x21, {0}, 0},
    {0x29, {0}, 0},
    {0, {0}, 0xff}};

This sequence list uses this data structure

/*The LCD needs a bunch of command/argument values to be initialized. They are
 * stored in this struct. */
typedef struct {
  # directive
  uint8_t cmd;
  # data
  uint8_t data[16];
  # Data length and type. Generally, the initialization data is not very long, and the used part is used for other purposes.
  # For example, 0x80 represents the need for delay. The delay time is specified by delaytime, and 0xFF represents the end.
  uint8_t databytes; // No of data in data; bit 7 = delay after set; 0xFF =
                     // end of cmds.
  uint8_t delaytime; // delaytime
} lcd_init_cmd_t;

Then write the initialization function, where the initialization of gpio and spi is placed.

 printf("ST7789 initialization.\\
");

  int ret = dirver_spi_init();
  if (ret != 0) {
    printf("SPI dev init fail!\\
");
  }

  gpio_init(disp_pin_dc);
  gpio_init(disp_pin_rst);
  gpio_init(disp_pin_bckl);

  HAL_SPI_CS(DEMO_SPI_PORT, 1);
  //Reset the displayc
  gpio_set_level(disp_pin_rst, 1);
  OS_MSleep(1);
  gpio_set_level(disp_pin_rst, 0);
  OS_MSleep(100);
  gpio_set_level(disp_pin_rst, 1);
  OS_MSleep(100);

  st7789_enable_backlight(true);
  OS_MSleep(100);

  // Send all the commands
  uint16_t cmd = 0;
  while (st7789_init_cmds[cmd].databytes != 0xff) {
    printf("Send command 0x x\\
", st7789_init_cmds[cmd].cmd);
    st7789_send_cmd(st7789_init_cmds[cmd].cmd);
    if ((st7789_init_cmds[cmd].databytes & amp; 0x1F) != 0) {
      st7789_send_data(st7789_init_cmds[cmd].data,
                       st7789_init_cmds[cmd].databytes & 0x1F);
    }
    if (st7789_init_cmds[cmd].databytes & amp; 0x80) {
      OS_MSleep(st7789_init_cmds[cmd].delaytime);
    }
    cmd + + ;
  }
  printf("init finish.\\
");
  st7789_set_orientation(DISPLAY_ORIENTATION);

The hardware connection is as shown in the figure

Screen Development Board
BLK B14
CS B06
DC B03
RES VCC
SDA B04
SCL B07

Why is the RES pin directly connected to VCC? Because I don’t know if it is a problem with this chip or something else. After the RES pin is connected to the push-pull output IO pin, the screen can also light up, but the brightness is inexplicably low. I have tested it on several screens in my hand and they are all the same.

Then write the functions for writing commands and writing data. When writing commands, you need to set the DC pin, and then switch the DC pin back to high level immediately after writing.

static void st7789_send_cmd(uint8_t cmd) {
  gpio_set_level(disp_pin_dc, 0);
  dirver_spi_send_data( & amp;cmd, 1);
  gpio_set_level(disp_pin_dc, 1);
}
static void st7789_send_data(void *data, uint16_t length) {
  dirver_spi_send_data(data, length);
}

The next step is to write the screen flip configuration function

static void st7789_set_orientation(uint8_t orientation) {
  // ESP_ASSERT(orientation < 4);

  const char *orientation_str[] = {"PORTRAIT", "PORTRAIT_INVERTED", "LANDSCAPE",
                                   "LANDSCAPE_INVERTED"};

  printf("Display orientation: %s\\
", orientation_str[orientation]);

  uint8_t data[] = {0xC0, 0x00, 0x60, 0xA0};

  printf("0x36 command value: 0x X\\
", data[orientation]);

  st7789_send_cmd(ST7789_MADCTL);
  st7789_send_data((void *) & amp;data[orientation], 1);
}

Finally, just write the screen writing function. In order to quickly refresh the screen, a relatively large buffer area is set up. I don’t know how to use the XR806’s DMA yet, but I’ve learned how to reduce the size of the cache RAM.

/* The ST7789 display controller can drive 320*240 displays, when using a
 * 240*240 display there's a gap of 80px, we need to edit the coordinates to
 * take into account that gap, this is not necessary in all orientations. */
void st7789_flush(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2,
                  uint16_t color) {
  uint8_t data[4] = {0};

  uint16_t offsetx1 = x1;
  uint16_t offsetx2 = x2;
  uint16_t offsety1 = y1;
  uint16_t offsety2 = y2;

#if (TFT_DISPLAY_OFFSETS)
  offsetx1 + = TFT_DISPLAY_X_OFFSET;
  offsetx2 + = TFT_DISPLAY_X_OFFSET;
  offsety1 + = TFT_DISPLAY_Y_OFFSET;
  offsety2 + = TFT_DISPLAY_Y_OFFSET;

#elif (LV_HOR_RES_MAX == 320) & amp; & amp; (LV_VER_RES_MAX == 320)
#if (DISPLAY_ORIENTATION_PORTRAIT)
  offsetx1 + = 80;
  offsetx2 + = 80;
#elif (DISPLAY_ORIENTATION_LANDSCAPE_INVERTED)
  offsety1 + = 80;
  offsety2 + = 80;
#endif
#endif

  /*Column addresses*/
  st7789_send_cmd(ST7789_CASET);
  data[0] = (offsetx1 >> 8) & amp; 0xFF;
  data[1] = offsetx1 & 0xFF;
  data[2] = (offsetx2 >> 8) & amp; 0xFF;
  data[3] = offsetx2 & 0xFF;
  st7789_send_data(data, 4);

  /*Page addresses*/
  st7789_send_cmd(ST7789_RASET);
  data[0] = (offsety1 >> 8) & amp; 0xFF;
  data[1] = offsety1 & 0xFF;
  data[2] = (offsety2 >> 8) & amp; 0xFF;
  data[3] = offsety2 & 0xFF;
  st7789_send_data(data, 4);

  /*Display On*/
  st7789_send_cmd(ST7789_DISPON);
  /*Memory write*/
  st7789_send_cmd(ST7789_RAMWR);

  uint32_t size = (y2 - y1) * (x2 - x1);

  uint32_t buffsize = (x2 - x1) * 80;
  unsigned char *burst_buffer = (unsigned char *)malloc(buffsize * 2);

  for (uint32_t i = 0; i < size + buffsize; i + = buffsize) {
    for (uint32_t j = 0; j < buffsize; j + + ) {
      burst_buffer[2 * j] = color >> 8;
      burst_buffer[2 * j + 1] = color;
    }
    st7789_send_data(burst_buffer, buffsize * 2);
  }

  free(burst_buffer);
}

You also need to add a screen refresh function as a test, let’s add it now.
Since the 1.69-inch screen does not need to set the screen window offset, just press the full screen to refresh.

void lcd_clear(uint16_t color) { st7789_flush(0, 240, 0, 320, color); }

Then just call the screen initialization and refresh functions in main.c.

#include "common/framework/platform_init.h"
#include "kernel/os/os.h"
#include <stdio.h>

extern void st7789_init();
extern void st7789_flush(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2,
                         uint16_t color);
extern void lcd_clear(uint16_t color);

int main(void) {

  platform_init();

  printf("gpio demo started.\\
");

  st7789_init();
  printf("flush color.\\
");
  // st7789_flush(0, 240, 0, 280, 0xFFFF);

  while (1) {
    lcd_clear(0x0000);
    OS_MSleep(1000);
    lcd_clear(0xFFFF);
    OS_MSleep(1000);
    lcd_clear(0xEF5D);
    OS_MSleep(1000);
    lcd_clear(0xF800);
    OS_MSleep(1000);
    lcd_clear(0x07E0);
    OS_MSleep(1000);
    lcd_clear(0x001F);
    OS_MSleep(1000);
  }

  printf("never run here.\\
");
  return 0;
}

# Clear errors
void main_cmd_exec(char *cmd) {}

The effect of refreshing the screen is as shown in the figure

After testing, both the 1.47-inch screen and the 1.69-inch st7789 screen on hand can be driven normally.
It’s just that the offset value and screen resolution settings need to be optimized again. Let’s talk about it in a few days.
The detailed code can be downloaded at the end of the article and placed in the example directory.