serbus

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:

PyBBIO on the BeagleBone Green

The Seeed Studio BeagleBone Green is a lower cost, stripped down derivative of the BeagleBone Black. It has no HDMI output or DC barrel jack, bringing the price down to $39 USD, and there are two Grove connectors for easy expansion using the Seeed Grove system.

BeagleBone Green

The BeagleBone Green ships with the PyBBIO Python library already installed, and in just a few steps you can get it updated to the latest version and get up and running with some example programs.

The first step is to get your BBG powered up and connected to the Internet. The quickest way to do that is to first connect up an Ethernet cable, then connect the Micro-USB cable for power. If you don't have hardwired access to a network you could try a USB WiFi dongle, or a quick search will find you plenty of tutorials on sharing your Internet connection with your BeagleBone over the USB connection.

Once you're BBG is booted and you have terminal access (e.g. over SSH or using the bash terminal in the Cloud9 IDE at http://beaglebone.local:3000), use pip (the Python package manager) to update PyBBIO to the latest and greatest version:

root@beaglebone:~# pip install --upgrade PyBBIO

Then you can head to the PyBBIO examples directory and run the blink.py demo to make sure everything is working:

root@beaglebone:~# cd /usr/local/lib/PyBBIO/examples/
root@beaglebone:/usr/local/lib/PyBBIO/examples# ls
ADS786x_test.py            encoder_test.py          mma7660_test.py
adt7310_test.py            EventIO_test.py          phant_test.py
ADXL345_test.py            fade.py                  remote_temp_control.py
analog_test.py             HTU21D_test.py           SafeProcess_test.py
available_pins.py          interrupt.py             security_cam.py
BBIOServer_mobile_test.py  knock.py                 serial_echo.py
BBIOServer_test.py         LiquidCrystal_clock.py   serial_server.py
blink.py                   LiquidCrystal_fps.py     Servo_sweep.py
BMP183_test.py             LiquidCrystal_glyphs.py  switch.py
DACx311_test.py            MAX31855_test.py         webcam_bbioserver_test.py
digitalRead.py             MLX90614_test.py         webcam_test.py
root@beaglebone:/usr/local/lib/PyBBIO/examples# python blink.py

You should see the two LEDs labeled D4 and D5 next to the Ethernet connector blinking back and forth. You now have the latest version of PyBBIO ready to go!

Be sure to check out the other example programs in that same directory, which show off a ton of PyBBIO's cool features and supported hardware, and take a look through the documentation over on Github.

Updated BeagleBone Pinout

I finally got around to updating PyBBIO's BeagleBone pinout diagram. It now includes the pinouts for all supported peripherals, with the new additions being SPI, I2C and eQEP, and it is color-coded by subsystem.


beaglebone_pinout


The PyBBIO documentation can be found at https://github.com/graycatlabs/PyBBIO/wiki

BeagleBone weather station

Here's a quick demo of PyBBIO's BMP183 and HTU21D libraries. This will push temperature, relative humidity, pressure and dew point data to ThingSpeak every 30 seconds.

import requests
from bbio import *
from bbio.libraries.BMP183 import BMP183
from bbio.libraries.HTU21D import HTU21D

bmp = BMP183(SPI0)
htu = HTU21D(I2C2)

API_KEY = "0000000000000000" # Put your write API here

def postData(*data):
  url = "https://api.thingspeak.com/update"
  params = {'api_key' : API_KEY}
  for i in range(0, len(data)):
    params['field%i' % (i+1)] = data[i]
  print params
  response = requests.post(url, params=params)
  print response

def setup():
  pass

def loop():
  pressure = bmp.getPressure() / 1000.0 # in kPa
  rh = htu.getHumidity()
  temp = htu.getTemp()
  dew_point = htu.calculateDewPoint(rh, temp)
  postData(temp, rh, pressure, dew_point)
  delay(30000)

run(setup, loop)

This uses the requests library to send the data to Thingspeak every 30 seconds in HTTP POST requests using the ThingSpeak API. You'll need to create a ThingSpeak channel first, then replace "0000000000000000" with the API key for your channel.

And here's the weather at Gray Cat Labs: https://thingspeak.com/channels/33102/

PyBBIO update – working towards v0.9.5

Working towards the v0.9.5 release, PyBBIO has a couple cool new libraries:

I've also added documentation for the LiquidCrystal library.

