Display · Drivers · EasyDMA · LCD Display · NRF52840 · NRF52840 DK board · PPI · QSPI · SPI · TWIM

LCD Demo 320×240 RGB(565) on nRF52840

This blog is to provide a video demo how fast the Nordic nRF52840 works with LCD display. The idea is to regular poll the touch sensor data and then load the image from QSPI flashto update the LCD screen through high speed SPIM3.

I used the Adafruit 1947 board ( ARDUINO / SHIELDS / TFTS & DISPLAYS / 2.8″ TFT TOUCH SHIELD FOR ARDUINO W/CAPACITIVE TOUCH) and connect to the nRF52840 DK board.


The nRF52840 SoC is the most advanced member of the nRF52 Series SoC family. It meets the challenges of sophisticated applications that need protocol concurrency and a rich and varied set of peripherals and features.  It offers generous memory availability for both Flash and RAM, which are prerequisites for such demanding applications.

The nRF52840 is built around the 32-bit ARM® Cortex™-M4 CPU with floating point unit running at 64 MHz. It has NFC-A Tag for use in simplified pairing and payment solutions. The ARM TrustZone® CryptoCell cryptographic unit is included on-chip and brings an extensive range of cryptographic options that execute highly efficiently independent of the CPU. It has numerous digital peripherals and interfaces such as high speed SPI and QSPI for interfacing to external flash and displays, PDM and I2S for digital microphones and audio, and a full speed USB device for data transfer and power supply for battery recharging.

Exceptionally low energy consumption is achieved using a sophisticated on-chip adaptive power management system.

LCD Display

I used the Adafruit 1947 (240 x 320 resolution) with SPI interface.


  • 240×320 resolution, 18-bit (262,000) color – our library uses 16 bit color mode
  • High speed SPI display with digital I2C touchscreen driver
  • The display uses digital pins 13-9. Touchscreen controller requires I2C pins SDA and SCL. microSD pin requires digital #4. That means you can use digital pins 2, 3, 5, 6, 7, 8 and analog 0-5. Pin 4 is available if not using the microSD
  • Works with any classic Arduino ‘328. Solder closed three jumpers to use the ICSP header for use with Leonardo or Mega
  • Onboard 3.3V @ 300mA LDO regulator, current draw depends on usage but is about 100mA for the display and touchscreen
  • 4 white LED backlight. On by default but you can connect the transistor to a digital pin for backlight control
  • Single-touch capacitive touch bonded on top


By using the High Speed SPI to draw the LCD and also reading the image from on board QSPI flash,

Loading image from QSPI and drive the LCD through fast SPI .

The idea is to use the

  • Touch Sensor Data — TWIM with DMA
  • Image data — QSPI with DMA
  • LCD Display interface — High Speed SPIM with DMA
Block diagram on the nRF52840

By using the nRF52840 SPI3, it can support to up 32Mbps SPI bus.

Sample Code

Step 1: Initialize the ili9341 display

void lcd_ili9341_init(void)
        nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
        spi_config.frequency      = NRF_SPIM_FREQ_32M;
        spi_config.mode           = NRF_SPIM_MODE_3;
        spi_config.ss_pin         = LCD_CS;
        spi_config.miso_pin       = LCD_MISO;
        spi_config.mosi_pin       = LCD_MOSI;
        spi_config.sck_pin        = LCD_SCK;
        spi_config.dcx_pin        = LCD_D_C;
        spi_config.use_hw_ss      = true;
        spi_config.ss_active_high = false;
        spi_config.ss_duration    = 8;
        APP_ERROR_CHECK(nrfx_spim_init(&spi, &spi_config, NULL, NULL));

        // set SCK to high drive

        // set MOSI to high drive

        // send LCD init sequence

Step 2: LCD initialize code

typedef struct {
        uint8_t op_type;
        size_t op_size;
        const uint8_t       *p_data;
} lcd_op_t;

