Following up to the blog LCD demo 320×240 @ nRF52840, I would like to describe how to use the QSPI interface to drive the bigger LCD display such as 360 x 360 resolution @ nRF52840.

QSPI Quad Serial Peripheral Interface

The QSPI peripheral provides support for communicating with an external flash memory device using SPI.


Listed here are the main features for the QSPI peripheral:
• Single/dual/quad SPI input/output
• 2–32 MHz configurable clock frequency
• Single-word read/write access from/to external flash
• EasyDMA for block read and write transfers
• Up to 16 MB/sec EasyDMA read rate
• Execute in place (XIP) for executing program directly from external flash

On the Nordic nRF52/nRF53 Series, the QSPI can support on two difference modes.

  1. Flash / XIP operation (It needs to follow the flash protocol)
  2. Sending Custom instructions (it limits to use the 1-wire on the QSPI for data sending).

On the instruction Set, it supports the Opcode with external flash device.

QSPI interface for the LCD

Basically, there are 6 GPIO pins for the QSPI interface.

In this blog, I would use the display manufacturer hannstar 360×360 with GalaxyCore Controller resolution for testing.

Initialize the QSPI Module

Configure all the 6 GPIO pins as the high drive output.

#define LCD_QSPI_RESET_PIN NRF_GPIO_PIN_MAP(0,27)
#define LCD_QSPI_CSN_PIN   NRF_GPIO_PIN_MAP(0,26)
#define LCD_QSPI_SCK_PIN   NRF_GPIO_PIN_MAP(0,02)
#define LCD_QSPI_IO0_PIN   NRF_GPIO_PIN_MAP(1,15)
#define LCD_QSPI_IO1_PIN   NRF_GPIO_PIN_MAP(1,14)
#define LCD_QSPI_IO2_PIN   NRF_GPIO_PIN_MAP(1,13)
#define LCD_QSPI_IO3_PIN   NRF_GPIO_PIN_MAP(1,12)

static void config_qspi_pin_high_drive(void)
{
        nrf_gpio_cfg(
                LCD_QSPI_SCK_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_CONNECT,
                NRF_GPIO_PIN_PULLDOWN,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_SENSE_HIGH);
        nrf_gpio_cfg(
                LCD_QSPI_CSN_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
                NRF_GPIO_PIN_NOPULL,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_NOSENSE);

        nrf_gpio_cfg(
                LCD_QSPI_IO0_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
                NRF_GPIO_PIN_PULLDOWN,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_NOSENSE);

        nrf_gpio_cfg(
                LCD_QSPI_IO1_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
                NRF_GPIO_PIN_PULLDOWN,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_NOSENSE);

        nrf_gpio_cfg(
                LCD_QSPI_IO2_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
                NRF_GPIO_PIN_PULLDOWN,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_NOSENSE);

        nrf_gpio_cfg(
                LCD_QSPI_IO3_PIN,
                NRF_GPIO_PIN_DIR_INPUT,
                NRF_GPIO_PIN_INPUT_DISCONNECT,
                NRF_GPIO_PIN_PULLDOWN,
                NRF_GPIO_PIN_H0H1,
                NRF_GPIO_PIN_NOSENSE);
}

Initialize the QSPI module and configure it as 24 bits address/32MHz clock.

