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,

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
        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);
}