Introduction

This is the chapter web page to support the content in Chapter 8 of the book: Exploring BeagleBone – Tools and Techniques for Building with Embedded Linux. The summary introduction to the chapter is as follows:

This chapter describes bus communication in detail, explaining and comparing the different bus types that are available on the Beagle boards. It describes how you can configure them for use, and how you can communicate with and control I2C, SPI, and UART devices, using both Linux tools and custom-developed C/C++ code. Practical examples are provided using different low-cost bus devices, such as a real-time clock, an accelerometer, a serial shift register with a seven-segment display, a USB-to-TTL 3.3 V cable, and a GPS receiver. Finally, the AM335x DCAN controller is used to send and receive messages to and from a CAN Bus using Linux SocketCAN. After reading this chapter, you should have the skills necessary to begin interfacing almost any type of bus device to the Beagle boards.

Learning Outcomes

  • Describe the most commonly used buses or interfaces that are available on the Beagle boards, and choose the correct bus to use for your application.
  • Configure the Beagle boards to enable I2C, SPI, CAN bus, and UART capabilities.
  • Attach circuits to a Beagle board that interface to its I2C bus, and use the Linux I2C-tools to communicate with those circuits.
  • Build circuits that interface to the SPI bus using shift registers, and write C code that controls low-level SPI communication.
  • Write C/C++ code that interfaces to and “wraps” the functionality of devices attached to the I2C and SPI buses.
  • Communicate between UART devices using both Linux tools and custom C code.
  • Build a basic distributed system that uses UART connections to the board to allow it to be controlled from a desktop PC.
  • Interface to a low-cost GPS sensor using a UART connection.
  • Build circuits that interface to the Beagle board CAN buses and use Linux SocketCAN to send and receive messages to and from the bus.
  • Add logic-level translation circuitry to your circuits in order to communicate between devices with different logic-level voltages.

Digital Media Resources

Here the digital resources referred to in the chapter web page are provided. There are high-resolution versions of some of the important figures and links to videos, resources and websites that are described in the chapter.

Additional Content (for First Edition)

SPI Analog to Digital Converter (ADC) Example

Here is the outline of an additional SPI example that I have written subsequent to the publication of the book. It is useful for a number of reasons:

  • It deals with writing and reading to/from an SPI device. The ADXL345 SPI example in the book focuses on reading/writing register values. While it is essentially the same thing, different class methods are required for this example.
  • This example is useful for communicating with SPI devices that have custom communication protocols.
  • The MCP3008 is a very low-cost device that is widely available (in PDIP form).
  • Chapter 6 describes how easy it is to damage the BeagleBone’s on-board Analog-to-Digital Converter (ADC) and therefore an external ADC might be preferable for some readers (e.g., the external ADC can easily be used with 3.3V levels). An external ADC also allows for the expansion of the total number of ADCs that are available on the BeagleBone.

For this example, the MCP3008 is used in a PDIP package (see the datasheet) – it is a low-cost, 8-channel 10-bit ADC with an SPI interface that can be powered at 3.3V. This particular IC can capture 75,000 samples per second when powered at 2.7V and 200,000 samples per second when powered at 5V – therefore the raw performance when powered at 3.3V  is somewhere within this range. However, it does not appear possible to achieve the upper level of performance under a regular Linux kernel with the configuration that is proposed. There are many similar SPI ADCs (and indeed DACs), including the MCP3004 that has four 10-bit input channels, the MCP3204 that has four 12-bit channels, and the MCP3208 that has eight 12-bit channels. The code that is presented in this section can be easily adapted to support any of these devices.

There is a BeagleBone ADC Cape available from CircuitCo that has a MCP3008 and can be used with this example code.

In addition to eight independent single-ended inputs, the MCP3008 (and most of the extended family) support pseudo-differential input pairs. For example, CH0 can be used as IN+ and CH1 can be used as IN- and the difference between the two inputs can be determined. In this mode, the IN+ input can range from IN- (a zero value digital output) to the sum of Vref plus IN- (a digital output of 1023).

 Figure 8.A1 The BeagleBone SPI ADC circuit

Figure 8.A1 illustrates how you can connect the SPI ADC to the BeagleBone Black using the pin configuration that is described in Table 8.A1 below. The Vref pin in Figure 8.A1 does not have to be set at 3.3V, however it is a useful initial range. For further information on the input characteristics study the datasheet.

Table 8.A1 Table of pin inputs/outputs for the 16-pin MCP3008.

IC Pins Pin Title Description
Pins 1-8 CH0-CH7 The eight ADC inputs
Pin 9 DGND Digital ground – connected to the internal digital ground. Can be connected to the BBB GND.
Pin 10 CS/SHDN Chip Select/Shutdown. Used to initiate communication with the device when pulled low. When pulled high it “ends the conversation” and puts the device in standby. Must be pulled high between conversions.
Pin 11 DIN Used to transfer data that is used configure the converter (e.g., chose which ADC input to use and whether to use single-ended or differential inputs)
Pin 12 DOUT The SPI serial data output provides the results of the analog to digital conversion. The data bit changes on the falling edge of the clock.
Pin 13 CLK The SPI clock can be used to clock out the bits. Ideally a minimum clock rate of 10KHz should be maintained to avoid introducing linearity errors.
Pin 14 AGND Analog ground – connected to the internal analog circuit GND.
Pin 15 VREF Reference voltage input
Pin 16 VDD Voltage Supply (2.7V to 5.5V). Can be connected directly to the BBB 3.3V supply rail.

