Question on a unblocking version of SPI transfer function

Status
Not open for further replies.

GeneL

Member
I am a newbie in Teensy/Arduino programming. In a prototype of my new project, Teensy 3 controller transfers data to two LED panels through SPI0 and SPI1 individually. No data are read from those panels.

I tried the standard uint8_t transfer(uint8_t data) function. It works well. A video using SPI.transfer() can be found on the following link:
https://youtu.be/i1zIILMzfDs

However, the transfer function works in a blocking way, which means it only returns until the operation is completed. We need a unblocking version which just writes data to SPI and then moves to next step. In this way, two SPI buses can work in parallel for a higher overall SPI throughput. I modified the transfer function in SPI.h and save it as transferWrite(). The codes of standard transfer() and transferWrite() listed as follows:

Code:
// Write to the SPI bus (MOSI pin) and also receive (MISO pin)
uint8_t transfer(uint8_t data) {
    port().SR = SPI_SR_TCF;
    port().PUSHR = data;
    while (!(port().SR & SPI_SR_TCF)) ; // wait
    return port().POPR;
}

// Write to the SPI bus (MOSI pin) and no read
void transferWrite(uint8_t data) {
     while (!(port().SR & SPI_SR_TCF)) ; // wait
     port().SR = SPI_SR_TCF;
     port().PUSHR = data;
}
In order to fire a transfer complete event at the beginning, I called the standard transfer function to send a random byte before the transferWrite function. SPI.transferWrite() works well with SPI0. But it doesn't work with SPI1 on either Teensy 3.5 or 3.6. A video when using transferWrite function can be found here:
https://youtu.be/HasJiw4dHk4

Is there anyone can explain why the transferWrite() doesn't work on SPI1? Thanks in advance.
 
Last edited by a moderator:
It might help to see more of the actual code on how it is setup.

Example what is port()...

Is this edited directly into the SPI.h file as part of the SPI class?

Now assuming that you need only output and don't need the input...

Looking at my SPIN library (github.com/kurte/SPIN) it was originally setup to test the different SPI busses when T3.6 beta was being worked on. A lot of the earlier code has now been wrapped back into the SPI library.

Again as I mentioned in PM, I would not wait on TCF (Transmit Complete)

My version of the ILI9341_t3n library (again up on github under kurte). Is setup to use SPIN and you can use it on any of the SPI busses...

It uses the strategy to PUSHR onto the SPI and then wait until the FIFO queue is not full. The interesting this is SPI0 has a fifo queue of 4 whereas SPI1 and SPI2 have a length...

Note SPI now has my same tables as SPIN, but not all of the functions... I used...

But inside of library you could probably do something like:
Code:
uint8_t transferWrite(uint8_t data) {
  port().SR = SPI_SR_TCF;
  port().PUSHR = data;

   // Wait until fifo queue is not full 
    uint32_t sr;
    uint32_t tmp __attribute__((unused));
    do {
        sr = port().SR;
        if (sr & 0xF0) tmp = port().POPR;  // drain RX FIFO
    } while ((uint32_t)(sr & (15 << 12)) > (uint32_t)((queue_size-1) << 12));
}

Or you could try to rearrange and have the loop at the start such that you only wait once you get back to this function for the queue....

Or you could split this up into multiple functions. like have a check to see if the queue is full and if not push your next value out...

Again lots of ways to skin it...

But again you might look at SPIN and ILI9341_t3n to see what this one is doing...
 
Thanks, Kurt!

Yes, it is the SPI.h file as part of the SPI class. I tried your version of transferWrite function(). It works in a nonblocking mode, but it also only works for the SPI0 not for SPI1. Why do you think it is better to wait FIFO queue instead of the transfer complete flag? What is value of the hardware.queue_size value for SPI0 and SPI1 on Teensy3.5 and 3.6?

I will try SPIN & ILI9341_t3n and keep you posted.
 
Again sometimes hard to know exactly what is going on, without seeing the actual sketch you are doing, now knowing what your assumptions are.

That is SPI0 will act differently then SPI1 and SPI2.

