SPI transfer freezes Photon but not Electron

I have an SD Card connected via SPI1, and managed using the SDFat library. This implementation of SPI uses DMA.

I also have an MQTT connection over TCP. One use case in my product reads a bunch of log entries off of the SD Card and publishes them via MQTT. This is a higher volume of traffic than otherwise my device normally requires out of either the SD Card or the TCP connection. This is in a multi-threaded application.

On the Photon (v0.6.3), when I start pulling logs, it quickly permanently hangs the main application thread on SDFat’s call to the SPI transfer fn. However, it only does this when I’m also publishing those packets over TCP.

On the Electron (v0.6.4) everything runs fine.

My theory is that there is some kind of conflict on the internal Particle implementation of SPI, since the photon does networking over SPI, while the Electron does it over USART.

Has anyone else experienced this? How can I avoid the call to SPI transfer from locking up? Is there something that isn’t fully threadsafe on the SPI implementation with respect to using Photon networking and SPI1 at the same time?

SPI is not thread safe, and causes problems on Gen 3 devices using the Ethernet FeatherWing. A fix is planned, but has not been implemented.

The Photon/P0 don’t use SPI to communicate with the WiFi radio, however. They use SDIO to the BCM43362.

Your symptoms do sound like a thread safety issue, so there very well may be one in something else other than SPI. It’s also possible that the STM32 SPI and SDIO interfaces share resources in some way.

2 Likes

Thanks for those insights. Always appreciate your product knowledge.

Ah yes, my mistake, SDIO it is. Yeah the only SPI peripheral I use is the SD Card. All my SPI / SD Card usage is mutex protected on the application firmware side, so it does seem like there is a shared resource somewhere at the system level.

While in theory a single threaded block might work here, this looks like this might unfortunately completely break my use case because I have a dedicated thread for a CAN bus that would not function very well with the possible 20ms+ blocking time of some of the SD Card reads and writes.

There isn’t any way just to pause task switching to the System thread is there? Doesn’t seem like messing with the thread priority level would be smart for a case like this, but I can’t think of any other way of selectively giving both of my threads continuous priority over the system networking code.

The Argon seems to be using UART to communicate with the ESP32, is that correct? In theory that should work ok in conjunction with SPI in this use case. Pity it’s not really production ready yet.

After playing with it a bit, it seems like wrapping all my SD card / SPI operations in SINGLE_THREADED_SECTIONs effectively minimizes the issue. While I can’t guarantee that it solves it, it seems to statistically reduce the chances of the conflict enough to be effectively zero, even in high stress corner cases. The timing seems to be acceptable for my application so far. For the photon only, I define my resource control locks as SINGLE_THREADED_SECTION instead of a mutex lock_guard.

For anyone else using an SD card via SPI or other SPI peripheral and doing any meaningful networking stuff, I definitely recommend doing the same. If you don’t need this to also be thread safe within the application context for the electron, you can define LOCK() as nothing just like UNLOCK(), or simply use SINGLE_THREADED_SECTION() directly. For a sample implementation that is working for me, see the below:

// sd card .cpp file

// private macros
#if PLATFORM_ID == 6 // photon
#define LOCK()      SINGLE_THREADED_SECTION()   // needed due to SPI conflict with system networking
#define UNLOCK()
#else // electron, others
#define LOCK()      std::lock_guard<std::mutex> sd_guard_(sd_mutex_);
#define UNLOCK()
#endif

// example use
void SdManager::MyFunction() {
     LOCK();
     //my code
     UNLOCK();
     return;
}
2 Likes