Table 8.A2 Configuration of the operation mode for the MCP3008.

Single-Ended Configuration (Single/Diff = 1) Differential Configuration (Single/Diff = 0)
Control Bit Selections Control Bit Selections
Channel D2 D1 D0 Channel D2 D1 D0
CH0 0 0 0 IN+=CH0 IN-=CH1 0 0 0
CH1 0 0 1 IN+=CH1 IN-=CH0 0 0 1
CH2 0 1 0 IN+=CH2 IN-=CH3 0 1 0
CH3 0 1 1 IN+=CH3 IN-=CH2 0 1 1
CH4 1 0 0 IN+=CH4 IN-=CH5 1 0 0
CH5 1 0 1 IN+=CH5 IN-=CH4 1 0 1
CH6 1 1 0 IN+=CH6 IN-=CH7 1 1 0
CH7 1 1 1 IN+=CH7 IN-=CH6 1 1 1

The source code example below uses the SPI class that is described in Chapter 8. This example uses a Single-Ended Configuration, therefore Single/Diff=1 and the left-hand side of Table 8.A2 is used. As also described in the table, to sample from Channel CH0, the D2, D1, and D0 values must be set to 0. Therefore, the bit pattern to be sent to the ADC input in this case is 1000. In the code example provided below, these bits are set as the most-significant bits of send[1]. The last bit in send[0] is 1, which acts as the start bit to inform the device that a sample is being requested. The full code example is in the directory /chp08/spi/adc_example/

/* Using an SPI ADC (e.g., the MCP3008)
* Written by Derek Molloy for the book "Exploring BeagleBone: Tools and
* Techniques for Building with Embedded Linux" by John Wiley & Sons, 2014
* ISBN 9781118935125. Please see the file README.md in the repository root
* directory for copyright and GNU GPLv3 license information.            */

#include <iostream>
#include <sstream>
#include "bus/SPIDevice.h"

using namespace std;
using namespace exploringBB;

short combineValues(unsigned char upper, unsigned char lower){
   return ((short)upper<<8)|(short)lower;
}

int main(){
   cout << "Starting EBB SPI ADC Example" << endl;
   SPIDevice *busDevice = new SPIDevice(1,0); //Using second SPI bus (both loaded)
   busDevice->setSpeed(4000000);      // Have access to SPI Device object
   busDevice->setMode(SPIDevice::MODE0);

   unsigned char send[3], receive[3];
   send[0] = 0b00000001; // The Start Bit followed
   // Set the SGL/Diff and D mode -- e.g., 1000 means single ended CH0 value
   send[1] = 0b10000000; // The MSB is the Single/Diff bit and it is followed by 000 for CH0
   send[2] = 0;          // This byte doesn't need to be set, just for a clear display
   busDevice->transfer(send, receive, 3);
   cout << "Response bytes are " << (int)receive[1] << "," << (int)receive[2] << endl;

   // Use the 8-bits of the second value and the two LSBs of the first value
   int value = combineValues(receive[1]&0b00000011, receive[2]);
   cout << "This is the value " << value << " out of 1024." << endl;
   cout << "End of EBB SPI ADC Example" << endl;
}

This example can be executed and will give the output as follows:

root@beaglebone:/lib/firmware# echo BB-SPIDEV0 > $SLOTS
root@beaglebone:/lib/firmware# cat $SLOTS
 0: 54:PF---
 1: 55:PF---
 2: 56:PF---
 3: 57:PF---
 4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
 5: ff:P-O-- Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
 6: ff:P-O-- Bone-Black-HDMIN,00A0,Texas Instrument,BB-BONELT-HDMIN
 7: ff:P-O-L Override Board Name,00A0,Override Manuf,BB-SPIDEV0

root@beaglebone:~/exploringBB/chp08/spi/adc_example# ./ADCExample
Starting EBB SPI ADC Example
Response value is 249 235
This is the value 491 out of 1024.
End of EBB SPI ADC Example
root@beaglebone:~/exploringBB/chp08/spi/adc_example#

This will give the output on the Logic Analyzer as in Figure 8.A2 below. In this figure you can see that the values which are sent to the SPI ADC are 0x01 followed by 0x80 (and a blank value of 0x00) on the MOSI line (orange). The SPI ADC responds on the MISO line (green) with the value 0xF9 and 0xEB. These values are 0xF9 = 249 decimal and 0xEB = 235 decimal in the code example output above. This data needs to be parsed to extract the actual ADC response value. This is achieved by taking the last two bits of the first byte (0xF9 = 0b11111001) and all eight bits of the second byte (0xEB = 0b11101011) in order to create the binary value 0b0111101011, which is 491 decimal (as outputted by the code example above).

Figure 8.A2 The Logic Analyzer output for the code example above (note: the colors align with the connection wires in Figure 8.A1)

Adapting this Example for the 12-bit MCP3204/08

To adapt this code for the 12-bit MCP3204/08 (see the datasheet) you need to make a few modifications to the source code. The circuit has the exact same wiring configuration as in Figure 8.A1 and uses a very similar structure. The significant difference in the configuration relates to Figure 6-1 in both datasheets. Since there are an additional 2 bits required for a MCP320x data transaction, the Start Bit and Data Channel selection bits are shifted left by two bits. Therefore, to complete a request for a single-ended sample from Channel 0 you must send the value 00000110 00xxxxxx xxxxxxxx = 0x060000. Therefore, send[0] = 0b00000110, and send[1] = send[2] =0. The response is now the 8-bits of receive[2] and the four LSBs of receive[1]. Therefore, the combineValues() function should be called as follows:

int value = combineValues(receive[1]&0b00001111, receive[2]);

which results in a 12-bit return value, so the result should be presented as “out of 4096”.

Gnuplot

Gnuplot is a very useful command-line-driven function and plotting program that can be easily installed on the BeagleBone. It can be configured to display on-screen custom plots, or to save the output plot to a file. To install gnuplot on the BeagleBone you can use the following command:

molloyd@beaglebone:~$ sudo apt-get install gnuplot gnuplot-x11

Make sure that you install gnuplot-x11 if you are planning to display the output on screen. In the following example I am using a VNC connection to the BeagleBone — such a configuration is described in detail in the book at the beginning of Chapter 11. You can see that I use ssh -XC on my Linux desktop machine to connect to the BeagleBone for this very reason.

Once gnuplot is installed, you can test it using the following steps:

molloyd@debian:~$ ssh -XC molloyd@192.168.7.2
Debian GNU/Linux 7
BeagleBoard.org BeagleBone Debian Image 2014-05-14
Support/FAQ: http://elinux.org/Beagleboard:BeagleBoneBlack_Debian
molloyd@192.168.7.2's password: 
molloyd@beaglebone:~$ gnuplot
	G N U P L O T
	Version 4.6 patchlevel 0    last modified 2012-03-04 
	Build System: Linux armv7l
	Copyright (C) 1986-1993, 1998, 2004, 2007-2012
	Thomas Williams, Colin Kelley and many others
	gnuplot home:     http://www.gnuplot.info
	faq, bugs, etc:   type "help FAQ"
	immediate help:   type "help"  (plot window: hit 'h')
Terminal type set to 'wxt'
gnuplot> plot sin(x)

This should result in the on-screen display of a plot of sin(x) as in Figure 8.A3 below.


Figure 8.A3 Testing the gnuplot program

Gnuplot can also be used to output a print-ready or raster version of the plot in a file. In the following code segment the function is plotted to a PostScript file and to a PNG file. The PostScript file is subsequently converted to a PDF file using the ps2pdf tool so that it is more generally viewable, while remaining in a vector-mapped format.

molloyd@beaglebone:~$ gnuplot
...
gnuplot> set term postscript
Terminal type set to 'postscript'
Options are 'landscape noenhanced defaultplex ...
gnuplot> set output "test.ps"
gnuplot> plot sin(x)
gnuplot> set term png
Terminal type set to 'png'
Options are 'nocrop font ...
gnuplot> set output "test.png"
gnuplot> replot
gnuplot> set term x11
Terminal type set to 'x11'
gnuplot> replot
Closing test.png
gnuplot> exit
molloyd@beaglebone:~$ ls test.*
test.png test.ps
molloyd@beaglebone:~$ ps2pdf test.ps test.pdf

For your information, the outputted files are available here for you to view: test.png (5.41KB), test.pdf (3.78KB).

In the next section gnuplot is used to display the output from the ADC circuit that is used to sample a signal for a number of samples (200 and 20,000). To use gnuplot to plot your own data, you can simply pass the name of a file to gnuplot that contains a single data sample on each new line — this is demonstrated in the example in the next sectionFor more information on gnuplot, please see: www.gnuplot.info

SPI ADC with Multiple Samples

A version of the SPI ADC example is also available in the same directory that can be used to test the SPI ADC sampling performance with multiple samples for different frequencies of input signal.

root@beaglebone:~/exploringBB/chp08/spi/adc_example# ./ADCmulti > data
root@beaglebone:~/exploringBB/chp08/spi/adc_example# echo 'plot "data" with linespoints lc rgb "blue"; pause 10' | gnuplot
root@beaglebone:~/exploringBB/chp08/spi/adc_example# echo 'set term postscript; set output "plot.ps"; plot "data" with linespoints lc rgb "blue";' | gnuplot
root@beaglebone:~/exploringBB/chp08/spi/adc_example# ls *.ps
plot.ps
root@beaglebone:~/exploringBB/chp08/spi/adc_example# ps2pdf plot.ps plot.pdf

A sine wave is applied to the ADC input using the Analog Discovery Waveform Generator in order to test the quality of the analog data capture. The 200 samples that are captured using the test application are plotted in Figure 8.A4 for different input sine wave signal frequencies of 50Hz, 500Hz, 1KHz, 2KHz, and 5KHz (moving left to right — click each image for a larger view).

    

Figure 8.A4 (a)-(f) The ADCmulti application output displaying 200 samples from an input signal that varies from 50Hz to 5KHz.

A PDF version of the output at 500Hz is available in the file: plot.pdf. The circuit set up works surprisingly well up until the 2KHz sine wave is applied as the input. There are modest levels of jitter — however, that would not be the case for a longer sample duration at high frequencies, as the ADCmulti program is heavy on BeagleBone CPU resources.