That is you can write 4 bytes to SPI0 and probably on either the 4th write or 5th write it will wait until the first byte completes.

With SPI1 and SPI2, you can write 2 bytes into the queue and on the 2nd write it will wait up for the first byte to complete before continuing...

Now if you wish for being able to write multiple bytes to your two displays without holding up, then as I mentioned in earlier PMs, the DMA approach may be the best approach.

Which you can use the current SPI library code to do it all for you....

Here is a quick and dirty app, that tries to show one way of doing it... It runs on T3.6, but I did not test the outputs using Logic Analyzer
Code:
#include <SPI.h>

EventResponder event;
EventResponder event1;

uint8_t buffer0[100];
uint8_t buffer1[100];
volatile bool spi_active = false;
volatile bool spi1_active = false;

void Event_SPI0_Responder(EventResponderRef event_responder) {
  digitalWriteFast(2, HIGH);
  SPI.endTransaction();
  Serial.println("SPI0 ended");
  spi_active = false;
}
void Event_SPI1_Responder(EventResponderRef event_responder) {
  digitalWriteFast(3, HIGH);
  SPI1.endTransaction();
  Serial.println("SPI1 ended");
  spi1_active = false;
}

void setup() {
  while (!Serial && (millis() < 2000)) ;
  Serial.println("Test SPI DMA on SPI and SPI1");

  pinMode(2, OUTPUT);
  digitalWriteFast(2, HIGH);
  SPI.begin();
  event.attachImmediate(&Event_SPI0_Responder);
  for (int i = 0; i < sizeof(buffer0); i++) buffer0[i] = i;

  pinMode(3, OUTPUT);
  digitalWriteFast(3, HIGH);
  SPI1.begin();
  event1.attachImmediate(&Event_SPI1_Responder);
  for (int i = 0; i < sizeof(buffer1); i++) buffer1[i] = sizeof(buffer1) - i;
}

void loop() {
  Serial.println("Start Two SPI transfers");
  spi_active = true;
  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  digitalWriteFast(2, LOW);
  SPI.transfer(buffer0, nullptr, sizeof(buffer0), event);

  spi1_active = true;
  SPI1.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  digitalWriteFast(3, LOW);
  SPI1.transfer(buffer1, nullptr, sizeof(buffer1), event1);

  while (spi_active || spi1_active) ; // wait until both are done
  Serial.println("Both done");
  delay(1000);  // wait a second

}

Edit here is output shown on Logic Analyzer SPI is on top 4 rows, SPI1 is on bottom 4 rows
screenshot.jpg
 
Last edited:
We need a unblocking version which just writes data to SPI and then moves to next step. In this way, two SPI buses can work in parallel for a higher overall SPI throughput.

can you send a block of data to spi and continue with other stuff?

in this case you may use dma to send data to spi.
then you only wait for dma finished 'BEFORE' you send next data block.
that is, you only start new dma when old one has finished.
 
Nice example of the EventResponder use KurtE.

Thanks,

Wonder if we should keep one for an example.... The example I used when developing the DMA based SPI code was with Teensyview display where I have a version that updates the display asynch and have a version that runs the graphic test on three displays on the 3.5 and 3.6...
 
>>>Is there anyone can explain why the transferWrite() doesn't work on SPI1? Thanks in advance.

Have you investigated if the issue could be your wiring or your 2nd LED panel rather than SPI1. Perhaps your 2nd panel can't handle data at twice the rate.
Some things you could try:
Swapping your spi connections to each panel. Does the issue follow SPI1?
Making some of those jumper wires shorter on the breadboard.
Running separate power and grounds to each panel.
Adding a large value capacitor between power and ground on the breadboard.
 
Yes, it shows how to use it. Though just pushing data out two ports to no end is hard to enjoy without a scope/analyzer. Though without that example there is only this to show it exists:
Code:
	// Asynch support (DMA )
#ifdef SPI_HAS_TRANSFER_ASYNC
	bool transfer(const void *txBuffer, void *rxBuffer, size_t count,  EventResponderRef  event_responder);

