Sensors#

Note

Although the examples given here are originally for the BeagleBone Black, many will also work on the other Beagles too. See the tabs for details on running the examples on other boards.

In this chapter, you will learn how to sense the physical world with BeagleBone Black. Various types of electronic sensors, such as cameras and microphones, can be connected to the Bone using one or more interfaces provided by the standard USB 2.0 host port, as shown in The USB 2.0 host port.

Note

All the examples in the book assume you have cloned the Cookbook repository on git.beagleboard.org. Go here Cloning the Cookbook Repository for instructions.

USB Host Port

Fig. 538 The USB 2.0 host port#

The two 46-pin cape headers (called P8 and P9) along the long edges of the board (Cape Headers P8 and P9) provide connections for cape add-on boards, digital and analog sensors, and more.

Cape Headers P8 and P9

Fig. 539 Cape Headers P8 and P9#

BeagleY-AI Front View

Fig. 540 BeagleY-AI Front View#

The 40-pin hat header along the long edge of the board provides connections for hat add-on boards, digital and analog sensors, and more.

The simplest kind of sensor provides a single digital status, such as off or on, and can be handled by an input mode of one of the Bone’s 65 general-purpose input/output (GPIO) pins. More complex sensors can be connected by using one of the Bone’s seven analog-to-digital converter (ADC) inputs or several I2C buses.

Displays and Other Outputs discusses some of the output mode usages of the GPIO pins.

All these examples assume that you know how to edit a file (Editing Code Using Visual Studio Code) and run it, either within the Visual Studio Code (VSC) integrated development environment (IDE) or from the command line (Getting to the Command Shell via SSH).

Choosing a Method to Connect Your Sensor#

Problem#

You want to acquire and attach a sensor and need to understand your basic options.

Solution#

Some of the many sensor connection options on the Bone. shows many of the possibilities for connecting a sensor.

Sensor Connection Modes

Fig. 541 Some of the many sensor connection options on the Bone.#

BeagleY-AI pinout shows many of the possibilities for connecting a sensor.

You will see pins referenced in several ways. While this is confusing at first, in reality, we can pick our favorite way and stick to it.

The two main ways of referring to GPIOs is by their number, so GPIO2, GPIO3, GPIO4 etc. as seen in the diagram below. This corresponds to the SoC naming convention. For broad compatibility, BeagleY-AI re-uses the Broadcom GPIO numbering scheme used by RaspberryPi.

The second (and arguably easier) way we will use for this tutorial is to use the actual pin header number (shown in dark grey). So, for the rest of the tutorial, if we refer to hat-08-gpio we mean the 8th pin of the GPIO header. Which, if you referenced the image below, can see refers to GPIO14 (UART TXD)

BeagleY-AI pinout

Fig. 542 BeagleY-AI pinout#

Go to https://pinout.beagleboard.io/ to see an interactive version of the figure.

Choosing the simplest solution available enables you to move on quickly to addressing other system aspects. By exploring each connection type, you can make more informed decisions as you seek to optimize and troubleshoot your design.

Input and Run a Python or JavaScript Application for Talking to Sensors#

Problem#

You have your sensors all wired up and your Bone booted up, and you need to know how to enter and run your code.

Solution#

You are just a few simple steps from running any of the recipes in this book.

bone$ cd
bone$ cd beaglebone-cookbook-code/02sensors
VSC bash tab

Fig. 543 Entering commands in the VSC bash tab#

Here, we issued the change directory (cd) command without specifying a target directory. By default, it takes you to your home directory. Notice that the prompt has changed to reflect the change.

Note

If you log in as debian, your home is /home/debian. If you were to create a new user called newuser, that user’s home would be /home/newuser. By default, all non-root (non-superuser) users have their home directories in /home.

Note

All the examples in the book assume you have cloned the Cookbook repository on git.beagleboard.org. Go here Cloning the Cookbook Repository for instructions.

  • Double-click the pushbutton.py file to open it.

  • Press ^S (Ctrl-S) to save the file. (You can also go to the File menu in VSC and select Save to save the file, but Ctrl-S is easier.) Even easier, VSC can be configured to autosave every so many seconds.

  • In the bash tab, enter the following commands:

debian@beaglebone:beaglebone-cookbook/code/02sensors$ ./pushbutton.py
data = 0
data = 0
data = 1
data = 1
^C

This process will work for any script in this book. (See the following sections for instructions on how to wire the pushbutton.)

Reading the Status of a Pushbutton or Magnetic Switch (Passive On/Off Sensor)#

Problem#

You want to read a pushbutton, a magnetic switch, or other sensor that is electrically open or closed.

Solution#

Connect the switch to a GPIO pin and read from the proper place in /sys/class/gpio.

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • Pushbutton switch.

  • Magnetic reed switch. (optional)

You can wire up either a pushbutton, a magnetic reed switch, or both on the Bone, as shown in Diagram for wiring a pushbutton and magnetic reed switch input.

Bone with pushbutton

Fig. 544 Diagram for wiring a pushbutton and magnetic reed switch input#

The code below reads GPIO port P9_42, which is attached to the pushbutton.

Note

If you are using a BeagleY-AI, wire the button to GPIO23 which is hat-16. This also appears at gpiochip0 and line 7.