void qspi_lcd_GC9c01_init(void)
{
        if (!m_is_qspi_init)
        {
                uint32_t err_code;
                nrf_drv_qspi_config_t qspi_lcd_config = NRF_DRV_QSPI_DEFAULT_CONFIG;
                //config.phy_if.sck_freq  = NRF_QSPI_FREQ_32MDIV1;        //NRF_QSPI_FREQ_32MDIV8; //32MHz
                qspi_lcd_config.phy_if.sck_freq  = NRF_QSPI_FREQ_32MDIV1;        //NRF_QSPI_FREQ_32MDIV8; //32MHz
                //qspi_lcd_config.phy_if.sck_freq  = NRF_QSPI_FREQ_32MDIV8;//NRF_QSPI_FREQ_32MDIV8; //8MHz

                qspi_lcd_config.pins.csn_pin     = LCD_QSPI_CSN_PIN;
                qspi_lcd_config.pins.sck_pin     = LCD_QSPI_SCK_PIN;
                qspi_lcd_config.pins.io0_pin     = LCD_QSPI_IO0_PIN;
                qspi_lcd_config.pins.io1_pin     = LCD_QSPI_IO1_PIN;
                qspi_lcd_config.pins.io2_pin     = LCD_QSPI_IO2_PIN;
                qspi_lcd_config.pins.io3_pin     = LCD_QSPI_IO3_PIN;

                err_code = nrf_drv_qspi_init(&qspi_lcd_config, qspi_lcd_handler, NULL);
                APP_ERROR_CHECK(err_code);

                NRF_LOG_INFO("QSPI LCD Init");
                NRF_QSPI->IFCONFIG0 |= (QSPI_IFCONFIG0_PPSIZE_512Bytes << QSPI_IFCONFIG0_PPSIZE_Pos);

                m_is_qspi_init = true;
        }
        else
        {
                NRF_LOG_ERROR("QSPI LCD has already initialized!");
                APP_ERROR_CHECK(-1);
        }
}

Basically, there are 2 operations on the LCD.

  1. LCD Command Code (such as the LCD init code).
  2. LCD Data write (write the data on the LCD screen).

LCD Command Mode

LCD Write Command

static uint32_t drv_GC9c01_bus_writeCmd(const uint8_t cmd)
{
        return send_qspi_cinstr_w0d(cmd);
}

static uint32_t drv_GC9c01_bus_writeCmdByte(const uint8_t cmd, const uint8_t data)
{
        uint32_t err_code = NRF_SUCCESS;
        err_code  = send_qspi_cinstr_w1d(cmd, data);
        return err_code;
}

 

LCD initialize code