I was assuming it used interrupt - though reading the code it sets up a DMA transaction. Which I see can fail if no DMA channel found.

If a retbuf[] is given the Rx data can be returned? Maybe a sample with a common SPI device/sensor MPU9250? (though I don't have such an SPI device).
Though I see special case for T_3.5 so two at once might have an issue?

For use on a display like ILI9341 the code would need to queue multiple writes as the code runs and the callback would need to process.next() from that queue ?
 
Yes having scope/Analyzer sure makes life much easier ;) ... Especially debugging timing my data is wrong...

I do have another SPI test program that only does one at a time and had several different combinations of transfers, and I would tie MISO to MOSI and then compare buffers...


As for the Transfer no wait, It may have worked on SPI1 but again different than on SPI as it has a queue of one item versus 4. So as soon as you push the 2nd item on, it will wait for the first item to complete before it returns... Where SPI would only start waiting on pushing the 4th item on, which maybe enough time had already elapsed... So again the issue may simply had been expectations...

So the dma approach should solve this... But again not knowing the programs full needs. If you simply want to output two panels, and have them both go out at full SPI speed, and don't mind waiting for it to complete you could write some form of function, that process both SPI and SPI1 in same loop.

Example here is another program that does it's own transfer to both SPI and SPI1 without using DMA... But it is all hard coded...

Code:
#include <SPI.h>

uint8_t buffer0[100];
uint8_t buffer1[100];

void setup() {
  while (!Serial && (millis() < 2000)) ;
  Serial.println("Test SPI DMA on SPI and SPI1");

  pinMode(2, OUTPUT);
  digitalWriteFast(2, HIGH);
  SPI.begin();
  for (uint16_t i = 0; i < sizeof(buffer0); i++) buffer0[i] = i;

  pinMode(3, OUTPUT);
  digitalWriteFast(3, HIGH);
  SPI1.begin();
  for (uint16_t i = 0; i < sizeof(buffer1); i++) buffer1[i] = sizeof(buffer1) - i;
}

void loop() {
  Serial.println("Start Two SPI transfers");
  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  digitalWriteFast(2, LOW);
  SPI1.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  digitalWriteFast(3, LOW);

  TransferTwoBuffers(buffer0, buffer1, sizeof(buffer0));

  digitalWriteFast(2, HIGH);
  SPI.endTransaction();

  digitalWriteFast(3, HIGH);
  SPI1.endTransaction();
  Serial.println("Finished transfers");
  delay(1000);
}
// Assume even number of bytes AND in standard MSB first order to simplify...
void TransferTwoBuffers(uint8_t *buffer1, uint8_t *buffer2, uint16_t count) {
  uint16_t w, w2;
  uint32_t sr, sr2;
  uint16_t count_read = count;
  uint16_t count_read2 = count;
  // Clear return buffers queue
  KINETISK_SPI0.MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F);
  KINETISK_SPI1.MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F);

  while (count > 0) {
    w = (*buffer1++) << 8;
    w |= *buffer1++;
    w2 = (*buffer2++) << 8;
    w2 |= *buffer2++;

    if (count == 2) {
      KINETISK_SPI0.PUSHR = w | SPI_PUSHR_CTAS(1);
      KINETISK_SPI1.PUSHR = w2 | SPI_PUSHR_CTAS(1);
    } else {
      KINETISK_SPI0.PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1);
      KINETISK_SPI1.PUSHR = w2 | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1);
    }
    // Lets loop while either of them have something in their FIFO?
    do {
      sr = KINETISK_SPI0.SR;
      sr2 = KINETISK_SPI1.SR;
      // remove anything in the queues...
      if (sr & 0xf0) {
        w = KINETISK_SPI0.POPR;
        count_read -= 2;
      }
      if (sr2 & 0xf0)  {
        w = KINETISK_SPI1.POPR;
        count_read2 -= 2;
      }

    } while ((sr & (15 << 12)) || (sr2 & (15 << 12))) ;
    count -= 2;
  }
  while (count_read || count_read2) {
    sr = KINETISK_SPI0.SR;
    sr2 = KINETISK_SPI1.SR;
    // remove anything in the queues...
    if (sr & 0xf0) {
      w = KINETISK_SPI0.POPR;
      count_read -= 2;
    }
    if (sr2 & 0xf0)  {
      w = KINETISK_SPI1.POPR;
      count_read2 -= 2;
    }
  }
}

