Teensy 3 SPI Basic Clock Questions

Status
Not open for further replies.

t3andy

Well-known member
What is the "default" SPI clock rate on the Teensy 3?

Please note: I would like to do a search, on this board, for only "SPI" but it does not work?


Does the default clock rate change with the K20 CPU clock frequency?

If so, what are the values below?

24 MHZ = x SPI clock rate ?

48 MHZ = x SPI clock rate ?

96 MHz (overclocked) = x SPI clock rate ?



Can you adjust this SPI "default" clock rate on the Teensy 3?


I found in the SPI library these defines

//#define SPI_CLOCK_DIV4 0x00
//#define SPI_CLOCK_DIV16 0x01
//#define SPI_CLOCK_DIV64 0x02
//#define SPI_CLOCK_DIV128 0x03
//#define SPI_CLOCK_DIV2 0x04
//#define SPI_CLOCK_DIV8 0x05
//#define SPI_CLOCK_DIV32 0x06

SPI.setClockDivider(SPI_CLOCK_DIV128);


Will the above command work on the Teensy 3?
 
Code:
#include <SPI.h>

const int  cs=9; //chip select 

//#define SPI_CLOCK_DIV2 0x04
//#define SPI_CLOCK_DIV4 0x00
//#define SPI_CLOCK_DIV8 0x05
//#define SPI_CLOCK_DIV16 0x01
//#define SPI_CLOCK_DIV32 0x06
//#define SPI_CLOCK_DIV64 0x02
//#define SPI_CLOCK_DIV128 0x03

void setup() {
  // set the cs as an output:
  pinMode(cs,OUTPUT); // chip select
}

void loop() { 
  delay(10000); // wait tens seconds for T3
  Serial.println("Start SPI Test");  
  // start the SPI library:
  SPI.begin(); 
  //SPI speed @ CPU Clock Teensy 3               @ 24  MHz      @ 48 Mhz       @ 96 MHz - (overclock)
  //SPI.setClockDivider(SPI_CLOCK_DIV2);    // ~1.738  MHz     3.039 MHz      3.275 MHz
  //SPI.setClockDivider(SPI_CLOCK_DIV4);    // ~1.211  MHz     2.202 MHz      2.351 MHz  <-- Default Teensy 3 SPI ?
  //SPI.setClockDivider(SPI_CLOCK_DIV8);    //   ~754  KHz     1.420 MHz      1.516 MHz
  //SPI.setClockDivider(SPI_CLOCK_DIV16);   //   ~446  KHz       860 KHz        886 KHz
  //SPI.setClockDivider(SPI_CLOCK_DIV32);   //   ~238  KHz       466 KHz        476 KHz
  //SPI.setClockDivider(SPI_CLOCK_DIV64);   //   ~127  KHz       251 KHz        253 KHz
  //SPI.setClockDivider(SPI_CLOCK_DIV128);  //   ~ 65  KHz       129 KHz        130 KHz
  
  SPI.setBitOrder(MSBFIRST); 
  SPI.setDataMode(SPI_MODE3);  
start: 
  digitalWrite(cs, LOW); 
  SPI.transfer(-1);
  SPI.transfer(0); 
  digitalWrite(cs, HIGH);
  goto start;   
}

I will answer my own questions to tech support.

Got out the Wavetek to measure the SPI frequencies at different clock speeds on the Teensy 3 (Beta8)


#1 Default SPI clock rate on Teensy 3 //SPI.setClockDivider(SPI_CLOCK_DIV4); // ~1.211 MHz @24 2.202 MHz @48 2.351 MHz @96 <-- Default Teensy 3 SPI ?

#2. Yes, the SPI clock rate changes when the Teensy 3 K20 clock changes. (see values above)

#3. Yes, the SPI command "SPI.setClockDivider(SPI_CLOCK_DIVx);" does change the SPI clock frequency. It should be always set (used) when using SPI.;)