The I2C back-end has also been completely rewritten, replacing the smbus dependency with a custom I2C driver written in C and a C Extension to interface with it (I did it in two parts so the C driver could potentially be used in other programs). The API is a lot simpler, while also being a lot more versatile, so it should support a broader range of devices now. Since it is completely implemented in C, it should also be quicker when writing and reading multiple bytes.

PyBBIO update – version 0.9.4

Version 0.9.4 of PyBBIO brings some back-end changes and bug fixes.

SPI The SPI driver has been replaced with a new C SPI driver and a C extension to interface to it. The C driver can potentially be used on any Linux system, and moving everything that was previously implemented in Python into a C extension should provide a nice performance increase when shifting multiple bytes at a time.

Bug fixes

  • Turns out there was a memory leak introduced in the GPIO C extension, which is now fixed.
  • There was a type check missing in the sysfs C extension, which, among other potential issues, was breaking the RotaryEncoder library.

Stay tuned for the v0.9.5 release, which should include some more exciting changes!

PyBBIO update – version 0.9.3

Version 0.9.3 of PyBBIO is out tonight, which includes some great performance upgrades and a new LCD library.

GPIO Speed

People tend to worry about the speed of GPIO operations when using sysfs entries. While it's in many ways arbitrary for timing sensitive applications because no GPIO access from userspace is real-time anyway, version 0.9.3 brings significantly faster GPIO operations. All of the GPIO related code is now in C extensions, and all GPIO state file descriptors used in a program are kept open until it exits.

Running this test program with v0.9.2:

from bbio import *
pinMode(GPIO1_28, OUTPUT)
while 1:
  digitalWrite(GPIO1_28, HIGH)
  digitalWrite(GPIO1_28, LOW)

The pin is toggled at about 1.5kHz. Running the same program with v0.9.3 now toggles the pin at about 25kHz, or around 17x faster.

LiquidCrystal

There's also a new library for driving HD44780 compatible character LCDs. It supports the majority of the LCD's functionality (though not all of it is tested yet!), including scrolling and custom glyphs. API docs coming soon...

--

The rest of the major changes are:

  • Moved all GPIO code to C extension
  • Added LiquidCrystal character LCD library
  • Improved sysfs interface for faster kernel driver file access
  • Removed 3.2 support, use 0.9.2 if you're still running 3.2 for some reason...
  • Libraries are now contained within the bbio package. This changes importing a bit, user code will need updating
  • Simplifies setup.py
    • Examples are no longer copied
    • DT overlays are distributed compiled and copied with setuptool as data_files
  • BBIOServer now serves from ~/.BBIOServer instead of from inside the package
  • Started adding stubs for universion-io support, not yet implemented

Project teaser – BeagleBone Black thermal imaging

Disclaimer: this is just a little teaser for a project I started working on recently. Details, code and hardware design to come!

I was inspired by Noah Feehan's awesome work on his GRID-EYE BLE thermal imaging camera (also on hackaday.io), based on Panasonic's AMG88xx Grid-EYE sensors. These are 8x8 thermal array sensors with a 60 degree viewing angle, and cost around $40 in single quantities.

I connected the AMG8852 to my BeagleBone Black, then wrote a Python program using PyBBIO and OpenCV to grab temperature arrays from the sensor, convert the temperatures to RGB color values within a linear gradient, scale up the 8x8 image, then save the frames to a video file.

Here's a sample scaling up to 250x250px at 4fps:

(I walk into frame, wave my arms, then walk out of frame and give a thumbs up)

And trying out a few different color mappings:

I have bigger plans for this sensor, so stay tuned!

PyBBIO update – version 0.9.2

A few new features have been added in version 0.9.2:

WebCam library

The last of the GSoC 2014 additions from rseetham, the WebCam library allows live streaming of WebCam video over TCP, as well as capturing of still images to JPG files.

BBIOServer updates

The included JQuery has been updated to version 1.11.1. Also merged an update from Ikario that adds a range slider input element.

I2C improvement

ycoroneos added new quickwrite() and readTransaction() methods to the I2C objects that allow communicating with a wider range of devices.
To update to v0.9.2:

# pip install --upgrade PyBBIO

PyBBIO update – version 0.9.1

This is a fairly minor update, and I really only tagged it as a new version because it squashes a small bug with the pip install process. However, there is one awesome new feature from my GSoC 2014 student Rekha: a library for the MMA7660 3-axis accelerometer.

The MMA7660 is a very neat accelerometer, and includes tap, shake and orientation detection. Rekha has implemented all the cool features in the library, and taps and shakes can easily fire software interrupts to run user code. Documentation coming soon!