This discussion also brings up an interesting point! The use of the BeagleBone frequency utilities is described in Chapter 5 (Page 153) and it is important to note that the CPU frequency of the BeagleBone has an impact on the performance of this application. For example, if the application is modified to take 20,000 samples and executed you will get a plot like this: Plot for On-Demand Governor (Also see Figure 8.A5 on the left-hand side). You can clearly see that there is a change in the observed frequency (after approximately 5,000 samples) — this is because the on-demand governor switches the BeagleBone CPU frequency to 1000MHz when the CPU is busy for an extended period.

Figure 8.A5 (a) The ADCmulti application running with an “on demand” governor and (b) with a “performance” governor.

You can resolve this problem by setting the governor and re-capturing the data as follows:

root@beaglebone:~/exploringBB/chp08/spi/adc_example# cpufreq-set -g performance
root@beaglebone:~/exploringBB/chp08/spi/adc_example# ./ADCmulti2 > data
root@beaglebone:~/exploringBB/chp08/spi/adc_example# echo 'set term postscript; set output "plot.ps"; plot "data" with linespoints lc rgb "blue";' | gnuplot
root@beaglebone:~/exploringBB/chp08/spi/adc_example# ps2pdf plot.ps plot.pdf

The PDF plot of the output is now consistent and appears like this: Plot for Performance Governor (See Figure 8.A5 on the right-hand side).

If required, you can set the governor back to “on demand” using:

root@beaglebone:~/exploringBB/chp08/spi/adc_example# cpufreq-set -g ondemand

BeagleBone Bus Videos

An I²C Tutorial Video

In this video I discuss the I²C bus and how we can connect and program devices attached to the bus using C/C++. I explain the use of i2c-tools on embedded Linux and then show how we can interface to a digital accelerometer (Bosch BMA180) or any other I²C device. Finally, I show how we can use sysfs to build a C++ class that wraps the functionality of the digital accelerometer.

Arduino Videos

An Introduction to the Arduino

This is a short introduction to the Arduino platform.

An Arduino on a Breadboard

This tutorial shows you how to build an Arduino on a breadboard. It describes the use of 16MHz and 20Mhz crystals for driving the Arduino and compares their use to the use of a resonator. A simple circuit is created that blinks an LED for 1000ms and 100ms. The Arduino is programmed on the breadboard using an Arduino shield’s Reset, TX and RX pins.

LCD Display Introduction

This short video looks at the different options available for connecting an LCD character display to an Arduino. It uses a wide set of displays: The nuelectronics display shield, 20×4, 20×2,16×2 and 8×2 display modules. It shows the code that you need to create an example display and describes the use of the POT in the display. The modules used are the nuelectronics display shield, JHD 204, WH1602, CM200200 and a YJ 802A.

Arduino Application – A Reaction Timer

In this video I combine three previous videos and write some code to create an Arduino based Reaction Timer. The entire circuit is built on a breadboard using an ATmega328P on its own, which is combined with a Newhaven Display LCD Module that has an RGB backlight. I work through the code in some detail and explain how to write the code for the reaction timer. At the end of the video I provide links to the previous videos that show how to build the three individual circuits: Breadboard PSU, Arduino on a Breadboard and Arduino LCD Tutorial.

8×8 LED Dot Matrix Display Tutorial

In this video a 2 Colour (red/green) 8×8 LED Dot Matrix Display circuit is developed that uses three 74HC595 ICs to drive the rows/columns and a darlington transistor array (UDN2981A) to source the current. An Arduino is used to provide the serial data and the source code is presented to show how this was achieved. The display is a common anode display and the experiment spends time examining the current constraints, explaining why we require transistor arrays to source or sink current.

Source Code

All of the source code that is described in this book is available in a public GitHub repository: Derek Molloy Exploring BeagleBone repository.

You can clone this repository on a Linux desktop computer or your BeagleBone using the command:

git clone https://github.com/derekmolloy/exploringBB.git

The code for this chapter can be accessed in the chp08 folder of the cloned repository. The formatted code is provided here for your convenience.

I2CDevice.h

Listing 8-2: I2CDevice.h
/*
 * I2C.h  Created on: 17 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone" 
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European 
 * Union Public License (the "EUPL") version 1.1 as published by the 
 * European Union. Any use of this Software, other than as authorized 
 * under this License is strictly prohibited (to the extent such use 
 * is covered by a right of the copyright holder of this Software).
 * 
 * This Software is provided under the License on an "AS IS" basis and 
 * without warranties of any kind concerning the Software, including 
 * without limitation merchantability, fitness for a particular purpose, 
 * absence of defects or errors, accuracy, and non-infringement of 
 * intellectual property rights other than copyright. This disclaimer 
 * of warranty is an essential part of the License and a condition for 
 * the grant of any rights to this Software.
 * 
 * For more details, see http://www.derekmolloy.ie/
 */

#ifndef I2C_H_
#define I2C_H_
#include"BusDevice.h"

#define BBB_I2C_0 "/dev/i2c-0"
#define BBB_I2C_1 "/dev/i2c-1"

namespace exploringBB{

/**
 * @class I2CDevice
 * @brief Generic I2C Device class that can be used to connect to any type of I2C device and read or write to its registers
 */
class I2CDevice:public BusDevice{

public:
	I2CDevice(unsigned int bus, unsigned int device);
	virtual int open();
	virtual int write(unsigned char value);
	virtual unsigned char readRegister(unsigned int registerAddress);
	virtual unsigned char* readRegisters(unsigned int number, unsigned int fromAddress=0);
	virtual int writeRegister(unsigned int registerAddress, unsigned char value);
	virtual void debugDumpRegisters(unsigned int number = 0xff);
	virtual void close();
	virtual ~I2CDevice();
};

} /* namespace exploringBB */

#endif /* I2C_H_ */

I2CDevice.cpp

Listing 8-2: I2CDevice.cpp
/*
 * I2C.cpp  Created on: 17 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone" 
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European 
 * Union Public License (the "EUPL") version 1.1 as published by the 
 * European Union. Any use of this Software, other than as authorized 
 * under this License is strictly prohibited (to the extent such use 
 * is covered by a right of the copyright holder of this Software).
 * 
 * This Software is provided under the License on an "AS IS" basis and 
 * without warranties of any kind concerning the Software, including 
 * without limitation merchantability, fitness for a particular purpose, 
 * absence of defects or errors, accuracy, and non-infringement of 
 * intellectual property rights other than copyright. This disclaimer 
 * of warranty is an essential part of the License and a condition for 
 * the grant of any rights to this Software.
 * 
 * For more details, see http://www.derekmolloy.ie/
 */

#include"I2CDevice.h"
#include<iostream>
#include<sstream>
#include<fcntl.h>
#include<iomanip>
#include<stdio.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
using namespace std;

#define HEX(x) setw(2) << setfill('0') << hex << (int)(x)

namespace exploringBB {

/**
 * Constructor for the I2CDevice class. It requires the bus number and device number. The constructor
 * opens a file handle to the I2C device, which is destroyed when the destructor is called
 * @param bus The bus number. Usually 0 or 1 on the BBB
 * @param device The device ID on the bus.
 */
I2CDevice::I2CDevice(unsigned int bus, unsigned int device):
	BusDevice(bus,device){
	this->open();
}

/**
 * Open a connection to an I2C device
 * @return 1 on failure to open to the bus or device, 0 on success.
 */
int I2CDevice::open(){
   string name;
   if(this->bus==0) name = BBB_I2C_0;
   else name = BBB_I2C_1;

   if((this->file=::open(name.c_str(), O_RDWR)) < 0){
      perror("I2C: failed to open the bus\n");
	  return 1;
   }
   if(ioctl(this->file, I2C_SLAVE, this->device) < 0){
      perror("I2C: Failed to connect to the device\n");
	  return 1;
   }
   return 0;
}

/**
 * Write a single byte value to a single register.
 * @param registerAddress The register address
 * @param value The value to be written to the register
 * @return 1 on failure to write, 0 on success.
 */

int I2CDevice::writeRegister(unsigned int registerAddress, unsigned char value){
   unsigned char buffer[2];
   buffer[0] = registerAddress;
   buffer[1] = value;
   if(::write(this->file, buffer, 2)!=2){
      perror("I2C: Failed write to the device\n");
      return 1;
   }
   return 0;
}

/**
 * Write a single value to the I2C device. Used to set up the device to read from a
 * particular address.
 * @param value the value to write to the device
 * @return 1 on failure to write, 0 on success.
 */
int I2CDevice::write(unsigned char value){
   unsigned char buffer[1];
   buffer[0]=value;
   if (::write(this->file, buffer, 1)!=1){
      perror("I2C: Failed to write to the device\n");
      return 1;
   }
   return 0;
}

/**
 * Read a single register value from the address on the device.
 * @param registerAddress the address to read from
 * @return the byte value at the register address.
 */
unsigned char I2CDevice::readRegister(unsigned int registerAddress){
   this->write(registerAddress);
   unsigned char buffer[1];
   if(::read(this->file, buffer, 1)!=1){
      perror("I2C: Failed to read in the value.\n");
      return 1;
   }
   return buffer[0];
}

/**
 * Method to read a number of registers from a single device. This is much more efficient than
 * reading the registers individually. The from address is the starting address to read from, which
 * defaults to 0x00.
 * @param number the number of registers to read from the device
 * @param fromAddress the starting address to read from
 * @return a pointer of type unsigned char* that points to the first element in the block of registers
 */
unsigned char* I2CDevice::readRegisters(unsigned int number, unsigned int fromAddress){
	this->write(fromAddress);
	unsigned char* data = new unsigned char[number];
    if(::read(this->file, data, number)!=(int)number){
       perror("IC2: Failed to read in the full buffer.\n");
	   return NULL;
    }
	return data;
}

/**
 * Method to dump the registers to the standard output. It inserts a return character after every
 * 16 values and displays the results in hexadecimal to give a standard output using the HEX() macro
 * that is defined at the top of this file. The standard output will stay in hexadecimal format, hence
 * the call on the last like.
 * @param number the total number of registers to dump, defaults to 0xff
 */

void I2CDevice::debugDumpRegisters(unsigned int number){
	cout << "Dumping Registers for Debug Purposes:" << endl;
	unsigned char *registers = this->readRegisters(number);
	for(int i=0; i<(int)number; i++){
		cout << HEX(*(registers+i)) << " ";
		if (i%16==15) cout << endl;
	}
	cout << dec;
}

/**
 * Close the file handles and sets a temporary state to -1.
 */
void I2CDevice::close(){
	::close(this->file);
	this->file = -1;
}

/**
 * Closes the file on destruction, provided that it has not already been closed.
 */
I2CDevice::~I2CDevice() {
	if(file!=-1) this->close();
}

} /* namespace exploringBB */

SPIDevice.h

Listing: SPIDevice.h
/*
 * SPIDevice.h  Created on: 22 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone"
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European
 * Union Public License (the "EUPL") version 1.1 as published by the
 * European Union. Any use of this Software, other than as authorized
 * under this License is strictly prohibited (to the extent such use
 * is covered by a right of the copyright holder of this Software).
 *
 * This Software is provided under the License on an "AS IS" basis and
 * without warranties of any kind concerning the Software, including
 * without limitation merchantability, fitness for a particular purpose,
 * absence of defects or errors, accuracy, and non-infringement of
 * intellectual property rights other than copyright. This disclaimer
 * of warranty is an essential part of the License and a condition for
 * the grant of any rights to this Software.
 *
 * For more details, see http://www.derekmolloy.ie/
 */

#ifndef SPIDEVICE_H_
#define SPIDEVICE_H_
#include<string>
#include<stdint.h>
#include"BusDevice.h"

#define SPI_PATH "/dev/spidev" /**< The general path to an SPI device **/

namespace exploringBB{

/**
 * @class SPIDevice
 * @brief Generic SPI Device class that can be used to connect to any type of SPI device and read or write to its registers
 */
class SPIDevice:public BusDevice {
public:
	/// The SPI Mode
	enum SPIMODE{
		MODE0 = 0,   //!< Low at idle, capture on rising clock edge
		MODE1 = 1,   //!< Low at idle, capture on falling clock edge
		MODE2 = 2,   //!< High at idle, capture on falling clock edge
		MODE3 = 3    //!< High at idle, capture on rising clock edge
	};

private:
	std::string filename;  //!< The precise filename for the SPI device
public:
	SPIDevice(unsigned int bus, unsigned int device);
	virtual int open();
	virtual unsigned char readRegister(unsigned int registerAddress);
	virtual unsigned char* readRegisters(unsigned int number, unsigned int fromAddress=0);
	virtual int writeRegister(unsigned int registerAddress, unsigned char value);
	virtual void debugDumpRegisters(unsigned int number = 0xff);
	virtual int write(unsigned char value);
	virtual int write(unsigned char value[], int length);
	virtual int setSpeed(uint32_t speed);
	virtual int setMode(SPIDevice::SPIMODE mode);
	virtual int setBitsPerWord(uint8_t bits);
	virtual void close();
	virtual ~SPIDevice();
	virtual int transfer(unsigned char read[], unsigned char write[], int length);
private:
	SPIMODE mode;     //!< The SPI mode as per the SPIMODE enumeration
	uint8_t bits;     //!< The number of bits per word
	uint32_t speed;   //!< The speed of transfer in Hz
	uint16_t delay;   //!< The transfer delay in usecs
};

} /* namespace exploringBB */

#endif /* SPIDEVICE_H_ */

SPIDevice.cpp

Listing: SPIDevice.cpp
/*
 * SPIDevice.cpp  Created on: 22 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone"
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European
 * Union Public License (the "EUPL") version 1.1 as published by the
 * European Union. Any use of this Software, other than as authorized
 * under this License is strictly prohibited (to the extent such use
 * is covered by a right of the copyright holder of this Software).
 *
 * This Software is provided under the License on an "AS IS" basis and
 * without warranties of any kind concerning the Software, including
 * without limitation merchantability, fitness for a particular purpose,
 * absence of defects or errors, accuracy, and non-infringement of
 * intellectual property rights other than copyright. This disclaimer
 * of warranty is an essential part of the License and a condition for
 * the grant of any rights to this Software.
 *
 * For more details, see http://www.derekmolloy.ie/
 */

#include "SPIDevice.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <string>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
using namespace std;

#define HEX(x) setw(2) << setfill('0') << hex << (int)(x)  //!< Macro for filling in leading 0 on HEX outputs

