In my earlier posts that should how to use Swift with the
Beaglebone Black I used the SwiftyGPIO library to
interact with digital GPIO ports. While
using digital GPIO ports can be very powerful the Beaglebone Black also has analog ports.
In this post I will show how we can use the TMP36 temperature sensor with an analog port to determine the temperature.
With the digital ports we measure one of two values (high or
low). With the analog ports we measure a
range a values. The Beaglebone Black
provides seven analog ports labeled AIN0 through AIN6. These ports are located on the P9 header and
the following list shows what pin corresponds to which analog inputs:
AIN0 - 39
AIN1 - 40
AIN2 - 37
AIN3 - 38
AIN4 - 33
AIN5 - 36
AIN6 - 35
The following image shows the full headers:
Lets get started, if you have not already installed Swift on
your Beaglebone Black you can check out my earlier post that talks about how to
install it and also how to use the SwiftyGPIO library. In
this post we cannot use the SwiftyGPIO library because it does not support
analog ports instead we will interact directly with GPIO ports through the file
system. This is how the SwiftGPIO
actually works behind the scenes when you use it to access the digital GPIO.
Like always the first thing we need to do is to connect our
sensor to the Beaglebone Black. The
following diagram shows how I connected the TMP36 temperature sensor to my
Beaglebone Black. Note that I have the
TMP36 sensor connected to the AIN1 port.
Now that we have the TMP36 Temperature sensor wired to our
Beaglebone Black lets power it up and see how we would read the Analog port. Before we write our Swift code lets see how
we would read the port manually from the shell.
The first thing we would need to do is to enable the analog ports. To enable the ports we would echo “BB-ADC”
to the /sys/devices/platform/bone_capemgr/slots file. Before we do that, lets look at the
file. If we run the following command we
can see the contents of the file:
cat /sys/devices/platform/bone_capemgr/slots
The contents of the file should look something like this:
0: PF----
-1
1: PF----
-1
2: PF----
-1
3: PF----
-1
4: P-O-L-
0 Override Board Name,00A0,Override Manuf,cape-universaln
Now lets enable the analog ports by running the following
command:
echo BB-ADC >
/sys/devices/platform/bone_capemgr/slots
If we cat out the contents of the slots file again, it
should now look something like this:
0: PF----
-1
1: PF----
-1
2: PF----
-1
3: PF----
-1
4: P-O-L-
0 Override Board Name,00A0,Override Manuf,cape-universaln
5: P-O-L-
1 Override Board Name,00A0,Override Manuf,BB-ADC
The last line shows that the analog ports are now enabled. Now that the ports are enabled, lets see how
we can read the AIN1 port. We can do
that by reading the contents of the /sys/bus/iio/devices/iio:device0/in_voltage1_raw
file like this:
cat /sys/bus/iio/devices/iio:device0/in_voltage1_raw
The output of this command should be a number around 1700 or
so. If you look in the /sys/bus/iio/devices/iio:device0/
directory, you should see 7 in_voltage
files numbered 0 through 7. These files correspond
to the seven AIN ports therefore if we connected our TMP36 temperature sensor
to AIN2 instead of AIN1 we would read the in_voltage2_raw file.
Now that we know how to get the value from the analog ports,
lets see how we would do this with Swift and convert that value to the current
temperature. The first thing we need to
do is to create a function that will read the value from a file. The following will do this and return an
optional that would be either a String value or nil.
func readStringFromFile(path: String) -> String?
{
let fp =
fopen(path, "r")
guard fp
!= nil else {
return
nil
}
var
oString = ""
let
bufSize = 8
let
buffer: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer.alloc(bufSize)
defer {
fclose(fp)
buffer.dealloc(bufSize)
}
repeat {
let
count: Int = fread(buffer, 1, bufSize, fp)
guard
ferror(fp) == 0 else {
break
}
if
count > 0 {
oString
+= stringFromBytes(buffer, count: count)
}
} while feof(fp) == 0
return
oString
}
In this function we use the fopen() function to open the
file. We use a guard statement to verify that the file opened
properly. If the file did not open properly
we return nil. To ensure that the file
is properly closed and the buffer that we used to read the data is properly
released we use a defer block to close the file and release
the buffer.
We then continuously read from the file with a repeat block until
we reach the end of the file. When we
read from the analog files, we will only go though this loop once and read four
or less bytes of data but it is good practice to keep the repeat block to
ensure we are reading all data.
We convert the bytes that we read from the file to a string
using the stringFromBytes() function.
The following code shows this function:
func stringFromBytes(bytes:
UnsafeMutablePointer<UInt8>, count: Int) -> String {
var
retString = ""
for index
in 0..<count {
if
bytes[index] > 47 && bytes[index] < 58 {
retString
+= String(Character(UnicodeScalar(bytes[index])))
}
}
return
retString
}
In this function we loop through the byte array and if the
value of the individual element is greater than 47 or less than 58 (ASCII
representations of 0-9 because we only want numbers) then we convert the byte
to a character and append it to the return string.
Using these two functions we would read the TMP36
temperature sensor and calculate the temperature like this:
var file = "/sys/bus/iio/devices/iio:device0/in_voltage1_raw"
if let input = readStringFromFile(file) {
if let
rawValue = Double(input) {
print("RawValue: \(rawValue)")
let
milliVolts = (rawValue / 4096.0) * 1800.0
print("milliVolts: \(milliVolts)")
let
celsius = (milliVolts - 500.0) / 10.0
print("Celsius: \(celsius)")
let
fahrenheit = (celsius * 9.0 / 5.0) + 32.0
print("Fahrenheit: \(fahrenheit)")
}
}
In this code we begin by reading the value from the /sys/bus/iio/devices/iio:device0/in_voltage1_raw
file. We then convert that string value
to a Double value. Once we have the Double
value we need to convert it to millivolts using the (value/4096) * 1800 equations. We then convert the millivolts to the temperature
in Celsius and then convert the Celsius temperature to Fahrenheit. This code will print out all of the values so
you can see how everything is calculated.
To put all of this together, we would create a file named main.swift
at put the following code into it:
import Glibc
func stringFromBytes(bytes:
UnsafeMutablePointer<UInt8>, count: Int) -> String {
var
retString = ""
for index
in 0..<count {
if
bytes[index] > 47 && bytes[index] < 58 {
retString
+= String(Character(UnicodeScalar(bytes[index])))
}
}
return
retString
}
func readStringFromFile(path: String) -> String?
{
let fp =
fopen(path, "r")
guard fp
!= nil else {
return
nil
}
var
oString = ""
let
bufSize = 8
let
buffer: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer.alloc(bufSize)
defer {
fclose(fp)
buffer.dealloc(bufSize)
}
repeat {
let
count: Int = fread(buffer, 1, bufSize, fp)
guard
ferror(fp) == 0 else {
break
}
if
count > 0 {
oString
+= stringFromBytes(buffer, count: count)
}
} while feof(fp) == 0
return
oString
}
var file = "/sys/bus/iio/devices/iio:device0/in_voltage1_raw"
while(true) {
if let
input = readStringFromFile(file){
if
let rawValue = Double(input) {
print("RawValue: \(rawValue)")
let
milliVolts = (rawValue / 4096.0) * 1800.0
print("milliVolts: \(milliVolts)")
let
celsius = (milliVolts - 500.0) / 10.0
print("Celsius: \(celsius)")
let
fahrenheit = (celsius * 9.0 / 5.0) + 32.0
print("Fahrenheit: \(fahrenheit)")
usleep(1000000)
}
} else {
break
}
}
We would then compile this application like this:
swiftc –o temperature main.swift
To run the application we need to ensure that we initiate
the analog ports first with the echo BB-ADC > /sys/devices/platform/bone_capemgr/slots command. You will only need to run this command once
after booting your Beaglebone. Once you
initiate the analog ports you can read the temperature like this.
./temperature
If everything is properly connected you should see the
temperature printed to the screen.