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

  1. Paul says:

    Wow! Thank you, thank you, thank you! I was considering using the R-Pi for the SPI control of just that chip/module and have very little time to make it work. If I do this, your help will be invaluable.
    Thank you, again.
    Paul

    • Paul says:

      Actually, I did not finish the code for this chip and i did not found an existing solution on Internet.

      The best way in my point of view to do it on a RPI is to use the working code existing on arduino and connect an arduino to the raspberry with serial line. That way you can make it work in 1 hour max.

      • Paul says:

        Yes. porting from the Arduino would be a good approach. I hacked together some working communications with this chip based on this web site: NerdFever.com

        However, it was not an embedded solution. It is still a good starting point. I would expect that I will start with only a custom/simple communication stack as I do not know of a simple stack such as Zigbee or 6LoPAN that is ported to the R-Pi … unless you know of such a stack for the R-Pi?

        Also, do you know a good web site that integrates the MRF24J40 with Arduino?

        Paul

      • Paul says:

        This projects works well with arduino : https://github.com/karlp/Mrf24j40-arduino-library

  2. KRISTTOPHER says:

    Hello Paul, I wonder if you could provide the fnal version of the code, I am beginner and I’m working on the project with the same specification of his, but I could not make this code execute with success in raspiberry.
    Thank You

    • Paul says:

      Hello, in fact I’m jumped on many different projects so I’m far away to finish this post and hack. BUT there are small & easy to port libraries for arduino you can just port on RPI, I assume a couple of hours only is needed for this and it should make the job.
      Unfortunately I did not save the link to these lib but Google will tell you the way to go. Have fun !
      And if you have some peace of code, do not hesitate to be back and push it to my site for sharing.

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.