Arduino Forum

Products => Arduino Due => Topic started by: mcleung on Mar 28, 2014, 10:39 pm

Title: Continuous SPI for multiple bytes
Post by: mcleung on Mar 28, 2014, 10:39 pm
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
https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino



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.

Code: [Select]
#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");
}
Title: Re: Continuous SPI for multiple bytes
Post by: Magician on Mar 28, 2014, 11:02 pm
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.
Title: Re: Continuous SPI for multiple bytes
Post by: graynomad on Mar 29, 2014, 12:25 am
Code: [Select]
  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
Title: Re: Continuous SPI for multiple bytes
Post by: dlloyd on Mar 29, 2014, 02:39 am
Try this line right after SPI.setDataMode ...
Code: [Select]
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).
Title: Re: Continuous SPI for multiple bytes
Post by: MarkT on Mar 29, 2014, 03:03 pm

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.
Title: Re: Continuous SPI for multiple bytes
Post by: mcleung on Mar 31, 2014, 11:24 pm
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.
http://e2e.ti.com/support/applications/high_reliability/f/30/t/331309.aspx

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 :)

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.


Try this line right after SPI.setDataMode ...
Code: [Select]
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).

Title: Re: Continuous SPI for multiple bytes
Post by: Magician on Mar 31, 2014, 11:50 pm
I wander, what they may respond, if there is an ARDUINO library experts working in TI ?
Title: Re: Continuous SPI for multiple bytes
Post by: graynomad on Apr 01, 2014, 12:46 am
Quote
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
Title: Re: Continuous SPI for multiple bytes
Post by: dlloyd on Apr 01, 2014, 02:13 am
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.
Title: Re: Continuous SPI for multiple bytes
Post by: mcleung on Apr 01, 2014, 03:47 am
@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 -> )
https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino

Arduino community is awesome, I get answers much faster here than with TI  :)
Title: Re: Continuous SPI for multiple bytes
Post by: dlloyd on Apr 01, 2014, 04:55 am
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)  ;)
Title: Re: Continuous SPI for multiple bytes
Post by: graynomad on Apr 01, 2014, 12:27 pm
Quote
You don't mean synchronous serial, you mean SPI.

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

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


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


______
Rob
Title: Re: Continuous SPI for multiple bytes
Post by: mcleung on Apr 01, 2014, 08:13 pm
One line solution did not help.

DMA found at https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino 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 :)

(http://i.imgur.com/z6ZlOlX.png)
Title: Re: Continuous SPI for multiple bytes
Post by: graynomad on Apr 01, 2014, 10:57 pm
Yikes.

Quote
probably my wires can't handle the high frequency.

I would say so.

______
Rob
Title: Re: Continuous SPI for multiple bytes
Post by: dlloyd on Apr 02, 2014, 03:26 am
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)

http://arduino.cc/en/Reference/DueExtendedSPI

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.
Title: Re: Continuous SPI for multiple bytes
Post by: mcleung on Apr 02, 2014, 03:22 pm
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 :)

Code: [Select]
/*
* 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.
https://github.com/manitou48/DUEZoo/blob/master/dmaspi.ino

Code: [Select]
// SPI functions
//==============================================================================
#if USE_ARDUINO_SPI_LIBRARY
#include <SPI.h>
Title: Re: Continuous SPI for multiple bytes
Post by: dlloyd on Apr 02, 2014, 04:20 pm
Quote
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...

Code: [Select]
// 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;
}
Title: Re: Continuous SPI for multiple bytes
Post by: mcleung on Apr 03, 2014, 12:02 am
Right, because PORTD is for the Uno/Mega...
Thanks!