Setting SPI clock on T4.0

Status
Not open for further replies.

OhioJim

Well-known member
This is a sub-question from my other active thread. I am putting it here so it would be more likely to be noticed.

How do I set the SPI clock rate on a T4.0?

If you look at the attached sketch, in the main loop you will see one section that is commented out, and another "active" section.

The active section sets the SPI clock to 4MHz (according to my scope). If I comment this out and uncomment the other section I get a different clock rate. Approximately 1MHz, even though I am using the same SPISettings() parameters.

I am suspecting that the problems described in my other thread is caused by the 4MHz clock rate being too much for the slave device.

Code:
#include <SparkFunAutoDriver.h>
#include <SparkFundSPINConstants.h>

#include <SPI.h>

// Create our AutoDriver instances. The parameters are the position in the chain of
//  boards (with board 0 being located at the end of the chain, farthest from the
//  controlling processor), CS pin, and reset pin.
#define motorCS 10
#define motorReset 31
#define motorBusy 32
#define NUM_BOARDS 1


AutoDriver DECmotor(0, motorCS, motorReset);

#define SPISPEED 500000

void setup() {
  // put your setup code here, to run once:
 Serial.begin(115200);

    pinMode(motorCS, OUTPUT);
    digitalWrite(motorCS, HIGH); // tied low is also OK.
    pinMode(motorBusy, INPUT_PULLUP);
    pinMode(motorReset, OUTPUT);
    pinMode(11, INPUT_PULLUP);
    digitalWrite(motorReset, LOW);
    delay(100); // LOW at least 4 clock cycles of onboard clock. 100 microseconds is enough
    digitalWrite(motorReset, HIGH); // now reset to default values

    delay(500);

  SPI.begin(); //start the spi-bus
 
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3));

    DECmotor.SPIPortConnect(&SPI);

}


void loop() {

/*  digitalWrite(motorCS,LOW);
    delayMicroseconds(1);
  SPI.transfer(0x38);  //get the config register value
    delayMicroseconds(1);
  digitalWrite(motorCS,HIGH);
  
  delayMicroseconds(5);
  digitalWrite(motorCS,LOW);
    delayMicroseconds(1);
  int param = SPI.transfer(0x00);
  delayMicroseconds(1);
  digitalWrite(motorCS, HIGH);
  
  delayMicroseconds(5);
  param <<= 8;
  digitalWrite(motorCS,LOW);
    delayMicroseconds(1);
  param |= SPI.transfer(0x00);
  delayMicroseconds(1);
  digitalWrite(motorCS,HIGH);
  
  Serial.print("Config: ");Serial.println(param,HEX);
  delay(20);
*/
 int param = DECmotor.getParam(CONFIG);
 delay(10);
  
  Serial.print("Config: ");Serial.println(param,HEX);
  delay(10);
 
}
 
Look into the :: SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3));

where this :: SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) {

And for reference on using begin and end transaction look into the SPI examples.
This from :: ...\hardware\teensy\avr\libraries\SPI\examples\DigitalPotControl\DigitalPotControl.ino
Code:
void digitalPotWrite(int address, int value) {
  // gain control of the SPI port
  // and configure settings
[B]  SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
[/B]  // take the SS pin low to select the chip:
  digitalWrite(slaveSelectPin,LOW);
  //  send in the address and value via SPI:
  SPI.transfer(address);
  SPI.transfer(value);
  // take the SS pin high to de-select the chip:
  digitalWrite(slaveSelectPin,HIGH);
  // release control of the SPI port
 [B] SPI.endTransaction();[/B]
}
 
Look into the :: SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3));