I like to point out that a user needs to know, at all times, what the SPI clock frequency is under
different K20 CPU clock frequencies. If a SPI device specify a maximum of 2 Mhz then exceeding this spec
might cause problems. Also, as further help, using the Arduino IDE, there should be another helpful indication on
the bottom task bar, along with the "Teensy type" and "comport port". The Teensy 3 K20 "CPU Mhz" should also be displayed.
 
Last edited:
The SPI question that begs to be answered is why limit the Teensy 3 SPI clock rate only 3.275 Mhz? The Arduino can run SPI faster than the K20? :confused:
 
Last edited:
The SPI question that begs to be answered is why limit the Teensy 3 SPI clock rate only 3.275 Mhz? The Arduino can run SPI faster than the K20? :confused:

If you read the datasheets on the mic that Teensy 3 uses, it's DSPI interface can run 25MHz at a limited voltage range and 12.5MHz over a full voltage range. Given that its a different serial interface (DSPI v. SPI), I would imagine that the Arduino library needs tweaks to make the most of the Teensy's SPI interface (such as setting the double clock bit, etc. etc.). I've been digging into this in order to maximize the speed at which I can write to a AMC7812 control chip (adds 16adc, 12dac, 8gpio over SPI up to 50MHz or I2C up to Fast Mode), so if I figure out the modifications to get the most out of the interface, I will gladly share.
 
Teensy 3.0's SPI port can run at 24 Mbit/sec speed, but the SPI library isn't going to work for high speed.

Aide from lacking the option to set faster than 8 Mbit/sec, the main problem is SPI.transfer() sends a byte and waits for all the bits to be shifted, so it can return the received byte. As you attempt higher speeds, the overhead of waiting and looping, even with the processor at 96 MHz, becomes a substantial part of the time. The hardware has a small FIFO which is intended to allow using the SPI at the maximum speed with no delay between transfers. But you can't use the FIFO with the Arduino SPI library. You'll need to use native code to fully utilize the SPI port.

Right now, the very native best code is in Bill Greiman's SdFat library. I also have some code I wrote, which I sent to Bill when he ported SdFat to Teensy 3.0. Bill made improvements and did a lot of speed testing, so your best option it to look at Bill's code. You'll need to get the new beta version. Here's the page to download it. Look for the Dec 3 version.

http://code.google.com/p/beta-lib/downloads/list

Look in Sd2Card.cpp. There is a LOT of code in that file, supporting high speed SPI in lots of ways, but most of it is for other chips, so don't be discouraged by the amount of code. The part you want is selected by USE_NATIVE_MK20DX128_SPI.

Bill's added some impressive features, like using both 8 and 16 bit transfers for lower overhead when 2 bytes are needed. Bill's spiSend(buffer, len) function is extremely fast when you need to send lots of bytes.
 
Hey guys, I am about to get some Teensy3's to upgrade my Atmega328's. I don't have a lot of SPI experience, but If I understand correctly, SPI has much faster transfer rates than serial. Is there a good way to use SPI to communicate between 2 teensy's? Or a library I can pull source from? I need something that can allow two boards to talk very fast. And if SPI isn't my solution, is there some other recommendation, like usb or something? Serial just won't work well for my applications.
 
SPI is the fastest way, but unfortunately all the software that exists only supports master mode. There's no library (yet) which supports slave mode. Writing such a library isn't simple, and it would be particularly complex to do with support for the fastest speeds. You'd certainly need to use DMA and complex buffer management to keep up with the highest speeds. It's theoretically possible, but in practice that software would be incredibly difficult to write.

I know you said serial won't work.... but why? Teensy 3.0 has a FIFO on Serial1 (but not on Serial2 and Serial3), so it's capable of fast baud rates. Exactly how fast has never been carefully tested, but I believe 1000000 baud is likely to work. The nice thing about Serial is the code is very well developed and easy to use.
 
Be a little cautious of some of the code in SdFat though; it does some nasty things, like setting MCR explicitly rather than changing bits it needs and leaving the rest alone.

The biggest issue this causes, is that it clobbers any attempts you might make to use hardware chip selects, you'll wind up with all the CS pins going active whenever you use SdFat calls.

