Introduction

This blog post describes a C library called iobb, that allows for easy general-purpose input/output (GPIO), SPI and I2C using the BeagleBone Black (BBB) and other BBB variants such as the BeagleBone Black IndustrialBeagleBone Black Industrial and BeagleBone Black WirelessBeagleBone Black Wireless. This library is also PocketBeaglePocketBeagle friendly. If you’re using the PocketBeagle, then read on through to understand how the library works, but then check out the section near the end, titled PocketBeagle Specifics for the specifics : )

 

This library is designed to be easy-to-use, it is quite simple, and it allows for reasonably speedy I/O. There is plenty of demo source code too.

 

The diagram here shows the pins that are available to be used by default. All the yellow pins can be used as general-purpose input/output, and the pink SPI pins can be used as general-purpose I/O too, if SPI functionality is not needed. Pink and blue are SPI and I2C buses respectively. This is a simplified diagram, it is possible to use alternative pins too.

 

History

A very long time ago (2013) I wrote a simple GPIO library for the BeagleBone Black. It was very ropey, and vegetableavenger took the code and improved it immensely. Since 2014 the code has remained unchanged, but it needed some housekeeping. The reason is, there have been some changes to the Linux images available for the BeagleBone Black, and some things are broken. I’ve taken VegetableAvenger’s code, and made some slight modifications to it and included some other code I wrote a long time ago, to include I2C functionality as well. It now compiles cleanly, and I’ve tested it with the current latest Linux image. The library has been renamed to iobb, to reduce the camel case.

 

The library now supports general input/output, I2C and SPI.

 

How is it used for GPIO?

In brief, if you include this header file in your C source code:

 

#include <iobb.h>

 

Then you’ll be able to do GPIO input/output using these types of commands shown here.

To initialize the library:

 

iolib_init();

 

To set a GPIO pin to become an input or output, use these commands (the first digit is 8 or 9 to represent the connector P8 or P9, and the second digit is the physical pin number on the connector):

 

iolib_setdir(8, 12, DigitalIn);
iolib_setdir(8, 12, DigitalOut);

 

You can write to an output pin using these commands:

 

pin_high(8, 12);
pin_low(8, 12);

 

Input can be handled with if statements, using:

 

if (is_low(8, 12))

 

or

 

if (is_high(8, 12))

 

At the end of the program, the library resources are freed up using:

 

iolib_free();

 

Putting it all together, the ‘Hello World’ of GPIO, toggling an LED, looks like this in C:

 

#include <iobb.h>
int
main(void)
{
  iolib_init();
  iolib_setdir(8, 12, DigitalOut);
  while(1)
  {
    pin_high(8, 12);
    iolib_delay_ms(500);
    pin_low(8, 12);
    iolib_delay_ms(500);
  }
  iolib_free();
  return(0);
}

 

Compiling it is easy. If the source file is called hello.c then to compile it, use:

 

gcc -o hello hello.c -liobb

 

And then you can run it with an LED and resistor connected to P8_12 and GND. Note that to run the program, you have to be superuser, or use sudo:

 

./hello

 

The simple example above works, but it could be refined slightly with some basic checks and Ctrl-C handling in C. The example below will check that the user is running as root (superuser), and will free the library when Ctrl-C is pressed.

 

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <iobb.h>

int not_finished=1;

void
check_root_user(void)
{
  if(geteuid()!=0)
    printf("Run as root user! (or use sudo)\n");
}

void
ctrl_c_handler(int dummyvar)
{
  not_finished=0;
}

int
main(void)
{
  signal (SIGINT, ctrl_c_handler);
  check_root_user();
  iolib_init();
  iolib_setdir(8, 12, DigitalOut);
  while(not_finished)
  {
    pin_high(8, 12);
    iolib_delay_ms(500);
    pin_low(8, 12);
    iolib_delay_ms(500);
  }
  printf("\nBye\n");
  iolib_free();
  return(0);
} 

 

Isn’t there some Stinkyness with Device Tree or Overlays?

Yes there is, but it’s less stinky, and a lot easier these days, provided you do not want to do anything unusual.

 

In the past, input/output was made difficult with the BBB, because before any pin could be used, it had to be entered in a file (called a device tree fragment file), transformed into some binary format, and then pushed into a folder where a device driver would decode it and program the pin accordingly. It was a complex and buggy mess.

 

Nowadays, in 2019, if you install any recent Linux image in the BBB, then you don’t need to do anything to be able to use most of the free pins on the BBB. The detail isn't needed here, but for those interested to read up, it is all due to a line in /boot/uEnv.txt that is:

 

enable_uboot_cape_universal=1

 

There are lots of pins on the two main connectors (P8 and P9), and some of them are shared with important features like HDMI, and eMMC. If you avoid those pins in particular (and a few others) then all the remainder pins can be used without having to know what a device tree is, or what an overlay file is, nor having to edit uEnv.txt. It means you still have more than 30 pins free to use.

 

In summary it is possible to directly use the GPIO library described here, without doing any further configuration.

 

How to Install the Library?

It is just a few lines.

 

After you have set up your BBB (see Getting Started with BeagleBone Black ), log into your BeagleBone Black, and create a development type of folder if you don’t already have one, and then the code can be downloaded and compiled:

 

mkdir development
cd development
git clone https://github.com/shabaz123/iobb.git
cd iobb
make

 

Next, as root user (or prepend sudo) type:

 

make install

 

It’s all done!

 

How Fast is it?

It is possible to toggle an output pin on and off, to generate a signal of frequency around 2.2 MHz using this library. If higher speeds and/or reduced jitter is needed, then an interesting feature of the BBB, called the Programmable Real-Time Unit (PRU) should be considered. Information on that is here: Coding for the BeagleBone PRU with C in 2019  and a series beginning here (there are links to the subsequent steps): BeagleBone Control Stepper Motors with PRU - Part 1: Intentions 

 

How is SPI used?

To use the SPI library, here is  some example code:

 

#include <stdio.h>
#include <iobb.h>

#define SPI_BUS SPI0

int
main(void)
{
  int i;
  int rxval;
  iolib_init();
  configure_spi_pins(17, 18, 21, 22); // CS, D1, D0, CLK
  if (spi_enable(SPI_BUS))
  {
    spi_ctrl(SPI_BUS, SPI_CH0, SPI_MASTER, SPI_RX, /* use SPI_TX or SPI_RXTX depending on needs */
             SPI_DIV32, SPI_CLOCKMODE3, /* 48 MHz divided by 32, SPI mode 3 */
             SPI_CE_ACT_LOW, SPI_OUTIN, 16); /* D0 is output and D1 is input, 16-bit transactions */
  }
  else
  {
    printf("error, spi is not enabled\n");
    iolib_free();
    return(1);
  }
  for (i=1; i<20; i++) 
  {
    spi_transact(SPI_BUS, SPI_CH0, 0 /*or integer to transmit data*/, &rxval);
    printf("Received: 0x%04x \n", rxval&0xffff);
    iolib_delay_ms(500);
  }
  spi_disable(SPI_BUS);
  iolib_free();
  return(0);
}     

 

The code uses connector P9 (only P9 is available for SPI) with these pins:

 

P9 SPI 0 PinDescription
17SPI 0 chip select (CS)
18MISO (i.e. data input to the BBB)
21MOSI (i.e. data sent from the BBB)
22SPI SCLK

 

Although some other pin combinations are possible for SPI on the BBB, no other pins were tested with the library, and only some are valid. If you try other valid pins, then the constant SPI_BUS in the example code above may need changing from SPI0 to SPI1.

 

Compiling is as before:

 

gcc -o spi_test spi_test.c -liobb

 

When run, it will display 16-bit values (0xffff if nothing is connected to the BBB) which are being read from the SPI interface. To write to the SPI interface, change SPI_RX in the code above to SPI_RXTX, and then supply an integer value to be written, as the third parameter in the function spi_transact.

 

A single SPI interface can connect to multiple SPI devices, by using additional chip-select pins (it is possible to use GPIO pins to manually select chips but be careful not to enable two chips at the same time [especially during boot]; that could cause damage. A small resistor in series in each MISO line from the chips, perhaps 100 ohm, can reduce the risk of damage).

 

Adding Another SPI Interface (SPI 1)

If a second SPI interface is needed (i.e. a whole additional SPI interface, not just an additional chip-select pin), then SPI 1 could be used. However, by default, SPI 1 is not available. See the I Need More Pins section below. Once you have enabled the additional pins needed for SPI 1 as described in that section, then SPI 1 can be used with the library. Use the following code for SPI 1 (the same example code as above can be used, just these lines need to be modified):

 

#define SPI_BUS SPI1
configure_spi_pins(28, 30, 29, 31); // CS, D1, D0, SCLK

 

These are the pins used on connector P9 for SPI 1:

 

P9 SPI 1 PinDescription
28SPI 1 chip select (CS)
30MISO (i.e. data input to the BBB)
29MOSI (i.e. data sent from the BBB)
31SPI SCLK

 

 

How is I2C used?

To use the I2C library to send data, here is a simple example. The I2C address is expressed as a 7-bit address between 0x00 and 0x7F, consisting of bits 7 to 1 (excluding bit 0 which is the read/write bit in I2C). The example is for a PCF85176 device.

 

#include <iobb.h>

#define I2CBUS 2
#define LCD_ADDR 0x38
int handle;

