Go Down

Topic: SPI.transfer(buffer,size) CLK working but no DATA (Read 60 times) previous topic - next topic

10starjester

Hi there, been having some problems with the SPI.transfer(buffer,size) command.  Initially it worked fine but now only the clock works and there is no data transferred.  I haven't changed anything as far as I'm aware and normal SPI transfers work fine.  I have tested on Uno and Due with slightly different results but still NO data!

Here is my super simple code, the first 3 SPI transfer commands work fine but the buffered one produces no data, any suggestions would be much appreciated?

Code: [Select]

#include <SPI.h>
byte test[3]={0b10101010,0b00001111,0b11110000};

void setup() {
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(1);
SPI.setDataMode(SPI_MODE0);
}

void loop() {
SPI.transfer(0b10101010);
SPI.transfer(0b10101010);
SPI.transfer(0b10101010);
SPI.transfer(test,3);
delay(1);
}


Below is the screenshots of whats occurring on the scope.  You can see the dead data line, although the Due stays high whilst the Uno drops low?

https://drive.google.com/open?id=0B2f5D6TncWHDLUZESU9HSWs3eU0
https://drive.google.com/open?id=0B2f5D6TncWHDTV9FeXFkYW41RGM

J-M-L

what happens if you put that into setup() instead of loop? or can you print the test array in your loop?

SPI transfer is based on a simultaneous send and receive: the received data is returned and stored in the buffer in-place ==> the old data that you initialized only once at compile time is replaced with the data received.

So if you are receiving 0 back from the SPI line at the first iteration of the loop -- and that runs quickly so may be you did not catch that on your oscilloscope, then subsequent calls in the loop() to SPI.transfer(test,3); is actually asking to transfer the values 0x0, not your 3 bytes



Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums

10starjester

Thanks for your quick reply, I have transferred it to setup() and triggered the scope on the first pulse and it is all working fine again.  Now you've explained whats happening I should be able to implement it in my actual code.

10starjester

I should've asked, is there a way off redirecting the incoming data into a different array so my data doesn't get overwritten every time?

J-M-L

Not if you use that function.

now source code is available... so everything is possible :-)



if you want to explore and understand a bit more what's happening here you go.

that's the code for the transfer function
Code: [Select]
inline static void transfer(void *buf, size_t count) {
    if (count == 0) return;
    uint8_t *p = (uint8_t *)buf;
    SPDR = *p;
    while (--count > 0) {
      uint8_t out = *(p + 1);
      while (!(SPSR & _BV(SPIF))) ;
      uint8_t in = SPDR;
      SPDR = out;
      *p++ = in;
    }
    while (!(SPSR & _BV(SPIF))) ;
    *p = SPDR;
  }



if you want to make sense of this code, you need to understand how SPI data transfer works.

At the core of the SPI architecture there is an 8-bit shift register --  both in the Master and the Slave -- and a clock signal generated by the Master. The Master sends a byte (M) to the Slave and at the same time receives a byte of data (S) from the Slave .

Before starting the communication, the Master places byte (M) in its dedicated hardware shift register, and the Slave places his byte (S) in its dedicated hardware shift register (or done in software).

Next the Master generates 8 clock pulses that you have seen in your oscilloscope, and the contents of the Master's shift register is transferred to the Slave's shift register and vice versa. So, at the end of the 8 clock ticks, the Master has completely received S, and the Slave has received M. The transmission and reception occurs at the same time, so it is a full duplex data transfer.

So in the code above

    uint8_t *p = (uint8_t *)buf;
we declare a pointer to the start of the data we want to send

    SPDR = *p;
we put that data in the SPI Data Register.  The SPI Data Register is a read/write register used for data transfer between the Register File and the SPI Shift Register. Writing to the SPDR register initiates data transmission.

    while (--count > 0) {
you iterate for all bytes

      uint8_t out = *(p + 1);
prepares next byte to send. done here because we have to wait anyway for the hardware to do it's job so let's not waste time and use the CPU to do something meaningful before actively waiting for the data to be finished transferring.

      while (!(SPSR & _BV(SPIF))) ;
SPSR is the SPI Status Register. SPIF is the SPI Interrupt Flag bit. It is set by hardware when a serial transfer is complete. So basically we check the status register interrupt flag and wait until it's raised. This is an active wait loop until the 8 clock ticks have happened and the full shift register data has been sent/received.

      uint8_t in = SPDR;
Reading the SPDR register causes the Shift Register received buffer to be read in hardware. We now have in the data register the byte (S) received from the slave. Note that the SPIF flag is cleared by hardware when the SPIF bit and the SPDR register are read so we are good to go for next round.

      SPDR = out;
In order to optimize things, we load the next byte to send which initiates the transfer in hardware

      *p++ = in;
While the transfer goes, we overwrite the buffer with the slave byte we have received.

then the while loops loops and we start again.

As the while will end with a byte still to send, so they have one more byte to deal with

    while (!(SPSR & _BV(SPIF))) ;
active wait loop until the 8 clock ticks have happened and last data exchange has happened

    *p = SPDR;
we did not progress the pointer, so overwrite the last byte of our array with the data from the slave. because we read SPDR, the SPIF flag is cleared by hardware so we are good to go for next time.


so it's not overly complicated.

so you could modify that library and add a function that gets another parameter (a buffer where you want to store the result) and instead of overwriting the buffer through the uint8_t *p pointer (that's *p++ = in; and final line *p = SPDR;), advance simultaneously a pointer in the new buffer and store the data there. of course that will slow down a bit the performance of SPI because now you have to progress 2 pointers instead of just one.

makes sense? hope this is useful - share your code once done :)
Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy