SPI Library for Python

The Onion SPI Library, libonionspi is a dynamic C library that provides functions to easily read from and write to devices communicating with the Omega over the GPIOs via the SPI protocol. The library can be used in C and C++ programs.

Also available is a Python module, called onionSpi, that implements an SPI object using functions from the C Library.

[[TOC]]

The SPI Protocol

The Serial Peripheral Interface (SPI) is a four-wire synchronous communication protocol, largely used to connect microprocessors or microcontrollers to sensors, memory, and other peripherals.

The four signals are:

SPI Signal Meaning
SCK System Clock
MOSI Master Out, Slave In - Data sent from the Master to the Slave
MISO Master In, Slave Out - Data sent from the Slave to the Master
CS/SS Chip Select/Slave Select

The fact that it is a synchronous data bus means that one of the lines is a clock, used to synchronize the bits being sent on the data lines.

The protocol is based on the Master-Slave architecture, so the Master will generate the System Clock and the Slave Select signals. In systems with multiple slaves, there will be multiple Slave Select signals.

For more details on SPI, check out the Wikipedia article.

Linux and SPI

Linux systems usually control SPI devices through kernel drivers. However, it is also possible to generate the SPI protocol by bit-banging on connecte GPIOs. Bit-banging SPI is done through an adapter usually found at /dev/spidevX.Y where X is the device number and Y is the bus number.

The Omega does not have an SPI bit-banging adapter setup by default, but this can be done by using the insmod command to insert a module into the kernel. The libonionspi library takes care of this for you so you don’t have to deal with the intricacies.

The Python Module

The onionSpi Python module provides a Python object, OnionSpi, that serves as a wrapper around the C library functions. The usage is slightly different since the Python module is object oriented and the C library is just a set of functions.

Source Code

The source code can be found in the Onion spi-gpio-driver GitHub Repo.

Programming Flow

The Python module revolves around the OnionSpi object; once it is initialized with the bus number and device ID that the desired SPI adapter uses, the rest of the class functions can be used to register the adapater, setup the adapter’s SPI options, and most importantly, make data transfers!

Once the object is initialized, the recommended flow is as follows:

The details of the specific functions to perform these actions are outlined below.

Using the Python Module

Installing the Module

To install the Python module, run the following commands:

opkg update
opkg install python-light pyOnionSpi

This will install the module to /usr/lib/python2.7/OmegaExpansion/

Note: this only has to be done once.

Using the Module

To add the Onion SPI Module to your Python program, include the following in your code:

import onionSpi

Example

An example of how the OnionSpi object is used can be found in the spi-gpio-driver repo.

Initialization of the Object

The object needs to be initialized before it can be used for reading and writing:

spi  = onionSpi.OnionSpi(busNumber, deviceId)

Arguments

The constructor requires the bus number and device ID of the SPI device adapter that is to be used. These numbers correspond to the device adapter itself: /dev/spidevX.Y where X is the device number and Y is the bus number.

Object Member Variables

The OnionSpi object has several member variables that are used to define specific SPI parameters and settings. The can be accessed and modified directly using the object.

The member variables are as follows:

Object Member Meaning
bus The device adapter bus number
device The device adapter device ID
speed Maximum transmission clock speed in Hz
delay Delay in us after last bit transferred before optionall deselecting the device before the next transfer
bitsPerWord Number of bits in a transmitted word
mode SPI mode: can be 0 to 3 (Mode 0 is the most common)
modeBits Additional SPI operating parameters
sck GPIO for SPI SCK signal
mosi GPIO for SPI MOSI signal
miso GPIO for SPI MISO signal
cs GPIO for SPI CS signal

The modeBits parameter may be a little difficult to work with so the following member variables were added:

Object Member Meaning
threewire Three-wire SPI: MOSI and MISO lines are shared on a single GPIO
lsbfirst Modify the protocol to transmit bytes LSB to MSB
loop Enable loopback mode
noCs Modify the protocol to not generate a Chip-Select line
csHigh Modify the protocol for an active-high Chip-Select line

Changing any of these parameters will modify the modeBits member as well.

Default Values

