Exploring Edison - SPI
Written by Harry Fairhead   
Thursday, 02 June 2016
Article Index
Exploring Edison - SPI
SPI Functions
A Loop Back Example
Implementing The User Mode Driver
General SPI Problems

A Loop Back Example

Because of the way that data is transferred on the SPI bus it is very easy to test that everything is working without having to add any components. All you have to do is connect MOSI to MISO so that anything sent it also received in a loop back mode. 

First connect pin J17/12 to pin J18/11 using a jumper wire and start a new project. 

The program is very simple.

First we initialize the  SPI bus:

mraa_spi_context spi = mraa_spi_init(0);

As this is a loop back test we really don't need to configure the bus, but for completeness:

mraa_spi_mode (spi, MRAA_SPI_MODE0 );
mraa_spi_frequency(spi, 400000);
mraa_spi_lsbmode(spi, 0);
mraa_spi_bit_per_word(spi,8);

Next we can send some data and receive it right back:

uint16_t read_data = mraa_spi_write(spi,0xAA);

The hex value AA is useful in testing because it generates the bit sequence 10101010, which is easy to see on a logic analyser 

We can check that the received data matches the sent data in a variety of ways:

 if( read_data== 0xAA)
         printf("data received correctly");

Finally we close the bus and the library:

mraa_spi_stop(spi);
return MRAA_SUCCESS;

Putting all of this together gives us the complete program: 

#include "mraa.h"
#include <stdio.h>
#include <unistd.h>
int main() {
mraa_spi_context spi = mraa_spi_init(0);
mraa_spi_mode(spi, MRAA_SPI_MODE0);
mraa_spi_frequency(spi, 400000);
mraa_spi_lsbmode(spi, 0);
mraa_spi_bit_per_word(spi, 8);
uint16_t read_data = mraa_spi_write(spi, 0xAA);

if (read_data == 0xAA) 
             printf("data received correctly");
mraa_spi_stop(spi);
return MRAA_SUCCESS;
}

If you run the program and don't get the "data received correctly" message then the most likely reason is that you have connected the wrong two pins together or not connected them at all. 

Some Edison SPI Problems

If you connect a logic analyser to the four pins involved - J17-10,11 and 12 and J18-11 -  you will see the data transfer: 

 spilogic1

 

If you look carefully you will see the CS0 line go low before the master places the first data bit on the MOSI and hence on the MISO lines. Notice that the clock rises in the middle of each data bit making this a mode 0 transfer. You can also see that the clock is measured to be 400KHz as promised.

All as expected. However if you change the program to repeatedly send a single byte of data:

for (;;) {
uint16_t read_data = mraa_spi_write(spi, 0xAA);
}

you will see something you might not have expected:

spilogic2

 

The clock rate may be specified as 400KHz, but the data rate is much slower. There is a .26ms delay between each byte transferred. This is not how SPI usually behaves. This is what Yocto Linux 3 does; before this the data rate was slowed down by the clock not starting for a long time after the CS line was active. The new behavior is much better and less likely to cause problems with SPI slaves. 

The delay effectively reduces the data rate to just less than 4.5K bytes/s from its theoretical upper limit of 50K bytes/s.

What this means is that the data rate isn't as dependent on the clock speed as you might expect:

1MHz       5K bytes/s

400KHz   4.49K bytes/s

200KHz  3.8K bytes/s

100KHz  3.24K bytes/s

50KHz    2.43K bytes/s

10KHz    0.83K bytes/s 

The reason for this behavior is simply the overheads in calling the SPI functions to send a single byte. 

Earlier versions of Yocto Linux showed the same data transfer rates, but the slowdown was achieved by putting a delay in after the CS line had been activated. 

You might think that the way to get a higher data rate is to use one of the  buffer transfer functions which don't toggle the CS line between each byte or word and which run the clock continuously. 

However, if you try this with Yocto Linux 3 you will find that its behaviour is a little strange.

For example, the program:

for (i = 0; i < 1000; i++)
 buf[i] = i;
uint8_t recv[1000];
int n = 1000;
mraa_result_t res=
    mraa_spi_transfer_buf(spi,buf,recv,n);
for (i = 0; i < n; i++) {
 if (recv[i] != buf[i])
 printf("error %d , %d,%d \n", recv[i], buf[i], i);
}

transfers 1000 bytes containing 0,1,2,3,4 and so on and then compares the received data.

You will find that you get error messages for all values after the first if the clock frequency is lower than about 800KHz

If you reduce the number of bytes sent to three or fewer then it does work. 

If you use a clock frequency of 800KHz to less than 1MHz then it works but the clock speed is less than set.

At 1MHz it works correctly and the clock speed is 1MHz with occasional pauses giving a data transfer rate of around 0.125Mbytes/s

At 2Mhz it is 0.26Mbytes/s , 10MHz gives 1.6Mbytes/s and at 25MHz the transfer rate is 3.12Mbytes/s.

However you have to keep the buffer size to less than around 5Kbytes or there are over run errors. 

The latest version of the SPI driver has changed to make use of DMA data transfer - hence the much higher speeds achievable. If you select a clock speed lower than 1MHz then the DMA seems to get out of sync with the bus.  

The documentation also says:

  • In a single-frame transfer, the SoC supports all four possible combinations for the serial clock phase and polarity.
  • In multiple frame transfer, the SoC supports SPH=1 and SPO= 0 or 1.
  • The SoC may toggle the slave select signal between each data frame for SPH=0

This means that multiple frame transfers only support Modes 1 and 3 and the CS line my be toggled between frames in modes 0 and 4.
In practice this doesn't seem to happen.  

A User Mode Driver

The SPI bus doesn't seem able to do data transfers with a clock much slower than 1MHz. This is good for fast devices such as video displays but some devices can't work this fast. There is also the problem of supporting more SPI devices than the Edison hardware supplies.

We can solve some of these problems with a software emulation of the SPI bus.  

The good news is that the SPI protocol is very simple. We will implement a mode 0 transfer of a single byte. The code presented is very simple and you could improve it a lot at the cost of clarity and perhaps, if you are not careful, speed.

The SPI protocol in mode 0 with CS active low and SCK active high is:

  1. Set CS1 to 0 wait a short time
  2. Put the data out on MOSI
  3. Wait 1 full delay
  4. Set SCK to 1 
  5. Read MISO
  6. Wait 1 full delay
  7. Set SCK to 0
  8. Send all eight bits - repeat 2 to 7 - and then set CS1 to 1 again. 

You can see that we are setting the data just after the falling edge of the clock and reading the data just after the rising edge. 



Last Updated ( Thursday, 02 June 2016 )