nRF52840DK LVGL Port

Description

Dear all,
I am trying to port LVGL library to Nordic’s nRF52840-DK for Adafruit’s 2.4" Featherwing display. I have already seen some posts in this forum, but none of them solved my problem. I have followed the LVGL’s official porting guide and I have implemented a driver for the touchscreen’s ILI9341 controller based on the lv_drivers. However, even though the firmware is compiled successfully and the library’s lv_task_handler() and lv_tick_inc() are running, the screen stays blank at all times. To clarify, the lv_task_handler() runs in the mainloop context every 5 ms, while the lv_tick_inc(1) runs inside a high priority timer handler every 1 ms.
To verify that the library runs as expected, I have assigned the nRF52840 on-board buttons to the library as an indev device with a graphical button (lv_ex_get_started_1()) and they seem to respond as expected. I suspect that the problem is located in the ILI9341 drivers.

What MCU/Processor/Board and compiler are you using?

I am using Nordic’s nRF52840-DK with Adafruit’s 2.4" Featherwing display. I am developing my application with the Segger Embedded Studio.

I am using LVGL release/v7 branch

What do you want to achieve?

Port the LVGL library to the nRF52840 DK.

What have you tried so far?

I have tried to modify the lv_drivers for the ILI9341 controller. I have also tried the available esp32 ILI9341 drivers without any success, too. Moreover, I have used Nordic’s default drivers (with modications) and the screen remains blank.
Note that the gfx example available with the nRF SDK is running as expected, so the display and the SPI connections are checked and seem to work fine.

Code to reproduce

main() function

void main(void)
{
   //
    // Initialize Error Variable
    //
    ret_code_t ErrorCode = NRF_SUCCESS;

    //
    // Initialize Low-Frequency Clock
    //
    ClockInit();

    //
    // Initialize Application Timer (For Buttons)
    //
    ErrorCode = app_timer_init();
    APP_ERROR_CHECK(ErrorCode);

    //
    // Initialize Buttons & LEDs
    //
    ErrorCode = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, BspEventHandler);

    APP_ERROR_CHECK(ErrorCode);

    //
    // Initialize Terminal Log
    //
    LogInit();

    lv_init();

    lv_disp_init();
    lv_indev_init();


    //
    // Initialize LVGL's Timer module
    //
    TimerInit();

    lv_ex_get_started_1();

    while(1)
    {
      nrf_delay_ms(5);
      lv_task_handler();

    }

}

lv_disp_init() function

void lv_disp_init(void)
{
    ili9341_init();

    static lv_disp_buf_t disp_buf;

    static lv_color_t buf_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
    
    lv_disp_buf_init(&disp_buf, buf_1, NULL, LV_HOR_RES_MAX * 10);   /*Initialize the display buffer*/

    lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = ILI9341_WIDTH;
    disp_drv.ver_res = ILI9341_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = ili9341_flush;

    /*Set a display buffer*/
    disp_drv.buffer = &disp_buf;


    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

ili9341_flush() function

void ili9341_flush(lv_disp_drv_t * drv, const lv_area_t * area, const lv_color_t * color_p)
{
    if(area->x2 < 0 || area->y2 < 0 || area->x1 > (ILI9341_WIDTH - 1) || area->y1 > (ILI9341_HEIGHT - 1)) {
        lv_disp_flush_ready(drv);
        return;
    }

    /* Truncate the area to the screen */
    int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
    int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
    int32_t act_x2 = area->x2 > ILI9341_WIDTH - 1 ? ILI9341_WIDTH - 1 : area->x2;
    int32_t act_y2 = area->y2 > ILI9341_HEIGHT - 1 ? ILI9341_HEIGHT - 1 : area->y2;

    int32_t y;
    uint8_t data[4];
    int32_t len = len = (act_x2 - act_x1 + 1) * 2;
    lv_coord_t w = (area->x2 - area->x1) + 1;

    /* window horizontal */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_CASET);
    data[0] = (uint8_t)(act_x1 >> 8);
    data[1] = (uint8_t)act_x1;
    data[2] = (uint8_t)(act_x2 >> 8);
    data[3] = (uint8_t)act_x2;
    ili9341_write_array(ILI9341_DATA_MODE, data, 4);

    /* window vertical */
    ili9341_write(ILI9341_CMD_MODE,  ILI9341_PASET);
    data[0] = (uint8_t)(act_y1 >> 8);
    data[1] = (uint8_t)act_y1;
    data[2] = (uint8_t)(act_y2 >> 8);
    data[3] = (uint8_t)act_y2;
    ili9341_write_array(ILI9341_DATA_MODE, data, 4);


    ili9341_write(ILI9341_CMD_MODE, ILI9341_RAMWR);

    for(y = act_y1; y <= act_y2; y++) {
        ili9341_write_array(ILI9341_DATA_MODE, (uint8_t *)color_p, len);
        color_p += w;
    }

    lv_disp_flush_ready(drv);
}

