Photon 2 SPI DMA transfer deadlock

I'm seeing stability issues driving a display via SPI. I'm using LVGL as a rendering library. For stresstesting, I'm driving a continuous animation, which eventually will come to a halt. I debugged it down to the SPI DMA transfer not completing. When not using DMA, this works.

To further debug, simplified my code by remoing all threading code (Device OS is still threaded), and stopped relying on the interrupt to be notified for completion, yet the DMA transfer method eventually will not complete :frowning:

I'm using DeviceOS 6.2 preview, but have not yet tested the same with an older DeviceOS.

The relevant, simplified code that block that communicates with the display is below. Note that if I transfer byte-by-byte (instead of DMA), i have not seen this hang. Am I fundamentally holding it wrong?

Thanks,
MikeS

void Display::SendCommandDma(const uint8_t *cmd, size_t cmd_size,
                             const uint8_t *param, size_t param_size) {
  SPI1.beginTransaction(spi_settings_);
  pinResetFast(pin_chipselect);
  pinResetFast(pin_datacommand);

  for (size_t i = 0; i < cmd_size; i++) {
    SPI1.transfer(cmd[i]);
  }

  pinSetFast(pin_datacommand);
  SPI1.transfer(param, NULL, param_size, NULL);
  // Not getting here when it hangs

  pinSetFast(pin_chipselect); // CS is not pulled up anymore
  SPI1.endTransaction();
  lv_display_flush_ready(display_);
}

Nothing is obviously wrong with that code, however to rule out possible issues that affect DMA:

  • Where is param stored (global variable, heap, stack, or flash)?
  • How big is the data typically? In particular, more than 4095 bytes?
  • Is param_size ever 0? That might be an issue.

Can you put a short delay, like a millisecond, between the command transfer and setting the datacommand GPIO? This is just to see if it makes a difference.

param is on the heap, allocated in setup. the data is often more than 4k, up to some ~150k if the update refreshes the whole screen. What happens at the 4k boundary? is it recommended to break the transfer into chunks?

The largest DMA transfer is 4095 bytes. The SPI.transfer with DMA call will split that up for you automatically, so you don't really need to split it, but it's worth investigating as a possible source of a bug.

The reason I mention the source is that you can't DMA from flash directly and it's copied in chunks, though that does not apply in your case, and now that I think about it, it might only apply to nRF52.

So it's not obvious why it's not working.