namespace exploringBB {

/**
 * The constructor for the SPIDevice that sets up and opens the SPI connection.
 * The destructor will close the SPI file connection.
 * @param bus The SPI bus number X (first digit after spidevX.Y)
 * @param device The device on the bus Y (second digit after spidevX.Y)
 */
SPIDevice::SPIDevice(unsigned int bus, unsigned int device):
	BusDevice(bus,device) {
	stringstream s;
	s << SPI_PATH << bus << "." << device;
	this->filename = string(s.str());
	this->mode = SPIDevice::MODE3;
	this->bits = 8;
	this->speed = 500000;
	this->delay = 0;
	this->open();
}

/**
 * This method opens the file connection to the SPI device.
 * @return 0 on a successful open of the file
 */
int SPIDevice::open(){
	//cout << "Opening the file: " << filename.c_str() << endl;
	if ((this->file = ::open(filename.c_str(), O_RDWR))<0){
		perror("SPI: Can't open device.");
		return -1;
	}
	if (this->setMode(this->mode)==-1) return -1;
	if (this->setSpeed(this->speed)==-1) return -1;
	if (this->setBitsPerWord(this->bits)==-1) return -1;
	return 0;
}

/**
 * Generic method to transfer data to and from the SPI device. It is used by the
 * following methods to read and write registers.
 * @param send The array of data to send to the SPI device
 * @param receive The array of data to receive from the SPI device
 * @param length The length of the array to send
 * @return -1 on failure
 */
int SPIDevice::transfer(unsigned char send[], unsigned char receive[], int length){
	struct spi_ioc_transfer	transfer;
	memset(&transfer,0,sizeof(transfer));
	transfer.tx_buf = (unsigned long) send;
	transfer.rx_buf = (unsigned long) receive;
	transfer.len = length;
	transfer.speed_hz = this->speed;
	transfer.bits_per_word = this->bits;
	transfer.delay_usecs = this->delay;
	int status = ioctl(this->file, SPI_IOC_MESSAGE(1), &transfer);
	if (status < 0) {
		perror("SPI: SPI_IOC_MESSAGE Failed");
		return -1;
	}
	return status;
}

/**
 * A method to read a single register at the SPI address
 * @param registerAddress the address of the register from the device datasheet
 * @return the character that is returned from the address
 */
unsigned char SPIDevice::readRegister(unsigned int registerAddress){
	unsigned char send[2], receive[2];
	memset(send, 0, sizeof send);
	memset(receive, 0, sizeof receive);
	send[0] = (unsigned char) (0x80 | registerAddress);
	this->transfer(send, receive, 2);
	//cout << "The value that was received is: " << (int) receive[1] << endl;
	return receive[1];
}

/**
 * A method to read a number of registers as a data array
 * @param number the number of registers to read
 * @param fromAddress the starting address of the block of data
 * @return the data array that is returned (memory allocated by the method)
 */
unsigned char* SPIDevice::readRegisters(unsigned int number, unsigned int fromAddress){
	unsigned char* data = new unsigned char[number];
	unsigned char send[number+1], receive[number+1];
	memset(send, 0, sizeof send);
	send[0] =  (unsigned char) (0x80 | 0x40 | fromAddress); //set read bit and MB bit
	this->transfer(send, receive, number+1);
	memcpy(data, receive+1, number);  //ignore the first (address) byte in the array returned
	return data;
}

/**
 *  A write method that writes a single character to the SPI bus
 *  @param value the value to write to the bus
 *  @return returns 0 if successful
 */
int SPIDevice::write(unsigned char value){
	unsigned char null_return = 0x00;
	//printf("[%02x]", value);
	this->transfer(&value, &null_return, 1);
	return 0;
}

/**
 *  A write method that writes a block of data of the length to the bus.
 *  @param value the array of data to write to the device
 *  @param length the length of the data array
 *  @return returns 0 if successful
 */
int SPIDevice::write(unsigned char value[], int length){
	unsigned char null_return = 0x00;
	this->transfer(value, &null_return, length);
	return 0;
}

/**
 *  Writes a value to a defined register address (check the datasheet for the device)
 *  @param registerAddress the address of the register to write to
 *  @param value the value to write to the register
 *  @return returns 0 if successful
 */
int SPIDevice::writeRegister(unsigned int registerAddress, unsigned char value){
	unsigned char send[2], receive[2];
	memset(receive, 0, sizeof receive);
	send[0] = (unsigned char) registerAddress;
	send[1] = value;
	//cout << "The value that was written is: " << (int) send[1] << endl;
	this->transfer(send, receive, 2);
	return 0;
}

/**
 *  A simple method to dump the registers to the standard output -- useful for debugging
 *  @param number the number of registers to dump
 */
void SPIDevice::debugDumpRegisters(unsigned int number){
	cout << "SPI Mode: " << this->mode << endl;
	cout << "Bits per word: " << (int)this->bits << endl;
	cout << "Max speed: " << this->speed << endl;
	cout << "Dumping Registers for Debug Purposes:" << endl;
	unsigned char *registers = this->readRegisters(number);
	for(int i=0; i<(int)number; i++){
		cout << HEX(*(registers+i)) << " ";
		if (i%16==15) cout << endl;
	}
	cout << dec;
}

/**
 *   Set the speed of the SPI bus
 *   @param speed the speed in Hz
 */
int SPIDevice::setSpeed(uint32_t speed){
	this->speed = speed;
	if (ioctl(this->file, SPI_IOC_WR_MAX_SPEED_HZ, &this->speed)==-1){
		perror("SPI: Can't set max speed HZ");
		return -1;
	}
	if (ioctl(this->file, SPI_IOC_RD_MAX_SPEED_HZ, &this->speed)==-1){
		perror("SPI: Can't get max speed HZ.");
		return -1;
	}
	return 0;
}

/**
 *   Set the mode of the SPI bus
 *   @param mode the enumerated SPI mode
 */
int SPIDevice::setMode(SPIDevice::SPIMODE mode){
	this->mode = mode;
	if (ioctl(this->file, SPI_IOC_WR_MODE, &this->mode)==-1){
		perror("SPI: Can't set SPI mode.");
		return -1;
	}
	if (ioctl(this->file, SPI_IOC_RD_MODE, &this->mode)==-1){
		perror("SPI: Can't get SPI mode.");
		return -1;
	}
	return 0;
}

/**
 *   Set the number of bits per word of the SPI bus
 *   @param bits the number of bits per word
 */
int SPIDevice::setBitsPerWord(uint8_t bits){
	this->bits = bits;
	if (ioctl(this->file, SPI_IOC_WR_BITS_PER_WORD, &this->bits)==-1){
		perror("SPI: Can't set bits per word.");
		return -1;
	}
	if (ioctl(this->file, SPI_IOC_RD_BITS_PER_WORD, &this->bits)==-1){
		perror("SPI: Can't get bits per word.");
		return -1;
	}
	return 0;
}

/**
 *   Close the SPI device
 */
void SPIDevice::close(){
	::close(this->file);
	this->file = -1;
}

/**
 *   The destructor closes the SPI bus device
 */
SPIDevice::~SPIDevice() {
	this->close();
}

} /* namespace exploringBB */