ili9341_init() function

void ili9341_init(void)
{
    volatile uint8_t data[15];

    hardware_init();

    /* software reset */
     ili9341_write(ILI9341_CMD_MODE, ILI9341_SWRESET);
    nrf_delay_ms(120UL);
    ili9341_write(ILI9341_CMD_MODE, ILI9341_DISPOFF);
    nrf_delay_ms(120UL);

    /* startup sequence */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRLB);
    data[0] = 0x00U;
    data[1] = 0x83U;
    data[2] = 0x30U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 3);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_PWSEQCTRL);
    data[0] = 0x64U;
    data[1] = 0x03U;
    data[2] = 0x12U;
    data[3] = 0x81U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 4);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_TIMECTRLA_INT);
    data[0] = 0x85U;
    data[1] = 0x01U;
    data[2] = 0x79U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 3);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRLA);
    data[0] = 0x39U;
    data[1] = 0x2cU;
    data[2] = 0x00U;
    data[3] = 0x34U;
    data[4] = 0x02U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 5);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_PUMPRATIO);
    ili9341_write(ILI9341_DATA_MODE, 0x20);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_TIMECTRLB);
    data[0] = 0x00U;
    data[1] = 0x00U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 2);

    /* power control */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRL1);
    ili9341_write(ILI9341_DATA_MODE, 0x26);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRL2);
    ili9341_write(ILI9341_DATA_MODE, 0x11);

    /* VCOM */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_VMCTRL1);
    data[0] = 0x35U;
    data[1] = 0x3eU;
    ili9341_write_array(ILI9341_DATA_MODE, data, 2U);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_VMCTRL2);
    ili9341_write(ILI9341_DATA_MODE, 0xbeU);

    /* set orientation */
    ili9341_rotate(0U, ILI9341_BGR);

    /* 16 bit pixel */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_PIXSET);
    ili9341_write(ILI9341_DATA_MODE, 0x55U);

    /* frame rate */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_FRMCTR1);
    data[0] = 0x00U;
    data[1] = 0x1bU;
    ili9341_write_array(ILI9341_DATA_MODE, data, 2U);

#if ILI9341_GAMMA
    /* gamma curve set */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_GAMSET);
    ili9341_write(ILI9341_DATA_MODE, 0x01U);

    /* positive gamma correction */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_PGAMCTRL);
    data[0]  = 0x1fU;
    data[1]  = 0x1aU;
    data[2]  = 0x18U;
    data[3]  = 0x0aU;
    data[4]  = 0x0fU;
    data[5]  = 0x06U;
    data[6]  = 0x45U;
    data[7]  = 0x87U;
    data[8]  = 0x32U;
    data[9]  = 0x0aU;
    data[10] = 0x07U;
    data[11] = 0x02U;
    data[12] = 0x07U;
    data[13] = 0x05U;
    data[14] = 0x00U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 15U);

    /* negative gamma correction */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_NGAMCTRL);
    data[0]  = 0x00U;
    data[1]  = 0x25U;
    data[2]  = 0x27U;
    data[3]  = 0x05U;
    data[4]  = 0x10U;
    data[5]  = 0x09U;
    data[6]  = 0x3aU;
    data[7]  = 0x78U;
    data[8]  = 0x4dU;
    data[9]  = 0x05U;
    data[10] = 0x18U;
    data[11] = 0x0dU;
    data[12] = 0x38U;
    data[13] = 0x3aU;
    data[14] = 0x1fU;
    ili9341_write_array(ILI9341_DATA_MODE, data, 15U);