But it's well worth a look. I used similar techniques (8/16 bit, 24MHz clock, FIFO) with my Teensy 3 optimized ST7735 LCD library.

- Peter
 
FYI, I ran some unconnected SPI tests with the SdFat SPI routines and the default SPI routines on teensy 3.0. See file SPIperf.txt at

https://github.com/manitou48/DUEZoo

There are also some SPI tests with SD (SDperf.txt) and with the W5200 ether chip (wizperf.txt). Results include tests with UNO, maple, and DUE.

your mileage may vary.
 
Yup. Those test results confirm the Arduino SPI library, built around the unbuffered AVR SPI, really isn't any good at utilizing the full potential of more advanced hardware!
 
Indeed.

I'm seeing around 20Mbit/s pixel fill (write) speed to my LCD via 24MHz SPI. I would assume SD card would be similar, within limitations of the card.

- Peter
 
I've considered writing a high performance SPI library. But at this point, there's 3 major problems.

1: It would depend on collaborating with others to support non-Teensy boards like Due and Maple.

2: I'm not sure what API would work well for all the hardware types. Obviously handling big buffers is a must. Maybe it should allow queuing multiple transfers? The API design would be tricky to keep Arduino-style simple yet allow good performance.

3: I have limited time available....

Over the long term, a high performance library would allow lots of other libraries to talk to high speed SPI devices and be compatible across boards, hopefully only requiring the SPI library to be ported to new boards?
 
Would this same issue apply to IC's like the TLC5940?
Spec says they can go upto 30Mhz, but if your stuck at 3Mhz on the Teensy 3 its kind of a killjoy.

My current project in planning requires me to send close to 350KBytes/S to 12 TLC5940's, and the faster the data moves means more time to do other things in between bursts.
My current plan is to send 144 Bytes every 428uS (337KB/s) and if possible every 300uS(480KB/s).

I'm planning a Mk2 Version of this.
Skip to 2:24 to avoid most of the boring stuff ;)
This was capstone I did with a friend.
This thing has a few short comings and I am looking to build something that avoids those. And adds alot more features and power.
 
Last edited:
So there is noone even willing to attempt to tackle SPI on the Teensy 3?

Is it possible to bitbang the teensy 3 fast enough to get around 15Mhz speeds?
 
I'm sure there will ultimately be a way to do it via a library, but in the meantime you can do it manually without too much trouble. The biggest issue may be interoperability with other SPI code/libraries, depending what you're using.

Here's some macros adapted from what I use for 24MHz SPI to the ST7735. Note this uses "software" chip select, as hardware chip select isn't really compatible with other libraries at this point.

Code:
Define SPI clock speed:
#define BAUD_DIV 0 /* 24MHz SPI */
//#define BAUD_DIV 1 /* 12MHz SPI */
//#define BAUD_DIV 2 /* 8MHz SPI */
//#define BAUD_DIV 3 /* 6MHz SPI */
//#define BAUD_DIV 4 /* 3MHz SPI */

Globals/members:
  uint32_t ctar0, ctar1, mcr;

Init code:
  // First set up SPI via library
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);

  // Now reconfigure some stuff
  
  // remember MCR
  mcr = SPI0_MCR;

  // Use both CTARs, one for 8 bit, the other 16 bit
  ctar0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(BAUD_DIV) | SPI_CTAR_CSSCK(BAUD_DIV) | SPI_CTAR_DBR;
  ctar1 = SPI_CTAR_FMSZ(15) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(BAUD_DIV) | SPI_CTAR_CSSCK(BAUD_DIV) | SPI_CTAR_DBR;

  SPI0_CTAR0 = ctar0;
  SPI0_CTAR1 = ctar1;

//And some macros:

#define SPI_WRITE_8(c) \
	do { \
		while ((SPI0_SR & SPI_SR_TXCTR) >= 0x00004000); \
		SPI0_PUSHR = ((c)&0xff) | SPI0_PUSHR_CTAS(0) | SPI0_PUSHR_CONT; \
	} while(0)