Listing 19 Monitoring a pushbutton (pushbutton.py)#
 1#!/usr/bin/env python
 2# ////////////////////////////////////////
 3# //	pushbutton.py
 4# //      Reads P9_42 and prints its value.
 5# //	Wiring:	Connect a switch between P9_42 and 3.3V
 6# //	Setup:	
 7# //	See:	
 8# ////////////////////////////////////////
 9import time
10import gpiod
11import os
12
13ms = 100    # Read time in ms
14CHIP = 'gpiochip0'
15LINE_OFFSET = [7] #  P9_42 is gpio 7
16chip = gpiod.Chip(CHIP)
17lines = chip.get_lines(LINE_OFFSET)
18lines.request(consumer='pushbutton.py', type=gpiod.LINE_REQ_DIR_IN)
19
20while True:
21    data = lines.get_values()
22    print('data = ' + str(data[0]))
23    time.sleep(ms/1000)

pushbutton.py

Listing 20 Monitoring a pushbutton (pushbutton.c)#
 1////////////////////////////////////////
 2//	pushbutton.c
 3//      Reads P9_42 and prints its value.
 4//	Wiring:	Connect a switch between P9_42 and 3.3V
 5//	Setup:	
 6//	See:	
 7////////////////////////////////////////
 8#include <gpiod.h>
 9#include <stdio.h>
10#include <unistd.h>
11
12#define CONSUMER        "pushbutton.c"
13
14int main(int argc, char **argv)
15{
16        int chipnumber = 0;
17        unsigned int line_num = 7;
18        struct gpiod_line *line;
19        struct gpiod_chip *chip;
20        int i, ret;
21
22        chip = gpiod_chip_open_by_number(chipnumber);
23        line = gpiod_chip_get_line(chip, line_num);
24        ret = gpiod_line_request_input(line, CONSUMER);
25
26        /* Get */
27        while(1) {
28                printf("%d\r", gpiod_line_get_value(line));
29                usleep(100);
30        }
31}

pushbutton.c

Put this code in a file called pushbutton.py following the steps in Input and Run a Python or JavaScript Application for Talking to Sensors. In the VSC bash tab, run it by using the following commands:

bone$ ./pushbutton.py
data = 0
data = 0
data = 1
data = 1
^C

The command runs it. Try pushing the button. The code reads the pin and prints its current value.

You will have to press ^C (Ctrl-C) to stop the code.

If you want to run the C version do:

bone$ gcc -o pushbutton pushbutton.c -lgpiod
bone$ ./pushbutton
data = 0
data = 0
data = 1
data = 1
^C

If you want to use the magnetic reed switch wired as shown in Diagram for wiring a pushbutton and magnetic reed switch input, change P9_42 to P9_26 which is gpio 14.

Mapping Header Numbers to gpio Numbers#

Problem#

You have a sensor attached to the P8 or P9 header and need to know which gpio pin it is using.

Solution#

The gpioinfo command displays information about all the P8 and P9 header pins. (Or the HAT header pins if you are on the BeagleY-AI.) To see the info for just one pin, use grep.

bone$ gpioinfo | grep -e chip -e P9.42
gpiochip0 - 32 lines:
        line   7: "P8_42A [ecappwm0]" "P9_42" input active-high [used]
gpiochip1 - 32 lines:
gpiochip2 - 32 lines:
gpiochip3 - 32 lines:

Or, if on the BeagleY-AI.

bone$ gpioinfo | grep -e chip -e GPIO23
gpiochip0 - 24 lines:
    line   7:     "GPIO23"       unused   input  active-high
 gpiochip1 - 87 lines:
 gpiochip2 - 73 lines:

This shows P9_42 (GPIO32) is on chip 0 and pin 7. To find the gpio number multiply the chip number by 32 and add it to the pin number. This gives 0*32+7=7.

For P9_26 you get:

bone$ gpioinfo | grep -e chip -e P9.26
gpiochip0 - 32 lines:
        line  14: "P9_26 [uart1_rxd]" "P9_26" input active-high [used]
gpiochip1 - 32 lines:
gpiochip2 - 32 lines:
gpiochip3 - 32 lines:

0*32+14=14, so the P9_26 pin is gpio 14.

Reading a Position, Light, or Force Sensor (Variable Resistance Sensor)#

Problem#

You have a variable resistor, force-sensitive resistor, flex sensor, or any of a number of other sensors that output their value as a variable resistance, and you want to read their value with the Bone.

Solution#

Note

The BeagleY-AI doesn’t have ADC’s, so you can skip this section.

Use the Bone’s analog-to-digital converters (ADCs) and a resistor divider circuit to detect the resistance in the sensor.

The Bone has seven built-in analog inputs that can easily read a resistive value. Seven analog inputs on P9 header shows them on the lower part of the P9 header.

Seven analog inputs on the *P9* header

Fig. 545 Seven analog inputs on P9 header#

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • 10k trimpot or

  • Flex resistor (optional)

  • 22 kΩ resistor

A variable resistor with three terminals#

Wiring a 10 kΩ variable resistor (trimpot) to an ADC port shows a simple variable resistor (trimpot) wired to the Bone. One end terminal is wired to the ADC 1.8 V power supply on pin P9_32, and the other end terminal is attached to the ADC ground (P9_34). The middle terminal is wired to one of the seven analog-in ports (P9_36).

Analog

Fig. 546 Wiring a 10 kΩ variable resistor (trimpot) to an ADC port#

The section below shows the code used to read the variable resistor. Add the code to a file called analogIn.py and run it; then change the resistor and run it again. The voltage read will change.

Listing 21 Reading an analog voltage (analogIn.py)#
 1#!/usr/bin/env python3
 2#//////////////////////////////////////
 3#	analogin.py
 4# 	Reads the analog value of the light sensor.
 5#//////////////////////////////////////
 6import time
 7import os
 8
 9pin = "2"        # light sensor, A2, P9_37
10
11IIOPATH='/sys/bus/iio/devices/iio:device0/in_voltage'+pin+'_raw'
12
13print('Hit ^C to stop')
14
15f = open(IIOPATH, "r")
16
17while True:
18    f.seek(0)
19    x = float(f.read())/4096
20    print('{}: {:.1f}%, {:.3f} V'.format(pin, 100*x, 1.8*x), end = '\r')
21    time.sleep(0.1)
22
23# // Bone  | Pocket | AIN
24# // ----- | ------ | --- 
25# // P9_39 | P1_19  | 0
26# // P9_40 | P1_21  | 1
27# // P9_37 | P1_23  | 2
28# // P9_38 | P1_25  | 3
29# // P9_33 | P1_27  | 4
30# // P9_36 | P2_35  | 5
31# // P9_35 | P1_02  | 6

analogIn.py

Listing 22 Reading an analog voltage (analogIn.js)#
 1#!/usr/bin/env node
 2//////////////////////////////////////
 3//	analogin.js
 4// 	Reads the analog value of the light sensor.
 5//////////////////////////////////////
 6const fs = require("fs");
 7const ms = 500;  // Time in milliseconds
 8
 9const pin = "2";        // light sensor, A2, P9_37
10
11const IIOPATH='/sys/bus/iio/devices/iio:device0/in_voltage'+pin+'_raw';
12
13console.log('Hit ^C to stop');
14
15// Read every 500ms
16setInterval(readPin, ms);
17
18function readPin() {
19    var data = fs.readFileSync(IIOPATH).slice(0, -1);
20    console.log('data = ' + data);
21 }
22// Bone  | Pocket | AIN
23// ----- | ------ | --- 
24// P9_39 | P1_19  | 0
25// P9_40 | P1_21  | 1
26// P9_37 | P1_23  | 2
27// P9_38 | P1_25  | 3
28// P9_33 | P1_27  | 4
29// P9_36 | P2_35  | 5
30// P9_35 | P1_02  | 6

analogIn.js

Note

The code above outputs a value between 0 and 4096.

A variable resistor with two terminals#

Some resistive sensors have only two terminals, such as the flex sensor in Reading a two-terminal flex resistor The resistance between its two terminals changes when it is flexed. In this case, we need to add a fixed resistor in series with the flex sensor. Reading a two-terminal flex resistor shows how to wire in a 22 kΩ resistor to give a voltage to measure across the flex sensor.

Flex Resistor

Fig. 547 Reading a two-terminal flex resistor#

The code in analogIn.py also works for this setup.

Reading a Distance Sensor (Analog or Variable Voltage Sensor)#

Problem#

You want to measure distance with a LV-MaxSonar-EZ1 Sonar Range Finder, which outputs a voltage in proportion to the distance.

Solution#

Note

The BeagleY-AI doesn’t have ADC’s, so you can skip this section.

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • LV-MaxSonar-EZ1 Sonar Range Finder

All you have to do is wire the EZ1 to one of the Bone’s analog-in pins, as shown in Wiring the LV-MaxSonar-EZ1 Sonar Range Finder to the P9_33 analog-in port. The device outputs ~6.4 mV/in when powered from 3.3 V.

Warning

Make sure not to apply more than 1.8 V to the Bone’s analog-in pins, or you will likely damage them. In practice, this circuit should follow that rule.

Analog

Fig. 548 Wiring the LV-MaxSonar-EZ1 Sonar Range Finder to the P9_33 analog-in port#

ultrasonicRange.py shows the code that reads the sensor at a fixed interval.

Listing 23 Reading an analog voltage (ultrasonicRange.py)#
 1#!/usr/bin/env python
 2# //////////////////////////////////////
 3# //	ultrasonicRange.js
 4# // 	Reads the analog value of the sensor.
 5# //////////////////////////////////////
 6import time
 7ms = 250;  # Time in milliseconds
 8
 9pin = "0"        # sensor, A0, P9_39
10
11IIOPATH='/sys/bus/iio/devices/iio:device0/in_voltage'+pin+'_raw'
12
13print('Hit ^C to stop');
14
15f = open(IIOPATH, "r")
16while True:
17    f.seek(0)
18    data = f.read()[:-1]
19    print('data= ' + data)
20    time.sleep(ms/1000)
21
22# // Bone  | Pocket | AIN
23# // ----- | ------ | --- 
24# // P9_39 | P1_19  | 0
25# // P9_40 | P1_21  | 1
26# // P9_37 | P1_23  | 2
27# // P9_38 | P1_25  | 3
28# // P9_33 | P1_27  | 4
29# // P9_36 | P2_35  | 5
30# // P9_35 | P1_02  | 6

ultrasonicRange.py

Listing 24 Reading an analog voltage (ultrasonicRange.js)#
 1#!/usr/bin/env node
 2//////////////////////////////////////
 3//	ultrasonicRange.js
 4// 	Reads the analog value of the sensor.
 5//////////////////////////////////////
 6const fs = require("fs");
 7const ms = 250;  // Time in milliseconds
 8
 9const pin = "0";        // sensor, A0, P9_39
10
11const IIOPATH='/sys/bus/iio/devices/iio:device0/in_voltage'+pin+'_raw';
12
13console.log('Hit ^C to stop');
14
15// Read every ms
16setInterval(readPin, ms);
17
18function readPin() {
19    var data = fs.readFileSync(IIOPATH);
20    console.log('data= ' + data);
21 }
22// Bone  | Pocket | AIN
23// ----- | ------ | --- 
24// P9_39 | P1_19  | 0
25// P9_40 | P1_21  | 1
26// P9_37 | P1_23  | 2
27// P9_38 | P1_25  | 3
28// P9_33 | P1_27  | 4
29// P9_36 | P2_35  | 5
30// P9_35 | P1_02  | 6

ultrasonicRange.js

Reading a Distance Sensor (Variable Pulse Width Sensor)#

Problem#

You want to use a HC-SR04 Ultrasonic Range Sensor with BeagleBone Black.

Solution#

The HC-SR04 Ultrasonic Range Sensor (shown in HC-SR04 Ultrasonic range sensor) works by sending a trigger pulse to the Trigger input and then measuring the pulse width on the Echo output. The width of the pulse tells you the distance.

HC-SR04 Ultrasonic Sensor

Fig. 549 HC-SR04 Ultrasonic range sensor#

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • 10 kΩ and 20 kΩ resistors

  • HC-SR04 Ultrsonic Range Sensor.

Wire the sensor as shown in Wiring an HC-SR04 Ultrasonic Sensor. Note that the HC-SR04 is a 5 V device, so the banded wire (running from P9_7 on the Bone to VCC on the range finder) attaches the HC-SR04 to the Bone’s 5 V power supply.

Wiring an HC-SR04 Ultrasonic Sensor

Fig. 550 Wiring an HC-SR04 Ultrasonic Sensor#

Driving a HC-SR04 ultrasound sensor (hc-sr04-ultraSonic.js) shows BoneScript code used to drive the HC-SR04.

Listing 25 Driving a HC-SR04 ultrasound sensor (hc-sr04-ultraSonic.js)#
 1#!/usr/bin/env node
 2
 3// This is an example of reading HC-SR04 Ultrasonic Range Finder
 4// This version measures from the fall of the Trigger pulse 
 5//   to the end of the Echo pulse
 6
 7var b = require('bonescript');
 8
 9var trigger = 'P9_16',  // Pin to trigger the ultrasonic pulse
10    echo    = 'P9_41',  // Pin to measure to pulse width related to the distance
11    ms = 250;           // Trigger period in ms
12    
13var startTime, pulseTime;
14    
15b.pinMode(echo,   b.INPUT, 7, 'pulldown', 'fast', doAttach);
16function doAttach(x) {
17    if(x.err) {
18        console.log('x.err = ' + x.err);
19        return;
20    }
21    // Call pingEnd when the pulse ends
22    b.attachInterrupt(echo, true, b.FALLING, pingEnd);
23}
24
25b.pinMode(trigger, b.OUTPUT);
26
27b.digitalWrite(trigger, 1);     // Unit triggers on a falling edge.
28                                // Set trigger to high so we call pull it low later
29
30// Pull the trigger low at a regular interval.
31setInterval(ping, ms);
32
33// Pull trigger low and start timing.
34function ping() {
35    // console.log('ping');
36    b.digitalWrite(trigger, 0);
37    startTime = process.hrtime();
38}
39
40// Compute the total time and get ready to trigger again.
41function pingEnd(x) {
42    if(x.attached) {
43        console.log("Interrupt handler attached");
44        return;
45    }
46    if(startTime) {
47        pulseTime = process.hrtime(startTime);
48        b.digitalWrite(trigger, 1);
49        console.log('pulseTime = ' + (pulseTime[1]/1000000-0.8).toFixed(3));
50    }
51}

hc-sr04-ultraSonic.js

This code is more complex than others in this chapter, because we have to tell the device when to start measuring and time the return pulse.

Accurately Reading the Position of a Motor or Dial#

Problem#

Todo

Update for BeagleY-AI

You have a motor or dial and want to detect rotation using a rotary encoder.

Solution#

Use a rotary encoder (also called a quadrature encoder) connected to one of the Bone’s eQEP ports, as shown in Wiring a rotary encoder using eQEP2.

Wiring a rotary encoder using eQEP2

Fig. 551 Wiring a rotary encoder using eQEP2#

Table 134 On the BeagleBone and PocketBeage the three encoders are:#

eQEP0

P9.27 and P9.42 OR P1_33 and P2_34

eQEP1

P9.33 and P9.35

eQEP2

P8.11 and P8.12 OR P2_24 and P2_33

Table 135 On the AI it’s:#

eQEP1

P8.33 and P8.35

eQEP2

P8.11 and P8.12 or P9.19 and P9.41

eQEP3

P8.24 and P8.25 or P9.27 and P9.42

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • Rotary encoder.

We are using a quadrature rotary encoder, which has two switches inside that open and close in such a manner that you can tell which way the shaft is turning. In this particular encoder, the two switches have a common lead, which is wired to ground. It also has a pushbutton switch wired to the other side of the device, which we aren’t using.

Wire the encoder to P8_11 and P8_12, as shown in Wiring a rotary encoder using eQEP2.

BeagleBone Black has built-in hardware for reading up to three encoders. Here, we’ll use the eQEP2 encoder via the Linux count subsystem.

Then run the following commands:

bone$ config-pin P8_11 qep
bone$ config-pin P8_12 qep
bone$ show-pins | grep qep
P8.12        12 fast rx  up  4 qep 2 in A    ocp/P8_12_pinmux (pinmux_P8_12_qep_pin)
P8.11        13 fast rx  up  4 qep 2 in B    ocp/P8_11_pinmux (pinmux_P8_11_qep_pin)

This will enable eQEP2 on pins P8_11 and P8_12. The 2 after the qep returned by show-pins shows it’s eQEP2.

Finally, add the code below to a file named rotaryEncoder.py and run it.

Listing 26 Reading a rotary encoder (rotaryEncoder.py)#
 1#!/usr/bin/env python
 2# // This uses the eQEP hardware to read a rotary encoder
 3# // bone$ config-pin P8_11 eqep
 4# // bone$ config-pin P8_12 eqep
 5import time
 6    
 7eQEP = '2'
 8COUNTERPATH = '/dev/bone/counter/counter'+eQEP+'/count0'
 9	
10ms = 100 	# Time between samples in ms
11maxCount = '1000000'
12
13# Set the eEQP maximum count
14f = open(COUNTERPATH+'/ceiling', 'w')
15f.write(maxCount)
16f.close()
17
18# Enable
19f = open(COUNTERPATH+'/enable', 'w')
20f.write('1')
21f.close()
22
23f = open(COUNTERPATH+'/count', 'r')
24
25olddata = -1
26while True:
27	f.seek(0)
28	data = f.read()[:-1]
29	# Print only if data changes
30	if data != olddata:
31		olddata = data
32		print("data = " + data)
33	time.sleep(ms/1000)
34
35# Black OR Pocket
36# eQEP0:	P9.27 and P9.42 OR P1_33 and P2_34
37# eQEP1:	P9.33 and P9.35
38# eQEP2:	P8.11 and P8.12 OR P2_24 and P2_33
39
40# AI
41# eQEP1:	P8.33 and P8.35
42# eQEP2:	P8.11 and P8.12 or P9.19 and P9.41
43# eQEP3:	P8.24 and P8.25 or P9.27 and P9.42

rotaryEncoder.py

Listing 27 Reading a rotary encoder (rotaryEncoder.js)#
 1#!/usr/bin/env node
 2// This uses the eQEP hardware to read a rotary encoder
 3// bone$ config-pin P8_11 eqep
 4// bone$ config-pin P8_12 eqep
 5const fs = require("fs");
 6    
 7const eQEP = "2";
 8const COUNTERPATH = '/dev/bone/counter/counter'+eQEP+'/count0';
 9	
10const ms = 100; 	// Time between samples in ms
11const maxCount = '1000000';
12
13// Set the eEQP maximum count
14fs.writeFileSync(COUNTERPATH+'/ceiling', maxCount);
15
16// Enable
17fs.writeFileSync(COUNTERPATH+'/enable', '1');
18
19setInterval(readEncoder, ms);    // Check state every ms
20
21var olddata = -1;
22function readEncoder() {
23	var data = parseInt(fs.readFileSync(COUNTERPATH+'/count'));
24	if(data != olddata) {
25		// Print only if data changes
26		console.log('data = ' + data);
27		olddata = data;
28	} 
29}
30
31// Black OR Pocket
32// eQEP0:	P9.27 and P9.42 OR P1_33 and P2_34
33// eQEP1:	P9.33 and P9.35
34// eQEP2:	P8.11 and P8.12 OR P2_24 and P2_33
35
36// AI
37// eQEP1:	P8.33 and P8.35
38// eQEP2:	P8.11 and P8.12 or P9.19 and P9.41
39// eQEP3:	P8.24 and P8.25 or P9.27 and P9.42

rotaryEncoder.js

Try rotating the encoder clockwise and counter-clockwise. You’ll see an output like this:

data = 32
data = 40
data = 44
data = 48
data = 39
data = 22
data = 0
data = 999989
data = 999973
data = 999972
^C

The values you get for data will depend on which way you are turning the device and how quickly. You will need to press ^C (Ctrl-C) to end.

See Also#

You can also measure rotation by using a variable resistor (see Wiring a 10 kΩ variable resistor (trimpot) to an ADC port).

Acquiring Data by Using a Smart Sensor over a Serial Connection#

Problem#

You want to connect a smart sensor that uses a built-in microcontroller to stream data, such as a global positioning system (GPS), to the Bone and read the data from it.

Solution#

The Bone has several serial ports (UARTs) that you can use to read data from an external microcontroller included in smart sensors, such as a GPS. Just wire one up, and you’ll soon be gathering useful data, such as your own location.

Here’s what you’ll need:

  • Breadboard and jumper wires.

  • GPS receiver

Wire your GPS, as shown in Wiring a GPS to UART 4.

Wiring a GPS to UART 4

Fig. 552 Wiring a GPS to UART 4#

The GPS will produce raw National Marine Electronics Association (NMEA) data that’s easy for a computer to read, but not for a human. There are many utilities to help convert such sensor data into a human-readable form. For this GPS, run the following command to load a NMEA parser:

bone$ npm install -g nmea

Running the code in Talking to a GPS with UART 4 (GPS.js) will print the current location every time the GPS outputs it.

Listing 28 Talking to a GPS with UART 4 (GPS.js)#
 1#!/usr/bin/env node
 2// Install with: npm install nmea
 3
 4// Need to add exports.serialParsers = m.module.parsers;
 5// to the end of /usr/local/lib/node_modules/bonescript/serial.js
 6
 7var b = require('bonescript');
 8var nmea = require('nmea');
 9
10var port = '/dev/ttyO4';
11var options = {
12    baudrate: 9600,
13    parser: b.serialParsers.readline("\n")
14};
15
16b.serialOpen(port, options, onSerial);
17
18function onSerial(x) {
19    if (x.err) {
20        console.log('***ERROR*** ' + JSON.stringify(x));
21    }
22    if (x.event == 'open') {
23       console.log('***OPENED***');
24    }
25    if (x.event == 'data') {
26        console.log(String(x.data));
27        console.log(nmea.parse(x.data));
28    }
29}

GPS.js

If you don’t need the NMEA formatting, you can skip the npm part and remove the lines in the code that refer to it.

Note

If you get an error like this TypeError: Cannot call method ‘readline’ of undefined

add this line to the end of file /usr/local/lib/node_modules/bonescript/serial.js:

exports.serialParsers = m.module.parsers;

UART outputs

Fig. 553 Table of UART outputs#

Measuring a Temperature#

Problem#

You want to measure a temperature using a digital temperature sensor.

Solution#

The TMP101 sensor is a common digital temperature sensor that uses a standard I2C-based serial protocol.

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • Two 4.7 kΩ resistors.

  • TMP101 temperature sensor.

Wire the TMP101, as shown in Wiring an I2C TMP101 temperature sensor.

|I2C| Temp

Fig. 554 Wiring an I2C TMP101 temperature sensor#

There are two I2C buses brought out to the headers. Table of I2C outputs shows that you have wired your device to I2C bus 2.

Table of |I2C| outputs

Fig. 555 Table of I2C outputs#

Running the following on the BeagleY-AI shows it has five i2c buses.

bone$ ls /sys/bus/i2c/devices/
2-0030  2-0050  2-0068  4-004c  i2c-1  i2c-2  i2c-3  i2c-4  i2c-5

But running https://pinout.beagleboard.io/ show only buses 1 and 4 are exposed on the HAT header. Here we’ll use bus 2 whose clock appears on hat-03 and data on hat-05.

Wire your tmp101 as shown in the table.

Function

hat

tmp101

Ground

09

2

3.3V

01

5

data

03

6

clock

05

1

Once the I2C device is wired up, you can use a couple handy I2C tools to test the device. Because these are Linux command-line tools, you have to use 2 as the bus number. i2cdetect, shown in I2C tools, shows which I2C devices are on the bus. The -r flag indicates which bus to use. Our TMP101 is appearing at address 0x49. You can use the i2cget command to read the value. It returns the temperature in hexadecimal and degrees C. In this example, 0x18 = 24{deg}C, which is 75.2{deg}F. (Hmmm, the office is a bit warm today.) Try warming up the TMP101 with your finger and running i2cget again.

Todo

fix deg

I2C tools#

One way to see what devices are on a given I2C bus is to use i2cdetect. Here is bus 2 on the BeagleBone.

bone$ i2cdetect -y -r 2
  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
  00:          -- -- -- -- -- -- -- -- -- -- -- -- --
  10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40: -- -- -- -- -- -- -- -- -- 49 -- -- -- -- -- --
  50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- --

bone$ i2cget -y 2 0x49
  0x18

Here is bus 1 on the BeagleY-AI.

bone$ i2cdetect -y -r 1
  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
  00:          -- -- -- -- -- -- -- -- -- -- -- -- --
  10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40: -- -- -- -- -- -- -- -- -- 49 -- -- -- -- -- --
  50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- --

bone$ i2cget -y 1 0x49
  0x18

Reading the temperature via the kernel driver#

The cleanest way to read the temperature from at TMP101 sensor is to use the kernel driver.

Assuming the TMP101 is on bus 2 (the last digit is the bus number)

Note

Switch bus 2 to bus 1 if you are using the BeagleY-AI.

I2C TMP101 via Kernel

bone$ cd /sys/class/i2c-adapter/
bone$ ls
i2c-0  i2c-1  i2c-2                   # Three i2c buses (bus 0 is internal)
bone$ cd i2c-2        # Pick bus 2
bone$ ls -ls
0 --w--w---- 1 root gpio 4096 Jul  1 09:24 delete_device
0 lrwxrwxrwx 1 root gpio    0 Jun 30 16:25 device -> ../../4819c000.i2c
0 drwxrwxr-x 3 root gpio    0 Dec 31  1999 i2c-dev
0 -r--r--r-- 1 root gpio 4096 Dec 31  1999 name
0 --w--w---- 1 root gpio 4096 Jul  1 09:24 new_device
0 lrwxrwxrwx 1 root gpio    0 Jun 30 16:25 of_node -> ../../../../../../../../firmware/devicetree/base/ocp/interconnect@48000000/segment@100000/target-module@9c000/i2c@0
0 drwxrwxr-x 2 root gpio    0 Dec 31  1999 power
0 lrwxrwxrwx 1 root gpio    0 Jun 30 16:25 subsystem -> ../../../../../../../../bus/i2c
0 -rw-rw-r-- 1 root gpio 4096 Dec 31  1999 uevent