where this :: SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) {

And for reference on using begin and end transaction look into the SPI examples.
This from :: ...\hardware\teensy\avr\libraries\SPI\examples\DigitalPotControl\DigitalPotControl.ino

I have looked into all the examples and can't make much sense out of them.

and why does this...
int param = DECmotor.getParam(CONFIG);
delay(10);
Serial.print("Config: ");Serial.println(param,HEX);
delay(10);
... not work, but but doing it the hard way like this...
digitalWrite(motorCS,LOW);
delayMicroseconds(1);
SPI.transfer(0x38);
delayMicroseconds(1);
digitalWrite(motorCS,HIGH);

delayMicroseconds(5);
digitalWrite(motorCS,LOW);
delayMicroseconds(1);
int param = SPI.transfer(0x00);
delayMicroseconds(1);
digitalWrite(motorCS, HIGH);

delayMicroseconds(5);
param <<= 8;
digitalWrite(motorCS,LOW);
delayMicroseconds(1);
param |= SPI.transfer(0x00);
delayMicroseconds(1);
digitalWrite(motorCS,HIGH);

Serial.print("Config: ");Serial.println(param,HEX);
delay(20);
....does work when both use the same SPISettings?????
 
No idea what ' DECmotor.getParam ' does compared to above. That is the problem with starting a second thread - maybe the context is on the other thread?

The note about the samples was how they use the .begin and .end wrapping each transaction. Maybe it isn't an issue with a single active SPI device. But that does show the SPI clock speed setting in use.
 
I started another thread because I have "solved" my first problem, which was caused by having the wrong SPI clock.

Now the problem is how to set the clock.


The getParam() function is part of the Sparkfun L6470 library and invokes a function named SPIXfer().
// Realize the "get parameter" function, to read from the various registers in
// the dSPIN chip.
long AutoDriver::getParam(byte param)
{
SPIXfer(param | GET_PARAM);
return paramHandler(param, 0);
}
This snippet of code comes from SparkFunAutoDriver.cpp.

The SPISetting() function does not appear to be working correctly, except if I bypass the getParam() and SPIXfer() functions.

I can not find any documentation on SPIXfer(), except some obscure reference regarding the Raspberry Pi.

And I can not find the source code for SPI so I can see what this function does.

How do I know I am using the correct version of the SPI library? Most of the tutorials I see talk about the Arduino, or at least older Teensy, not the t4.0.

How do I replace the libraries with a different, hopefully correct, version?
 
Sorry, but it feels like you keep going around and around in circles and most of these things have been covered in the previous forum threads....

You are using the Sparkfun library: https://github.com/sparkfun/L6470-AutoDriver
And all of this code is contained within that library.

So very likely you won't find anything on the next for some specific internal method with some generic names like SPIXfer...

But I am pretty sure I pointed that one out in one of the previous threads...
It is in the file SparkFunAutoDriverSupport.cpp:

Code:
byte AutoDriver::SPIXfer(byte data)
{
  byte dataPacket[_numBoards];
  int i;
  for (i=0; i < _numBoards; i++)
  {
    dataPacket[i] = 0;
  }
  dataPacket[_position] = data;
  digitalWrite(_CSPin, LOW);
  _SPI->beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE3));
  _SPI->transfer(dataPacket, _numBoards);
  _SPI->endTransaction();
  digitalWrite(_CSPin, HIGH);
  return dataPacket[_position];
}

Again as I pointed out in other threads, that this code sets it's own SPI speed... So if you need a different speed, than you need to change it here...
 
Sorry about that. I don't know how I missed it in SparkFunAutoDriverSupport.cpp.

So why do all the tutorials talk about setting the SPI speed using SPISettings, when deep inside the library it is hard coded????

And as I understand it, the beginTransaction() and endTransaction() functions allow multiple slave devices with different requirements. So why are they inside the SPIXfer() function??? Is this a bug in the library?

I do not have a cpp compiler. If I move those statements outside of SPIXfer(), will the Teensyduino IDE know what to do?
 
The idea of SPI.beginTransaction()... SPI.tranfer()..... SPI.endTransaction()
Is setup this way as to allow you to use multiple devices which may or may not be compatible with each other.
So each library tries to setup each logical transaction such that the settings should be compatible with their device when you are talking to their device.

So it is expected that library developers will build in the calls to SPI.beginTransaction()... Likewise if you are developing SPI for your own devices than you need to understand this and do the stuff to make sure things are correct for your device.

Most libraries are setup with the understanding of their device and the specs for their device. Sometimes one can push speeds faster than the library developer thinks it is safe in a generic way and other times libraries are too optimistic about how fast you can drive the device and run into issues.

