TSPISlave Library

Status
Not open for further replies.

tonton81

Well-known member
I have seen alot of SPI slave questions, seeing as SPI_MST is more or less a protocol and controller in itself. I decided to write a user end library that's capable of running multiple SPI slave ports on Teensy 3.x and LC, for users interested in writing their own data handling in a simple interface. The functions provided allow ease of access for the user without divulging into specific registers, especially on the Teensy LC. The sketch will work on all T3.x/LC models without any other differences in the commands. I tried to make it as simple as I could for the user, and all SPI ports (SPI0,1,2) on T3/LC support independant 8 or 16 bit mode via constructor.

However unless anyone has better suggestions, I had no other idea but to use uint8_t's for the pins, perhaps enums would have been better?

Irregardless, this is where I am at:

Code:
#include "TSPISlave.h"

TSPISlave mySPI = TSPISlave(SPI2, 51, 52, 53, 43, 8); // MISO,MOSI,SCK,CS,8bit transfers
void setup() {
  Serial.begin(115200);
  mySPI.onReceive(myFunc); // callback for mySPI
}

void loop() {
  delay(1000);
  Serial.println(millis());
}

void myFunc() {
  Serial.println("START: ");
  uint16_t arr[2] = { 0x1111, 0x2222 };
  uint8_t i = 0;
  while ( mySPI.active() ) {
    if (mySPI.available()) {
      mySPI.pushr(arr[i++]);
      Serial.print("VALUE: 0x");
      Serial.println(mySPI.popr(), HEX);
    }
  }
  Serial.println("END");
}


I started working on this since yesturday and finally got the LC running on SPI0 and SPI1 before I finished off testing SPI1 and 2 of T3.5

The master has no library, you can send data to the slave as you wish.
while ( mySPI.active() ) allows the user to handle multiple bytes/words within the same transaction, an array is there as an example to shift out data for the master on sequential transfers
available() must be checked before read/writing the data

popr and pushr are used to read and write respectively the data once the available() is triggered
popr and pushr also handle the DL and DH registers of the LC so the user wouldn't need to worry about that, essentially making the sketch work accross all recent teensy boards
if anyone has constructor ideas or just to leave that as is, let me know, I should have a release uploaded later today. I tested all SPI busses on LC and T3.x before writing this post

T3.x connections:
MISO -> MISO
MOSI -> MOSI

LC connections:
MISO -> MOSI
MOSI -> MISO

Also on the LC, you should run your pushr() after the popr(), as LC uses same register for both, youll need to push the data last

https://github.com/tonton81/TSPISlave

EDIT, just posted the library, I will update the supported SPI pin list for each port in a few minutes

Tony
 
Last edited:
This library has been helpful to me, thanks! But, I'm unable to get past some behavior related tolatency.

Here's what I'd like to do:
The master sends the slave a command. The slave processor does stuff with it and gets the response ready, typically a couple of bytes of status info, so that when the next command is issued by the master, the response is clocked from slave to master in the usual way. The commands don't show up very often, so there's plenty of time to get ready.

The problem I'm having is that I can't figure out how to set things up so that the first byte that is clocked out, is not "stale" data left over from the end of the last write cycle. There seems to be a one-byte queue somewhere. I'm using SPI1 and the ref manual says SPI1 has a TX FIFO size of 1. Is this the cause of the behavior? If so, can the FIFO be bypassed?

I'm using 8-bit mode, but the same happens in 16-bit mode.

Here's some code I've been using for testing. I've stripped it down to almost nothing. The payload variable is slowly incremented giving me plenty of time to read it out multiple times and look at the results. The SPI master is a T3.6, but the problem isn't there. I'm analyzing the bus traffic on a scope, so I can see that I always get one byte of the old value, after "payload" has been updated.

cheers, Doug

Code:
#include "TSPISlave.h"

uint8_t miso = 1;
uint8_t mosi = 0;
uint8_t sck = 32;
uint8_t cs = 31;
uint8_t spimode = 8;
uint8_t i=0;
volatile uint8_t payload;

TSPISlave mySPI = TSPISlave(SPI1, miso, mosi, sck, cs, spimode);

void setup() {
  Serial.begin(115200);
  mySPI.onReceive(myFunc);
}

void loop() {
  delay(4000);
  Serial.println(i);
  cli();
  payload=i;
  sei();
  i++;
}