Again I did this for output only as I think the idea was to drive two displays...

And it looks like it is working...
screenshot.jpg

But this was hard coded for same size count for two buffers (not hard to change) and also setup that counts are even sized... I did this to compress the writes on the queue... Did not fully fill SPI queue, but looks like it keeps both running full speed.
 
>>>Is there anyone can explain why the transferWrite() doesn't work on SPI1? Thanks in advance.

Have you investigated if the issue could be your wiring or your 2nd LED panel rather than SPI1. Perhaps your 2nd panel can't handle data at twice the rate.
Some things you could try:
Swapping your spi connections to each panel. Does the issue follow SPI1?
Making some of those jumper wires shorter on the breadboard.
Running separate power and grounds to each panel.
Adding a large value capacitor between power and ground on the breadboard.

Thank you for your suggestions. Both panels work well when I called the SPI.transfer() in the library, so the wiring should be OK. Both panels also have the same hardware and firmware, so I can rule out hardware issues.
 
Thank you for your suggestions. Both panels work well when I called the SPI.transfer() in the library, so the wiring should be OK. Both panels also have the same hardware and firmware, so I can rule out hardware issues.

Well no, you can't rule out hardware issues. In your first test the hardware is running at 1/2 the speed of the 2nd test because it is waiting for each SPI transaction to complete ( as you point out ). In the 2nd test the panels are switching at the same time instead of alternately and that will result in more noise and switching currents on the power lines. It may not be the hardware or wiring at fault, but it is worth investigating.
 
I tried transfer(const void * buf, void * retbuf, size_t count, evnt) in my sketch. Both panels didn't show any patterns for unknown reason although the time of transferring 132000 bytes reduced from 529ms to 1ms!!? When I replaced it with transfer(const void * buf, void * retbuf, size_t count); both SPI0 and SPI1 work well in a blocking mode. I listed my codes as follows for your reference.

Code:
#include <SPI.h>
IntervalTimer myTimer;
volatile uint8_t stripeRow = 0;
volatile uint16_t updateCnt = 0;
    
// Global constants
// ============================================================================

// SPI and and I2C communication parameters
//const uint8_t SPI_NUM_SLAVES = 8;                              // # panels which can be stacked 
const uint8_t SPI_NUM_SLAVES = 2;                                // # panels which can be stacked 
const uint8_t I2C_NUM_SLAVES = 4;                                // Four i2c slaves per panel
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,5,6,7,8,9,3,4};     // SPI chip select lines
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,4,5,6,7};             // SPI chip select lines
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,4,5};             // SPI chip select lines
const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {10,31};             // SPI chip select lines

const uint8_t I2C_TYPE_2_MSG_SIZE = 9;
const uint8_t I2C_TYPE_16_MSG_SIZE = 33;

const uint8_t SPI_TYPE_2_MSG_SIZE = 4*I2C_TYPE_2_MSG_SIZE;
const uint8_t SPI_TYPE_16_MSG_SIZE = 4*I2C_TYPE_16_MSG_SIZE;

EventResponder event;
EventResponder event1;

void Event_SPI0_Responder(EventResponderRef event_responder){
}

void Event_SPI1_Responder(EventResponderRef event_responder){
}

SPISettings settingsA(4200000, MSBFIRST, SPI_MODE0); 

//pattern sent to the panels, it is bar moving from left to right
const uint8_t barBuffer[16][132] ={{1,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,1,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,1,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,1,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,1,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,1,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,1,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,1,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,1,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,1,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,1,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,1,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,1,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,1,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,1,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,1,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15,0,0,0,15},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,1,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240,0,0,0,240}};

