Teensy 4.1 - questions about asynchronous SPI transfers

MCollins-Lowell

New member
For an application where a number of SPI-based inertial sensors must be sampled at intervals of 1 ms or less, we could benefit from asynchronous operations involving two SPI modules, with a second transfer initiated while the first is in progress. Based upon information in this thread, along with another referenced within, this appears to be possible. At present, the system is fully functional with up to four sensors interfaced through SPI0 operating synchronously, however we hope to expand capabilities to eight ports.

I've been working for some time now to utilize the non-blocking 8-bit method KurtE describes in the thread referenced above, with only limited success. While it appears that the call to SPI.transfer() returns immediately, a simple semaphore operation in my callback evidently does not execute. I suspect that the problem may be related to invalid assignment of the callback function.

To demonstrate the issue, I've generated a simple example sketch with extraneous diagnostic output. Because the system I'm using for development is remote, I added watchdog reset capability since some of my test cases have put the Teensy into a state where the Arduino IDE cannot recover. In the sketch, flow stalls indefinitely while waiting for bit 0 in the semaphore word to be reset.

C-like:
#include <stdio.h>
#include <SPI.h>
#include "Watchdog_t4.h"

#define spi_fSCLK       1000000
#define spi_bitOrder    MSBFIRST
#define spi_Mode        SPI_MODE0

/*
 * Global semaphore word
 */
volatile unsigned int stStat;

/*
 * Global eventResponder object
 */
EventResponder erSPI;

WDT_T4<WDT3> wdt;

/*
 * COPI data for SPI transfer sequence
 */
unsigned int txData[] = {
  0x20012005,
  0x20010034,
  0x20010013,
  0x30000000,
  0x30002000,
  0x30000000,
  0  // end of sequence marker
};

/*
 * Watchdog timer callback function
 */
void cbWDT() {
  printf("Watchdog timeout -- system reset imminent\r\n");
}

/*
 * SPI callback function
 *
 * Simply clears bit 0 in semaphore word
 */
void cbSPI(EventResponderRef eventResponder) {
  extern volatile unsigned int stStat;
  stStat &= ~1;
  return;
}

void setup() {

  /*
   * Configure watchdog timer to reset MCU if loop stalls
   */
  WDT_timings_t config;

  config.timeout = 8000;
  config.callback = cbWDT;
  wdt.begin(config);

  Serial.begin(1000000);
  delay(4000);
  SPI.begin();
  SPI.beginTransaction(SPISettings(spi_fSCLK, spi_bitOrder, spi_Mode));
  erSPI.attach(cbSPI);
  printf("SPI transfer demo, 2025-09-08, r09\r\n");
}

void loop() {
  extern volatile unsigned int stStat;
  extern unsigned int txData[];

  unsigned char rxByte[4];
  unsigned char txByte[4];
  int byteCount;
  int loopCount;
  int wordCount;
  unsigned int rxData;
  unsigned int *txdPtr;

  loopCount = 0;
  do {
    printf("loopCount: %d\r\n", loopCount);
    wordCount = 0;
    txdPtr = txData;

    /*
     * Reverse txData byte ordering
     */
    do {
       txByte[0] = (*txdPtr >> 24) & 0xff;
       txByte[1] = (*txdPtr >> 16) & 0xff;
       txByte[2] = (*txdPtr >>  8) & 0xff;
       txByte[3] =  *txdPtr & 0xff;

      printf("  txData[%d]: 0x%.8x\r\n", wordCount, *txdPtr);
      for (byteCount = 0; byteCount < 4; ++byteCount)
        printf("    txByte[%d]: 0x%.2x\r\n", byteCount, txByte[byteCount]);

      /*
       * Set semaphore bit
       */
      stStat |= 1;

      printf("  SPI.transfer(0x%x, 0x%x, %d, 0x%x)\r\n",
             (unsigned int) &txByte[0], (unsigned int) &rxByte,
             (size_t) 4, (unsigned int) cbSPI);

      SPI.transfer((const void *) &txByte[0], (void *) &rxByte[0],
                   (size_t) 4, erSPI);

    /*
     * Wait for status bit to be cleared in callback function
     */
     printf("Awaiting semaphore change of state...\r\n");
     while (stStat != 0)
       ;
      
      /*
       * Assemble rdData word from byte array
       */
      rxData = (rxByte[3] << 24) + (rxByte[2] << 16) +
               (rxByte[1] << 8) + rxByte[0];

      printf("wordCount: %d, txData: 0x%.8x, rxData: 0x%.8x\r\n",
             wordCount, *txData, rxData);
      ++wordCount;
      ++txdPtr;
    } while (*txdPtr != 0);

  delay(2000);

  printf("Watchdog reset\r\n");
  wdt.feed();
  } while (1);
}

