Continuous SPI for multiple bytes

As of April 2, 2014, this has been fixed using DMA SPI. See reply #15 for working copy of the code.
DMA SPI obtained from
DUEZoo/dmaspi.ino at master · manitou48/DUEZoo · GitHub

Hi guys,

I'm trying to program a ship that requires 4 wire serial programming (essentially SPI)
For those who are curious, it's the LM96570.

The chip uses up to 80MHz on the serial line.
I've been able to create a simple program for my Due to communicate with the chip.
The issue I'm having is that although the SPICONTINUE works as it is intended to (by leaving the LE low), the data is transmitted in little "burts" of information, one byte (8 bits) at a time, then a long delay before the next byte.
The clock flips as I expect it to, but again, flips 8 times and then there is a long pause.

I am suspecting that it's causing mis-communication problems with my IC chip, and the lowest I've been able to achieve a constant clock is down at 800KHz. I was wondering if any of you had any luck pushing continuous SPI communication to tens of MHz without these long pauses between bytes.

Any suggestions to make my code more efficient is also welcomed.
I am passing the bytes from a python serial script.

#include <SPI.h>
// Define SPI Pins
// Can only use 4 10 and 52
#define LEPin 10


// Save Words to write to API
// Integer is 4 byte in Due (32 bits)
// Worst Case scenario is need to write 70 bits (5 addr + 0 + 64 data)
unsigned char Word1 = 0xAA;
unsigned char Word2 = 0xFF;
unsigned char Word3 = 0x00;
unsigned char Word4 = 0x00;
unsigned char Word5 = 0x00;
unsigned char Word6 = 0x00;
unsigned char Word7 = 0x00;
unsigned char Word8 = 0x00;
unsigned char Word9 = 0x00;


void setup() 
{
  // Set up Serial communication (Computer)
  Serial.begin(115200);
  Serial.println("Initialisation Started");
  
  // Set up SPI communication (Chip)
  SPI.begin(LEPin);
  SPI.setClockDivider(LEPin, 42); // Desired 80 MHz Due is 84, so try div 2
  // 84 should correspond to 1MHz
  SPI.setBitOrder(LEPin, MSBFIRST);
  
  // TODO: SPI Set Data Mode http://arduino.cc/en/Reference/SPISetDataMode
  SPI.setDataMode(LEPin, 0);
  
  Serial.println("Initialisation Completed");
}


void loop() {
  if (Serial.available()) {
    serialRead();
  }
}

/*
  SerialEvent occurs whenever a new data comes in the
 hardware serial RX.  This routine is run between each
 time loop() runs, so using delay inside loop can delay
 response.  Multiple bytes of data may be available.
*/
void serialRead() {
  Serial.println("    "); // Unknown why it's required
  
  //Serial.print("READ");
  Word1 = Serial.read();
  Word2 = Serial.read();
  Word3 = Serial.read();
  Word4 = Serial.read();
  Word5 = Serial.read();
  Word6 = Serial.read();
  Word7 = Serial.read();
  Word8 = Serial.read();
  Word9 = Serial.read();
  //Serial.print("READSUCESS");
  
  SPI.transfer(LEPin, Word1, SPI_CONTINUE);
  SPI.transfer(LEPin, Word2, SPI_CONTINUE);
  SPI.transfer(LEPin, Word3, SPI_CONTINUE);
  SPI.transfer(LEPin, Word4, SPI_CONTINUE);
  SPI.transfer(LEPin, Word5, SPI_CONTINUE);
  SPI.transfer(LEPin, Word6, SPI_CONTINUE);
  SPI.transfer(LEPin, Word7, SPI_CONTINUE);
  SPI.transfer(LEPin, Word8, SPI_CONTINUE);
  SPI.transfer(LEPin, Word9, SPI_LAST);
    
  // Return 1 so computer knows that Arduino is ready to receive next command
  delay(1000);
  Serial.print("1");
  //Serial.print("DONE");
}