BusDevice.h

Listing: BusDevice.h
/*
 * BusDevice.h  Created on: 23 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone"
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European
 * Union Public License (the "EUPL") version 1.1 as published by the
 * European Union. Any use of this Software, other than as authorized
 * under this License is strictly prohibited (to the extent such use
 * is covered by a right of the copyright holder of this Software).
 *
 * This Software is provided under the License on an "AS IS" basis and
 * without warranties of any kind concerning the Software, including
 * without limitation merchantability, fitness for a particular purpose,
 * absence of defects or errors, accuracy, and non-infringement of
 * intellectual property rights other than copyright. This disclaimer
 * of warranty is an essential part of the License and a condition for
 * the grant of any rights to this Software.
 *
 * For more details, see http://www.derekmolloy.ie/
 */

#ifndef BUSDEVICE_H_
#define BUSDEVICE_H_

namespace exploringBB {

/**
 * @class BusDevice
 * @brief This class is the parent of I2C and SPI devices, so that devices that use both
 * SPI and I2C interfaces can use those interfaces interchangeably. Because it contains
 * abstract methods, the child classes MUST implement the methods that are listed in this
 * class.
 */
class BusDevice {
protected:
	unsigned int bus;    /**< the bus number */
	unsigned int device; /**< the device number on the bus  */
	int file;            /**< the file handle to the device */
public:
	BusDevice(unsigned int bus, unsigned int device);
	virtual int open()=0;
	virtual unsigned char readRegister(unsigned int registerAddress)=0;
	virtual unsigned char* readRegisters(unsigned int number, unsigned int fromAddress=0)=0;
	virtual int write(unsigned char value)=0;
	virtual int writeRegister(unsigned int registerAddress, unsigned char value)=0;
	virtual void debugDumpRegisters(unsigned int number = 0xff)=0;
	virtual void close()=0;
	virtual ~BusDevice();
};

} /* namespace exploringBB */

#endif /* BUSDEVICE_H_ */

BusDevice.cpp

Listing: BusDevice.h
/*
 * BusDevice.cpp  Created on: 23 May 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone"
 * See: www.exploringbeaglebone.com
 * Licensed under the EUPL V.1.1
 *
 * This Software is provided to You under the terms of the European
 * Union Public License (the "EUPL") version 1.1 as published by the
 * European Union. Any use of this Software, other than as authorized
 * under this License is strictly prohibited (to the extent such use
 * is covered by a right of the copyright holder of this Software).
 *
 * This Software is provided under the License on an "AS IS" basis and
 * without warranties of any kind concerning the Software, including
 * without limitation merchantability, fitness for a particular purpose,
 * absence of defects or errors, accuracy, and non-infringement of
 * intellectual property rights other than copyright. This disclaimer
 * of warranty is an essential part of the License and a condition for
 * the grant of any rights to this Software.
 *
 * For more details, see http://www.derekmolloy.ie/
 */

#include "BusDevice.h"

namespace exploringBB {

/**
 * Constructor for a generic bus device
 * @param bus the bus number
 * @param device the device number
 */
BusDevice::BusDevice(unsigned int bus, unsigned int device) {
	this->bus = bus;
	this->device = device;
	this->file=-1;
}

/**
 * Destructor is unused
 */
BusDevice::~BusDevice() {}

} /* namespace exploringBB */

Some High-Resolution Figures from this Chapter

Here are high-resolution images of some of the more complex figures in this chapter, which may help you in wiring the circuits. Please note that you can close this pop-up window by pressing the Escape key.

External Resources

Important Documents

External Web Sites

ADXL345 Datasheet

The ADXL345 Datasheet

BeagleBone Black System Reference Manual

The BeagleBone Black System Reference Manual (SRM)

  • The I²C Manual, Jean‐Marc Irazabal and Steve Blozis, Philips Semiconductors, TecForum at DesignCon 2003 in San Jose, CA, on January 27, 2003, at tiny.cc/ebb805.
  • The Linux I²C Subsystem , at i2c.wiki.kernel.org.
  • Serial Programming Guide for POSIX Operating Systems, 5th ed., Michael R. Sweet, 1994‐99, at tiny.cc/ebb803.
  • Serial Programming HOWTO, Gary Frerking, Revision 1.01, at tiny.cc/ebb804.

Errata

Second Edition

  • None so far

First Edition

  • Page 277 7-bit addressing goes from 0x00 to 0x7F (not 0x00 to 0x80)
  • Page 298 the file handle fd should be of type int, rather than type unsigned int. This prevents the test to see that the file opened correctly (<0) from working correctly.
  • Page 311 (loc 8869) there is a typo in the command to load the UART4 virtual cape at the top of the page. The command should read “sudo sh -c …” instead of “sudo su -c”
  • Page 311 (loc 8891) there is a typo in the call to the minicom command. It should read “$ minicom -b 9600 -o -D /dev/ttyO4″ rather than 9200.
  • Page 317 (loc 9017) there is a typo in the third bullet point that describes the termios structure. The cflag_t should be tcflag_t, just like the other entries. The structure is used correctly elsewhere.