static const uint8_t lcd_init_ILI9341_SWRESET[] = { ILI9341_SWRESET };
static const uint8_t lcd_init_ILI9341_DISPOFF[] = { ILI9341_DISPOFF };
static const uint8_t lcd_init_ILI9341_PWCTRB[]  = { ILI9341_PWCTRB, 0x00, 0xC1, 0x30 };
static const uint8_t lcd_init_ILI9341_TIMCTRA[] = { ILI9341_TIMCTRA, 0x85, 0x00, 0x78 };
static const uint8_t lcd_init_ILI9341_PWCTRSEQ[] = { ILI9341_PWCTRSEQ, 0x39, 0x2C, 0x00, 0x34, 0x02 };
static const uint8_t lcd_init_ILI9341_PUMP[] = { ILI9341_PUMP, 0x20 };
static const uint8_t lcd_init_ILI9341_TIMCTRB[] = { ILI9341_TIMCTRB, 0x00, 0x00};
static const uint8_t lcd_init_ILI9341_PWCTR1[] = { ILI9341_PWCTR1, 0x23 };
static const uint8_t lcd_init_ILI9341_PWCTR2[] = { ILI9341_PWCTR2, 0x10 };
static const uint8_t lcd_init_ILI9341_VMCTR1[] = { ILI9341_VMCTR1, 0x3E, 0x28 };
static const uint8_t lcd_init_ILI9341_VMCTR2[] = { ILI9341_VMCTR2, 0x86 };
static const uint8_t lcd_init_ILI9341_MADCTL[] = { ILI9341_MADCTL, 0x48 };
static const uint8_t lcd_init_ILI9341_PIXFMT[] = { ILI9341_PIXFMT, 0x55 };
static const uint8_t lcd_init_ILI9341_FRMCTR1[] = { ILI9341_FRMCTR1, 0x00, 0x18 };
static const uint8_t lcd_init_ILI9341_DFUNCTR[] = { ILI9341_DFUNCTR, 0x08, 0x82, 0x27 };
static const uint8_t lcd_init_ILI9341_ENGMCTR[] = { ILI9341_ENGMCTR, 0x00 };
static const uint8_t lcd_init_ILI9341_GAMMASET[] = { ILI9341_GAMMASET, 0x01 };
static const uint8_t lcd_init_ILI9341_GMCTRP1[] = { ILI9341_GMCTRP1, \
                                                    0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, \
                                                    0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,};
static const uint8_t lcd_init_ILI9341_GMCTRN1[] = { ILI9341_GMCTRN1, \
                                                    0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1,     \
                                                    0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,};
static const uint8_t lcd_init_ILI9341_SLPOUT[] = { ILI9341_SLPOUT };
static const uint8_t lcd_init_ILI9341_DISPON[] = { ILI9341_DISPON };

static const lcd_op_t lcd_init_ili9341_seq[] =
        {OP_CMD, sizeof(lcd_init_ILI9341_SWRESET ), lcd_init_ILI9341_SWRESET },
        {OP_DELAY, 120,                 NULL       },
        {OP_CMD, sizeof(lcd_init_ILI9341_DISPOFF ), lcd_init_ILI9341_DISPOFF },
        {OP_DELAY, 120,                 NULL       },
        {OP_CMD, sizeof(lcd_init_ILI9341_PWCTRB  ), lcd_init_ILI9341_PWCTRB  },
        {OP_CMD, sizeof(lcd_init_ILI9341_TIMCTRA ), lcd_init_ILI9341_TIMCTRA },
        {OP_CMD, sizeof(lcd_init_ILI9341_PWCTRSEQ), lcd_init_ILI9341_PWCTRSEQ},
        {OP_CMD, sizeof(lcd_init_ILI9341_PUMP    ), lcd_init_ILI9341_PUMP    },
        {OP_CMD, sizeof(lcd_init_ILI9341_TIMCTRB ), lcd_init_ILI9341_TIMCTRB },
        {OP_CMD, sizeof(lcd_init_ILI9341_PWCTR1  ), lcd_init_ILI9341_PWCTR1  },
        {OP_CMD, sizeof(lcd_init_ILI9341_PWCTR2  ), lcd_init_ILI9341_PWCTR2  },
        {OP_CMD, sizeof(lcd_init_ILI9341_VMCTR1  ), lcd_init_ILI9341_VMCTR1  },
        {OP_CMD, sizeof(lcd_init_ILI9341_VMCTR2  ), lcd_init_ILI9341_VMCTR2  },
        {OP_CMD, sizeof(lcd_init_ILI9341_MADCTL  ), lcd_init_ILI9341_MADCTL  },
        {OP_CMD, sizeof(lcd_init_ILI9341_PIXFMT  ), lcd_init_ILI9341_PIXFMT  },
        {OP_CMD, sizeof(lcd_init_ILI9341_FRMCTR1 ), lcd_init_ILI9341_FRMCTR1 },
        {OP_CMD, sizeof(lcd_init_ILI9341_DFUNCTR ), lcd_init_ILI9341_DFUNCTR },
        {OP_CMD, sizeof(lcd_init_ILI9341_ENGMCTR ), lcd_init_ILI9341_ENGMCTR },
        {OP_CMD, sizeof(lcd_init_ILI9341_GAMMASET), lcd_init_ILI9341_GAMMASET},
        {OP_CMD, sizeof(lcd_init_ILI9341_GMCTRP1 ), lcd_init_ILI9341_GMCTRP1 },
        {OP_CMD, sizeof(lcd_init_ILI9341_GMCTRN1 ), lcd_init_ILI9341_GMCTRN1 },
        {OP_CMD, sizeof(lcd_init_ILI9341_SLPOUT  ), lcd_init_ILI9341_SLPOUT  },
        {OP_DELAY, 120,                 NULL       },
        {OP_CMD, sizeof(lcd_init_ILI9341_DISPON  ), lcd_init_ILI9341_DISPON  },