I'm not sure about 10th of MHz, but to go faster you better use DMA + SPI channel, w/o a library. Look inside spi.c file for start.

  if (Serial.available()) {
    serialRead();
...
  Word1 = Serial.read();
  Word2 = Serial.read();
  Word3 = Serial.read();
  Word4 = Serial.read();
  Word5 = Serial.read();
  Word6 = Serial.read();
  Word7 = Serial.read();
  Word8 = Serial.read();
  Word9 = Serial.read();

You wait for what is almost certainly a single byte then read 9 bytes from the serial port, that doesn't seem kosher.

The whole point of sync serial comms is that it's not timing dependant, gaps between bytes should have no effect.

How about you test one thing at a time, try sending hard-coded bytes to the chip, when that works prove that you are receiving bytes correctly, then join the two up.


Rob

Try this line right after SPI.setDataMode ...

SPI0->SPI_CSR[0] = SPI0->SPI_CSR[0] & 0xFFFFFF; // clear delay between consecutive transfers (DLYBCT = 0)

Note: The default (DLYBCT = 1) keeps CS enabled for 32 MCLK after a completed transfer. Some device needs that for working properly (from spi.cpp).

Graynomad:
The whole point of sync serial comms is that it's not timing dependant, gaps between bytes should have no effect.

You don't mean synchronous serial, you mean SPI. Synchronous serial is stuff like I2S
that can be driven by the SSC module and for which timing is everything.

I'll give this a try and report back to you guys.
I've posted the corresponding question to the TI E2E forums, and I'm awaiting their responses.

I would hope that the clock doesn't need to be synchronous (hence why it needs a clock line) but we will see what the TI Engineers say. Hopefully I don't need to port my system to an FPGA to meet the timing requirements, I prefer programming with Arduino :slight_smile:

Graynomad: I know, for now I am transferring a total of 9 bytes, but eventually I want to be able to transfer an arbitrary number of bytes.

dlloyd:
Try this line right after SPI.setDataMode ...

SPI0->SPI_CSR[0] = SPI0->SPI_CSR[0] & 0xFFFFFF; // clear delay between consecutive transfers (DLYBCT = 0)

Note: The default (DLYBCT = 1) keeps CS enabled for 32 MCLK after a completed transfer. Some device needs that for working properly (from spi.cpp).

I wander, what they may respond, if there is an ARDUINO library experts working in TI ?

I know, for now I am transferring a total of 9 bytes, but eventually I want to be able to transfer an arbitrary number of bytes.

Either way the code is wrong, you can't just do N Serial.reads() unless you know there are N characters to read.


Rob

To elaborate a little more about changing DLYBCT, I've been able to communicate with SPI using 0 delay between transfers at a clock speed of 12 MHz and temporarily at 30 MHz, but my interconnect leads were too long to maintain 30 MHz reliably. A major difference with my testing is that I had the Due in SPI slave mode and directly utilized the SPI registers. The master (which controlled the SCLK rate) was an FTDI 2232H interface.

Note that Due's SPI can be set for 16 bit (maximum) transfer mode. When transferring more than 16 bits, there needs to be enough time to check the flags and read the Receive Data Register (a few MCLK cycles). This may limit the maximum SCLK rate for reliable transmission, but perhaps 21 MHz would work when the delay between transfers is set to 0.

The delay between transfers by default will put a 32 MCK gap between every transfer. It can only be changed in steps of 32 MCKs.

The drawback for large bit size continuous transfers is that low level programming using the SPI User Interface is required.

@dlloyd, I'm not too familiar with low level programming on micro-controllers. I'll try implementing your one line change tomorrow. But from my understanding is that the SPI library coded the delay, and therefore I can remove/override it with DLYBCT = 0.

I also found this DMA SPI on Github. Would I be able to use it? (I'm trying to understand the arrow notation -> )

Arduino community is awesome, I get answers much faster here than with TI :slight_smile:

Yes, the one line will only change DLYBCT to 0 and keep all other settings. I'm still quite green at "C" and digging under the hood of Arduino. Just starting to use the arrow notation in spi.c. To me, its descriptive enough, but knowing where all the variables are set and a list of all possible arrow notation commands would be good to have. I find ATMEL's ASF and website good resources also.

I've also seen the DMA SPI on Github. I think DMA would be beneficial for your application, especially as you get in to the high end of SPI clock rates. Unfortunately, I've never used it and am waiting for more examples.

Good luck with your project ... and by the way, All I can guarantee is that the code "should" copy and paste OK - that's it. (after seeing that the LM96570 chip is used medical ultrasound applications) :wink:

You don't mean synchronous serial, you mean SPI.

Any serial comms with an accompanying clock signal is synchronous AFAIK.

Wiki

The Serial Peripheral Interface or SPI bus is a synchronous serial data link,

Arduino reference

Serial Peripheral Interface (SPI) is a synchronous serial data protocol


Rob

One line solution did not help.

DMA found at DUEZoo/dmaspi.ino at master · manitou48/DUEZoo · GitHub works perfectly.
I tested at 42MHz (Div by 2)

Still didn't manage to program the chip, but I don't think it's Arduino's fault anymore, probably my wires can't handle the high frequency.
See scope obtained, I transmitted 0xAAAA 0xAAAA ... etc
Channel 1 is the MOSI, at 42 MHz
Channel 2 is the latch enable which is pulled low!

Thanks for your help everyone :slight_smile:

Yikes.

probably my wires can't handle the high frequency.

I would say so.


Rob

Just some observations:

From my understanding, it looks like latch enable (Channel 2) is your CS line which is continuously held low and never goes high to end the transfer which prevents the chip from being programmed.

dmaspi.ino from GitHub says "a year ago" and this sketch is always controlling the CS (pin 10) independently with digitalWrite. However, Arduino (newer 1.5X versions) already automatically controls CS so it looks like this could create a conflict (especially if line 3 is changed to #define USE_ARDUINO_SPI_LIBRARY 1)

33.7.3.9 Peripheral Deselection with DMAC (page 691 in the datasheet):
It looks like there's some relevant info here as to why NPCS does not rise in all cases.

Yes, my CS goes low and back high, it's just off the frame. I'm using the CS down slope to trigger my scope.
Here is an updated copy of my code (working) for those in the future who may run into this problem also.

My next step is to remind myself how to do direct port manipulation for the CS LOW/HIGH, as there is a long delay before / after the actual transfer before the CS pin flips.
IIRC should be something like PORTD = PORTD & 0b00100000 ....
Anyhow, I don't need help on that, I can figure it out :slight_smile:

/*
 * SPI Communication via Arduino, version 2.0 using DMA
 * Send via USB Serial
 * Used for programming/Interfacing with LM96570
 * 
 * DMASPI obtained from
 * https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino
 *
 * Author: Michael, 2014
 * mcleung@uwaterloo.ca
 */

// dmaspi   from SdFat lib

#define USE_ARDUINO_SPI_LIBRARY 0
#define  USE_NATIVE_SAM3X_SPI 1

// #include <SPI.h>
#define USE_ARDUINO_SPI_LIBRARY 0
#define USE_NATIVE_SAM3X_SPI 1

#define CS 10
#define SPI_RATE 2 // 84 / SPI_RATE = 42 MHz

// Save Words to write to chip
#define SPI_BUFF_SIZE 9
uint8_t rx_buffer[SPI_BUFF_SIZE]; // Not used
uint8_t tx_buffer[SPI_BUFF_SIZE];

void setup() 
{
  // Set up Serial communication (Computer)
  Serial.begin(115200);
  Serial.println("Initialisation Started");
  pinMode(CS,OUTPUT);
  digitalWrite(CS,HIGH);
  spiBegin();
  spiInit(SPI_RATE);
  
  Serial.println("Initialisation Completed");
}


void loop() {
  if (Serial.available()) {
    serialRead();
  }
}

/*
  SerialEvent occurs whenever a new data comes in the
 hardware serial RX.  This routine is run between each
 time loop() runs, so using delay inside loop can delay
 response.  Multiple bytes of data may be available.
*/
void serialRead() {
  Serial.println("    "); // Unknown why it's required
  tx_buffer[0]=Serial.read();
  tx_buffer[1]=Serial.read();
  tx_buffer[2]=Serial.read();
  tx_buffer[3]=Serial.read();
  tx_buffer[4]=Serial.read();
  tx_buffer[5]=Serial.read();
  tx_buffer[6]=Serial.read();
  tx_buffer[7]=Serial.read();
  tx_buffer[8]=Serial.read();
  //Serial.print("READ");
  //Word1 = Serial.parseInt();
  //Serial.print("READSUCESS");
  digitalWrite(CS,LOW);
  spiSend(tx_buffer,SPI_BUFF_SIZE);
  digitalWrite(CS,HIGH);

  // Return 1 so computer knows that Arduino is ready to recieve next command
  delay(1000);
  Serial.print("1");
  //Serial.print("DONE");

  // Clear unsused bytes from the buffer
  // This should only occur from transmission errors
  byte dummy;
  while(Serial.available() > 0) {
    dummy = Serial.read();
  }
}

And then the DMA SPI part, copied word for word, starting from line 37.
I can't post the entire part as I'm over the character limit.

// SPI functions
//==============================================================================
#if USE_ARDUINO_SPI_LIBRARY
#include <SPI.h>

My next step is to remind myself how to do direct port manipulation for the CS LOW/HIGH, as there is a long delay before / after the actual transfer before the CS pin flips.
IIRC should be something like PORTD = PORTD & 0b00100000 ....

There is a digitalWriteDirect function (from another thread) that I think is about 9X faster...

// example: digitalWriteDirect(10, HIGH)
inline void digitalWriteDirect(int pin, boolean val){
  if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
  else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
}

Right, because PORTD is for the Uno/Mega...
Thanks!