static void configure_lcd()
{
        drv_GC9c01_bus_writeCmd(0xfe);
        drv_GC9c01_bus_writeCmd(0xef);

        drv_GC9c01_bus_writeCmdByte(0x80, 0x11);
        drv_GC9c01_bus_writeCmdByte(0x81, 0x70);

        drv_GC9c01_bus_writeCmdByte(0x82, 0x09);
        drv_GC9c01_bus_writeCmdByte(0x83, 0x03);
        
        drv_GC9c01_bus_writeCmdByte(0x84, 0x20);
        drv_GC9c01_bus_writeCmdByte(0x85, 0x42);

        drv_GC9c01_bus_writeCmdByte(0x86, 0xfc);
        drv_GC9c01_bus_writeCmdByte(0x87, 0x09);

        drv_GC9c01_bus_writeCmdByte(0x89, 0x10);
        drv_GC9c01_bus_writeCmdByte(0x8a, 0x4f);

        drv_GC9c01_bus_writeCmdByte(0x8c, 0x59);
        drv_GC9c01_bus_writeCmdByte(0x8d, 0x51);
        
        drv_GC9c01_bus_writeCmdByte(0x8e, 0xae);
        drv_GC9c01_bus_writeCmdByte(0x8f, 0xf3);

        drv_GC9c01_bus_writeCmdByte(0x36, 0x00);
        drv_GC9c01_bus_writeCmdByte(0x3a, 0x05);
        drv_GC9c01_bus_writeCmdByte(0xec, 0x77);

        uint8_t parmater1_buffer[] = {0x01, 0x80, 0x00, 0x00, 0x00, 0x00};
        send_qspi_cinstr_long_frame_word(0x74, parmater1_buffer, 6);
        drv_GC9c01_bus_writeCmdByte(0x98, 0x3e);
        drv_GC9c01_bus_writeCmdByte(0x99, 0x3e);
        drv_GC9c01_bus_writeCmdByte(0xc3, 0x2A);
        drv_GC9c01_bus_writeCmdByte(0xc4, 0x18);
        send_qspi_cinstr_w1d_2data(0xa1,0x01,0x04);
        send_qspi_cinstr_w1d_2data(0xa2,0x01,0x04);
        drv_GC9c01_bus_writeCmdByte(0xa9, 0x1c);

        send_qspi_cinstr_w1d_2data(0xa5,0x11,0x09);
        drv_GC9c01_bus_writeCmdByte(0xb9, 0x8a);
        drv_GC9c01_bus_writeCmdByte(0xa8, 0x5e);
        drv_GC9c01_bus_writeCmdByte(0xa7, 0x40);
        drv_GC9c01_bus_writeCmdByte(0xaf, 0x73);
        drv_GC9c01_bus_writeCmdByte(0xae, 0x44);
        drv_GC9c01_bus_writeCmdByte(0xad, 0x38);

        drv_GC9c01_bus_writeCmdByte(0xa3, 0x5d);

        drv_GC9c01_bus_writeCmdByte(0xc2, 0x02);

        drv_GC9c01_bus_writeCmdByte(0xc5, 0x11);


        drv_GC9c01_bus_writeCmdByte(0xc6, 0x0e);

        drv_GC9c01_bus_writeCmdByte(0xc7, 0x13);
        drv_GC9c01_bus_writeCmdByte(0xc8, 0x0d);
        drv_GC9c01_bus_writeCmdByte(0xcb, 0x02);

        send_qspi_cinstr_w1d_2data(0x7c,0xb6,0x26);
        drv_GC9c01_bus_writeCmdByte(0xac, 0x24);
        drv_GC9c01_bus_writeCmdByte(0xf6, 0x80);



        send_qspi_cinstr_w1d_2data(0xb5,0x09,0x09);
        send_qspi_cinstr_w1d_4data(0x60, 0x38,0x0b,0x5b,0x56);
        send_qspi_cinstr_w1d_4data(0x63, 0x3a,0xe0,0x5b,0x56);

        uint8_t parmater2_buffer[] = {0x38, 0x0d, 0x72, 0xdd, 0x5b, 0x56};
        send_qspi_cinstr_long_frame_word(0x64, parmater2_buffer, 6);
        uint8_t parmater3_buffer[] = {0x38, 0x11, 0x72, 0xe1, 0x5b, 0x56};
        send_qspi_cinstr_long_frame_word(0x66, parmater3_buffer, 6);
        uint8_t parmater4_buffer[] = {0x3b, 0x08, 0x08, 0x00, 0x08, 0x29,0x5b};
        send_qspi_cinstr_long_frame_word(0x68, parmater4_buffer, 7);
        uint8_t parmater5_buffer[] = {0x00,0x00,0x00,0x07,0x01,0x13,0x11,0x0b, \
                                      0x09,0x16,0x15,0x1d,0x1e,0x00,0x00,0x00, \
                                      0x00,0x00,0x00,0x1e,0x1d,0x15,0x16,0x0a, \
                                      0x0c,0x12,0x14,0x02,0x08,0x00,0x00,0x00};
        send_qspi_cinstr_long_frame_word(0x6e, parmater5_buffer, 32);

        drv_GC9c01_bus_writeCmdByte(0xbe, 0x11);
        uint8_t parmater6_buffer[] = {0xcc, 0x0c, 0xcc, 0x84, 0xcc, 0x04,0x50};
        send_qspi_cinstr_long_frame_word(0x6c, parmater6_buffer, 7);
        drv_GC9c01_bus_writeCmdByte(0x7d, 0x72);
        drv_GC9c01_bus_writeCmdByte(0x7e, 0x38);
        uint8_t parmater7_buffer[] = {0x02,0x03,0x09,0x05,0x0c,0x06,0x09,0x05,0x0c,0x06};
        send_qspi_cinstr_long_frame_word(0x70, parmater7_buffer, 10);
        send_qspi_cinstr_w1d_4data(0x90, 0x06,0x06,0x05,0x06);
        send_qspi_cinstr_w1d_3data(0x93, 0x45,0xff,0x00);

        uint8_t parmater8_buffer[] = {0x45, 0x09, 0x08, 0x08, 0x26, 0x2a};
        send_qspi_cinstr_long_frame_word(0xf0, parmater8_buffer, 6);
        uint8_t parmater9_buffer[] = {0x43, 0x70, 0x72, 0x36, 0x37, 0x6f};
        send_qspi_cinstr_long_frame_word(0xf1, parmater9_buffer, 6);
        uint8_t parmater10_buffer[] = {0x45, 0x09, 0x08, 0x08, 0x26, 0x2a};
        send_qspi_cinstr_long_frame_word(0xf2, parmater10_buffer, 6);
        uint8_t parmater11_buffer[] = {0x43, 0x70, 0x72, 0x36, 0x37, 0x6f};
        send_qspi_cinstr_long_frame_word(0xf3, parmater11_buffer, 6);
        drv_GC9c01_bus_writeCmd(0xfe);
        drv_GC9c01_bus_writeCmd(0xee);


        // User Command Set (UCS = CMD1)------------------------------------------
        //       drv_GC9c01_bus_writeCmdByte(GC9c01_CMD_WRITE_CMD_MODE_PAGE, 0x00);

        //tearing effect line on--------------------------------------------------
        //       drv_GC9c01_bus_writeCmdByte(GC9c01_CMD_TEARING_EFFECT_ON, 0x00);

        // Interface Pixel Format-------------------------------------------------
        //       drv_GC9c01_bus_writeCmdByte(GC9c01_CMD_INTERFACE_PIXEL_FORMAT, 0x55);// 0x55 -> 565

        // Set_DSPI Mode----------------------------------------------------------
        //       drv_GC9c01_bus_writeCmdByte(GC9c01_CMD_SET_DSPI_MODE, 0x80);

        // Write display brightness-----------------------------------------------
        //GC9c01_drv_SetBrightness(0xCC);


        // Sleep-out and Display on-----------------------------------------------
        GC9c01_DRV_SLEEP_OUT();

        nrf_delay_ms(120);

        GC9c01_DRV_DISPLAY_ON();

        nrf_delay_ms(20);

        GC9c01_drv_setColRowPosition(0, 0);

        NRF_LOG_INFO("After %s", __func__);
}