// PWM type identifiers
const uint8_t PWM_TYPE_2 = 0;
const uint8_t PWM_TYPE_16 = 1;
//const uint8_t PWM_TYPE_MASK = 0x01;

// Counter used for updating to next pattern in buffer  - sets stripe speed.
//const uint16_t PWM_UPDATE_CNT_TYPE_16 = 15;  
const uint16_t PWM_UPDATE_CNT_TYPE_16 = 30;  
const uint16_t PWM_UPDATE_CNT_TYPE_2 = 100;  

// Display configuration parameters
const uint8_t MATRIX_NUM_ROW = 8;
const uint8_t MATRIX_NUM_COL = 8;
const uint8_t PANEL_NUM_ROW = 2*MATRIX_NUM_ROW;


// Timing parameters
const uint16_t LOOP_DELAY_TYPE_16 =  300;   // us
const uint16_t LOOP_DELAY_TYPE_2 =  150;    // us


// Demo pwm type
const uint8_t DEMO_PWM_TYPE = PWM_TYPE_16;
//const uint8_t DEMO_PWM_TYPE = PWM_TYPE_2;


// Function prototypes
// ============================================================================

// Display update demo functions
inline void type16DisplayUpdate();


// Arduino entry point functions 
// ============================================================================  
void setup()
{
    Serial.begin(115200);   

    // Initialize SPI communications
    for (uint8_t spiSlave=0; spiSlave < SPI_NUM_SLAVES; spiSlave++)
    { 
        pinMode(SPI_PIN_ARRAY[spiSlave], OUTPUT);
        digitalWrite(SPI_PIN_ARRAY[spiSlave], HIGH);
    }

    event.attachImmediate(&Event_SPI0_Responder);
    event1.attachImmediate(&Event_SPI1_Responder);
    
    SPI.usingInterrupt(myTimer);
    SPI1.usingInterrupt(myTimer);
    SPI.begin();
    SPI1.begin();
    SPI.beginTransaction(settingsA);
    SPI1.beginTransaction(settingsA);

    SPI.transfer(0);
    SPI1.transfer(0);
    //check the SPI speed
        uint32_t wt;
        uint32_t start;
        uint32_t end;
        while(!Serial);
        Serial.print("\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nStart\r\n");
        Serial.flush();
        delay(2);
        start = millis();
        while(start == millis());   

    //measure the SPI throughts on SPI0 and SPI1
    for (uint16_t iteration=0; iteration<1000; iteration++)
    {
        digitalWrite(10, LOW);
        //SPI.transfer(&barBuffer[stripeRow][0],nullptr,132);
        SPI.transfer(&barBuffer[stripeRow][0],nullptr,132, event);
        digitalWrite(10, HIGH);
        
        digitalWrite(31, LOW);
        //SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132);
        SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132, event1);
        digitalWrite(31, HIGH);
        
        SPI.endTransaction();
        SPI1.endTransaction();
         
   }

        end = millis();

        wt = (end - start) - 1;
        Serial.print("Time to write 132000 bytes:");
        Serial.print(wt, DEC);
        Serial.println("ms");
        float bpms = 132000.0 / wt;
        Serial.print(bpms, 0);
        Serial.println("Bytes/msec");
        //printf_P(PSTR("%f Bytes/msec\r\n%f Bytes/sec\r\n"), bpms, bpms * 1000);


   myTimer.begin(type16DisplayUpdate, 1000); // type16DisplayUpdate to run every 1000 mseconds
   
}


void loop()
{

}


void type16DisplayUpdate()
{

    SPI.beginTransaction(settingsA);
    SPI1.beginTransaction(settingsA);

    digitalWrite(10, LOW);
    SPI.transfer(&barBuffer[stripeRow][0],nullptr,132, event);
    //SPI.transfer(&barBuffer[stripeRow][0],nullptr,132);
    digitalWrite(10, HIGH);
        
    digitalWrite(31, LOW);
    SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132, event1);
    //SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132);
    digitalWrite(31, HIGH);
 
    
    // Update pattern information
    updateCnt++;
    if (updateCnt%PWM_UPDATE_CNT_TYPE_16 == 0)
    {
        stripeRow = (stripeRow + 1)%PANEL_NUM_ROW;
    }
}
 
