Linux I2C and SPI in C

My last post was about the serbus library, which provides clean and simple C and Python APIs for controlling I2C and SPI buses from userspace on GNU/Linux systems. I wrote serbus as a way to offer a friendlier Python API than the alternative libraries provide, and to do so while keeping it as low-latency as possible I first created an API in C that I could interface with from inside Python C extensions. In the last post I showed a few examples of using the serbus Python package, but because serbus consists of C extensions that use a separate C API, that C API can be used by itself, providing a clean level of abstraction above the ioctl calls used to interface with I2C and SPI device files. So here I'll show a couple of examples of using serbus in C.

As serbus is really just a wrapper for the standard Linux I2C and SPI ioctl calls, it requires that I2C and SPI kernel drivers be loaded to expose /dev/i2c-N and /dev/spidevX.Y device files. For example, on the BeagleBone Black you could load the I2C or SPI Device Tree overlays using cape manager, e.g.:

# echo I2C1 > /sys/devices/bone_capemgr.*/slots
# echo SPIDEV0 > /sys/devices/bone_capemgr.*/slots

Other GNU/Linux systems will have different ways to enable I2C and SPI.

I2C

This program shows how to read the current relative humidity and temperature from the HTU21D sensor:

/**
 * @file i2c_htu21d.c
 * @author Alex Hiam - <alex@graycat.io>
 *
 * @brief Uses serbus to get RH and temperature data from an HTU21D.
 *
 * Requires an I2C Kernel driver be loaded to expose a /dev/i2c-N interface
 * and an HTU21D be connected on the I2C bus.
 */


#include "i2cdriver.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define HTU21D_BUS      1    // Connected to /dev/i2c-1
#define HTU21D_ADDR     0x40 // HTU21D slave address
#define HTU21D_CMD_TEMP 0xe3 // Command to read temperature
#define HTU21D_CMD_RH   0xe5 // Command to read relative humidity

/**
 * @brief Reads and returns the current temperature from the HUT21D
 *
 * @param i2c_fd I2C bus file descriptor
 *
 * @return the current temperature in Celsius
 */