int
main(void)
{
  unsigned char buf[6]={0xc9, 0xe0, 0xf0, 0xf8, 0x00, 0xff};
  configure_i2c_pins(19, 20);
  handle=i2c_open(I2CBUS, LCD_ADDR);
  i2c_write(handle, buf, 6);
  i2c_close(handle);
  return(0);
}

 

Note that the I2C pins are only available on connector P9, and the code uses pins 19 and 20 to be SCL and SDA respectively.

 

P9 I2C 2 PinDescription
19SCL (I2C clock, requires external pullup)
20SDA (I2C data, requires external pullup)

 

The steps to compiling the code are the same as before:

 

gcc -o i2c-test i2c-test.cc -liobb

 

There are a few other possible pins usable for I2C, but they require testing with the library, and the I2CBUS constant in the example above may need changing too. However, if you stick to pins 19 and 20 then the example above works.

 

Pull-up resistors to 3.3V are needed on the SCL and SDA pins (something in the ballpark of 4.7 kohm should be fine).

To see how to read from the I2C bus, or for more advanced use, see the header file that is available when the library is built and installed, in /usr/local/include (and there is also some older I2C information here that could be useful).

 

Which pins can I control with the GPIO, I2C and SPI functions?

On connector P8, the following pins have been successfully tested:

Pins 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26

 

On connector P9, the following pins have been successfully tested:

Pins 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 23, 24, 26, 27, 30, 41, 42

 

Note that if you want to use SPI, then keep P9 pins 17, 18, 21 and 22 free for that.

 

For I2C, P9 pins 19 and 20 are used. Other options are possible for I2C and SPI pins, but I have not tested other pins.

 

Also, note that when the I2C or SPI functions are used, then the library automatically configures those pins, and they cannot be reused for GPIO read/write functions. This is based on the assumption that if hardware is connected to use I2C or SPI, then it is unlikely that the pins will be needed for GPIO. However if you wish to use those pins again for GPIO, either reboot the BBB, or alternatively type this from a command prompt, for each pin that was used:

 

config-pin P9_17 gpio

 

The config-pin utility is part of all new Linux builds for the BBB, and it can be used in a few ways (this is just informational; it is not necessary to do this to use the library). For instance, if you type config-pin -l P9_17 then it will list all possible modes of operation for pin P9_17. The current mode can be queried using config-pin -q P9_16 .

 

What Doesn’t Work?

There are some things that don't currently function, but I do not know the reason : (

The main issue found so far is:

 

1. The PWM functions in the library do not work, and they would be nice to have functioning. I could not figure out why they don’t work.

 

I Need More Pins

It is possible to use the pins allocated for eMMC, and/or HDMI for general input/output, if the eMMC and/or HDMI is not needed. The eMMC is not needed if the micro SD card is being used to house the Linux image. I have not tested them with the library, but if you wish to attempt it, then to disable eMMC and/or HDMI, it is possible to do it by editing the /boot/uEnv.txt file (make a backup just in case) and rebooting the BBB. Information will be added once I've tested the procedure.

 

If you need just a few extra pins (4 more pins), but still want HDMI and eMMC to function, then there is the possibility of disabling just the HDMI audio. To do that, as superuser, edit the /boot/uEnv.txt file and remove the # from a line such that it looks like:

disable_uboot_overlay_audio=1

 

Then, reboot the BBB (type reboot as superuser) and pins 25, 28, 29 and 31 will be freed up and usable with the library as additional GPIO, or three of these pins can be used as part of a second SPI interface (SPI1). However, these four pins are also used by some audio cards, since they form an I2S digital audio interface, so depending on your needs, you might not want to disable audio. 

 

PocketBeagle Specifics

The library is used in much the same way as described above for the BeagleBone Black, but there are a few fairly self-explanatory differences. For instance, any function described above call that uses the number 8 or 9 to represent header pin connectors P8 or P9 on the BeagleBone Black, are replaced with 1 and 2 for the PocketBeagle to represent connectors P1 and P2.

 

The diagram here shows all the pins available. The yellow pins can be used as general-purpose I/O, so (for example), to set connector P1 pin 4 as an output and to set it high, use these lines as part of your C code:

 

iolib_setdir(1, 4, DigitalOut);
pin_high(1, 4);

 

 

Unlike the BeagleBone Black, the PocketBeagle by default has more I2C and SPI interfaces available.

 

SPI with the PocketBeagle

Use the same procedures as listed earlier, except for these differences.

 

Instead of the configure_spi_pins(..) function used with the BeagleBone Black, you can use configure_spi_pins_p1() or configure_spi_pins_p2() on the PocketBeagle, depending on if you wish to use the SPI interface on connector P1 (SPI0) or connector P2 (SPI1). Incidentally, this function is unnecessary since the PocketBeagle by default already has the two SPI interfaces configured. It’s an optional, unnecessary function for the PocketBeagle.

 

