3 Comments

Talking SPI on Raspberry Pi

The Serial Peripheral Interface (SPI) Bus is used for communication between microcontrollers and other digital devices. On the Raspberry Pi it can be very handy for doing things like acquiring values from an Analog-to-Digital converter, reading a temperature sensor, or communicating with another microcontroller.

When using SPI on the Raspberry Pi, there are many options for controlling and interacting with the bus.

Using Straight-up C

If you are comfortable coding in C, you can access the SPI bus using the Unix ioctl() function.


    // Mode options: SPI_LOOP, SPI_CPHA, SPI_CPOL, SPI_LSB_FIRST, 
    //               SPI_CS_HIGH, SPI_3WIRE, SPI_NO_CS, SPI_READY
    static uint8_t mode = SPI_CPHA;
    static uint8_t bits = 8;
    static uint32_t speed = 3000000;
    static uint16_t delay = 0;
    
    SPI_RESULT_T SPI_DoTransfer(char * device, uint8_t * p_rx_buf, uint8_t * p_tx_buf, uint32_t len)
    {
      int fd;
      int ret;

      // Open the device
      fd = open(device, O_RDWR);
      if (fd < 0) return SPI_ERR_OPENING_DEVICE;

      // Set SPI mode
      ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      // Set the Bits per Word
      ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      // Set the max speed
      ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
      if (ret == -1) return SPI_ERR_CFGING_PERIPH;

      struct spi_ioc_transfer tr = 
      {
        .tx_buf = (unsigned long)p_tx_buf,
        .rx_buf = (unsigned long)p_rx_buf,
        .len = len,
        .delay_usecs = delay,
        .speed_hz = speed,
        .bits_per_word = bits,
      };

      ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
      close(fd);
      
      if (ret < 1) 
        return SPI_XFER_ERROR;
      else
        return SPI_SUCCESS;
    }

Ruby Gem: PiPiper

Another option for interacting with the SPI bus is by using a Ruby gem. There are several out there, but (so far) the one I like the best is PiPiper. PiPiper is a nice gem that provides easy access to not only the SPI peripheral but also I2C and GPIO pins. One nice feature that PiPiper provides is the ability to poll a SPI device at regular intervals. You can also setup watches on GPIO pins which makes it really simple to execute code on pin transitions.

To test out PiPiper, I made a Ruby class to read temperature data from a SPI thermocouple amplifier (Maxim's MAX6675). Here is what it looks like.


require 'pi_piper'

class Thermocouple
    include PiPiper

    def initialize()
    end

    def read_sensor
      PiPiper::Spi.begin do |spi|
        PiPiper::Spi.set_mode(0,1)

        # Setup the chip select behavior
        spi.chip_select_active_low(active_low = true, Spi::CHIP_SELECT_0)

        # Set the bit order to MSB
        spi.bit_order Spi::MSBFIRST

        # Set the clock divider to get a clock speed of 2MHz
        spi.clock 2000000

        # Activate the chip select
        spi.chip_select do
          # Do the SPI write
          return SensorResponse.new spi.read(2)
        end
      end
    end
end

Before using PiPiper, you must install Ruby and the Ruby Development gem on your pi.

sudo apt-get install ruby ruby1.9.1-dev

Then all you have to do is install the pi_piper gem.

sudo gem install pi_piper

One important note is that the PiPiper Ruby gem is still under development. I noticed that the current version on Rubygems.org does not support changing the SPI mode. I tried pulling the latest code from the master branch on GitHub, and the issue appears to be resolved there.