Last edited by a moderator:
Well no, you can't rule out hardware issues. In your first test the hardware is running at 1/2 the speed of the 2nd test because it is waiting for each SPI transaction to complete ( as you point out ). In the 2nd test the panels are switching at the same time instead of alternately and that will result in more noise and switching currents on the power lines. It may not be the hardware or wiring at fault, but it is worth investigating.

Please correct me if I am wrong. I think both panels are work at the same speed in both tests. In the first test, SPI0 and SPI1 works in a serial way. SPI1 starts transferring data after SPI0 finished its data transfer. In test2, SPI0 and SPI1 works in a parallel way. That means data are written to the SPI0 FIFO and then move to the next step. Same data are written to the SPI1 FIFO. then both SPIs will transfer data with a SPI clock rate 4Mhz individually.
 
You did not put in any of the code to wait for the Asynchronous transfers to complete. These calls return as soon as they setup the transfer and then you should wait until the transfer is completed...

Here is a version of your program that outputs something to both on in your setup function. Not sure yet about your other function. Edited, but did not look to see why it may not output...
Code:
#include <SPI.h>
IntervalTimer myTimer;

volatile uint8_t stripeRow = 0;
volatile uint16_t updateCnt = 0;

// Global constants
// ============================================================================

// SPI and and I2C communication parameters
//const uint8_t SPI_NUM_SLAVES = 8;                              // # panels which can be stacked
const uint8_t SPI_NUM_SLAVES = 2;                                // # panels which can be stacked
const uint8_t I2C_NUM_SLAVES = 4;                                // Four i2c slaves per panel
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,5,6,7,8,9,3,4};     // SPI chip select lines
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,4,5,6,7};             // SPI chip select lines
//const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {2,4,5};             // SPI chip select lines
const uint8_t SPI_PIN_ARRAY[SPI_NUM_SLAVES]  = {10, 31};            // SPI chip select lines

const uint8_t I2C_TYPE_2_MSG_SIZE = 9;
const uint8_t I2C_TYPE_16_MSG_SIZE = 33;

const uint8_t SPI_TYPE_2_MSG_SIZE = 4 * I2C_TYPE_2_MSG_SIZE;
const uint8_t SPI_TYPE_16_MSG_SIZE = 4 * I2C_TYPE_16_MSG_SIZE;

EventResponder event;
EventResponder event1;
volatile uint8_t SPI0_Active;
volatile uint8_t SPI1_Active;


void Event_SPI0_Responder(EventResponderRef event_responder) {
  SPI0_Active = 0;
  digitalWrite(10, HIGH);
  SPI.endTransaction();
}

void Event_SPI1_Responder(EventResponderRef event_responder) {
  SPI1_Active = 0;
  digitalWrite(31, HIGH);
  SPI1.endTransaction();
}

SPISettings settingsA(4200000, MSBFIRST, SPI_MODE0);

//pattern sent to the panels, it is bar moving from left to right
const uint8_t barBuffer[16][132] = {{1, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 1, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 1, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 1, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 1, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 1, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 1, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 1, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 1, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 1, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 1, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 1, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 1, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 1, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 1, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 1, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15},
  {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 1, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240, 0, 0, 0, 240}
};

// PWM type identifiers
const uint8_t PWM_TYPE_2 = 0;
const uint8_t PWM_TYPE_16 = 1;
//const uint8_t PWM_TYPE_MASK = 0x01;

// Counter used for updating to next pattern in buffer  - sets stripe speed.
//const uint16_t PWM_UPDATE_CNT_TYPE_16 = 15;
const uint16_t PWM_UPDATE_CNT_TYPE_16 = 30;
const uint16_t PWM_UPDATE_CNT_TYPE_2 = 100;

// Display configuration parameters
const uint8_t MATRIX_NUM_ROW = 8;
const uint8_t MATRIX_NUM_COL = 8;
const uint8_t PANEL_NUM_ROW = 2 * MATRIX_NUM_ROW;


// Timing parameters
const uint16_t LOOP_DELAY_TYPE_16 =  300;   // us
const uint16_t LOOP_DELAY_TYPE_2 =  150;    // us


// Demo pwm type
const uint8_t DEMO_PWM_TYPE = PWM_TYPE_16;
//const uint8_t DEMO_PWM_TYPE = PWM_TYPE_2;


// Function prototypes
// ============================================================================

// Display update demo functions
inline void type16DisplayUpdate();


// Arduino entry point functions
// ============================================================================
void setup()
{
  Serial.begin(115200);

  // Initialize SPI communications
  for (uint8_t spiSlave = 0; spiSlave < SPI_NUM_SLAVES; spiSlave++)
  {
    pinMode(SPI_PIN_ARRAY[spiSlave], OUTPUT);
    digitalWrite(SPI_PIN_ARRAY[spiSlave], HIGH);
  }

  event.attachImmediate(&Event_SPI0_Responder);
  event1.attachImmediate(&Event_SPI1_Responder);

  SPI.usingInterrupt(myTimer);
  SPI1.usingInterrupt(myTimer);
  SPI.begin();
  SPI1.begin();
  SPI.beginTransaction(settingsA);
  SPI1.beginTransaction(settingsA);

  SPI.transfer(0);
  SPI1.transfer(0);
  //check the SPI speed
  uint32_t wt;
  uint32_t start;
  uint32_t end;
  while (!Serial);
  Serial.print("\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nStart\r\n");
  Serial.flush();
  delay(2);
  start = millis();
  while (start == millis());

  //measure the SPI throughts on SPI0 and SPI1
  for (uint16_t iteration = 0; iteration < 1000; iteration++)
  {
    SPI0_Active = 1;
    SPI1_Active = 1;
    digitalWrite(10, LOW);
    //SPI.transfer(&barBuffer[stripeRow][0],nullptr,132);
    SPI.transfer(&barBuffer[stripeRow][0], nullptr, 132, event);

    digitalWrite(31, LOW);
    //SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132);
    SPI1.transfer(&barBuffer[stripeRow][0], nullptr, 132, event1);

    // Wait for this one to complete
    while (SPI0_Active || SPI1_Active) ;

  }

  end = millis();

  wt = (end - start) - 1;
  Serial.print("Time to write 132000 bytes:");
  Serial.print(wt, DEC);
  Serial.println("ms");
  float bpms = 132000.0 / wt;
  Serial.print(bpms, 0);
  Serial.println("Bytes/msec");
  //printf_P(PSTR("%f Bytes/msec\r\n%f Bytes/sec\r\n"), bpms, bpms * 1000);


  myTimer.begin(type16DisplayUpdate, 1000); // type16DisplayUpdate to run every 1000 mseconds

}


void loop()
{

}


void type16DisplayUpdate()
{

  SPI.beginTransaction(settingsA);
  SPI1.beginTransaction(settingsA);

  SPI0_Active = 1;
  SPI1_Active = 1;

  digitalWrite(10, LOW);
  SPI.transfer(&barBuffer[stripeRow][0], nullptr, 132, event);
  //SPI.transfer(&barBuffer[stripeRow][0],nullptr,132);

  digitalWrite(31, LOW);
  SPI1.transfer(&barBuffer[stripeRow][0], nullptr, 132, event1);
  //SPI1.transfer(&barBuffer[stripeRow][0],nullptr,132);

  // wait for the two transfers to complete
//  while (SPI0_Active || SPI1_Active) ;

  // Update pattern information
  updateCnt++;
  if (updateCnt % PWM_UPDATE_CNT_TYPE_16 == 0)
  {
    stripeRow = (stripeRow + 1) % PANEL_NUM_ROW;
  }
}

And here is the start of the output ...
screenshot.jpg

Edit: The function now works on the Timer, needed to remove the wait between rows. It assumes the previous transfers completed in time, which it does...
Here is another capture showing Interval timer outputs
screenshot2.jpg
 