Beyond that, there are two main differences. Firstly, instead of using spi_enable and spi_disable functions, use spi_enable_pb and spi_disable_pb. The second difference is that when using the spi_ctrl function, use the parameter SPI_INOUT for PocketBeagle. This is because the PocketBeagle has MOSI and MISO pre-defined on the silkscreen on the board, so using SPI_INOUT associates the right signals to the right pins. If you had used SPI_OUTIN, then MISO and MOSI would be swapped around.

 

Here is a PocketBeagle SPI 0 example C program snippet:

 

int rxval;
int BUS=SPI0;
iolib_init();
configure_spi_pins_p1();
spi_enable_pb(BUS);
spi_ctrl(BUS, SPI_CH0, SPI_MASTER, 
         SPI_RX, SPI_DIV32, SPI_CLOCKMODE3, 
         SPI_CE_ACT_LOW, SPI_INOUT, 16);
spi_transact(BUS, SPI_CH0, 0, &rxval);
spi_disable_pb(BUS);
iolib_free();

 

The example above receives a 16-bit value from the SPI bus 0 (on connector P1). To transmit as well, the code would be similar, except SPI_RX would be changed to SPI_RXTX, and the third parameter for spi_transact would be the value to send (it is set to zero in the example above, for receive only mode).

 

Here is a table of the pins that need wiring up if you’re using SPI on the PocketBeagle:

 

For SPI 0 on connector P1:

 

P1 SPI 0 PinDescription
6SPI 0 chip select (CS)
10MISO (i.e. data input to the PocketBeagle)
12MOSI (i.e. data output to the PocketBeagle)
8SPI SCLK

 

For SPI 1 on connector P2:

 

P2 SPI 1 PinDescription
31SPI 1 chip select (CS)
27MISO (i.e. data input to the PocketBeagle)
25MOSI (i.e. data sent from the PocketBeagle)
29SPI SCLK

 

 

I2C with the PocketBeagle

Using I2C with the PocketBeagle is very similar to the BeagleBone. The only main difference is that instead of using configure_i2c_pins(19,20) (the BeagleBone Black uses these pins on connector P9 for I2C), use the correct values for the PocketBeagle. There are two I2C interfaces on PocketBeagle, one on connector P1 and the the other on connector P2. I2C1 is on P2, and I2C2 is on P1 (i.e. swapped around).

 

For the I2C interface on connector P1, which is I2C 2:

 

configure_i2c_pins(28, 26); // P1 pins 28 (SCL) and 26 (SDA)

 

For the I2C interface on connector P2, which is I2C 1:

 

configure_i2c_pins(9, 11); // P2 pins 9 (SCL) and 11 (SDA)

 

Since the PocketBeagle by default has both interfaces configured for I2C anyway, the configure_i2c_pins function is optional and doesn’t need to be used.

 

There is no other difference to using I2C compared with the earlier example for the BeagleBone Black.

 

#include <iobb.h>

// set I2CBUS to 2 for connector P1 pin 28 (SCL) and 26 (SDA)
// set I2CBUS to 1 for connector P2 pin 9 (SCL) and 11 (SDA)
#define I2CBUS 2
#define LCD_ADDR 0x38
int handle;

int
main(void)
{
  unsigned char buf[6]={0xc9, 0xe0, 0xf0, 0xf8, 0x00, 0xff};
  configure_i2c_pins(19, 20);
  handle=i2c_open(I2CBUS, LCD_ADDR);
  i2c_write(handle, buf, 6);
  i2c_close(handle);
  return(0);
}

 

Here is a summary of the pins that need wiring (don’t forget to use pull-up resistors of about 4.7k ohm, if they are not present on the attached I2C device circuit board):

 

For the I2C bus on connector P1 (I2C 2):

 

P1 I2C2 PinDescription
28SCL (I2C clock, requires external pullup)
26SDA (I2C data, requires external pullup)

 

For the I2C bus on connector P2 (I2C 1):

 

P2 I2C1 PinDescription
9SCL (I2C clock, requires external pullup)
11SDA (I2C clock, requires external pullup)

 

Summary

The BeagleBone Black has lots of input/output capability which in the past was difficult to easily control, especially with C programming. With the new(ish) iobb library, that should be greatly simplified. The application programming interface (API) with iobb is quite straightforward too, and the GPIO is controlled at a reasonable speed (2.2MHz signal output is possible).

 

The library can be downloaded and built and installed in less than a minute, and, thanks to the newer Linux images, requires no additional configuration, in order to have access to 30+ input/output pins, including I2C and SPI buses.

 

If you make modifications/improvements/fix bugs, or come up with interesting programs with the library, or discover additional functionality/new ways of working with the BBB, please do share the details below! : )

 

The diagrams below are printer-ink-friendly versions of the earlier diagrams in the blog post.

 

Thanks for reading!