My code uses the nrfx SPIM driver. This gives you the option to use interrupts or not. Interrupts are great for long message transfers, allowing sleep while waiting for the end. But for very short (one or two byte messages, especially) I found it to be much faster and use less processor cycles to use the NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER flag for those messages and then just poll the END event flag. This saves all the time to wake up and for the driver to then call my handler to clear the spi_xfer_active flag. Polling is about 10 times less processor time for these short one or two byte messages. This also means that I can use those messages (to check if a serial flash chip is still writing or erasing, for example) from an equal priority interrupt handler (like the timer) without worrying about a deadlock with the SPI interrupt handler.
The code I use is shown. There was one problem that I had to work around. Though the driver code is not supposed to enable the SPI interrupt if the NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER flag is set, one out of a thousand times the interrupt happens anyway. So if the polling loop is only looking for the END event, that rare time that the interrupt happens when it shouldn't causes the END flag to be cleared before the loop gets to see it go high, resulting in a hang of my application. Sometimes it would take 15-30 minutes for this to happen, but it always would eventually. I verified that the SPI IRQ handler was always being triggered right before the hang with the Ozone trace debugger, and I could see that the spi_xfer_active flag was being cleared by the interrupt handler. So the workaround is to check both the END register and the spi_xfer_active flag, so either the polling or the interrupt will terminate the wait for the end of the SPI transfer.
I don't understand why these rare interrupts show up, but the workaround masks the problem.
// The method of detecting the end of the SPI transfer depends on the
// number of bytes in the sequence.
if ( MAX(tx_len, rx_len) > MAX_SPI_BYTES_FOR_POLLING_OF_END_EVENT )
{
// For longer sequences, use the default method of an SPI interrupt
// which will wake the processor to call the interrupt handler which
// will clear the spi_xfer_active flag to signal transmission complete.
// This allows sleeping while we wait, saving a lot of energy.
spi_xfer_active = true;
err_code = nrfx_spim_xfer(&spi, &xfer_desc, 0);
APP_ERROR_CHECK(err_code);
if (err_code == NRF_SUCCESS)
{
// Sleep while we wait
while (spi_xfer_active)
{
nrf_pwr_mgmt_run();
}
}
} else {
/* For short sequences it is faster, and thus more efficient, to skip
* the interrupt and handler overhead and just directly poll the
* EVENT_END register for this SPI instance. This allows this routine
* to be called by an interrupt service routine without a deadlock
* between the two interrupts, and it is also much faster. The very
* frequently used 2 byte sequence of reading the serial flash chip's
* status register (e.g.- for WIP busy flag) takes 37 usecs to be
* recognized as done using the interrupt scheme, even though the
* transmission only takes 4 usecs (@ 4 Mbps). But with this scheme
* it takes only 3.6 usecs (signaling the end of RX/TX buffers even
* before the last bits are clocked out), thus 10 times faster and
* less energy.
*/
// Indicate the SPI bus activity with the flag
spi_xfer_active = true;
err_code = nrfx_spim_xfer(&spi, &xfer_desc, NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER);
APP_ERROR_CHECK(err_code);
if (err_code == NRF_SUCCESS)
{
// Wait for the SPI EVENT_END register to flag the end of the TX and RX buffers
/* Check the spi_xfer_active as well. This is a workaround for the problem with
* the SPI interrupt still happening (very rarely) even though the
* NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER flag is specified. When the IRQ happens
* it clears both spi_xfer_active and the END event bit. So the workaround
* ensures we see the end of the SPI transfer via one way or the other rather
* than hanging here forever for an END event that will never come. */
while ( !*p_end_event_reg && spi_xfer_active )
;
}
// The driver doesn't release the ss_pin if there is an event handler
// but we have overridden it with the NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER flag.
// So we need to do it ourselves here to tell the chip
// that this SPI transfer is finished.
nrf_gpio_pin_write(SF_NRFX_SPIM_SS_PIN, SF_SS_PIN_ACTIVE_STATE ? 0 : 1);
spi_xfer_active = false;
}