After reviewing a number of alternatives, it appears that the easiest approach for accessing the GPIO pins of the DragonBoard 410C running Android 5.1 was to use the legacy sysfs special device files method.
I'm not sure if this is the only workable solution. Using Android Things as well as using libgpiod both seem to require a more recent Linux kernel than Android 5.1 uses.
I have written an article on CodeProject.com providing details about working up this solution. See Using Windows 10 for Development with DragonBoard 410C and Android.
Which Android runs which Linux kernel? https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
Android Version |API Level |Linux Version in AOSP |Header Version
----------------------------------------------------------------------------------------
4.4 Kit Kat |19, 20 |(3.10) |2.6.18
5.x Lollipop |21, 22 |(3.16.1) |3.14.0
6.0 Marshmallow |23 |(3.18.10) |3.18.10
This method also seems to be the easiest in that the library used is written in Kotlin as well.
Using the legacy sysfs special device GPIO interface
See this StackOverFlow post about Linux pseudo files and special device files and the legacy sysfs interface for GPIO pins, what is the /sys/class/gpio/export and `/sys/class/gpio/unexport mechanism and what is the underlying sysfs functionality? .
I found a simple GPIO library written in Java that provided necessary source code. Android Studio has a tool that converted the Java to Kotlin which I included into my project. The source code is below in files Gpio.kt and GpioProcessor.kt.
However in order for this to work I had to make a change to my DragonBoard startup scripts in order to make sure that the necessary special device files were created and available with the proper permissions allowing a user program to manipulate the GPIO pins.
The following procedure is from the Coursera class Internet of Things: Sensing and Actuation from Devices, Lesson 5a: Access GPIO through programs (Android) video #2, Modify boot script. The procedure was to:
- use adb to pull a copy of /etc/init.qcom.post_boot.sh from the DragonBoard to my PC
- use Notepad to modify the shell script to create the special device files
- use adb to push the modified copy back to the Dragonboard
- use adb to reboot the DragonBoard
The additional shell code to add to the bottom of /etc/init.qcom.post_boot.sh is as follows. However these special device files are for Android 5.1 only. Linux uses different GPIO pin names.
set -A pins 938 915 1017 926 937 930 914 971 901 936 935
for i in 0 1 2 3 4 5 6 7 8 9 10
do
echo ${pins[i]} > /sys/class/gpio/export;
chmod 777 /sys/class/gpio/gpio${pins[i]};
chmod 777 /sys/class/gpio/gpio${pins[i]}/value;
chmod 777 /sys/class/gpio/gpio${pins[i]}/direction;
done
A Note on sysfs device attributes
Here is some documentation on the GPIO Sysfs Inferface for Userspace from kernel.org. In addition to the two attributes that I use, direction
and value
, there are several others such as edge
and active_low
.
“direction” … reads as either “in” or “out”. This value may normally
be written. Writing as “out” defaults to initializing the value as
low. To ensure glitch free operation, values “low” and “high” may be
written to configure the GPIO as an output with that initial value.
Note that this attribute will not exist if the kernel doesn’t support
changing the direction of a GPIO, or it was exported by kernel code
that didn’t explicitly allow userspace to reconfigure this GPIO’s
direction.
“value” … reads as either 0 (low) or 1 (high). If the GPIO is
configured as an output, this value may be written; any nonzero value
is treated as high.
If the pin can be configured as interrupt-generating interrupt and if
it has been configured to generate interrupts (see the description of
“edge”), you can poll(2) on that file and poll(2) will return whenever
the interrupt was triggered. If you use poll(2), set the events
POLLPRI and POLLERR. If you use select(2), set the file descriptor in
exceptfds. After poll(2) returns, either lseek(2) to the beginning of
the sysfs file and read the new value or close the file and re-open it
to read the value.
“edge” … reads as either “none”, “rising”, “falling”, or “both”. Write
these strings to select the signal edge(s) that will make poll(2) on
the “value” file return.
This file exists only if the pin can be configured as an interrupt
generating input pin.
“active_low” … reads as either 0 (false) or 1 (true). Write any
nonzero value to invert the value attribute both for reading and
writing. Existing and subsequent poll(2) support configuration via the
edge attribute for “rising” and “falling” edges will follow this
setting.
Kotlin source code for using sysfs
The complete testing application I am using to explore this topic of using the DragonBoard 410C with Android is in my GitHub repository, https://github.com/RichardChambers/dragonboard_410c
Source for file Gpio.kt
package com.example.myapplication
import java.io.*
/**
* Created by Ara on 7/21/15.
* From https://www.instructables.com/id/DragonBoard-How-to-Access-GPIOs-Using-Java/
* Java source from the article was converted to Kotlin using Android Studio.
*
* See as well https://github.com/IOT-410c/DragonBoard410c_GpioLibrary
*
*/
class Gpio(pin: Int) {
private val pin: Int
/*
* The GPIO pins are represented by folders in the Linux file system
* within the folder /sys/class/gpio. Each pin is represented by a folder
* whose name is the prefix "gpio" followed by the pin number.
* Within the folder representing the pin are two files, "value" used to
* set or get the value of the pin and "direction" used to set or get
* the direction of the pin.
*
* This function creates the path to the Linux file which represents a particular
* GPIO pin function, "value" or "direction".
*/
private fun MakeFileName(pin: Int, op: String): String {
return "/sys/class/gpio/gpio$pin$op"
}
/*
* Get or set the current direction of a pin.
* A pin may be either an Input pin or an Output pin.
*/
var direction: String
get() {
println("Getting Direction")
var line = ""
try {
val br = BufferedReader(FileReader(MakeFileName(pin, "/direction")))
line = br.readLine()
br.close()
} catch (e: Exception) {
println("Error: " + e.message)
}
return line
}
private set(direction) {
println("Setting Direction")
try {
val out = BufferedWriter(FileWriter(MakeFileName(pin, "/direction"), false))
out.write(direction)
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Get or Set pin value.
* @param value Value of pin.
* 0 -> Low Level.
* 1 -> High Level
*/
var value: Int
get() {
println("Getting Value")
var line = ""
try {
val br = BufferedReader(FileReader(MakeFileName(pin, "/value")))
line = br.readLine()
br.close()
} catch (e: Exception) {
println("Error: " + e.message)
}
return line.toInt()
}
private set(value) {
println("Setting Value")
try {
val out = BufferedWriter(FileWriter(MakeFileName(pin, "/value"), false))
out.write(Integer.toString(value))
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Set pin as high.
*/
fun pinHigh() {
value = HIGH
}
/**
* Set pin as low.
*/
fun pinLow() {
value = LOW
}
/**
* Set pin as output.
*/
fun pinOut() {
direction = "out"
}
/**
* Set pin as input.
* @param pin - Desirable pin.
*/
fun pinIn() {
direction = "in"
}
fun exportPin() {
println("Exporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/export", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Disable access to GPIO.
* @param pin GPIO pin to disable access.
*/
fun unexportPin() {
println("unExporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/unexport", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
companion object {
const val HIGH = 1
const val LOW = 0
private const val PATH = "/sys/class/gpio"
}
/**
* Set desirable pin for the GPIO class.
*/
init {
println("Initializing pin $pin")
this.pin = pin
}
}
Source for GpioProcessor.kt
package com.example.myapplication
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
import java.util.*
/**
* Created by Ara on 7/21/15.
* From https://www.instructables.com/id/DragonBoard-How-to-Access-GPIOs-Using-Java/
* Java source from the article was converted to Kotlin using Android Studio.
*
* See as well https://github.com/IOT-410c/DragonBoard410c_GpioLibrary
*
* Simple example main()
*
* public class Main {
*
* public static void main(String[] args) {
* int count = 0;
* int buttonValue = 0;
*
* GpioProcessor gpioProcessor = new GpioProcessor();
*
* // Get reference of GPIO27 and GPIO29.
*
* Gpio gpioPin27 = gpioProcessor.getPin27();
* Gpio gpioPin29 = gpioProcessor.getPin29();
*
* // Set GPIO27 as output.Set GPIO29 as input.
* gpioPin27.pinOut();
* gpioPin29.pinIn();
*
* while(count<20){
* count++;
* // Read value of GPIO29.
* buttonValue=gpioPin29.getValue();
*
* if(buttonValue == 0){
* // Set GPIO27 as low level.
* gpioPin27.pinLow();
* } else{
* // Set GPIO27 as high level.
* gpioPin27.pinHigh();
* }
*
* try {
* Thread.sleep(1000);
* } catch(InterruptedException e){
* // TODO Auto-generated catch block
* e.printStackTrace();
* }
* }
*
* // Disable access GPIO27 and GPIO29.
* gpioProcessor.closePins();
* }
* }
*/ /*
This class abstracts the use of the gpio pins. This class can be utilized on any linux operating
system that has gpio pins defined in the /sys/class/gpio directory. It is required that the gpio
pins themselves are available for access by the user of this application, and may require a
change of permissions.
*/
class GpioProcessor {
private val PATH = "/sys/class/gpio"
private val pins: MutableList<Int> = ArrayList()
// mapping of physical pin number to GPIO file number.
// the mapping varies depending on the operating system
private val androidPin23 = 938
private val androidPin24 = 914
private val androidPin25 = 915
private val androidPin26 = 971
private val androidPin27 = 1017
private val androidPin28 = 901 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val androidPin29 = 926 // (input only)
private val androidPin30 = 927
private val androidPin31 = 937
private val androidPin32 = 936
private val androidPin33 = 930
private val androidPin34 = 935
private val linuxPin23 = 36
private val linuxPin24 = 12
private val linuxPin25 = 13
private val linuxPin26 = 69
private val linuxPin27 = 115
private val linuxPin28 = 4 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val linuxPin29 = 24 // (input only)
private val linuxPin30 = 25
private val linuxPin31 = 35
private val linuxPin32 = 34
private val linuxPin33 = 28
private val linuxPin34 = 33
private val physicalPin23 = androidPin23
private val physicalPin24 = androidPin24
private val physicalPin25 = androidPin25
private val physicalPin26 = androidPin26
private val physicalPin27 = androidPin27
private val physicalPin28 = androidPin28 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val physicalPin29 = androidPin29 // (input only)
private val physicalPin30 = androidPin30
private val physicalPin31 = androidPin31
private val physicalPin32 = androidPin32
private val physicalPin33 = androidPin33
private val physicalPin34 = androidPin34
/**
* Get function of specific pin.
* @param pin Desirable pin.
*/
fun getPin(pin: Int): Gpio {
exportPin(pin)
pins.add(pin)
return Gpio(pin)
}
/**
* Get pin 23;
* @returns {Gpio}
*/
val pin23: Gpio
get() = getPin(physicalPin23)
/**
* Get pin 24.
* @returns {Gpio}
*/
val pin24: Gpio
get() = getPin(physicalPin24)
/**
* Get pin 25.
* @returns {Gpio}
*/
val pin25: Gpio
get() = getPin(physicalPin25)
/**
* Get pin 26.
* @returns {Gpio}
*/
val pin26: Gpio
get() = getPin(physicalPin26)
/**
* Get pin 27.
* @returns {Gpio}
*/
val pin27: Gpio
get() = getPin(physicalPin27)
/**
* Get pin 28.
* @returns {Gpio}
*/
val pin28: Gpio
get() = getPin(physicalPin28)
/**
* Get pin 29.
* @returns {Gpio}
*/
val pin29: Gpio
get() = getPin(physicalPin29)
/**
* Get pin 30.
* @returns {Gpio}
*/
val pin30: Gpio
get() = getPin(physicalPin30)
/**
* Get pin 31.
* @returns {Gpio}
*/
val pin31: Gpio
get() = getPin(physicalPin31)
/**
* Get pin 32.
* @returns {Gpio}
*/
val pin32: Gpio
get() = getPin(physicalPin32)
/**
* Get pin 33.
* @returns {Gpio}
*/
val pin33: Gpio
get() = getPin(physicalPin33)
/**
* Get pin 34.
* @returns {Gpio}
*/
val pin34: Gpio
get() = getPin(physicalPin34)
/**
* Get all GPIO's pins.
* @return List of pins.
*/
val allPins: Array<Gpio?>
get() {
val allPins = arrayOfNulls<Gpio>(12) // android linux
allPins[0] = pin23 // GPIO 938 GPIO 36
allPins[1] = pin24 // GPIO 914 GPIO 12
allPins[2] = pin25 // GPIO 915 GPIO 13
allPins[3] = pin26 // GPIO 971 GPIO 69
allPins[4] = pin27 // GPIO 1017 GPIO 115
allPins[5] = pin28 // Reserved
allPins[6] = pin29 // GPIO 926 GPIO 24 (input only)
allPins[7] = pin30 // GPIO 927 GPIO 25
allPins[8] = pin31 // GPIO 937 GPIO 35
allPins[9] = pin32 // GPIO 936 GPIO 34
allPins[10] = pin33 // GPIO 930 GPIO 28
allPins[11] = pin34 // GPIO 935 GPIO 33
return allPins
}
/**
* Enable access to GPIO.
* @param pin GPIO pin to access.
*/
private fun exportPin(pin: Int) {
println("Exporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/export", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Disable access to GPIO.
* @param pin GPIO pin to disable access.
*/
private fun unexportPin(pin: Int) {
println("unExporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/unexport", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
fun closePins() {
for (pin in pins) {
unexportPin(pin)
}
pins.clear()
}
companion object {
const val TAG = "GpioProcessor"
}
}
Example source using the GpioProcessor class
I used the GPIO sysfs interface library in an Android app within a fragment by linking a button press to a listener. I have two buttons, one to turn the LED on by driving a pin high and a second to turn the LED off by driving a pin low.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.button_second).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
val txtScroll = view.findViewById(R.id.LedStatus) as TextView
// find the button whose id is button_Location and then set an listener for
// any clicks on that button. In the following listener we are going to have
// the "Location" button, defined in the file fragment_first.xml, generate a
// list of the GPS service providers by creatinga LocationManager object to
// generate a list.
val gpioProcessor_x = GpioProcessor()
// Get reference of GPIO23.
val gpioPin23_x = gpioProcessor_x.pin23
gpioPin23_x.exportPin()
view.findViewById<Button>(R.id.button_led_off).setOnClickListener {
val gpioProcessor = GpioProcessor()
// Get reference of GPIO27.
val gpioPin23 = gpioProcessor.pin23
// Set GPIO23 as output.
gpioPin23.pinOut()
gpioPin23.pinLow() // drive pin low to turn off LED.
txtScroll.append("LED Off\n")
}
view.findViewById<Button>(R.id.button_led_on).setOnClickListener {
val gpioProcessor = GpioProcessor()
// Get reference of GPIO27.
val gpioPin23 = gpioProcessor.pin23
// Set GPIO23 as output.
gpioPin23.pinOut()
gpioPin23.pinHigh() // drive pin high to turn on LED
txtScroll.append("LED On\n")
}
}
mraa
library is a path to desperation and disaster, never ever use it. For GPIO access you need to utilizelibgpiod
library or any equivalent. – Ofilia