[nRF52840 DK] SPI transaction interrupted by ISR ?


I've got a display driven over SPI at 8MHz by the lib LVGL in a dedicated Zephyr thread and an ISR configured on one gpio pin that triggers a short routine registering the rising edge of the signal (basically incrementing a var).
The used Zephyr SPI write method is from <drivers/spi.h> :
static inline int spi_write_dt(const struct spi_dt_spec *spec, const struct spi_buf_set *tx_bufs);

When the ISR is highly solicited (i.e. lots of pulses on gpio pin -> >1k/s ), I've noticed the display starts to show artifacts that isn't cleared if the drawn area position isn't dynamic.

I'm assuming it comes from the case where the ISR is called in the middle of the display SPI update transaction and some SPI data is shifted out during this time as the SCK still continues from hardware.

Is there a way to make sure the SPI transaction doesn't "freeze" in this kind of configuration ?

Thanks for your time !
  • Hello,

    Have you been able to observe how the interrupts affects the SPI transfer on an logic analyzer?

    I do not see any reason why a frequent pin interrupt should affect the SPI transfer, because once SPI transfer is started it will transfer continously the amount of byte in one chunk (through EasyDMA), unless you have disabled EasyDMA, and thereby using interrupt based SPI transfer. Maybe you can comment whether CONFIG_SPI_0_NRF_SPI or CONFIG_SPI_0_NRF_SPIM is used in your project?


  • I just probed on MOSI and, so far I can tell, it doesn't seem to be affected indeed.

    And I don't have any of CONFIG_SPI_0_NRF_SPIM or CONFIG_SPI_0_NRF_SPI settled in the project. How to be sure EasyDMA is effectively used ?

    Anyway I tried shutting down some non-critical applicative threads and it doesn't change anything, artifacts still there when heavy ISR calls.


    I also noticed some SPI data may be interpreted as SPI command one (causing random behaviors like inverting the display color, etc.). Looking into the driver, the gpio for data_control pin is handled manually using gpio_pin_set_d before and after spi_write_dt for data command:


      gpio_pin_set_dt(&config->dc_gpio, 1);
      errno = spi_write_dt(&config->bus, &tx_bufs_cmd);
      gpio_pin_set_dt(&config->dc_gpio, 0);

    May it cause issues if process is preempted over there?

  • Are you sure you don't have any electrical problems, that you have poor grounding or similiar?

    Do I understand it correctly that your ISR does not trigger any SPI transfer? In other words the ISR does not directly/indirectly affect the transfer of SPI (other than the potential delay it may cause).


