Use SPI port on raspberry PI

SPI is a frequent way to interface slave device with a micro-controller / cpu. SPI is a 4 wires synchronous connection between a slave and a master. As part of the 4 wires, there is a CLK signal, the speed is not defined and depends on the slave but it is usually > 10Mhz. SPI allows fast data transfer. Two data signals (MISO and MOSI) for Master (In/Out) Slave (In/Out) are used to transfer data in sync with the CLK clock. The last wire is a chip select signal named CEx for Chip Enable. As you can manage multiple slave, you have to manage different CEx signal, one per slave.

Raspberry PI has 2 SPI channels you can use, for example with wiringPi or using directly the spidev functions. As an example, here is how to connect a MRF24j40MA zigbee device with the RPI :

RaspberryPi connected to zigbee mrf24j40MA over SPI
RaspberryPi connected to zigbee mrf24j40MA over SPI

You can see that on the slave, the pin name can be different : SDI is Slave Data In, SDO, Slave Data Out, SCK is Slave ClocK and CS is Chip Select signal. By the way, signals are connected directly together and it’s all what you need for the electrical part.

Please note that after having some trouble to read data, I added a 120ohm resistor in series on SDO line. This has solved misreading like getting a 0xFE when 0xFF has been written.

Concerning the software part, the first step is to load the Linux SPI driver, on raspberryPI this is done by loading spi_bcm2708 module :

# modprobe spi_bcm2708

Once done, you should have two new devices : /dev/spidev0.0 and /dev/spidev0.1 corresponding to the two SPI channels available on the RPI extended port.

Before being able to use it, there is a couple of thing to understand about SPI:

  • As there is no signal to indicate if you want to read or write, what will be done depend only of what master will write.
  • This means, master will always initiate the dialog with the slave by sending data
  • Depending on these data, slave will read or write after it has decoded the master message.
  • As a consequence, an access to a slave is made with a WriteAndRead function
  • As what is done depend on the slave protocol, at SPI level, we do not exactly know when to write data and when to write data
  • The best way is to do both in parallel !

So, what is a Write and Read function ?

As we have 2 wires for Input and Output, we can in parallel write data and read data, this is what we are going to do : this function will get a buffer containing the data to be sent on the MOSI line; this function is also listening on MISO line and fill the read bits in the given buffer. You just have to indicates how many byte you want to transmit / listen and the buffer you send is returned with the read bytes.

Let’s take an example :

mrf24j40 read chronograph
mrf24j40 read timing

This means that the slave is expecting 8bits on SDI to identify a short address read, the next 8 bits do not care for the slave. But during the time of the next 8 bits, the slave will write the expected data on the SDO line. The Linux driver will get these bits and store them in the given buffer.

Let’s see how to code it :

#include <fcntl.h>               
#include <sys/ioctl.h>            
#include <linux/spi/spidev.h>    

#define ZIGBEE_SPI_NUM    0            // SPI port number used (by default 0)
#define ZIGBEE_SPI_FREQ   1000000      // example with 1MHz CLK line
#define ZIGBEE_SPI_BYTE_PER_WORD 8     // 8 Bits per word
demoSPI() {
    int mySPI;  // spidev file descriptor
    // Assuming spi_bcm2708 module has already been loaded
    if ( ZIGBEE_SPI_NUM == 0 ) {
        mySPI = open("/dev/spidev0.0", O_RDWR);
    } else {
        mySPI = open("/dev/spidev0.1", O_RDWR);
    }
    if ( mySPI < 0 ) {
        return -1;
    }

    //SPI_MODE_0 (0,0)     CPOL=0 (Clock Idle low level), CPHA=0 (SDO transmit/change edge active to idle)
    int spiWRMode = SPI_MODE_0;
    int spiRDMode = SPI_MODE_0;
    int spiBitsPerWord = ZIGBEE_SPI_BYTE_PER_WORD;
    int spiSpeed = ZIGBEE_SPI_FREQ;


    if (   ( ioctl(mySPI, SPI_IOC_WR_MODE, &spiWRMode) < 0 )
        || ( ioctl(mySPI, SPI_IOC_WR_BITS_PER_WORD, &spiBitsPerWord) < 0 )
        || ( ioctl(mySPI, SPI_IOC_WR_MAX_SPEED_HZ, &spiSpeed)  < 0 )
        || ( ioctl(mySPI, SPI_IOC_RD_BITS_PER_WORD, &spiBitsPerWord) < 0 )
        || ( ioctl(mySPI, SPI_IOC_RD_MODE, &spiRDMode) < 0 )
        || ( ioctl(mySPI, SPI_IOC_RD_MAX_SPEED_HZ, &spiSpeed)  < 0 )
        ) {
        return -1;
    }

    // Read data @ address 0x12
    unsigned char buff[2];
    buff[0] = (0x0 << 7) | (0x12 << 1 ) | 0x0;
    buff[1] = 0x0;

    struct spi_ioc_transfer spi[1];
    spi[0].tx_buf        = (unsigned long)(buff) ;  // transmit from "data"
    spi[0].rx_buf        = (unsigned long)(buff) ;  // receive into "data"
    spi[0].len           = 2
    spi[0].speed_hz      = ZIGBEE_SPI_FREQ;
    spi[0].bits_per_word = ZIGBEE_SPI_BYTE_PER_WORD;
    spi[0].cs_change     = 0;                       // this keep CS active between the different transfers
    spi[0].delay_usecs   = 0;                       // delay between two transfer

    if ( ioctl(mySPI, SPI_IOC_MESSAGE(1), &spi) < 0 ) {
        return -1;
    }
    printf(" # Read value %d \n",buff[1]);
    return 0;
}

In this example, buff[0] contains the command to be transfered to the Slave ; according to the documentation, we are sending a bit to 0 to indicate a short address order, then 6 bits indicates the address, then we are finishing with a 0 to indicate a read. The buff[1] do not care but we will keep it clean.

Once executed, the buff[0] will be cleared as the timing figure indicate SDO is by default low and buff[1] will contain the read value.

This is all what we need to use SPI, now, as SPI is not a serial communication port like a RS232 but a way interconnected master & slave, you have to implement an upper layer protocol, specific to the slave. For those interested in how to use the MFR24j40MA board with RPI, let’s take a look to my future article, I’ll detail it once implemented !

This entry was posted in Hardware and tagged , , . Bookmark the permalink.

6 Responses to Use SPI port on raspberry PI

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.