#endif

    /* window horizontal */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_CASET);
    data[0] = 0U;
    data[1] = 0U;
    data[2] = (ILI9341_WIDTH - 1U) >> 8U;
    data[3] = (ILI9341_WIDTH - 1U);
    ili9341_write_array(ILI9341_DATA_MODE, data, 4U);

    /* window vertical */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_PASET);
    data[0] = 0U;
    data[1] = 0U;
    data[2] = (ILI9341_HEIGHT - 1U) >> 8U;
    data[3] = (ILI9341_HEIGHT - 1U);
    ili9341_write_array(ILI9341_DATA_MODE, data, 4U);

    ili9341_write(ILI9341_CMD_MODE, ILI9341_RAMWR);

#if ILI9341_TEARING
    /* tearing effect off */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_TEOFF);

    /* tearing effect on */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_TEON);
#endif

    /* entry mode set */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_ETMOD);
    ili9341_write(ILI9341_DATA_MODE, 0x07U);

    /* display function control */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_DISCTRL);
    data[0] = 0x0aU;
    data[1] = 0x82U;
    data[2] = 0x27U;
    data[3] = 0x00U;
    ili9341_write_array(ILI9341_DATA_MODE, data, 4U);

    /* exit sleep mode */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_SLPOUT);

    nrf_delay_ms(100U);

    /* display on */
    ili9341_write(ILI9341_CMD_MODE, ILI9341_DISPON);

    nrf_delay_ms(20U);
}

ili9341_write() and ili9341_write_array() functions

static inline void ili9341_write(uint8_t Mode, uint8_t Data)
{
    //
    // Write Command
    //
    if(Mode == ILI9341_CMD_MODE)
    {
        nrf_gpio_pin_clear(ILI9341_DC_PIN);
    }

    //
    // Write Data
    //
    else if(Mode == ILI9341_DATA_MODE)
    {
        nrf_gpio_pin_set(ILI9341_DC_PIN);
    }
    
    nrf_gpio_pin_clear(ILI9341_SS_PIN);
    APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, Data, 1U, NULL, 0));
    nrf_gpio_pin_set(ILI9341_SS_PIN);
}


static inline void ili9341_write_array(uint8_t Mode, uint8_t *Data, uint16_t Len)
{ 
    //
    // Write Command
    //
    if(Mode == ILI9341_CMD_MODE)
    {
        nrf_gpio_pin_clear(ILI9341_DC_PIN);
    }

    //
    // Write Data
    //
    else if(Mode == ILI9341_DATA_MODE)
    {
        nrf_gpio_pin_set(ILI9341_DC_PIN);

    }
    
    nrf_gpio_pin_clear(ILI9341_SS_PIN);
    APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, Data, Len, NULL, 0));
    nrf_gpio_pin_set(ILI9341_SS_PIN);

}   // End Of ili9341_write_array()

I am really sorry for the long post, but I cannot figure it out. It may be something simple that I am not seeing right now.

Thank you in advance for your help,

Aris

Can you confirm that the function ili9341_flush is called?

One ‘strange’ code I found:

 /* set orientation */
    ili9341_rotate(0U, ILI9341_BGR);

ILI9341_BGR is not for setting orientation I think.
For me it looks like a constant for setting RGB order.
Orientation should be something with portrait or landscape.

Just a guess…