Reply Children
  •   I don't think so, when I probe the different channels signals are all clean. I also reduced the SPI clock frequency to see if it changes something but not really.

    And yes, your understanding is right, the ISR is just putting a value in a message queue. Then, another thread is fed from this same queue and does computation before another updates periodically the view/display. 

    Removing the interrupt only get rid of the issue even having the trigger signal on.

  • AnomalySmith said:
    Then, another thread is fed from this same queue and does computation before another updates periodically the view/display. 

    Is it possible that this thread is doing any manipulation or update of the ongoing SPI buffer used by the:

    gpio_pin_set_dt(&config->dc_gpio, 1);
    errno = spi_write_dt(&config->bus, &tx_bufs_cmd);
    gpio_pin_set_dt(&config->dc_gpio, 0);

    Maybe try to double buffer the SPI data before spi_write_dt()?


  • Thanks for your reply again, only the first thread is updating data as the second reads them and is responsible from SPI transaction (calling LVGL's related functions). Also, mutexes are used in order to guaranty the integrity of data while accessing them.

    The buffer size is handled by the call (by LVGL lib) to the writes related functions through the SSD1333 driver (default display declared in overlay dts file) :

    static inline int ssd1333_write_bus(const struct device *dev, BYTE cmd, BYTE *buf, size_t data_len) {
      // printk("[SSD1333] WRITE BUS\n");
      const struct ssd1333_config *config = dev->config;
      int errno;
      BYTE cmd_buf[] = {cmd};
      struct spi_buf tx_buf_cmd = {
        .buf = cmd_buf,
        .len = 1
      struct spi_buf_set tx_bufs_cmd = {
        .buffers = &tx_buf_cmd,
        .count = 1
      gpio_pin_set_dt(&config->dc_gpio, 1);
      errno = spi_write_dt(&config->bus, &tx_bufs_cmd);
      gpio_pin_set_dt(&config->dc_gpio, 0);
      if(!buf || data_len < 1) {
        return errno;
      struct spi_buf tx_buf = {
        .buf = buf,
        .len = data_len
      struct spi_buf_set tx_bufs = {
        .buffers = &tx_buf,
        .count = 1
      errno = spi_write_dt(&config->bus, &tx_bufs);
      return errno;
    int ssd1333_write(const struct device *dev, const uint16_t x, const uint16_t y,
            const struct display_buffer_descriptor *desc,
            const void *buf) {
      // printk("[SSD1333] WRITE\n");
      size_t buf_len;
      if(desc->pitch < desc->width) {
        LOG_ERR("Pitch is smaller than width");
        return -1;
      buf_len = desc->buf_size; // / 8 ???
      if(buf == NULL || buf_len == 0U) {
        LOG_ERR("Display buffer is not available");
        return -1;
      if(desc->pitch > desc->width) {
        LOG_ERR("Unsupported mode");
        return -1;
      // if((y & 0x7) != 0U) {
      //   LOG_ERR("Unsupported origin y %u", y);
      //   return -1;
      // }
      /* Check we do not overflow x or y from buffer display_buffer_descriptor */
      struct display_capabilities capabilities;
      display_get_capabilities(dev, &capabilities);
      if((x + desc->width) > capabilities.x_resolution) {
        LOG_ERR("Unsupported origin x %u with buffer width setled to %u (screen overflow)", x, desc->width);
        return -1;
      if((y + desc->height) > capabilities.y_resolution) {
        LOG_ERR("Unsupported origin y %u with buffer height setled to %u (screen overflow)", y, desc->height);
        return -1;
      LOG_WRN("x %u, y %u, pitch %u, width %u, height %u, buf_len %u",
        x, y, desc->pitch, desc->width, desc->height, buf_len);
      BYTE x_range[] = {SSD1333_DATA_ADDR_RANGE_X[0] + x, SSD1333_DATA_ADDR_RANGE_X[0] + x +(desc->width - 1)};
      BYTE y_range[] = {SSD1333_DATA_ADDR_RANGE_Y[0] + y, SSD1333_DATA_ADDR_RANGE_Y[0] + y + (desc->height - 1)};
      // uint32_t total_rows = y2 - y1;
      // uint32_t total_cols = x2 - x1;
      if(ssd1333_write_bus(dev, SSD1333_CMD_SETCOLUMN, x_range, sizeof(x_range)) || ssd1333_write_bus(dev, SSD1333_CMD_SETROW, y_range, sizeof(y_range))) {
        LOG_ERR("Failed to write command");
        return -1;
      ssd1333_write_bus(dev, SSD1333_CMD_WRITEINTORAM, (BYTE *)buf, buf_len);
      return 0;
    static struct display_driver_api ssd1333_driver_api = {
      .blanking_on = ssd1333_blanking_on,
      .blanking_off = ssd1333_blanking_off,
      .write = ssd1333_write,
      .read = ssd1333_read,
      .get_framebuffer = ssd1333_get_framebuffer,
      .set_brightness = ssd1333_set_brightness,
      .set_contrast = ssd1333_set_contrast,
      .get_capabilities = ssd1333_get_capabilities,
      .set_pixel_format = ssd1333_set_pixel_format,
      .set_orientation = ssd1333_set_orientation
    #define SSD1333_DEFINE(inst)                                                \
      static const struct ssd1333_config ssd1333_config_##inst = {              \
        .bus = SPI_DT_SPEC_INST_GET(                                            \
          inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0),    \
        .dc_gpio = GPIO_DT_SPEC_INST_GET(inst, data_ctrl_gpios),                \
        .reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios),                 \
        .width = DT_INST_PROP(inst, width),                                     \
        .height = DT_INST_PROP(inst, height),                                   \
      };                                                                        \
      static struct ssd1333_data ssd1333_driver_data_##inst;                    \
      DEVICE_DT_INST_DEFINE(inst,                                               \
                            ssd1333_init,                                       \
                            NULL,                                               \
                            &ssd1333_driver_data_##inst,                        \
                            &ssd1333_config_##inst,                             \
                            POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY,          \

  • Kenneth said:
    gpio_pin_set_dt(&config->dc_gpio, 1);
    errno = spi_write_dt(&config->bus, &tx_bufs_cmd);
    gpio_pin_set_dt(&config->dc_gpio, 0);

    If you (for debugging) just add some delay between these lines, can you see the same symptoms? I am just trying to understand (since SPI data is not altered during SPI transmission) what delay is causing the problem.


  • I just tried adding 1ms delay between these lines and also next to spi_write_dt: without success.

    For now, I only think of LVGL having something to do with this weird problem