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

Implementing The User Mode Driver

In principle the two delays could be different to allow for a difference between a slaves setup and hold time. In practice we generally select a the longest delay. Notice that most SPI devices don't demand that the clock pulses are of the same length so we don't have to worry about being accurate. 

As long as you are using the latest version 3 of the IoT software you can make use of the same lines as the Edison's SPI bus to implement your own. Before version 3 you couldn't use all of the SPI bus lines as GPIO lines because they were used in the Linux Kernel.

To get started first we need to define some global variables. 

mraa_gpio_context CS0;
mraa_gpio_context SCK;
mraa_gpio_context MOSI;
mraa_gpio_context MISO;

If you want to tidy things up these could be placed into a stuct and passed as a single parameter. You could then use the stuct to set up any four GPIO lines as an SPI bus. 

The function that we are going to create is:

uint8_t sendbyte(uint8_t byte, int delay);

it sends byte and returns the byte received. The delay parameter sets the clock frequency.

We could have function to initialise the GPIO lines to work as an SPI bus but for the moment let's just put them in the main program:

int main() {

First we set up the program to run in a FIFO scheduling group. This should make it the only program to run until it gives up control, see Chapter 6 for more information.

const struct sched_param priority = { 1 };
sched_setscheduler(0, SCHED_FIFO, &priority);

Next we initlaize each of the SPI lines in turn: 

CS1 = mraa_gpio_init(9);
mraa_gpio_dir(CS1, MRAA_GPIO_OUT);
mraa_gpio_use_mmaped(CS1, 1);
mraa_gpio_write(CS1, 1);

SCK = mraa_gpio_init(10);
mraa_gpio_dir(SCK, MRAA_GPIO_OUT);
mraa_gpio_use_mmaped(SCK, 1);
mraa_gpio_write(SCK, 0);

MOSI = mraa_gpio_init(11);
mraa_gpio_dir(MOSI, MRAA_GPIO_OUT);
mraa_gpio_use_mmaped(MOSI, 1);
mraa_gpio_write(MOSI, 0);

MISO = mraa_gpio_init(24);
mraa_gpio_use_mmaped(MISO, 1);
mraa_gpio_dir(MISO, MRAA_GPIO_IN);
mraa_gpio_read(MISO);

The only thing that you might wonder about is the final read of the MISO line. Why bother? The answer is that the first read takes longer than subsequent reads because the pin is setup at this point. So a gratuitous read, throwing the result away, speeds up the first byte transfer. It is always a good idea to read an input line when you first set it up. 

Now we need to implement the function we can return to the main program in a moment. We need two loop variables and a byte variable to read the data in:

uint8_t sendbyte(uint8_t byte, int delay) {

int i, j;
int read=0;

Now we are all set to send eight bits one bit at a time. First we set the CS1 line low to select the slave, then we need a short pause to give the slave time, and then we can start the loop that sends each of the bits:

mraa_gpio_write(CS1, 0);
for (j = 1; j < 100; j++) {
};
for (i = 0; i < 8; i++) {

We need to set the data line high or low depending on the most significant bit in byte:

mraa_gpio_write(MOSI, byte & 0x80);

and then we need to shift the entire bit pattern one to the left to get the next bit into the most significant bit position:

byte = byte << 1;

Now we busy wait, and this is the only way to wait that doesn't return control to the operating system, for half a clock period and then set the clock high: 

for (j = 1; j < delay; j++) {
};

mraa_gpio_write(SCK, 1);

Next we need to read the data on MISO. This is the most significant bit and it needs to be shifted one place to the left to ensure it eventually gets to the correct position:

read = read << 1;
read = read | (mraa_gpio_read(MISO));

 Notice that the first shift doesn't actually do anything but its easier to put up with this waste than to waste even more time trying to test to eliminate it. 

Next we wait for another half a clock period and then set the clock low. 

for (j = 1; j < delay-10; j++) {
};
mraa_gpio_write(SCK, 0);
}

This ends the loop that processes the eight bits and now all that remains is to deactivate the CS1 line and return the result.

mraa_gpio_write(CS1, 1);
for (j = 1; j < 20; j++) {
};
return (uint8_t) read;
}

Continuing with the main program from where we left off, we can now make use of the function to send and receive some data:

int delay = 0;
uint8_t read;
for(;;){
read = sendbyte(0xAA, delay);
if(read!=0xAA)printf("Error \n");
}

return MRAA_SUCCESS;
}