Write LCD Data

It uses the Opcode = 0x32 PP4O for the writing LCD data.

static uint32_t gc9c01_bus_lcd_write_buffer(uint8_t *p_tx_buffer, uint32_t len, uint32_t addr)
{
        uint32_t err_code = 0;
        m_finished = false;
        err_code = nrf_drv_qspi_write(p_tx_buffer, len, addr);
        if(err_code != NRFX_SUCCESS)
        {
                // NRF_LOG_INFO("error code= 0x%x",err_code);
        }
        WAIT_FOR_PERIPH();
        return err_code;
}

static void BMP_picture_show(uint32_t delay, const uint16_t *p_data)
{
        uint32_t err_code;
        uint32_t * p_color;
        uint32_t i,k;
        uint32_t tran_size,now_size=0;
        uint32_t remain_size = GC9c01_DRV_ALL_SIZE;

        //NRF_LOG_INFO("%s block_dx = %d, Color=%04x", __func__, a block_dx, color);
        //static uint8_t __ALIGN(4) bank_buf[QSPI_PAG_SIZE];

        tran_size = QSPI_PAG_SIZE;
        memcpy(&m_buffer_tx[0], (uint8_t *)&p_data[0]+tran_size, tran_size);
        err_code = gc9c01_bus_lcd_write_buffer(m_buffer_tx, QSPI_PAG_SIZE, 0x002C00);

        remain_size -= tran_size;
        now_size=tran_size;
        while (remain_size)
        {
                if (remain_size > QSPI_PAG_SIZE)
                {
                        tran_size = QSPI_PAG_SIZE;
                }
                else
                {
                        tran_size = remain_size;
                }

                memcpy(m_buffer_tx, (uint8_t *)&p_data[0]+now_size, tran_size);

                now_size += tran_size;
                err_code = gc9c01_bus_lcd_write_buffer(&m_buffer_tx[0], tran_size, 0x003C00);

                remain_size -= tran_size;
        }

        nrf_delay_ms(delay);
}

Video Demo on the QSPI @ nRF52840