Introduction

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

This chapter integrates the Linux, programming, and electronics groundwork from earlier chapters to show you how to build circuits and write programs that interface to Beagle board single-wire inputs and outputs.

Learning about binary signals and programming can be difficult if you do it by yourself,  you can check Get Educated Online’s Review of Datacamp to see the positive outcomes of it, and start learning about coding and programming, it will be much easier to have an expert with you every step of the way so you can give this chapter a more profound outlook and have a better understanding with no issues.

In this chapter, you will see practical examples that explain how to use a general-purpose input/output (GPIO) to output a binary signal to switch on an LED, or to read in a binary input from a push button. Optocoupler circuits are discussed so that you can safely interface to externally powered circuits. Also included are the steps required to read in an analog input and to send out a pulse-width modulated (PWM) output. GPIO interfacing is first performed using sysfs to ensure that you have skills that are transferrable to other embedded Linux devices. Next, BoneScript and memory-mapped approaches are investigated, which are largely specific to the AM335x SoC. Finally, there is a brief discussion on the impact of udev rules and Linux permissions on GPIO interfacing.

Learning Outcomes

After completing this chapter, you should be able to do the following:

  • Use a Beagle board GPIO to output a binary signal to a digital circuit, or read in a binary input from a digital circuit.
  • Write shell scripts and C++ code in order to control a Beagle board GPIO.
  • Describe the use of internal pull-up and pull-down resistors.
  • Describe the device tree model that is used to configure certain hardware on the Beagle boards and make minor modifications to the model.
  • Use a PWM pin to output an analog voltage or as a control signal for motors and certain types of servos.
  • Use an analog input to safely read in a value from an analog circuit, using an op-amp to protect the board from damage and/or invalid measurement.
  • Write C++ code, which utilizes advanced functionality, to efficiently read in a digital input using a custom GPIO
  • Use an advanced Linux configuration that allows for user-level control of the sysfs entries.

Digital Media Resources

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

BeagleBone and PocketBeagle Header Charts

Figure 6-8 and Figure 6-9 from Chapter 6 are available for download as a high-resolution PNG raster format using the following links:

These are high-resolution images that can be displayed and/or printed in color.

Optocoupler Circuits

Figure color key:

  • RED — pins are already allocated (e.g., to HDMI or eMMC). See Chapter 6 for information on how to re-allocate these pins. Do not use these pins without re-allocating them.
  • GREEN — pin mode is for enhanced GPIOs for the PRU-ICSS (see later chapters)
  • BLUE — pin mode is for UART functionality on the PRU-ICSS

Source Code

The full source code listing for this chapter is available in the GitHub repository directory exploringBB/chp06. There are the important source code listings for this chapter, which describe how you can interact with a GPIO using a custom class and how you can interact with the GPIOs using a Bone-Keys device:

GPIO.h

/*
 * GPIO.h  Created on: 29 Apr 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 GPIO_H_
#define GPIO_H_
#include<string>
#include<fstream>
using std::string;
using std::ofstream;

#define GPIO_PATH "/sys/class/gpio/"

namespace exploringBB {

typedef int (*CallbackType)(int);
enum GPIO_DIRECTION{ INPUT, OUTPUT };
enum GPIO_VALUE{ LOW=0, HIGH=1 };
enum GPIO_EDGE{ NONE, RISING, FALLING, BOTH };

class GPIO {
private:
	int number, debounceTime;
	string name, path;

public:
	GPIO(int number); //constructor will export the pin
	virtual int getNumber() { return number; }

	// General Input and Output Settings
	virtual int setDirection(GPIO_DIRECTION);
	virtual GPIO_DIRECTION getDirection();
	virtual int setValue(GPIO_VALUE);
	virtual int toggleOutput();
	virtual GPIO_VALUE getValue();
	virtual int setActiveLow(bool isLow=true);  //low=1, high=0
	virtual int setActiveHigh(); //default
	//software debounce input (ms) - default 0
	virtual void setDebounceTime(int time) { this->debounceTime = time; }

	// Advanced OUTPUT: Faster write by keeping the stream alive (~20X)
	virtual int streamOpen();
	virtual int streamWrite(GPIO_VALUE);
	virtual int streamClose();

	virtual int toggleOutput(int time); //threaded invert output every X ms.
	virtual int toggleOutput(int numberOfTimes, int time);
	virtual void changeToggleTime(int time) { this->togglePeriod = time; }
	virtual void toggleCancel() { this->threadRunning = false; }

	// Advanced INPUT: Detect input edges; threaded and non-threaded
	virtual int setEdgeType(GPIO_EDGE);
	virtual GPIO_EDGE getEdgeType();
	virtual int waitForEdge(); // waits until button is pressed
	virtual int waitForEdge(CallbackType callback); // threaded with callback
	virtual void waitForEdgeCancel() { this->threadRunning = false; }

	virtual ~GPIO();  //destructor will unexport the pin

private:
	int write(string path, string filename, string value);
	int write(string path, string filename, int value);
	string read(string path, string filename);
	int exportGPIO();
	int unexportGPIO();
	ofstream stream;
	pthread_t thread;
	CallbackType callbackFunction;
	bool threadRunning;
	int togglePeriod;  //default 100ms
	int toggleNumber;  //default -1 (infinite)
	friend void* threadedPoll(void *value);
	friend void* threadedToggle(void *value);
};

void* threadedPoll(void *value);
void* threadedToggle(void *value);

} /* namespace exploringBB */

#endif /* GPIO_H_ */

GPIO.cpp

Listing 6-1: GPIO.cpp
/*
 * GPIO.cpp  Created on: 29 Apr 2014
 * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
 * Made available for the book "Exploring BeagleBone"
 * If you use this code in your work please cite:
 *   Derek Molloy, "Exploring BeagleBone: Tools and Techniques for Building
 *   with Embedded Linux", Wiley, 2014, ISBN:9781118935125.
 * 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 "GPIO.h"
#include<iostream>
#include<fstream>
#include<string>
#include<sstream>
#include<cstdlib>
#include<cstdio>
#include<fcntl.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<pthread.h>
using namespace std;

namespace exploringBB {

/**
 *
 * @param number The GPIO number for the BBB
 */
GPIO::GPIO(int number) {
	this->number = number;
	this->debounceTime = 0;
	this->togglePeriod=100;
	this->toggleNumber=-1; //infinite number
	this->callbackFunction = NULL;
	this->threadRunning = false;

	ostringstream s;
	s << "gpio" << number;
	this->name = string(s.str());
	this->path = GPIO_PATH + this->name + "/";
	this->exportGPIO();
	// need to give Linux time to set up the sysfs structure
	usleep(250000); // 250ms delay
}

int GPIO::write(string path, string filename, string value){
   ofstream fs;
   fs.open((path + filename).c_str());
   if (!fs.is_open()){
	   perror("GPIO: write failed to open file ");
	   return -1;
   }
   fs << value;
   fs.close();
   return 0;
}

string GPIO::read(string path, string filename){
   ifstream fs;
   fs.open((path + filename).c_str());
   if (!fs.is_open()){
	   perror("GPIO: read failed to open file ");
    }
   string input;
   getline(fs,input);
   fs.close();
   return input;
}

int GPIO::write(string path, string filename, int value){
   stringstream s;
   s << value;
   return this->write(path,filename,s.str());
}

int GPIO::exportGPIO(){
   return this->write(GPIO_PATH, "export", this->number);
}

int GPIO::unexportGPIO(){
   return this->write(GPIO_PATH, "unexport", this->number);
}

int GPIO::setDirection(GPIO_DIRECTION dir){
   switch(dir){
   case INPUT: return this->write(this->path, "direction", "in");
      break;
   case OUTPUT:return this->write(this->path, "direction", "out");
      break;
   }
   return -1;
}

int GPIO::setValue(GPIO_VALUE value){
   switch(value){
   case HIGH: return this->write(this->path, "value", "1");
      break;
   case LOW: return this->write(this->path, "value", "0");
      break;
   }
   return -1;
}

int GPIO::setEdgeType(GPIO_EDGE value){
   switch(value){
   case NONE: return this->write(this->path, "edge", "none");
      break;
   case RISING: return this->write(this->path, "edge", "rising");
      break;
   case FALLING: return this->write(this->path, "edge", "falling");
      break;
   case BOTH: return this->write(this->path, "edge", "both");
      break;
   }
   return -1;
}

int GPIO::setActiveLow(bool isLow){
   if(isLow) return this->write(this->path, "active_low", "1");
   else return this->write(this->path, "active_low", "0");
}

int GPIO::setActiveHigh(){
   return this->setActiveLow(false);
}

GPIO_VALUE GPIO::getValue(){
	string input = this->read(this->path, "value");
	if (input == "0") return LOW;
	else return HIGH;
}

GPIO_DIRECTION GPIO::getDirection(){
	string input = this->read(this->path, "direction");
	if (input == "in") return INPUT;
	else return OUTPUT;
}

GPIO_EDGE GPIO::getEdgeType(){
	string input = this->read(this->path, "edge");
	if (input == "rising") return RISING;
	else if (input == "falling") return FALLING;
	else if (input == "both") return BOTH;
	else return NONE;
}

int GPIO::streamOpen(){
	stream.open((path + "value").c_str());
	return 0;
}
int GPIO::streamWrite(GPIO_VALUE value){
	stream << value << std::flush;
	return 0;
}
int GPIO::streamClose(){
	stream.close();
	return 0;
}

int GPIO::toggleOutput(){
	this->setDirection(OUTPUT);
	if ((bool) this->getValue()) this->setValue(LOW);
	else this->setValue(HIGH);
    return 0;
}

int GPIO::toggleOutput(int time){ return this->toggleOutput(-1, time); }
int GPIO::toggleOutput(int numberOfTimes, int time){
	this->setDirection(OUTPUT);
	this->toggleNumber = numberOfTimes;
	this->togglePeriod = time;
	this->threadRunning = true;
    if(pthread_create(&this->thread, NULL, &threadedToggle, static_cast<void*>(this))){
    	perror("GPIO: Failed to create the toggle thread");
    	this->threadRunning = false;
    	return -1;
    }
    return 0;
}

// This thread function is a friend function of the class
void* threadedToggle(void *value){
	GPIO *gpio = static_cast<GPIO*>(value);
	bool isHigh = (bool) gpio->getValue(); //find current value
	while(gpio->threadRunning){
		if (isHigh)	gpio->setValue(HIGH);
		else gpio->setValue(LOW);
		usleep(gpio->togglePeriod * 500);
		isHigh=!isHigh;
		if(gpio->toggleNumber>0) gpio->toggleNumber--;
		if(gpio->toggleNumber==0) gpio->threadRunning=false;
	}
	return 0;
}

// Blocking Poll - based on the epoll socket code in the epoll man page
int GPIO::waitForEdge(){
	this->setDirection(INPUT); // must be an input pin to poll its value
	int fd, i, epollfd, count=0;
	struct epoll_event ev;
	epollfd = epoll_create(1);
    if (epollfd == -1) {
	   perror("GPIO: Failed to create epollfd");
	   return -1;
    }
    if ((fd = open((this->path + "value").c_str(), O_RDONLY | O_NONBLOCK)) == -1) {
       perror("GPIO: Failed to open file");
       return -1;
    }

    //ev.events = read operation | edge triggered | urgent data
    ev.events = EPOLLIN | EPOLLET | EPOLLPRI;
    ev.data.fd = fd;  // attach the file file descriptor

    //Register the file descriptor on the epoll instance, see: man epoll_ctl
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
       perror("GPIO: Failed to add control interface");
       return -1;
    }
	while(count<=1){  // ignore the first trigger
		i = epoll_wait(epollfd, &ev, 1, -1);
		if (i==-1){
			perror("GPIO: Poll Wait fail");
			count=5; // terminate loop
		}
		else {
			count++; // count the triggers up
		}
	}
    close(fd);
    if (count==5) return -1;
	return 0;
}

// This thread function is a friend function of the class
void* threadedPoll(void *value){
	GPIO *gpio = static_cast<GPIO*>(value);
	while(gpio->threadRunning){
		gpio->callbackFunction(gpio->waitForEdge());
		usleep(gpio->debounceTime * 1000);
	}
	return 0;
}

int GPIO::waitForEdge(CallbackType callback){
	this->threadRunning = true;
	this->callbackFunction = callback;
    // create the thread, pass the reference, address of the function and data
    if(pthread_create(&this->thread, NULL, &threadedPoll, static_cast<void*>(this))){
    	perror("GPIO: Failed to create the poll thread");
    	this->threadRunning = false;
    	return -1;
    }
    return 0;
}

GPIO::~GPIO() {
	this->unexportGPIO();
}

} /* namespace exploringBB */

Read ADC

readLDR.cpp
/** Simple LDR Reading Application
* 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<fstream>
#include<string>
#include<sstream>
using namespace std;

#define LDR_PATH "/sys/bus/iio/devices/iio:device0/in_voltage"

int readAnalog(int number){
   stringstream ss;
   ss << LDR_PATH << number << "_raw";
   fstream fs;
   fs.open(ss.str().c_str(), fstream::in);
   fs >> number;
   fs.close();
   return number;
}

int main(int argc, char* argv[]){
   cout << "Starting the readLDR program" << endl;
   int value = readAnalog(0);
   cout << "The LDR value was " << value << " out of 4095." << endl;
   return 0;
}

Bone Keys (dts)

readLDR.cpp
/dts-v1/;
/plugin/;

/{
    compatible = "ti,beaglebone", "ti,beaglebone-black";

    part-number = "BB-BONE-KEYS";
    version = "00A0";

    fragment@0 {
        target = <&am33xx_pinmux>;
        __overlay__ {
            pushbutton_pins: pinmux_pushbutton_pins{
                pinctrl-single,pins = <
                    0x040 0x37 // P9_15 pull-up mode 7 GPIO1_16
                >;
            };
        };
    };

    fragment@1 {
        target = <&ocp>;
        __overlay__ {
            /* avoid warnings */            
            #address-cells = <1>;
            #size-cells = <1>;                            

            gpio_keys {
                compatible = "gpio-keys";
                pinctrl-names = "default";
                pinctrl-0 = <&pushbutton_pins>;
                #address-cells = <1>;
                #size-cells = <0>;

                button_P9_15 {
                    debounce_interval = <50>;
                    linux,code = <28>;
                    label = "button9_15";
                    gpios = <&gpio2 16 0x1>;
                    gpio-key,wakeup;
                    autorepeat;
                };                
            };
        };
    };
};

Bone Keys (cpp)

readLDR.cpp
/** BB-BONE-GPIO Test code to test the GPIO-KEYS interface.
* Written by Derek Molloy (www.derekmolloy.ie) for the book
* Exploring BeagleBone.
*
* This code is based on work in the document:
*    www.kernel.org/doc/Documentation/input/input.txt
*
* 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<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<linux/input.h>
#include <unistd.h>
using namespace std;

#define KEY_PRESS 1
#define KEY_RELEASE 0

int main(){
   int fd, count=0;
   struct input_event event[64];
   if(getuid()!=0){
      cout << "You must run this program as root. Exiting." << endl;
      return -1;
   }
   cout << "Starting BB-BONE-GPIO Test (press 10 times to end):" << endl;
   if ((fd = open("/dev/input/event1", O_RDONLY)) < 0){
      perror("Failed to open event1 input device. Exiting.");
      return -1;
   }
   while(count < 20){  // Press and Release are one loop each
      int numbytes = (int)read(fd, event, sizeof(event));
      if (numbytes < (int)sizeof(struct input_event)){
         perror("The input read was invalid. Exiting.");
         return -1;
      }
      for (int i=0; i < numbytes/sizeof(struct input_event); i++){
         int type = event[i].type;
         int val  = event[i].value;
         int code = event[i].code;
         if (type == EV_KEY) {
            if (val == KEY_PRESS){
               cout << "Press  : Code "<< code <<" Value "<< val<< endl;
            }
            if (val == KEY_RELEASE){
               cout << "Release: Code "<< code <<" Value "<< val<< endl;
            }
         }
      }
      count++;
   }
   close(fd);
   return 0;
}

Errata

Second edition:

  • None so far

First edition:

  • Page 246. At the top of the page “P9_15(GPIO49)” should be “P9_15(GPIO48)”. It is correct in all other locations.
  • Page 247(Loc 7296). There are three ‘-‘ characters when there should be two ‘-‘ characters in three locations, in front of ‘path’, ‘attribute’, and ‘action’ on the command calls to udevadm.