accessing GPIO low power connector on DragonBoard 410C running Android
Asked Answered
P

1

0

I'm working with a recently purchased DragonBoard 410C running the Android 5.1 operating system and using Android Studio with Kotlin for generating a sample application that explores some of the hardware such as the 40 pin low power connector.

My question is how to access the 40 pin low power connector with the GPIO pins using Kotlin and Android Studio.

From my research thus far, it appears that the mraa library is the path to success however I have been unable to find any documentation on using the library with Kotlin.

How do I get started with the mraa library with Kotlin to access the 40 pin low power connector?

Or is there a different approach?

My first example is a simple blink the LED application however I have no idea as to how to access pins of the low power connector using Kotlin.

Notes and resources

mraa documentation page

Libmraa is a C/C++ library with bindings to Python, Javascript and Java to interface with the I/O on Galileo, Edison & other platforms, with a structured and sane API where port names/numbering matches the board that you are on. Use of libmraa does not tie you to specific hardware with board detection done at runtime you can create portable code that will work across the supported platforms.

upm library for mraa GitHub repository

The UPM repository provides software drivers for a wide variety of commonly used sensors and actuators. These software drivers interact with the underlying hardware platform (or microcontroller), as well as with the attached sensors, through calls to MRAA APIs.

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

sysfs is dead! long live libgpiod! (libgpiod for linux & CircuitPython)

This is basically code that will replace our Python DHT driver, and has the benefit of being forward compatible with any other Linux board that runs a 4.8+ kernel. We’ll slowly be replacing other CircuitPython code to use libgpiod, so that we can have broad support for CircuitPython on a Raspberry Pi, BeagleBone or Onion.io.

There’s not a lot of libgpiod code out there, and libgpiod doesn’t come stock on Linux distros yet which may be why its taking a little while to catch on. There’s bindings for C and Python. Here’s a script that can help you get started by compiling it for you https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/libgpiod.sh

A DragonBoard 410C GPIO library on GitHub https://github.com/IOT-410c/DragonBoard410c_GpioLibrary which is written in Java and uses the "/sys/class/gpio" method of Linux to access GPIO pins. This looks like this is a repository in a set of repositories used for Coursera courses on Internet of things, some of which use the DragonBoard 410C.

The Linux Kernel: Legacy GPIO Interfaces

This provides an overview of GPIO access conventions on Linux.

These calls use the gpio_* naming prefix. No other calls should use that prefix, or the related _gpio* prefix.

Android Studio and adb

Android Studio is the application for developing Android apps. It can be downloaded for installation from https://developer.android.com/studio/releases

In addition there are the Android Platform Tools which are a separate download. The adb shell application is part of these tools. The tools can be downloaded for installation from SDK Platform Tools release notes. Pick the particular version needed for your operating system (Windows, Linux, MacOS).

Android Things

While Android Things looks helpful, it appears only a couple of boards are actually supported and the DragonBoard 410C is not one of those. And I'm not sure that Android Things would work with Android 5.1 anyway.

https://developer.android.com/things/get-started

However there was a Brillo (now Android Things) port for DragonBoard 410C https://discuss.96boards.org/t/android-things-on-the-dragonboard/1128

Android Developers > Docs > Android Things > Guides > GPIO

In order to open a connection to a GPIO port, you need to know the unique port name. During the initial stages of development, or when porting an app to new hardware, it's helpful to discover all the available port names from PeripheralManager using getGpioList():

Android Things GitHub repositories https://github.com/androidthings/

See also the following stackoverflow posts which have something to say on this subject. See tag [android-things] in stackoverflow as well.

How can a GpioCallback register "false" twice in a row?

Android Things Pin naming convention

PeripheralManagerService throws NoClassDefFoundError

Pansophy answered 30/8, 2020 at 4:10 Comment(4)
No, mraa library is a path to desperation and disaster, never ever use it. For GPIO access you need to utilize libgpiod library or any equivalent.Ofilia
@Ofilia libgpiod looks promising however this article android.stackexchange.com/questions/51651/… indicates its requires Linux 4.8+ kernel and Android 5.1 is running 3.16.1 according to blog.adafruit.com/2018/11/26/… so can you suggest an equivalent?Pansophy
Use sysfs (obsoleted) interface then. kernel.org/doc/html/latest/driver-api/gpio/legacy.htmlOfilia
@Ofilia thank you for the link to the Linux legacy library gpiolib. I have some reading and experimenting to do. I'll let you know how it goes with a comment here.Pansophy
P
2

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")
    }
}
Pansophy answered 2/9, 2020 at 4:27 Comment(3)
Cool! One remark about shell boot script. You may use array directly as you do in for-loop (btw, there is seq command to make it easier, but here it's not needed): for pin in 938 915 1017 .... And better to have the list of the pins sorted, it's easier to maintain.Ofilia
@Ofilia thank you for the suggestion about the sorted order for the pin numbers in the shell script. I pulled this from the Coursera video and just copied what they showed on screen. I think they may have used physical pin order. In the video they mention other attributes for the sysfs special files so this morning I went looking and found a document on kernel.org that provides some additional documentation so I've made an edit to include that in this answer.Pansophy
Sorted or not (the latter may be useful from electrical perspective, to not mess up with connected hardware) the additional array is not needed.Ofilia

© 2022 - 2024 — McMap. All rights reserved.