Which is why with many libraries we try to build in a way to change the default speed.

For example with my ILI9341_t3n library we have two default SPI speeds (one for write operations and one for Read operations which is a lot slower).

So for example the begin method has a way to specify a different speed.

Code:
  void begin(uint32_t spi_clock = ILI9341_SPICLOCK,
             uint32_t spi_clock_read = ILI9341_SPICLOCK_READ);
So I may default to 30mhz
#define ILI9341_SPICLOCK 30000000u

But can try 40mhz by doing tft.begin(40000000);
Or if I have long wires and issues can try: tft.begin(20000000);

But again not many libraries do this. They will point you to their source files to change them.

As for CPP the Arduino compiler is simply a wrapper around the GNU compilers including the cpp compiler which is used to compile your INO. If your sketch has .cpp files in it, it will use the C++ compiler if your sketch has .c files it uses the C compiler. This is true for files in your sketch folder as well as ones found in the libraries and/or core files for the board you are building for.
 
It appears that there is a problem with using the SparkFun AutoDriver Library on a T4.0. Assuming that it works correctly on other slower processors.

My conclusion is that the T4.0 is too fast, and tries to access the L6470 before it is ready.

I got the SPIXfer() function to work by inserting some delayMicroseconds() statements in certain key locations. After setting the CS low, after the transfer() function, and after returning the CS high.

If anyone else wants to use the L6470 with a T4.0 they should keep this in mind.
 
Here is the modified SPIXfer function, which is located in SparkFunAutoDriverSupport.cpp.

byte AutoDriver::SPIXfer(byte data)
{
byte dataPacket[_numBoards];
int i;
for (i=0; i < _numBoards; i++)
{
dataPacket = 0;
}
dataPacket[_position] = data;
digitalWrite(_CSPin, LOW);
delayMicroseconds(1);
_SPI->beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE3));
_SPI->transfer(dataPacket, _numBoards);
delayMicroseconds(1);
_SPI->endTransaction();
digitalWrite(_CSPin, HIGH);
delayMicroseconds(5);
return dataPacket[_position];
}
 
Glad you are making progress!

Not sure if rearranging the code slightly would help or not?

Code:
byte AutoDriver::SPIXfer(byte data)
{
  digitalWrite(_CSPin, LOW);
  byte dataPacket[_numBoards];
  int i;
  for (i=0; i < _numBoards; i++)
  {
    dataPacket[i] = 0;
  }
  dataPacket[_position] = data;
  delayMicroseconds(1); // Wonder if still needed 
  _SPI->beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE3));
  _SPI->transfer(dataPacket, _numBoards);
  delayMicroseconds(1);
  _SPI->endTransaction();
  digitalWrite(_CSPin, HIGH); 
  delayMicroseconds(5);  // Wondering why this one is needed?  Unless some call after this?  like can not call back to this function again?
  return dataPacket[_position];
}

As I commented in your code, wondering why the longer delay is needed? Is it needed here? or is needed between calls to here?

Also you should either raise an issue against the Sparkfun library and/or if you are setup to do so, try to issue a Pull request.
 
.....

As I commented in your code, wondering why the longer delay is needed? Is it needed here? or is needed between calls to here?

I feel that they are needed. The delay values are what I found to work in the "straight line" code that bypassed getParam() (and consequently SPIXfer()).

There needs to be a delay between asserting CS and the byte transfer. Then another between the transfer and deasserting CS. Then the delayMicroseconds(5) is there to give the L6470 time to process the data. The SPIXfer() could be invoked multiple times in quick succession. For example, to read the configuration register requires 3 byte transfers. Maybe other values could be used, but this is what I found to work.

Also you should either raise an issue against the Sparkfun library and/or if you are setup to do so, try to issue a Pull request.[/QUOTE]
 
Note the code you posted the delay 5 is after you already changed state of cs pin

...which allows the L6470 sufficient time to process the data before the next time CS is asserted.

All I can say is that it now works when the original code did not. Maybe some day I will try other delays.
 
Status
Not open for further replies.
Back
Top