A related question. In my code, I reverse the byte order of a 32-bit COPI word and store it into a four-byte array, then pass the address of the array along with a count value of 4 to SPI.transfer. Am I correct in assuming that four 8-bit transfers will be completed before the callback is invoked, with CIPO data stored in the corresponding rxData array?

Any assistance would be most appreciated.

-- Mike --
 
Bad form to reply to one's own posting, however I've answered one of my questions and can perhaps simplify the other. Looking at some transfers with an oscilloscope, it appears that queuing of multiple 8-bit data words from the controller to the peripheral is as needed for my application.

If someone could simply provide an example showing how to attach a callback to SPI.transfer, I should be in business. Or better, provide a link to detailed documentation for the library.
 
Have you looked into the event responder method in SPI?
It allows you do to async transfers and trigger a callback when it's done - you can see my working example here https://forum.pjrc.com/index.php?th...tristate-and-one-pin.72337/page-2#post-354257


Code:
#include <SPI.h>

// SPI configuration
#define SPI_CLOCK 1000000 // 1 MHz
#define SPI_CS_PIN 10     // Chip Select Pin

EventResponder spiEventResponder; // EventResponder for async transfer
#define BUFFER_SIZE 32
uint8_t txBuffer[BUFFER_SIZE] = {0};       // Data to send
uint8_t rxBuffer[BUFFER_SIZE] = {0};       // Buffer for received data

volatile bool transferInProgress = false; // Tracks transfer status

void setup() {
    Serial.begin(115200);

    // Initialize SPI
    SPI.begin();
    SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));

    pinMode(SPI_CS_PIN, OUTPUT);
    digitalWrite(SPI_CS_PIN, HIGH); // Deselect device

    // Configure EventResponder callback
    spiEventResponder.attachImmediate(spiCompleteCallback);

    // Fill txBuffer with initial data
    for (int i = 0; i < BUFFER_SIZE; i++) {
        txBuffer[i] = i;
    }

    // Start the first SPI transfer
    startSPITransfer();
}

void loop() {
    // Main loop can handle other tasks
    delay(100); // Simulate other processing
}

// Start a new SPI transfer
void startSPITransfer() {
    if (!transferInProgress) {
        transferInProgress = true;

        // Select the device
        digitalWrite(SPI_CS_PIN, LOW);

        // Start async SPI transfer
        bool success = SPI.transfer(txBuffer, rxBuffer, sizeof(txBuffer), spiEventResponder);
        if (!success) {
            Serial.println("Failed to start SPI transfer!");
            transferInProgress = false;
            digitalWrite(SPI_CS_PIN, HIGH); // Deselect device on failure
        }
    }
}

// SPI transfer complete callback
void spiCompleteCallback(EventResponder &event) {
    // Deselect the device
    digitalWrite(SPI_CS_PIN, HIGH);

    // Print received data for debugging
    Serial.println("SPI transfer completed!");
    for (size_t i = 0; i < BUFFER_SIZE; i++) {
        Serial.print("Received byte ");
        Serial.print(i);
        Serial.print(": 0x");
        Serial.println(rxBuffer[i], HEX);
    }

    // Modify txBuffer if necessary
    for (int i = 0; i < BUFFER_SIZE; i++) {
        txBuffer[i]++; // Example: Increment data for the next transfer
    }

    // Reset the transferInProgress flag
    transferInProgress = false;

    // Start the next transfer
    startSPITransfer();
}
 
Back
Top