void myFunc() {
  Serial.println("START: ");
  uint8_t i = 0;
//  while ( mySPI.active() ) {
    if (mySPI.available()) {
      mySPI.pushr(payload);
      Serial.print("VALUE: 0x");
      Serial.println(mySPI.popr(), HEX);
    }
//  }
  Serial.println("END");
}
 
"i" should be declared volatile uint8_t (your sharing it between the loop() and an ISR), but as for SPI, the last byte set in the slave is the one transfered back to master when master writes to slave. Slave has to set it in advance. Thats why usually the master sends a first instruction byte or dword, then dummy bytes to read the actual data that the slave is putting in the buffer. The slave first byte/dword is irrelevent during assertion, that word/byte just tells the slave what the next data it should put in the TX for the next master transfer, and the rest of data should be in sync after that
 
Thanks for the reply. (volatile uint8_t i fixed).

I'm not sure I follow. Are you saying that the first byte of the slave message should always be ignored? That seems a bit surprising.

I get that this is usually ok because typically the master isn't yet interested in the reply because it's still sending out a command. I could take that approach and make it work, but it seems that there should be some sort of mechanism to flush out the old byte and be ready with a well-formed reply.

In my situation I receive a command, then I've got hardware stuff to do (non Teensy) before I can be sure that everything is happy and working ok. Generally everything will be ok, so it seemed nice and clean to receive an SPI command and then many mS later, when the next command comes in, reply with a status. Because I'm responding to the *previous* command it seems I should be ready to go with good data immediately.

Looking at the manual, there's a register to bypass the FIFO, so I've been experimenting with that. I have found no change in behavior, but I can also believe I didn't flip the bit correctly. This is out of my comfort zone... There's also a bit for flushing the FIFO, but I suspect that won't do what I want if the FIFO is still in the chain.

Thanks very much
Doug
 
Unfortunately the first master byte gets whatever is in the slave pushr register. So if you change it in the ISR it's too late, thats for the next transfer. You should use while active() because this keeps your loop from resetting the variable while the master is still asserting the slave. The slave is supposed to take the first master transfer, lets say 0x25 value, and decide that for that value set the pushr for the next value amd the next byte, even if its a dummy from master, the master will receive what you set in the slave. That while loop is also a way to send an array of data in case the master sends several dummy bytes after 0x25, you can keep incrementing the array indice and set the next pushr with that value.
 
Yes, I understand the issues with the while loop and clocking out the data. That's all working fine in my real program. (My test was just a very stripped down example. )

I guess there's just no way to clear out that byte because ultimately that lowest level of that hardware block is a shift register that's clocked externally. I guess it can't clock out that byte without becoming a master (which would not be sensible).

I can certainly make this work just fine. I was just looking for that extra "niceness".
cheers,
Doug
 
Good info here and this library looks to be just what I need. I can make the above example work but the problem I'm having is that the data is all shifted by one bit. I assume because I need SPI mode 3 and I am stuck in SPI Mode 0. I'm using the deprecated function SPI.setDataMode(SPI_MODE3) but it doesn't appear to be working. I'm not sure I can or should use SPI.begintransaction since this is slave only. How can I change the SPI mode?
 
Last edited:
you need to edit the source, only MODE 0 is supported (hardcoded) at this time. You need to edit the CHPA and CPOL bits in the registers
 
I'm trying to capture 8-bit SPI data (SPI mode 3) to an LCD on a different device. The circuit I'm "SPIing" on does not use a slave select since it's the only device on the bus. I'm using a Teensy output pin looped back to CS as a slave select. The problem is there are 2 additional discrete data lines I'm trying to log that are synced to the SPI messages (may change state between messages).. SPI clock is 3.125MHz (320nS) and the state of the extra data pins appear to only be valid for a minimum of 500nS after the 8th clock pulse in a message. I have not been able to reliably capture the state of these pins. In some cases where extra pins change state at or close to 500nS of a completed message, my captured data will show the new state, not the old state that was valid during the message. The first thing I do in interrupt is do a digitalReadFast of the pins and then pop the message. No problem reading the SPI messages. It just appears that there is >500nS of latency from message complete to interrupt code execution. I haven't really found any source of significant latency in the library code and am unsure what to try next. Any suggestions? Thanks.

