SPI clock polarity setup not long enough..

Status
Not open for further replies.

quiver

Active member
Hi there,

I've got a project I've transferred from Arduino to the Teensy 3.2. It has six SPI devices on the bus, of which half are SPI mode 1, and the other half are SPI mode 3. This change in clock polarity wasn't an issue on the Arduino.. but on the Teensy it appears to be. My logic analyzer is telling me the clock remains low after the devices with mode 1 & 2 finish talking to the Teensy, and it corrects only a quarter of the way through the first cycle of the new clock. This confuses the mode 3 device and it's ignoring the first packet it receives.

Putting the logic analyzer on the Arduino, it's clear that it checks and if necessary corrects the polarity as soon as SPI.beginTransaction(settings) is called. Even if I add a delay between the SPI.beginTransaction(settings) and the digitalWrite(SS) command, this doesn't make a change. It seems the Teensy waits until the clock actually starts before it checks if it's the correct polarity.

Interestingly, it seems to be fine converting the polarity the other way. There's a little setup time, not much, but enough for those devices (they're faster devices - 20MHz against the 1MHz of the mode 3 devices).

Is there a way to force SPI on the Teensy to check the clock polarity as part of the setup?

Thanks in advance!
 
I know this should be "trivial", but I'd like to ask you to please post a short program that transmits 1 byte in each mode. Please check the waveforms with your logic analyzer. Then as I look at this and view the waveforms on my oscilloscope, we'll be working with the exact same program and talking about the same waveforms.

Yeah, I know you've described the problem pretty well. But to really work on this and talk meaningfully, we need to be running the same small test program and viewing the exact same waveforms. If we each craft a different program, differences between them are only going to make communicating 10X harder.
 
That's entirely reasonable :)

Here's a test program:
Code:
#include <SPI.h>

SPISettings test1(20000000, MSBFIRST, SPI_MODE1);
SPISettings test2(1000000, LSBFIRST, SPI_MODE3);

uint8_t cs1 = 22;
uint8_t cs2 = 8;

void setup() {

  pinMode(cs1,OUTPUT);
  pinMode(cs2,OUTPUT);
  digitalWrite(cs1,HIGH);
  digitalWrite(cs2,HIGH);

  SPI.begin();

} // END SETUP

void loop() {

  SPI.beginTransaction(test1);
  digitalWrite(cs1,LOW);
  SPI.transfer(0x00);
  digitalWrite(cs1,HIGH);
  SPI.endTransaction();

  SPI.beginTransaction(test2);
  digitalWrite(cs2,LOW);
  SPI.transfer(0x00);
  digitalWrite(cs2,HIGH);
  SPI.endTransaction();

} // END LOOP

Here's a screenshot from my analyzer on the Teensy:
SPI Setup Region.jpg

Here's a screenshot from my analyzer on the Arduino:
SPI Setup Region Arduino.jpg
 
My temporary workaround is that I've created a function in each of the libraries for the devices as follows:
Code:
void prepare() {
  SPI.beginTransaction(test1);
  SPI.transfer(0x00);
  SPI.endTransaction();
}
This fixes my problem for now, but it's a bit of an ugly fix. You need to transfer the empty byte - it won't correct the polarity without it - but at least I don't need to bring the SS low.

Should the SPI settings not check and correct the polarity when they're called by SPI.beginTransaction ?
 
I've had a look through the SPI library, and tried adding this to the SPI.beginTransaction(SPIsettings settings) function:
Code:
digitalWrite(SCK,(settings.ctar >> 26 & B1));
Unfortunately this hasn't fixed things - I don't understand these complex library files well enough to be able to diagnose it further.
 
Any advice on how I can patch this SPI library, friends?

I'd be surprised if I'm the first to encounter this, even some of the faster SPI devices in my chain are ignoring the first packet as the clock setup time is insufficient..
 
Update: I've been reading through the NXP documentation for the MK20DX256, and added CSSCK delay to the CTAR register by doing the following in the SPI.beginTransaction(SPIsettings settings) function in the SPI.h library file:
Code:
settings.ctar |= B11 << 12;
This successfully adds setup time, but the problem still exists as the SPI.beginTransaction() function doesn't change the polarity; it's only when you write to SPI.transfer() that it corrects the polarity, which is by necessity after the CS line has already been asserted.

This is a frustrating exercise. Anyone with more familiarity with this SPI library.. I'd really appreciate your help.
 
Last edited:
Expect it is on Paul's list - you provided good start point for a repro. Might be a day or a month ... Does the workaround work for now? It may turn out to be a chip errata?
 
Thanks, yes, it may be. The workaround only kind of works. Any time I change clock polarity - including back to the ethernet controller - I need to fire off that dead packet as per the above. That means modifying four libraries, including the very busy ethernet library.

The more I read from the NXP documentation, the more I'm coming to realise this may not even be achievable. It seems their preferred method of SPI control is where you assert the CS through the SPI interface, which is limited to just four SPI devices. This would also make my libraries incompatible with Arduino, and is very clunky.

Essentially the documentation appears to suggest that the CPOL can only be configured while the module is *not* running, which probably means it cannot change the polarity until you tell it to write something.

I don't appear to be able to override it, in any event. Writing directly to the SCK pin using digitalWrite doesn't work, even while the module is halted.
 
Last edited:
That sounds ugly - any chance it works using SCK0 and alternate pins 13 and 14 separating them by polarity and swapping that between devices with : SPIxxx.setSCK( SPI_MST_SCK );
 
As you mentioned your hack would be ugly as you would have to change 4 libraries... Do all 4 libraries use beginTransaction?

If so have you tried hacking the SPI library? If you look at the SPI.cpp at about line 522, you see:
Code:
		if (port().CTAR0 != settings.ctar) {
			port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x3F);
			port().CTAR0 = settings.ctar;
			port().CTAR1 = settings.ctar| SPI_CTAR_FMSZ(8);
			port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x3F);
		}
You could try to build in your hack maybe something like:
Code:
		if (port().CTAR0 != settings.ctar) {
			port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x3F);
			[COLOR="#FF0000"]bool polarity_changed = (port().CTAR0 & SPI_CTAR_CPOL) != (settings.ctar & SPI_CTAR_CPOL);[/COLOR]
			port().CTAR0 = settings.ctar;
			port().CTAR1 = settings.ctar| SPI_CTAR_FMSZ(8);
			port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x3F);
			[COLOR="#FF0000"]if (polarity_changed) transfer(0);[/COLOR]
		}
Again typed on the fly so not sure if it will compile or work...
 
Just to confirm, I've put this on my high priority bug list to investigate.

However, I can't promise any fix will make it into 1.42, which is nearing a couple final betas then release.
 
Hi friends,

Thanks Paul - I'm sure you're plenty busy!

Kurt, I got to the same place the other day, I've just done it more wholesale. Mine fires the empty packet anytime there's a mismatch in settings.ctar & port().CTAR0, so it's a little less efficient. Much better to only do it when the polarity has actually changed - ta!
 
Yeah it's a hardware issue it appears. Which means the solution is unlikely to get a whole lot more elegant.
 
Status
Not open for further replies.
Back
Top