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.
nRF52840
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.
TECHNICAL DETAILS
- 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
https://www.adafruit.com/product/1947

By using the High Speed SPI to draw the LCD and also reading the image from on board QSPI flash,
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

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
nrf_gpio_cfg(
LCD_SCK,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_H0H1,
NRF_GPIO_PIN_NOSENSE);
// set MOSI to high drive
nrf_gpio_cfg(
LCD_MOSI,
NRF_GPIO_PIN_DIR_OUTPUT,
NRF_GPIO_PIN_INPUT_DISCONNECT,
NRF_GPIO_PIN_NOPULL,
NRF_GPIO_PIN_H0H1,
NRF_GPIO_PIN_NOSENSE);
// send LCD init sequence
lcd_ILI9341_proc_op_list(lcd_init_ili9341_seq);
}
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);
APP_ERROR_CHECK(err_code);
}
else if (p_op_list->op_type == OP_DELAY)
{
// wait awhile
nrf_delay_ms(p_op_list->op_size);
}
else
{
// something goes wrong!
break;
}
p_op_list++;
}
}
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);
APP_ERROR_CHECK(err_code);
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);
APP_ERROR_CHECK(err_code);
}
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);
APP_ERROR_CHECK(err_code);
//nrf_delay_ms(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);
APP_ERROR_CHECK(err_code);
}
// lcd_ili9341_display_invert(false);
}
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?
LikeLike
Please send the email to me. I will share it to you.
LikeLike
Thank you! My email is musicman.tc@gmail.com
LikeLike
Hi, I just started working with the display driver. would you mind sharing the driver?
LikeLike
Please leave your email to here. Also, I have put the LCD driver on the blog how to use the SPIM on the nRF52840.
LikeLike
Mr. jimmy, do you have the driver for CST816S in Windows for Touch pad. The vendor has linux driver, but we need windows driver. we are using NRF52840.
LikeLike
rahmat.dwiputra@gmail.com
LikeLike
Hello friends, please, Can you share your code wtih me, I also work with nRF52840.
LikeLike
Please check on your mail box. Just sent!!
LikeLike
Hi Jimmy
I have a customer who encounters some problems when driving LCD with spim3. Can you share this code with me?
LikeLike
I put some sample codes on the LCD init and update the screen for reference.
LikeLike
Hi Jimmy,
I am using nrf52840 to drive ili9486 lcd, but I have encountered a problem, can you share your code with me?
LikeLike
My email: imliubo@makingfun.xyz
LikeLike
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!
LikeLike
Dear,
Jimmy;
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!
LikeLike
can you please shared lcd driver code to me
my email id – anupnsec123@gmail.com
LikeLike
Sent to your email!
LikeLike
Hi, can you share your LCD driver code me?
My email is yonglanyouyou@gmail.com
Thank you in advance
LikeLike
Like!! I blog quite often and I genuinely thank you for your information. The article has truly peaked my interest.
LikeLike
Would you mind sharing the code of the lcd driver to me?
Thank you very much!
LikeLike
the display is placed at https://github.com/jimmywong2003/nrf52840-display-ST7735. It used the ST7735 instead of ILI9341 (but they are similiar).
LikeLike
Thank you very much! I will check it out.
LikeLike
Hi Jimmy,
Your blog is great, please can you share your code, this is my mail: Rayan@bandindustries.com
LikeLike
You can find the code at https://github.com/jimmywong2003/nrf52840-display-ST7735.
LikeLike
Could you shared ILI9341 lcd driver code to me ? My e-mail is mikechen20130705@gmail.com
LikeLike
You find the code at https://github.com/jimmywong2003/nrf52840-display-ST7735/blob/master/Source/drivers/lcd_ili93491/drv_lcd_ili9341.c.
LikeLike
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.
LikeLike
I saw your screen have no tear when you slider the screen.Have you ever use the TE pin to solve it?
LikeLike
I don’t know what you mean.
LikeLike
LCD tear effect.
LikeLike
The demo is using the frame by frame update. If it is fast enough, you should not see such tear effect.
LikeLike
Thanks for your reply.
Have you ever test the frame rate in 52840 with spim lcd and load from qspi flash?
I want to know how fast is it.
LikeLike
I used the logic analyzer to measure. Pure ram to lcd for 1 frame is 38.4ms. Loading flash from qspi (depend on the order of bitmap) is around 10ms to 15ms (additional).,
LikeLike
Hi Jimmy,
I’m using a nrf52840 with a st7789h2 Lcd module, and I’ve been using regular spi to communicate, but pixel writing is quite slow. I’d like to switch to spim3, so could you maybe send the spim3 config code? My email address is maheshjpsk@gmail.com.
LikeLike
Jimmy, great demo and very useful quick question. Where did you store the images? QSPI flash on the dev kit?
LikeLike
the demo is to load the image from QSPI flash. You can use the nrfjprog tool to write the image to QSPI flash.
LikeLike
Hi Jimmy,
How much memory (RAM) on the nRF do you end up using in this configuration?
We’re finding very little available for application after the framebuffer is allocated.
LikeLike
Hi Amit,
if you use the 1 frame buffer, it uses a lot of RAM.
for example, RGB 16 bits (320×240 resolution), size of 1 frame buffer = 2 bytes (16 bits) * 320 x 240 = 150KB.
You can only use the partial approach instead of 1 full frame if you need to optimize the RAM usage.
Jimmy
LikeLike
Hey Jimmy , i’m trying to use nrfx_spim.h library for st7735 lcd but it is not working can you tell me what is the issue..
https://drive.google.com/file/d/1s7fGJXKXbquvJkZSFkN6JrcEW1PMqEu3/view?usp=sharing
LikeLike
Hello i wnat to interface LCD with NRF52Dk can you share the code , i wnat some gudiles please help me out
LikeLike