The I2C and SPI drivers that were written for PyBBIO are now available as their own library for C and Python, called serbus (like serial bus, get it?... I'm not very good at naming things...).

I wrote serbus as a way to get a friendly, clean API for I2C and SPI communication in Python, as I was not a huge fan of the APIs provided by the existing smbus-cffi and spidev libraries. It consists of two C files, which are really just wrappers for the ioctl calls supported by the Linux I2C and SPI drivers' device files (/dev/i2c-N and /dev/spidevX.Y), and two Python C extension providing a Python class for each. All the file I/O is done in the C code, making it about as low-latency as I2C and SPI from userspace Python can possibly be, and the two classes provide simple methods that cover pretty much any potential use case (if it's missing something let me know!).

I2C

Here's an example of using the I2CDev class to write some data to an I2C EEPROM (24LC256 or equivalent) then read it back:

import serbus, time

eeprom_addr = 0x50 # I2C slave address of EEPROM
start_msb   = 0x00 # High byte of location in EEPROM to write/read
start_lsb   = 0x00 # Low byte of location in EEPROM to write/read

data_to_write = range(10)

# Create an I2CDev instance for interfacing to /dev/i2c-2:
bus = serbus.I2CDev(2)
bus.open()

print "Writing data: {}".format(data_to_write)
# Write the data to the EEPROM:
bus.write(eeprom_addr, [start_msb, start_lsb] + data_to_write)
# The I2C write is asynchronous - give it a bit of time to complete:
time.sleep(0.01) # 10ms should be more than enough

# Read the data from the EEPROM:
bus.write(eeprom_addr, [start_msb, start_lsb])
read_data = bus.read(0x50, len(data_to_write))
print "Data read: {}".format(read_data)

if read_data == data_to_write:
  print "EEPROM write successful!"
else:
  print "EEPROM write failed, is WP enabled?"

bus.close()

The simple read() and write() methods make it a straight-forward task. I2CDev also provides a readTransaction() method, which writes a single byte then immediately reads a block of data. This can be used for tasks like reading from memory mapped I2C slave devices, or from I2C devices that take a command then return data, like the HTU21D relative humidity sensor used in this example:

import serbus, time

htu21d_bus      = 1    # Connected to /dev/i2c-1
htu21d_addr     = 0x40 # HTU21D slave address
htu21d_cmd_temp = 0xe3 # Command to read temperature
htu21d_cmd_rh   = 0xe5 # Command to read relative humidity

bus = serbus.I2CDev(1)
bus.open()

def getTemp():
  # Read the 3 bytes of data:
  msb, lsb, crc = bus.readTransaction(htu21d_addr, htu21d_cmd_temp, 3)
  # The crc (cyclic redundancy check) can be used to verify the data was
  # received without error - ignore it here
  # Combine the high and low bytes:
  raw_value = (msb<<8) | lsb
  # Clear the two status bits (see datasheet):
  raw_value &= ~0b11
  # Convert to Celsius and return (conversion from datasheet):
  return -46.85 + 175.72 * (raw_value/65536.0)

def getRH():
  msb, lsb, crc = bus.readTransaction(htu21d_addr, htu21d_cmd_rh, 3)
  raw_value = (msb<<8) | lsb
  raw_value &= ~0b11
  # Convert to %RH and return (conversion from datasheet):
  return -6.0 + 125.0 * (raw_value/65536.0)

try:
  while True:
    print
    print "Temperature:       {:0.2f}C".format(getTemp())
    print "Relative humidity: {:0.2f}%".format(getRH())
    time.sleep(1)

except KeyboardInterrupt:
  bus.close()

Since the readTransaction() method is implemented in C, it cuts down significantly on the latency that would be introduced by implementing the same routine in Python.

SPI

In this example the SPIDev class is used to control an AD7390 12-bit SPI DAC:

import serbus, time

# The SPI bus and chip select the AD7390 is connected to:
spi_bus = 1
cs      = 0
# /dev/spidev1.0

bus = serbus.SPIDev(spi_bus)
bus.open()

# Set bus configuration, as described in the AD7390 datasheet:
bus.setMaxFrequency(cs, 1000000)
bus.setBitsPerWord(cs, 16)
bus.setClockMode(cs, 3)
bus.setCSActiveHigh(cs)
bus.setMSBFirst(cs)

try:
  while True:
    # Ramp up from 0V to full-scale:
    for i in range(0, 2**12, 100):
      bus.write(cs, [i])
      time.sleep(0.01)

    # Ramp down from full-scale to 0V:
    for i in range(2**12-1, 0, -100):
      bus.write(cs, [i])
      time.sleep(0.01)

except KeyboardInterrupt:
  bus.close()

Like the I2CDev class, the SPIDev class has simple write() and read() methods which accept and return lists of bytes, allowing for reading or writing any number of words (up to the maximum 4096 bytes) with the same method calls.

Links

Check out the git repository and the documentation for more info on how to install and use serbus:

Share on RedditShare on LinkedInShare on FacebookTweet about this on TwitterShare on Google+