#define SPI_WRITE_16(w) \
	do { \
		while ((SPI0_SR & SPI_SR_TXCTR) >= 0x00004000); \
		SPI0_PUSHR = ((w)&0xffff) | SPI0_PUSHR_CTAS(1) | SPI0_PUSHR_CONT; \
	} while(0)

#define SPI_WAIT() \
	while ((SPI0_SR & SPI_SR_TXCTR) != 0); \
	while (!(SPI0_SR & SPI_SR_TCF)); \
	SPI0_SR |= SPI_SR_TCF;

#define SPI_CS_ON(pin) digitalWrite(pin, 0); \
		SPI0_CTAR0 = ctar0; SPI0_CTAR1 = ctar1; SPI0_MCR = mcr;

#define SPI_CS_OFF(pin) SPI_WAIT(); \
		digitalWrite(pin, 1);

So, more or less, having done the appropriate init code, you should be able to send a sequence of bytes via:

Code:
void spi_send_bytes(uint8_t *data, int len)
{
  SPI_CS_ON(MY_CS_PIN);
  for (int i=0; i < len; i++)
    SPI_WRITE_8(data[i]);
  SPI_CS_OFF(MY_CS_PIN);
}

Or for word transfers:

Code:
void spi_send_words(uint16_t *data, int len)
{
  SPI_CS_ON(MY_CS_PIN);
  for (int i=0; i < len; i++)
    SPI_WRITE_16(data[i]);
  SPI_CS_OFF(MY_CS_PIN);
}

Or just mix and match:

Code:
  SPI_CS_ON(MY_CS_PIN);
  SPI_WRITE_8(12);
  SPI_WRITE_16(1234);
  SPI_WRITE_16(5678);
  SPI_WRITE_8(34);
  SPI_WRITE_16(1234);
  SPI_WRITE_16(5678);
  SPI_CS_OFF(MY_CS_PIN);


Hope this helps,
- Peter
 
Thanks Ploveday, that's a start in the right direction.

I think one of the big factors thats killing alot of us less educated people is that we just cant find documentation on how the libraries work.
Ive had a basic C programming class and using small command prompt programs on a computer and understanding what alot of what I see in arduino libraries is just totally different.


OK, playing around, either I put it in the wrong place or im missing something or probably both. :( Think I will sleep on it.

Code:
#include <SPI.h>


//Define SPI clock speed:
#define BAUD_DIV 0 /* 24MHz SPI */
//#define BAUD_DIV 1 /* 12MHz SPI */
//#define BAUD_DIV 2 /* 8MHz SPI */
//#define BAUD_DIV 3 /* 6MHz SPI */
//#define BAUD_DIV 4 /* 3MHz SPI */

//Globals/members:
  uint32_t ctar0, ctar1, mcr;

void setup(){
//Init code:
  // First set up SPI via library
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);

  // Now reconfigure some stuff
  
  // remember MCR
  mcr = SPI0_MCR;

  // Use both CTARs, one for 8 bit, the other 16 bit
  ctar0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(BAUD_DIV) | SPI_CTAR_CSSCK(BAUD_DIV) | SPI_CTAR_DBR;
  ctar1 = SPI_CTAR_FMSZ(15) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(BAUD_DIV) | SPI_CTAR_CSSCK(BAUD_DIV) | SPI_CTAR_DBR;

  SPI0_CTAR0 = ctar0;
  SPI0_CTAR1 = ctar1;

//And some macros:

#define SPI_WRITE_8(c) \
	do { \
		while ((SPI0_SR & SPI_SR_TXCTR) >= 0x00004000); \
		SPI0_PUSHR = ((c)&0xff) | SPI0_PUSHR_CTAS(0) | SPI0_PUSHR_CONT; \
	} while(0)

#define SPI_WRITE_16(w) \
	do { \
		while ((SPI0_SR & SPI_SR_TXCTR) >= 0x00004000); \
		SPI0_PUSHR = ((w)&0xffff) | SPI0_PUSHR_CTAS(1) | SPI0_PUSHR_CONT; \
	} while(0)

#define SPI_WAIT() \
	while ((SPI0_SR & SPI_SR_TXCTR) != 0); \
	while (!(SPI0_SR & SPI_SR_TCF)); \
	SPI0_SR |= SPI_SR_TCF;

#define SPI_CS_ON(pin) digitalWrite(pin, 0); \
		SPI0_CTAR0 = ctar0; SPI0_CTAR1 = ctar1; SPI0_MCR = mcr;

#define SPI_CS_OFF(pin) SPI_WAIT(); \
		digitalWrite(pin, 1);
}