float getTemp(int i2c_fd) {
  uint8_t rx_buffer[3];
  int raw_value;
  // Set the slave address:
  if (I2C_setSlaveAddress(i2c_fd, HTU21D_ADDR) < 0) {
    printf("*Could set slave address to %d\n", HTU21D_ADDR);
    exit(0);
  }
  // Read the 3 bytes of data:
  I2C_readTransaction(i2c_fd, HTU21D_CMD_TEMP, (void*) rx_buffer, 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 = (rx_buffer[0]<<8) | rx_buffer[1];
  // Clear the two status bits (see datasheet):
  raw_value &= ~0b11;
  // Convert to Celsius and return (conversion from datasheet):
  return -46.85 + 175.72 * (((float)raw_value)/65536.0);
}

/**
 * @brief Reads and returns the current humidity from the HUT21D
 *
 * @param i2c_fd I2C bus file descriptor
 *
 * @return the current temperature in Celsius
 */

float getRH(int i2c_fd) {
  uint8_t rx_buffer[3];
  int raw_value;
  if (I2C_setSlaveAddress(i2c_fd, HTU21D_ADDR) < 0) {
    printf("*Could set slave address to %d\n", HTU21D_ADDR);
    exit(0);
  }
  I2C_readTransaction(i2c_fd, HTU21D_CMD_RH, (void*) rx_buffer, 3);
  raw_value = (rx_buffer[0]<<8) | rx_buffer[1];
  raw_value &= ~0b11;
  // Convert to %RH and return (conversion from datasheet):
  return -6.0 + 125.0 * (raw_value/65536.0);
}

int main() {
  int i2c_fd;
  float temp, rh;
  // Open the I2C device file:
  i2c_fd = I2C_open(HTU21D_BUS);
  if (i2c_fd < 0) {
    printf("*Could not open I2C bus %d\n", HTU21D_BUS);
    exit(0);
  }
  // Read and print the current temp and rh:
  temp = getTemp(i2c_fd);
  rh = getRH(i2c_fd);
  printf("Temp : %5.2fC\n", temp);
  printf("RH   : %5.2f%%\n", rh);
  // Close the I2C file descriptor:
  I2C_close(i2c_fd);
  return 0;
}

Though this is specific to the HTU21D, this same strategy would apply to a huge number of other I2C sensors that use memory-mapped or command-response protocols. And there's also separate I2C_write() and I2C_read() functions for communicating with devices that don't use these types of protocols.

SPI

In this example we use the SPI driver to control an AD7390 digital-to-analog converter:

/**
 * @file spi_ad7390.c
 * @author Alex Hiam - <alex@graycat.io>
 *
 * @brief Uses serbus to control an AD7390 DAC.
 *
 * Requires an SPI Kernel driver be loaded to expose a /dev/spidevX.Y
 * interface and an AD7390 be connected on the SPI bus.
 */


#include "spidriver.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>

#define AD7390_BUS       1        // Connected to /dev/spidev1.X bus
#define AD7390_CS        0        // Using chip select 0 (/dev/spidev1.0)
#define AD7390_FREQ      1000000  // SPI clock frequency in Hz
#define AD7390_BITS      16       // SPI bits per word
#define AD7390_CLOCKMODE 3        // SPI clock mode

static uint8_t running;

/**
 * @brief Sets the given SPI bus per the AD7390's required configuration
 *
 * @param spi_fd SPI bus file descriptor
 */

void AD7390_SPIConfig(int spi_fd) {
  SPI_setMaxFrequency(spi_fd, AD7390_FREQ);
  SPI_setBitsPerWord(spi_fd, AD7390_BITS);
  SPI_setClockMode(spi_fd, AD7390_CLOCKMODE);
  SPI_setCSActiveHigh(spi_fd);
  SPI_setBitOrder(spi_fd, SPI_MSBFIRST);
}

/**
 * @brief Sets the output of the AD7390 in the range 0-4095
 *
 * @param spi_fd SPI bus file descriptor
 * @param value DAC value in range 0-4095
 */

void AD7390_setValue(int spi_fd, uint16_t value) {
  SPI_write(spi_fd, (void*) &value, 1);
}


/**
 * @brief Called when Ctrl+C is pressed - triggers the program to stop.
 */

void stopHandler(int sig) {
  running = 0;
}

int main() {
  int spi_fd, value;
  // Open the SPI device file:
  spi_fd = SPI_open(AD7390_BUS, AD7390_CS);
  if (spi_fd < 0) {
    printf("*Could not open SPI bus %d\n", AD7390_BUS);
    exit(0);
  }
  // Configure the SPI bus:
  AD7390_SPIConfig(spi_fd);

  // Loop until Ctrl+C pressed:
  running = 1;
  signal(SIGINT, stopHandler);
  while(running) {
  // Ramp up from 0V to full-scale:
    for (value=0; value<4095; value++) {
      AD7390_setValue(spi_fd, value);
    }
    // Ramp down from full-scale to 0V:
    for (value=4096; value>0; value--) {
      AD7390_setValue(spi_fd, value);
    }
  }
  // Set DAC output to 0 and close the SPI file descriptor:
  AD7390_setValue(spi_fd, 0);
  SPI_close(spi_fd);
  return 0;
}

The SPI driver provides functions to configure all the various SPI settings, and simple read(), write() and transfer() functions for sending and receiving arbitrary sized chunks of data.

Note: Because the SPI protocol allows for multiple word sizes, the read(), write() and transfer() functions take void pointers for their send and receive buffers, as well as a parameter specifying the number of words to transfer. If the word size is a power of 2, data to write should be stored in an array of the corresponding size (e.g. uint8_t for 8-bit words, uint16_t for 16-bit words, etc.), and for other arbitrary word sizes the type used should be the smallest type that can fit the words (e.g. uint16_t for 12-bit words, uint32_t for 17-bit words, etc.). Any data received follows this same byte alignment pattern, and therefore the receive buffers should follow the same type rules.

Documentation

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.

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/

BeagleBone’s SETDATAOUT and CLEARDATAOUT registers

I recently received an email from Mark Stephens (mrmorphic on github) pointing out that I could be using the AM3359's GPIO_SETDATAOUT and GPIO_CLEARDATAOUT to set the state of the BeagleBone's GPIO pins in the digitalWrite() routine of my PyBBIO library (Mark has been working on a similar library in Go at github.com/mrmorphic/hwio). I had previously been setting pin states the way I'm used to doing it on AVR and MSP430 microcontrollers, which is to grab the entire pin state register (GPIO_DATAOUT on AM335x chips), which might look something like this:

00000010 00000000 00100000 00000000

Then create an equal length value of all 0s with the bit corresponding to the desired pin set to 1, e.g. if setting the state of pin 7 in the register it would 1<<7, or:

00000000 00000000 00000000 10000000

Then to set the pin high, the GPIO_DATAOUT register is set to its previous value bitwise OR-ed with the new value:

   00000010 00000000 00100000 00000000
OR 00000000 00000000 00000000 10000000
   -----------------------------------
 = 00000010 00000000 00100000 10000000

And to then set the pin low, the GPIO_DATAOUT register is set to its value bitwise AND-ed with the inverse, or twiddled version, of the new value:

 ~ 00000000 00000000 00000000 10000000
   -----------------------------------
 = 11111111 11111111 11111111 01111111

    00000010 00000000 00100000 10000000
AND 11111111 11111111 11111111 01111111
    -----------------------------------
  = 00000010 00000000 00100000 00000000

The issue with using this method in PyBBIO is that packing and unpacking 32-bit memory registers in Python is a relatively slow process. Luckily the AM335x MPUs provide two additional registers for manipulating the state of digital output pins: GPIO_CLEARDATAOUT and GPIO_SETRDATAOUT, which are used to set output pins low and high respectively. As soon as a 1 is written to any of the bits in one of these registers, the according pin is set high or low, and the bit is set back to 0. So writing a 0 to a bit in one of these registers will have no effect, because that is the normal state of all the bits. This means that the SETDATAOUT register can only set pins high, and the CLEARDATAOUT register can only set pins low, and we no longer have to worry about maintaining register states. Using these registers in digitalWrite(), we can cut our register access in half.

To see how much of an improvement this change actually makes, I wrote a quick program to toggle the state of a GPIO pin as fast as possible:

from bbio import *

def setup():
  pinMode(GPIO1_6, OUTPUT)

def loop():
  state = 1
  while(True):
    digitalWrite(GPIO1_6, state)
    state ^= 1

run(setup, loop)

I then hooked my scope and ran the program with digitalWrite() not using the SETDATAOUT and CLEARDATAOUT registers, and I clocked it at around 10.6KHz (frequency is in the bottom right of the image):

PyBBIO_digitalWrite_slow

Then I made the change to digitalWrite() and ran the program again, and low and behold it just about doubled the speed of the toggling to just under 20KHz:

PyBBIO_digitalWrite_improved

BeagleBone IO using Python and mmap

The BeagleBone runs an Angstrom linux distro, which includes the special /dev/mem file, which provides access to the processor's entire physical memory. More on /dev/mem in the linux man pages here. The BeagleBone is built around the TI AM3359 ARM Cortex-A8 microprocessor, and breaks out many of its I/O pins to two 2x23 pin female headers, including 4 UART ports, 7 ADC inputs, 4 PWM outputs, a whole bunch of GPIO pins and more. The use of these peripherals is very well documented in the AM335x Technical Reference Manual. The use of each module is done through reading and writing to different 16- and 32-bit memory registers, whose addresses are given in the manual. A Unix mmap can be used to create a memory map of the /dev/mem file, which allows read and write access to it through byte addressing. This way the module registers may be accessed by their addresses in section 2.1 of the reference manual.

The most basic module to use is the GPIO module. Other modules get more complicated, but they are all very well documented in the Technical Reference Manual. The biggest difference is that many of the other modules have clocks which must be enabled before they can be used. This is done using the clock module registers, and is described in detail in chapter 8.

I'll walk through blinking one of the on-board LEDs using Python's mmap module. Before starting, we need to know which pin to use. To do so, we want a copy of the BeagleBone schematic handy, which can be found in the links here or on the uSD card supplied with the BeagleBone. According to page 3 of the schematics, the USR1 LED is connected to the GPIO1_22 pin (easily found by searching 'USR1' in the pdf), which means pin 22 in the GPIO1 module. Now it's time for a little Python. First we need to import the mmap module, which is not on the BeagleBone by default, but can be installed by running '# opkg install python-mmap', and defining a few addresses:

from mmap import mmap
import time, struct

GPIO1_offset = 0x4804c000
GPIO1_size = 0x4804cfff-GPIO1_offset
GPIO_OE = 0x134
GPIO_SETDATAOUT = 0x194
GPIO_CLEARDATAOUT = 0x190
USR1 = 1<<22

Mapping the entire /dev/mem file would require that over a gigabyte be allocated in Python's heap, so the offset address and size variables are used to keep the mmap as small as possible, in this case just the GPIO1 register. These values are straight out of the memory map in section 2.1 of the Technical Reference Manual. the GPIO_OE, GPIO_SETDATAOUT and GPIO_CLEARDATAOUT addresses are found in section 25.4, which shows the address offsets of each register within the GPIO modules, starting from the base module address. Chapter 25 explains how to use the GPIO registers. All we need to do is set a pin as an output, then set and clear its output state. To do the first, we need the 'output enable' register (GPIO_OE above). Then the GPIO_SETDATAOUT and GPIO_CLEARDATAOUT registers will do the rest. Each one of these registers is 32 bits long, each bit of which corresponding to one of 32 GPIO pins, so for pin 22 we need bit 22, or 1 shifted left 22 places.

Next we need to make the mmap, using the desired size and offset:

with open("/dev/mem", "r+b" ) as f:
  mem = mmap(f.fileno(), GPIO1_size, offset=GPIO1_offset)

Now we need to set the pin as an output. I'll do this line by line:

The mmap is addressed byte by byte, so we can't just set a single bit. The easiest thing to do is grab the whole 4-byte register:

packed_reg = mem[GPIO_OE:GPIO_OE+4]

We now have 32 bits packed into a string, so to do any sort of bitwise operations with it we must unpack it:

reg_status = struct.unpack("<L", packed_reg)[0]

The 'L' tells struct.unpack() to unpack the string into an unsigned long, which will give us the full 32-bit register. The '<' tells it that the string is packed little-endian, or least-significant byte first. The BeagleBone's memory is little-endian, so if we tell this to struct.unpack() it will return the 32 bits in the order they are shown in the reference manual register maps.

We now have the 32-bit integer value of the register, so we can configure the LED as an output by clearing its bit:

reg_status &= ~(USR1)

Now all that's left to do is to pack it little-endian back into a string and update the mmap:

mem[GPIO_OE:GPIO_OE+4] = struct.pack("<L", reg_status)

Now that we know the pin is configured as an output, it's time to get blinking. We could use the GPIO_DATAOUT register to do this, but we would want to preserve the state of all the other bits in it, so we would need to do the same process of unpacking, manipulating then repacking. That's what the SETDATAOUT and CLEARDATAOUT registers are for. Writes to them affect only the pins whose bits are set to 1, making the next step much easier:

for i in range(5):
  # Set it high:
  mem[GPIO_SETDATAOUT:GPIO_SETDATAOUT+4] = struct.pack("<L", USR1)
  time.sleep(0.5)
  # Set it low:
  mem[GPIO_CLEARDATAOUT:GPIO_CLEARDATAOUT+4] = struct.pack("<L", USR1)
  time.sleep(0.5)

mem.close()

Which will blink the LED 5 times then close the mmap and exit.

There is an issue here which is not at all obvious, which is that setting a pin as an output in the output enable register in no way guarantees that the corresponding physical pin will be connected to it. In fact, there is no guarantee that any pin will be as it is labeled in the schematics! This is due to the fact that the AM335x processors have a whole lot more module inputs and outputs than there are physical pins, so every external pin is actually the output of a multiplexer with up to 7 different possible modes. All this pinmuxing is handled by the AM335x control module. Of course there's a catch, which is hiding in section 9.1:

Note: For writing to the control module registers, the Cortex A8 MPU will need to be in privileged mode of operation and writes will not work from user mode.

Luckily, thanks to the friendly BeableBone developers, there is a user-level workaround. There is a file for each external pin found in /sys/kernel/debug/omap_mux/. Writing to these files tells a driver to configure the pin multiplexers as desired. To find the proper file names is a bit of a pain, and requires one more document; the AM3359 datasheet, found here. The first step is to find the pin of interest in the Beaglebone schematics. The USRx LED outputs actually are muxed correctly when the board boots, so for this example I'll choose a different GPIO pin. Lets say we want to use GPIO2_7, which is pin 46 on the P8 header, as found on page 11 of the schematics. We need to find where the pin is connected to the AM3359, which is easily done by searching 'GPIO2_7'. What we're after is the long list of modes for the pin, which in this case is:

LCD_DATA1/GPMC_A1//EHRPWM2B//PR1_PRU1_PRU_R30_1/PR1_PRU1_PRU_R31_1/GPIO2_7

Now we open up the AM3359 datasheet and go to Table 2-7. What we're looking for is the pin for which the first item in this list, in this case LCD_DATA1, is the first item in the 'signal name' column. Notice if you use the search function to find it, it shows up a few times in the 'signal name' column. These are different pins which can also be set to this function, but they are not the actual physical pin we are looking for. Once found, look down the 'signal name' column within the LCD_DAT1 row until you find GPIO2_7, and take note of the mode number just to the right of it (7 in this case). Also take note of the 'pin name' for mode 0, as this will be the name of the file we need to write to, in this case 'lcd_data1' (these are all upper-case in the schematics, but the file names are all lower-case). While here, take a look at the 'buffer strength' column. This is the maximum current the pins can source; only 6mA for the GPIO pins! This is usable for low-power LEDs and the such, but external transistors are generally a must for any sort of power supply switching.

Now it's back to the reference manual for a minute, to Table 9-58 in section 9.3.1.50. This shows us everything we can configure with the control module files, including pullup and pulldown resistors, and 'input enable'. The input enable has no effect on output behavior, so it can just be left set for the GPIO pins. So, what we want is to set the LCD_DAT1 multiplexer to mode 7 with rx_enabled. The pinmux files expect the desired value to be written in hexadecimal, so the binary value we want of 100111 (1<<5 for receiver enable | 111 for mode 7) must be written as its hex value of 27. This can be done at the command line:

# echo 27 > /sys/kernel/debug/omap_mux/lcd_data1

Or in Python:

with open("/sys/kernel/debug/omap_mux/lcd_data1", 'wb') as f:
  f.write('27')

And we can easily confirm the change with:

# cat /sys/kernel/debug/omap_mux/lcd_data1

So to show the whole process, here's a script that will blink an LED connected to GPIO2_7 until ctrl-c is pressed (notice that the mmap offset has changed, as the GPIO pin used here is in the GPIO2 module, where as we were using GPIO1 before):

from mmap import mmap
import time, struct

GPIO2_offset = 0x481ac000
GPIO2_size = 0x481acfff-GPIO2_offset
GPIO_OE = 0x134
GPIO_SETDATAOUT = 0x194
GPIO_CLEARDATAOUT = 0x190
LED = 1<<7

with open("/dev/mem", "r+b" ) as f:
  mem = mmap(f.fileno(), GPIO2_size, offset=GPIO2_offset)

with open("/sys/kernel/debug/omap_mux/lcd_data1", 'wb') as f:
  f.write('27')

reg = struct.unpack("&lt;L", mem[GPIO_OE:GPIO_OE+4])[0]
mem[GPIO_OE:GPIO_OE+4] = struct.pack("<L", reg & ~LED)

try:
  while(True):
    mem[GPIO_SETDATAOUT:GPIO_SETDATAOUT+4] = struct.pack("<L", LED)
    time.sleep(0.5)
    mem[GPIO_CLEARDATAOUT:GPIO_CLEARDATAOUT+4] = struct.pack("<L", LED)
    time.sleep(0.5)

except KeyboardInterrupt:
  mem.close()

I've been working on a Python library to make the tedious process above as painless as possible, which can be found at github.com/alexanderhiam/PyBBIO. As of writing this, it includes a simple API for the GPIO modules, the ADC, and the UART serial ports, as well as a few built-in libraries, like one for building simple web interfaces to control hardware IO. To show the difference, here's the same script above, but this time using PyBBIO:

from bbio import *

LED = GPIO2_7

def setup():
  pinMode(LED, OUTPUT) # This does the pinmuxing automatically

def loop():
  toggle(LED)
  delay(500) # In milliseconds

run(setup, loop)

Super Simple BeagleBone Web Interface!

I've been working for a while now on PyBBIO, my Python library for BeagleBone hardware expansion. It's got some neat features, but my favorite so far is the BBIOServer library, which makes creating web interfaces to PyBBIO incredibly simple.

BBIOServer provides two things: a web server class called BBIOServer, and a HTML page class called Page. To use it you create a Page instance for every page you want your web interface to have, add content to them through the Page methods, then pass them all to the start() method of a BBIOServer instance, and it will create the pages complete with a sidebar to navigate between them, then start the server on the default port of 8000, or whatever port you have specified.

To create a single page with a button that toggles the USR3 on-board LED, as well as a monitor of its current state, would look something like this:

from bbio import *
from BBIOServer import *

server = BBIOServer()

def setup():
  page = Page("Server test")
  page.add_text("This is a test of the BBIOServer library.")
  page.add_button(lambda: toggle(USR3), "Toggle USR3", newline=True)
  page.add_monitor(lambda: pinState(USR3), "Current state:")
  server.start(page)
  stop()

def loop():
  pass

run(setup, loop)

And if you run that and point your browser to http://your_beaglebone_ip_address:8000 you should see something like this:

bbioserver

In this case the Page instance uses the default stylesheet, but you can also point to other stylesheets in the PyBBIO/libraries/BBIOServer/src/ directory. Besides the default stylesheet, BBIOServer includes a stylesheet that makes the pages get along well with mobile browsers. If you replace page = Page("Server test") in the above code with page = Page("Server test", stylesheet="mobile.css"), then head to http://your_beaglebone_ip_address:8000 on your phone, you should see something like this:

bbioserver_mobile

Here's a quick video of one of the BBIOServer examples included in PyBBIO (see the code here):

Be sure to check out the BBIOServer documentation.

Power management with LiPoly batteries

I'm a huge fan of single-cell Lithium Polymer batteries; they're small, relatively cheap, and they come in a wide range of different capacities. However, their output range of 3.0-4.2V from discharged (with protection circuitry) to fully charged can be a bit of a hassle, especially if working with 3.3V devices. Of course there are plenty of power management ICs out there, from very bare-bones to extremely complex, but the majority of them are really not that well suited for the average home PCB fabrication set-up, whether it be due to their exceedingly tiny surface mount packages or there large number of required external components.

The majority of devices I make consist of some combination of 3.3V and 5V components. To power these with a LiPoly I need three things: a consistent 3.3V supply, a consistent 5V supply and a charging circuit. Of course there are many great modules and breakout boards that accomplish these tasks and more, for instance from Adafruit and SparkFun, but I'm not generally a fan of the different-breakout-board-for-every-component look, so I've put a fair bit of time into searching for the simplest to use PMICs, and I've discovered a few good options.

Charging:

By far the easiest LiPoly charging solution I've found is Maxim's MAX1555, which can be purchased at SparkFun. It comes in a friendly SOT-23 package, and the only external components it requires are a few decoupling capacitors. To quote the MAX1555 datasheet:

The MAX1551/MAX1555 charge a single-cell lithium-ion (Li+) battery from both USB* and AC adapter sources. They operate with no external FETs or diodes, and accept operating input voltages up to 7V... With USB connected, but without DC power, the charge current is set to 100mA (max). This allows charging from both powered and unpowered USB hubs with no port communication required. When DC power is connected, charging current is set at 280mA (typ)...

The datasheet shows the typical configuration, which can be modified to use an LED as a charge indicator like this:

max1555

The great thing about this IC is that it is an in-system charger, meaning it will happily power your circuit while charging the battery (unless of course the circuit draws more than the charging current, in which case there will be a net drain on the battery!).

5V Supply:

Getting 5V from a LiPoly battery is not all that tricky either, especially using an IC like the MCP1640 adjustable boost regulator. Available in a SOT-23-6 package, the MCP1640 can supply 5V at up to 300mA from a single-cell LiPoly at a little over 90% efficiency, using only a few external components, like so:

mcp1640
(This circuit is straight from the MCP1640 datasheet)

So in the case of a circuit which only needs a 5V supply, the output of the charging circuit can be routed straight to this boost circuit, and all is well.

3.3V Supply:

This is where it starts to get a bit tricky. As the maximum voltage of the LiPoly is greater than 3.3V, and the minimum voltage is less than 3.3V, neither a regulator nor a boost circuit alone will work. One obvious option is to simply stick a 3.3V linear regulator on the output of the above boost circuit, but this is not generally the best idea. For one, if the boost circuit is 90% efficient, followed by a voltage regulator that might be 80% efficient or less, you're looking at an effective efficiency of 72%. That means that if the circuit draws all its current from this 3.3V supply, 28% of the power into the circuit is wasted!

The cleanest and most efficient way to get a 3.3V supply is straight from the battery using a buck–boost converter. The problem is that the majority of buck-boost ICs come in very small packages. Not only does this make them very hard to solder without stenciling and cooking in a reflow oven, but it means it is extremely difficult to etch your own board if it uses one. After hours of catalog searching, however, I have found a few buck-boost ICs that seem suitable for DIY use.

Most recently I stumbled across the MAX710 and MAX711. I don't know much about them yet, but judging by the datasheet they seem a bit outdated. They both have an input range of 1.8-11V, and the MAX710 has a selectable output of 3.3V or 5V, where as the MAX711 has an adjustable output range of 2.7-5.5V. Both can source a maximum of 700mA. Their efficiency is not all that impressive, and they sell for a whopping $8.61 on DigiKey, but they are the only buck-boost I've seen that come in the lovely SOIC package, which makes them super simple to etch for and solder.

I think the best buck-boost option I've seen is the LTC3440. Like many buck-boosts, the LTC3440 was designed for single-cell Lithium batteries, meaning it is optimized for an input voltage of ~3.7V. It has a maximum continuous output current of 600mA, and has an efficiency of ~95% when supplying 3.3V at 100mA with a 3.7V input. What's great about the LTC3440 is that it comes in a 10-MSOP package. Though the 0.5mm lead pitch doesn't leave much room for error, having exposed pads makes soldering pretty easy using a solder wick (great tutorial on skywired.net), and it shouldn't be too hard to etch.

I'm working on developing breakout boards for a few different combinations of these components, which I'll post write-ups for as I get them done.