Please correct me if I am wrong. I think both panels are work at the same speed in both tests. In the first test, SPI0 and SPI1 works in a serial way. SPI1 starts transferring data after SPI0 finished its data transfer. In test2, SPI0 and SPI1 works in a parallel way. That means data are written to the SPI0 FIFO and then move to the next step. Same data are written to the SPI1 FIFO. then both SPIs will transfer data with a SPI clock rate 4Mhz individually.

The SPI clock is the same as before, but with functions that do not block, the panels are handling up to twice as many writes in one second as they did with the blocking functions.
 
You did not put in any of the code to wait for the Asynchronous transfers to complete. These calls return as soon as they setup the transfer and then you should wait until the transfer is completed...


Super Kurt!!! After I added the codes to wait for the Asynchronous transfers to complete, the prototype works beautifully. By the way, why is it named as asynchronous transfer? I thought SPI is always synchronous because of the SCK between the master and slave.
 
Hard to know what to call it... Could simply call it non-blocking transfers...

Glad it is working better for you
 
The SPI clock is the same as before, but with functions that do not block, the panels are handling up to twice as many writes in one second as they did with the blocking functions.

I use a timer ISR to send each frame data through SPI0 and SPI1 every 1ms. So SPI throughput on each SPI is the same for both cases. The reason why I want to implement a non-blocking data transfer is to increase the frame rate.
 
Hard to know what to call it... Could simply call it non-blocking transfers...

Glad it is working better for you

To summarize the errors and trials we tried, can we say transferWrite() doesn't work for the SPI1 because there is no enough FIFO to support a non-blocking data transfer on SPI1. We have to use DMA to create a queue on each SPI bus in order to implement non-blocking data transfer?
 
have you tried teensythreads? that or intervaltimer may get you microseconds (uS, not ms) for faster rates...
 
To summarize the errors and trials we tried, can we say transferWrite() doesn't work for the SPI1 because there is no enough FIFO to support a non-blocking data transfer on SPI1. We have to use DMA to create a queue on each SPI bus in order to implement non-blocking data transfer?

There are lots of ways to be able to implement this. Posting #10 showed one function that output to both SPI and SPI1 at the same time, so you could get both displays done at the same time... But the code drives it directly, so the call won't return until all bytes have been update (or at least queued).

You could write a version of SPI transfer functions that enables interrupts and feeds in the next byte each interrupt... So a variety of ways you could do this.

Kurt
 
There are lots of ways to be able to implement this. Posting #10 showed one function that output to both SPI and SPI1 at the same time, so you could get both displays done at the same time... But the code drives it directly, so the call won't return until all bytes have been update (or at least queued).

You could write a version of SPI transfer functions that enables interrupts and feeds in the next byte each interrupt... So a variety of ways you could do this.

Kurt

I just tested TransferTwoBuffers() in post #10. It also works well for SPI0 and SPI1. Super Kurt!!!

Your example codes, such as event_responder and hard-coded TransferTwoBuffers() are very helpful for us. Would you kindly give me an example how to write a SPI transfer function that enables interrupts and feeds in the next byte each interrupt? Thank you very much.
 
Hi GeneL
I have been working on a project using a Teensy 3.6 and SPI 0 & 1. I had to read blocks of 164 bytes from two cameras at 20 MHz. By modifying the transfer function to:
void transfer1(uint8_t data)
{
port().SR = SPI_SR_TCF;
port().PUSHR = data;
}
uint8_t transfer2(void)
{
while (!(port().SR & SPI_SR_TCF)) ; // wait
return port().POPR;
}
.. I was able to read 2 * 164 bytes in 120 us. And I was even able to execute some code between the calls to transferx().

Example from my *.ino:
for (ui8 = 0; ui8 < PAC_SIZE; ui8++)
{
SPI.transfer1(0); // start two SPI readings
SPI1.transfer1(0);
// .. do something here
pac0[ui8] = SPI.transfer2(); // wait for SPI end and read data
pac1[ui8] = SPI1.transfer2();
}

I hope this makes sense!
 
Status
Not open for further replies.
Back
Top