When the object is first initialized, all of the member variables are initialized to legitimate values:

SPI Setting Programmed Default
speed 100000 Hz (100 kHz)
delay 0
bitsPerWord 0 (Corresponds to 8 bits per word)
mode SPI Mode 0

Sets the SPI lines to the following GPIOs:

SPI Signal Omega Gpio
SCK 6
MOSI 18
MISO 1
CS/SS 7

Examples

Creating an SPI object, modifying some of the parameters, and then printing parameters:

spi 	= onionSpi.OnionSpi(0, 1)

spi.cs 		= 20
spi.sck		= 19
spi.csHigh 	= 1
spi.speed 	= 400000

print 'SPI CS GPIO:   %d'%(spi.cs)
print 'SPI SCK GPIO:  %d'%(spi.sck)
print 'SPI MISO GPIO: %d'%(spi.miso)
print 'SPI MOSI GPIO: %d'%(spi.mosi)

print 'SPI Speed: %d Hz (%d kHz)'%(spi.speed, spi.speed/1000)
print 'Mode Bits: 0x%x, CS-High: %d'%(spi.modeBits, spi.csHigh)

Setup Functions

The following functions are used to register the SPI device adapter and setup any SPI protocol options as required.

Check if Device is Registered

To check if a device adapter with the bus number and device ID that were specified in the object constructor is already registered with the system:

return = spi.checkDevice()

Return Values

The function will return 0 if the device adapter is already mapped.

The return value will be 1 if the device adapter is NOT mapped/

Register Device

This function will register an SPI device with the bus number, device ID, and other SPI parameters as specified in the object variable members:

return = spi.registerDevice()

It will first check if a device with the specified bus number and device ID is already registered. If it is, it will just return 0.

If not, it will attempt to register the SPI device adapter by inserting an SPI-gpio module into the kernel. If this operation is successful, the function will return 0, if not, 1 is returned.

The function uses the following information to register the device:

Example

A short example showing how to register an SPI device adapter:

spi 	= onionSpi.OnionSpi(0, 1)

# modify any SPI settings as required
spi.cs 		= 20
spi.speed 	= 400000

# register the device
spi.registerDevice()

Setup SPI Device

This function will setup additional SPI parameters on the device adapter

return  = spi.setupDevice()

It will setup the following SPI parameters:

Conclusion

Once a device is registered, data can be read from and written to the device via SPI.

Reading Function

This function reads a specified number of bytes from a specified address on an SPI device:

values 	= spi.readBytes(addr, size)

The read bytes are returned in the form of a list, even if there is only one byte.

Arguments

The addr argument specifies from which address on the SPI device to read.

The size argument specifies the number of bytes to read.

Examples

Read a byte from address 0x33:

rdVal = spi.readBytes(0x33, 1)
# rdVal[0] now contains the byte that was read

Read three byres from address 0x00:

rdVal = spi.readBytes(0x00, 3)
# the rdVal list now contains the three bytes that were read 

Writing Functions

The OnionSpi class has two functions that can be used to write data via SPI:

Write Bytes

This function will write a list of bytes to a specified address on an SPI device

return 	= spi.writeBytes(addr, values)

Arguments

The addr argument specifies to which address on the SPI device to write.

The values argument is a list of values to be written. Even if there is only a single byte, it should be in list form.

Examples

Write 0x23 to address 0x91:

ret 	= spi.writeBytes(0x91, [0x23])

Write 0x23, 0x33, 0x07 to address 0x96:

vals 	= [0x23, 0x33, 0x07]
ret 	= spi.writeBytes(0x96, vals)

Write Bytes without an Address

This function will just write a list of bytes to an SPI device (without specifying an address):

return 	= spi.write(values)

Arguments

The values argument is a list of values to be written. Even if there is only a single byte, it should be in list form.

Examples

Write 0x08, 0x34, 0x02, 0x07:

ret 	= spi.write([0x08, 0x34, 0x02, 0x07])

Write 0x00, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D:

vals 	= [0x00, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D]
ret 	= spi.write(vals)

Write 0x11:

ret 	= spi.write([0x11])