Assuming the TMP101 is at address 0x49

bone$ echo tmp101 0x49 > new_device

Note

If this returns new_device: Permission denied, you will need to run the following first.

bone$ sudo chown debian:gpio *

This tells the kernel you have a TMP101 sensor at address 0x49. Check the log to be sure.

bone$ dmesg -H | tail -3
[ +13.571823] i2c i2c-2: new_device: Instantiated device tmp101 at 0x49
[  +0.043362] lm75 2-0049: supply vs not found, using dummy regulator
[  +0.009976] lm75 2-0049: hwmon0: sensor 'tmp101'

Yes, it’s there, now see what happened.

bone$ ls
2-0049  delete_device  device  i2c-dev  name new_device  of_node  power  subsystem  uevent

Notice a new directory has appeared. It’s for i2c bus 2, address 0x49. Look into it.

bone$ cd 2-0049/hwmon/hwmon0
bone$ ls -F
device@  name  power/  subsystem@  temp1_input  temp1_max temp1_max_hyst  uevent  update_interval
bone$ cat temp1_input
24250

There is the temperature in milli-degrees C.

Other i2c devices are supported by the kernel. You can try the Linux Kernel Driver Database, https://cateee.net/lkddb/ to see them.

Once the driver is in place, you can read it via code. i2cTemp.py` shows how to read the TMP101.

Listing 29 Reading an I2C device (i2cTemp.py)#
 1#!/usr/bin/env python
 2# ////////////////////////////////////////
 3# //	i2cTemp.py
 4# //      Read a TMP101 sensor on i2c bus 2, address 0x49
 5# //	Wiring:	Attach to i2c as shown in text.
 6# //	Setup:	echo tmp101 0x49 > /sys/class/i2c-adapter/i2c-2/new_device
 7# //	See:	
 8# ////////////////////////////////////////
 9import time
10
11ms = 1000   # Read time in ms
12bus = '2'
13addr = '49'
14I2CPATH='/sys/class/i2c-adapter/i2c-'+bus+'/'+bus+'-00'+addr+'/hwmon/hwmon0';
15
16f = open(I2CPATH+"/temp1_input", "r")
17
18while True:
19    f.seek(0)
20    data = f.read()[:-1]    # returns mili-degrees C
21    print("data (C) = " + str(int(data)/1000))
22    time.sleep(ms/1000)

i2cTemp.py

Listing 30 Reading an I2C device (i2cTemp.js)#
 1#!/usr/bin/env node
 2////////////////////////////////////////
 3//	i2cTemp.js
 4//      Read at TMP101 sensor on i2c bus 2, address 0x49
 5//	Wiring:	Attach to i2c as shown in text.
 6//	Setup:	echo tmp101 0x49 > /sys/class/i2c-adapter/i2c-2/new_device
 7//	See:	
 8////////////////////////////////////////
 9const fs = require("fs");
10
11const ms = 1000;   // Read time in ms
12const bus = '2';
13const addr = '49';
14I2CPATH='/sys/class/i2c-adapter/i2c-'+bus+'/'+bus+'-00'+addr+'/hwmon/hwmon0';
15
16// Read every ms
17setInterval(readTMP, ms);
18
19function readTMP() {
20    var data = fs.readFileSync(I2CPATH+"/temp1_input").slice(0, -1);
21    console.log('data (C) = ' + data/1000);
22 }

i2cTemp.js

Run the code by using the following command:

bone$ ./i2cTemp.js
data (C) = 25.625
data (C) = 27.312
data (C) = 28.187
data (C) = 28.375
^C

Notice using the kernel interface gets you more digits of accuracy.

Reading i2c device directly#

The TMP102 sensor can be read directly with i2c commands rather than using the kernel driver. First you need to install the i2c module.

bone$ sudo apt install python3-smbus
Listing 31 Reading an I2C device (i2cTemp.py)#
 1#!/usr/bin/env python
 2# ////////////////////////////////////////
 3# //	i2ctmp101.py
 4# //      Read at TMP101 sensor on i2c bus 2, address 0x49
 5# //	Wiring:	Attach to i2c as shown in text.
 6# //	Setup:	pip install smbus
 7# //	See:	
 8# ////////////////////////////////////////
 9import smbus
10import time
11
12ms = 1000               # Read time in ms
13bus = smbus.SMBus(2)    # Using i2c bus 2
14addr = 0x49             # TMP101 is at address 0x49
15
16while True:
17    data = bus.read_byte_data(addr, 0)
18    print("temp (C) = " + str(data))
19    time.sleep(ms/1000)

i2ctmp101.py

This gets only 8 bits for the temperature. See the TMP101 datasheet (https://www.ti.com/product/TMP101) for details on how to get up to 12 bits.

Reading Temperature via a Dallas 1-Wire Device#

Problem#

You want to measure a temperature using a Dallas Semiconductor DS18B20 temperature sensor.

Solution#

Todo

Update for BeagleY-AI

The DS18B20 is an interesting temperature sensor that uses Dallas Semiconductor’s 1-wire interface. The data communication requires only one wire! (However, you still need wires from ground and 3.3 V.) You can wire it to any GPIO port.

To make this recipe, you will need:

  • Breadboard and jumper wires.

  • 4.7 kΩ resistor

  • DS18B20 1-wire temperature sensor.

Wire up as shown in Wiring a Dallas 1-Wire temperature sensor.

1-wire

Fig. 556 Wiring a Dallas 1-Wire temperature sensor#

Edit the file /boot/uEnt.txt. Go to about line 19 and edit as shown:

17 ###
18 ###Additional custom capes
19 uboot_overlay_addr4=BB-W1-P9.12-00A0.dtbo
20 #uboot_overlay_addr5=<file5>.dtbo

Be sure to remove the # at the beginning of the line.

Reboot the bone:

bone$ reboot

Now run the following command to discover the serial number on your device:

bone$ ls /sys/bus/w1/devices/
28-00000114ef1b  28-00000128197d  w1_bus_master1

I have two devices wired in parallel on the same P9_12 input. This shows the serial numbers for all the devices.

Finally, add the code below in to a file named w1.py, edit the path assigned to w1 so that the path points to your device, and then run it.

Listing 32 Reading a temperature with a DS18B20 (w1.py)#
 1#!/usr/bin/env python
 2# ////////////////////////////////////////
 3# //	w1.js
 4# //      Read a Dallas 1-wire device on P9_12
 5# //	Wiring:	Attach gnd and 3.3V and data to P9_12
 6# //	Setup:	Edit /boot/uEnv.txt to include:
 7# //              uboot_overlay_addr4=BB-W1-P9.12-00A0.dtbo
 8# //	See:	
 9# ////////////////////////////////////////
10import time
11
12ms = 500   # Read time in ms
13#  Do ls /sys/bus/w1/devices and find the address of your device
14addr = '28-00000d459c2c' # Must be changed for your device.
15W1PATH ='/sys/bus/w1/devices/' + addr
16
17f = open(W1PATH+'/temperature')
18
19while True:
20    f.seek(0)
21    data = f.read()[:-1]
22    print("temp (C) = " + str(int(data)/1000))
23    time.sleep(ms/1000)

w1.py

Listing 33 Reading a temperature with a DS18B20 (w1.js)#
 1#!/usr/bin/env node
 2////////////////////////////////////////
 3//	w1.js
 4//      Read a Dallas 1-wire device on P9_12
 5//	Wiring:	Attach gnd and 3.3V and data to P9_12
 6//	Setup:	Edit /boot/uEnv.txt to include:
 7//              uboot_overlay_addr4=BB-W1-P9.12-00A0.dtbo
 8//	See:	
 9////////////////////////////////////////
10const fs = require("fs");
11
12const ms = 500   // Read time in ms
13// Do ls /sys/bus/w1/devices and find the address of your device
14const addr = '28-00000d459c2c'; // Must be changed for your device.
15const W1PATH ='/sys/bus/w1/devices/' + addr;
16
17// Read every ms
18setInterval(readW1, ms);
19
20function readW1() {
21    var data = fs.readFileSync(W1PATH+'/temperature').slice(0, -1);
22    console.log('temp (C) = ' + data/1000);
23 }

w1.js

bone$ ./w1.js
temp (C) = 28.625
temp (C) = 29.625
temp (C) = 30.5
temp (C) = 31.0
^C

Each temperature sensor has a unique serial number, so you can have several all sharing the same data line.

Playing and Recording Audio#

Todo

Remove?

Problem#

BeagleBone doesn’t have audio built in, but you want to play and record files.

Solution#

One approach is to buy an audio cape, but another, possibly cheaper approach is to buy a USB audio adapter, such as the one shown in A USB audio dongle.

Audio Dongle

Fig. 557 A USB audio dongle#

Drivers for the Advanced Linux Sound Architecture (ALSA) may already installed on the Bone. If not, run the following:

bone$ sudo apt install alsa-utils

You can list the recording and playing devices on your Bone by using aplay and arecord, as shown in Listing the ALSA audio output and input devices on the Bone. BeagleBone Black has audio-out on the HDMI interface. It’s listed as card 0 in Listing the ALSA audio output and input devices on the Bone. card 1 is my USB audio adapter’s audio out.

Listing the ALSA audio output and input devices on the Bone#

bone$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Black [TI BeagleBone Black], device 0: HDMI nxp-hdmi-hifi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: Device [C-Media USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

bone$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Device [C-Media USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

In the aplay output shown in Listing the ALSA audio output and input devices on the Bone, you can see the USB adapter’s audio out. By default, the Bone will send audio to the HDMI. You can change that default by creating a file in your home directory called ~/.asoundrc and adding the code in Change the default audio out by putting this in ~/.asoundrc (audio.asoundrc) to it.

Listing 34 Change the default audio out by putting this in ~/.asoundrc (audio.asoundrc)#
 1pcm.!default {
 2  type plug
 3  slave {
 4    pcm "hw:1,0"
 5  }
 6}
 7ctl.!default {
 8  type hw
 9  card 1
10}

audio.asoundrc

You can easily play .wav files with aplay:

bone$ aplay test.wav

You can play other files in other formats by installing mplayer:

bone$ sudo apt update
bone$ sudo apt install mplayer
bone$ mplayer test.mp3

Discussion#

Adding the simple USB audio adapter opens up a world of audio I/O on the Bone.