Code:
void GetSPI()
{
    record[i].extra = digitalReadFast(A0) | (digitalReadFast(CONT) << 1);  //pin 14 = CONT, pin 15 = A0
    record[i].time_stamp = micros();
    record[i++].data = mySPI.popr();
    if (i > qty) 
      digitalWriteFast(SS_CON,1);  //turn off -SlaveSelect (pin high)
}
 
I'm trying to capture 8-bit SPI data (SPI mode 3) to an LCD on a different device. The circuit I'm "SPIing" on does not use a slave select since it's the only device on the bus. I'm using a Teensy output pin looped back to CS as a slave select. The problem is there are 2 additional discrete data lines I'm trying to log that are synced to the SPI messages (may change state between messages).. SPI clock is 3.125MHz (320nS) and the state of the extra data pins appear to only be valid for a minimum of 500nS after the 8th clock pulse in a message. I have not been able to reliably capture the state of these pins. In some cases where extra pins change state at or close to 500nS of a completed message, my captured data will show the new state, not the old state that was valid during the message. The first thing I do in interrupt is do a digitalReadFast of the pins and then pop the message. No problem reading the SPI messages. It just appears that there is >500nS of latency from message complete to interrupt code execution. I haven't really found any source of significant latency in the library code and am unsure what to try next. Any suggestions? Thanks.

Code:
void GetSPI()
{
    record[i].extra = digitalReadFast(A0) | (digitalReadFast(CONT) << 1);  //pin 14 = CONT, pin 15 = A0
    record[i].time_stamp = micros();
    record[i++].data = mySPI.popr();
    if (i > qty) 
      digitalWriteFast(SS_CON,1);  //turn off -SlaveSelect (pin high)
}

I tried moving my A0 and CONT pins to 16 and 17 respectively because on this teensy 3.5, these pins are PORTB.0 and PORTB.1. Before, these pins were on two different ports. This certainly speed up my reads but it's still not fast enough.

Next, I tried adding a direct GPIOB_ PDIR read in the TSPISlave library popr function and transferred the PORTB read back to main program in the popr return value MSB. This actually almost works. Every once in a while, it will get the extra data pins right. Almost there. Maybe I can not use an interrupt and just poll the SPI data in a tight loop?
 
I tried moving my A0 and CONT pins to 16 and 17 respectively because on this teensy 3.5, these pins are PORTB.0 and PORTB.1. Before, these pins were on two different ports. This certainly speed up my reads but it's still not fast enough.

Next, I tried adding a direct GPIOB_ PDIR read in the TSPISlave library popr function and transferred the PORTB read back to main program in the popr return value MSB. This actually almost works. Every once in a while, it will get the extra data pins right. Almost there. Maybe I can not use an interrupt and just poll the SPI data in a tight loop?

I think the above changes and running a tight loop is the answer. I commented out the "NVIC_ENABLE_IRQ(spi_irq)" in the library and did this in my loop:
Code:
    while (i<qty)  //wait in this loop for buffer full
    {
      if (mySPI.available())
      {
        mydat = mySPI.popr();
        record[i].time_stamp = micros();
        record[i].data = mydat & 0xFF;
        record[i++].extra = mydat >> 8;
      }
    }

Now, the extra bits look to be associated correctly with the SPI data. I'm still interested in hearing what others think. Thanks.
 
I think the above changes and running a tight loop is the answer. I commented out the "NVIC_ENABLE_IRQ(spi_irq)" in the library and did this in my loop:
Code:
    while (i<qty)  //wait in this loop for buffer full
    {
      if (mySPI.available())
      {
        mydat = mySPI.popr();
        record[i].time_stamp = micros();
        record[i].data = mydat & 0xFF;
        record[i++].extra = mydat >> 8;
      }
    }

Now, the extra bits look to be associated correctly with the SPI data. I'm still interested in hearing what others think. Thanks.

Still not right. Data becomes progressively corrupt now. Looks like bits are being shifted. Arghhh!!!
 
Still not right. Data becomes progressively corrupt now. Looks like bits are being shifted. Arghhh!!!

I changed the "available()" function in library to check the SPI_SR_RFDF flag instead of the FIFO counter and also turned off FIFO mode. It seems to work better but after about 2500 messages captured, the data become corrupt like the bits are all shifted. Not sure where to go next..

Any help out there???
 
Status
Not open for further replies.
Back
Top