Assuming that the MISO and MOSI pins are connected together in a loop back, the output AA should equal the input AA. 

If you run this and measure the clock frequency you will find it is around 666KHz and the data rate is 66K bytes/s. 

This is the fastest data transfer things can be slowed down by setting other values to delay:

Delay  Clock Transfer

0     666KHz    66K bytes/s

10   500KHz    62K bytes/s

100 222KHz    26K bytes/s

200 133KHz   16K bytes/s

500   47KHz   7.2K bytes/s

1000 31KHz   3.8K bytes/s

Once you get to a delay of 500 or more you will discover that the delay after setting the CS1 line might not be enough. It really needs to be a percentage of the clock frequency. If you want to work down at these frequencies change the delay for CS1 to: 

for (j = 1; j < delay/8+100; j++) {}

You can easily tidy up this function and program to produce something more like a library function. In addition you can modify it to create a buffer transfer function which works at similar speeds. An example of how to do this is given in the next chapter. 

Notice that while data is being transferred the function hogs one core of the Edison main CPU and the rest of your program makes no progress. However, if you replace the busy waits by other instructions you do have time to perform some light processing of the input data.  

The complete program is:

#include "mraa.h"

#include <stdio.h>
#include <unistd.h>

uint8_t sendbyte(uint8_t byte, int delay);
mraa_gpio_context CS1;
mraa_gpio_context SCK;
mraa_gpio_context MOSI;
mraa_gpio_context MISO;

int main() {
 const struct sched_param priority = { 1 };
 sched_setscheduler(0, SCHED_FIFO, &priority);

 CS1 = mraa_gpio_init(9);
 mraa_gpio_use_mmaped(CS1, 1);
 mraa_gpio_dir(CS1, MRAA_GPIO_OUT);
 mraa_gpio_write(CS1, 1);

 SCK = mraa_gpio_init(10);
 mraa_gpio_use_mmaped(SCK, 1);
 mraa_gpio_dir(SCK, MRAA_GPIO_OUT);
 mraa_gpio_write(SCK, 0);

 MOSI = mraa_gpio_init(11);
 mraa_gpio_use_mmaped(MOSI, 1);
 mraa_gpio_dir(MOSI, MRAA_GPIO_OUT);
 mraa_gpio_write(MOSI, 0);

 MISO = mraa_gpio_init(24);
 mraa_gpio_use_mmaped(MISO, 1);
 mraa_gpio_dir(MISO, MRAA_GPIO_IN);
 mraa_gpio_read(MISO);

 int delay = 1000;
 uint8_t read;
 for (;;) {
  read = sendbyte(0xAA, delay);
  if (read != 0xAA)
  printf("Error \n");
 }

 return MRAA_SUCCESS;

}

uint8_t sendbyte(uint8_t byte, int delay) {
 int i, j;
 int read = 0;

 mraa_gpio_write(CS1, 0);
 for (j = 1; j < delay / 8 + 100; j++) {
 };
 for (i = 0; i < 8; i++)
{

  mraa_gpio_write(MOSI, byte & 0x80);
  byte = byte << 1;
  for (j = 1; j < delay; j++) {
  };

  mraa_gpio_write(SCK, 1);
  read = read << 1;
  read = read | (mraa_gpio_read(MISO));
  for (j = 1; j < delay; j++) {
  };
  mraa_gpio_write(SCK, 0);

 }

 mraa_gpio_write(CS1, 1);

 for (j = 1; j < delay / 8 + 20; j++) { };
 return (uint8_t) read;
}

 

 



Last Updated ( Thursday, 02 June 2016 )