void lcd_ILI9341_proc_op_list(const lcd_op_t *p_op_list)
        nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TX(spi_tx_buff, p_op_list->op_size);//1);

        while (p_op_list->op_type != OP_NULL)
                if (p_op_list->op_type == OP_CMD)
                        // send LCD command
                        memcpy(spi_tx_buff, p_op_list->p_data, p_op_list->op_size);

                        xfer_desc.tx_length = p_op_list->op_size;
                        nrfx_err_t err_code = nrfx_spim_xfer_dcx(&spi, &xfer_desc, 0, 1);
                else if (p_op_list->op_type == OP_DELAY)
                        // wait awhile
                        // something goes wrong!

Step 3:

static void lcd_ILI9341_set_window(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
        uint8_t tx_buff[5];
        nrfx_err_t err_code;
        nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TX(tx_buff, 5);

        tx_buff[0] = 0x2A;
        tx_buff[1] = ( x          >> 8) & 0xFF;
        tx_buff[2] = ( x              ) & 0xFF;
        tx_buff[3] = ((x + w - 1) >> 8) & 0xFF;
        tx_buff[4] = ((x + w - 1)     ) & 0xFF;
        err_code = nrfx_spim_xfer_dcx(&spi, &xfer_desc, 0, 1);

        tx_buff[0] = 0x2B;
        tx_buff[1] = ( y          >> 8) & 0xFF;
        tx_buff[2] = ( y              ) & 0xFF;
        tx_buff[3] = ((y + h - 1) >> 8) & 0xFF;
        tx_buff[4] = ((y + h - 1)     ) & 0xFF;
        err_code = nrfx_spim_xfer_dcx(&spi, &xfer_desc, 0, 1);

void lcd_ili9341_put_gfx(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t const *p_lcd_data)
        lcd_ILI9341_set_window(x, y, w, h);

        uint8_t tx_buff[1];
        nrfx_err_t err_code;
        nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TX(tx_buff, 1);

//        lcd_ili9341_display_invert(true);

        // send LCD command
        tx_buff[0] = ILI9341_RAMWR;
        err_code = nrfx_spim_xfer_dcx(&spi, &xfer_desc, 0, 1);


        uint32_t len = (uint32_t)w * (uint32_t)h * 2;
        uint32_t pos = 0, stp = 0;

        // send LCD data
        for (pos = 0; pos < len; pos += stp)
                //stp = MIN((len - pos), 65534);
                stp = MIN((len - pos), (0x10000-2));
                //NRF_LOG_INFO("%x %x %x", len, pos, stp);
                xfer_desc.tx_length = stp;
                xfer_desc.p_tx_buffer = p_lcd_data + pos;
                err_code = nrfx_spim_xfer_dcx(&spi, &xfer_desc, 0, 0);
//        lcd_ili9341_display_invert(false);

27 thoughts on “LCD Demo 320×240 RGB(565) on nRF52840

  1. I have started a project with nRF52840 and a display with ili9341 controller chip but it is very slow using the driver and GFX library that Nordic provides. Would you be willing to share the display driver you used that works with the SPIM3?


  2. Hi Jimmy
    I have a customer who encounters some problems when driving LCD with spim3. Can you share this code with me?


  3. I use NRF52840 drive ILI9341 TFT display screen, I use the screen driver you provide code, images in the display refresh can still see the scanning process, but the rate is not high enough, I may call the function wrong, can you provide me with the full display driver and QSPI FLASH PROJECT FILE? Thank you!


  4. Dear,

    I now also used the ST7789V driver display, in your driver code to change the corresponding register initialization parameters also found that the display is not good, you can see the display from the top down refresh of the display phenomenon, so please send me the whole NRF52840 project file for reference, thank you!


  5. Hi Jimmy,
    I am working with nRF52 series with ILI9341 2.4″ TFT color Display . and I used GFx library. When I used to show Image that i need to convert into HEX but the color in not worthy. and also i can’t able to load multiple images.
    my email id : sunilnehru026@gmail.com
    Can you please share your Driver and also example code so that it will helpful for me.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.