void loop(){
 
 // empty till I fix the Init script....
  
  
}

SPI_screwup.ino: In function 'void setup()':
SPI_screwup:24: error: 'SPI0_MCR' was not declared in this scope
SPI_screwup:27: error: 'SPI_CTAR_FMSZ' was not declared in this scope
SPI_screwup:27: error: 'SPI_CTAR_PBR' was not declared in this scope
SPI_screwup:27: error: 'SPI_CTAR_BR' was not declared in this scope
SPI_screwup:27: error: 'SPI_CTAR_CSSCK' was not declared in this scope
SPI_screwup:27: error: 'SPI_CTAR_DBR' was not declared in this scope
SPI_screwup:30: error: 'SPI0_CTAR0' was not declared in this scope
SPI_screwup:31: error: 'SPI0_CTAR1' was not declared in this scope
 
Last edited:
Ok, now with my dunce cap on I will answer my own question....

You have to have the Teensy 3 selected under Board in the Arduino IDE. :eek:
 
Ploveday is SPI_SR_TXCTR part of a library or do i just set it up as an a value?
What sets its value in the first place?

When I drop your last code example into the main loop it gives me "SPI_screwup.ino:76:3: error: 'SPI_SR_TXCTR' was not declared in this scope" .
 
Last edited:
My apologies, that one is missing from the mk20dx128.h file it seems, I forgot I had added my own define for it.

Code:
#define SPI_SR_TXCTR 0x0000f000

- Peter
 
One of the things that I've been doing with the FastSPI_LED2 library is making it so that the portions related to fast spi are usable on their own, even without anything having to do with LEDs - and across all the arduino-friendly platforms for bonus. This weekend i've been putting in support for using the teensy 3's SPI directly rather than the arduino emulation layer and right now I'm pushing ~20Mbps (real data rates) out to the LPD8806 strip (best I can pull off of an AVR is about 6.5Mbps). I'm still doing some cleanup with it, and then want to get DMA support in, and then i'll throw up a 3rd preview of the library with the teensy SPI tunings. It would've saved me a couple of hours if I had found this thread earlier today, though :) What i've got going is pretty similar, code wise, to what's in here. Alas, at this data rate the LPD8806's seem to like to glitch :/
 
dgarcia42 have you tried putting your outputs on a logic analyzer, it may be since your running at such high rates that your running in limited voltage range which may mess with the LPD8806's.
 
There might be something to that, if I ground the other end of the strip as well as supply 5v to power at that end as well, then I can get up to 14-15Mbps for up to about 200-210 leds, then I start seeing glitching again. Any thoughts on what I can do to work around this - or is this just a limitation with the chipsets? (I've seen this in the past with various other SPIish chipsets - the longer the run, the more likely there's glitching at higher data rates).
 
There is a limit to the number of chips a single output pin on a device can drive. Each LPD8806 draws a small amount of current on the signal lines. The datasheet for them is very poorly created and lacks information that would tell us how much current they draw for signals.

If you have access to an oscilloscope I would try that, go down to 100 led's and see what your signals look like then go up to 200 and look at the same signals.
Im not sure you can see the voltage ranges using a logic analyzer also.

If its a matter of overdraw on the pin, there are devices that can allow you to enhance the signal in the line.
 
Status
Not open for further replies.
Back
Top