1

我正在使用使用 Android 5.1 和 Kotlin 的 DragonBoard 410C 来试验 40 针低功耗连接器上的 GPIO 针脚。我正在使用的库使用 sysfs 接口与 GPIO 引脚进行交互,这需要在/sys/class/gpio/目录树中打开各种伪文件并读取和写入这些文件的值,请参阅在运行 Android 的 DragonBoard 410C 上访问 GPIO 低功耗连接器

我的理解是,我可以将 GPIO 引脚设置为输入和边沿触发,这将允许我连接一个带有瞬时接触开关的简单电路,并能够检测何时按下开关。

但是,我找到的文档表明,我需要在文件描述符上使用poll(2)系统服务或select(2)/value系统服务,以检测我正在使用的 GPIO 引脚的伪文件何时使用 Edge被检测到,例如/sys/class/gpio/gpio910/value

如何在 Kotlin 中使用带有文件描述符的系统服务poll(2)或系统服务?方法select(2)poll(2)一样的吗?ready()FileReader

也许需要类似于 JavaWatchService功能的东西?http://www.java2s.com/Tutorials/Java/java.nio.file/WatchService/0060__WatchService.poll_.htm

除非这是错误的方法,否则我的计划是拥有一个实用功能,例如:

// pollPseudoFile() polls the specified GPIO pin using the sysfs interface in order
// to wait for a pin value due to an external event such as a momentary switch press
//
// Returns:
//   - 0  -> an error of some kind
//   - 1  -> time out expired with no value
//   - 2  -> value change detected
public fun pollPseudoFile (pinPathFull : String, timeOut : Int) : Int {
    println("    pollPseudoFile - String")
    var iStatus : Int = 0     // status return indicating if poll() completed or not.
    try {
        val br = FileReader(pinPathFull)
        br.poll(timeOut)     // wait for data to be available or time out. how to do poll?
        iStatus = 2          // indicate value change unless the poll() timed out
        if (br.pollExpired) iStatus = 1     // poll timed out. how to check for poll status?
        br.close()
    } catch (e: Exception) {
        println("Error: " + e.message)
    }

    return iStatus;
}

public fun pollGetValue (pinPathFull : String) : Int {
    println("    pollGetValue - String")
    var line = ""
    try {
        val br = BufferedReader(FileReader(pinPathFull))
        line = br.readLine()
        br.close()
    } catch (e: Exception) {
        println("Error: " + e.message)
    }

    return line.toInt()
}

https://www.kernel.org/doc/Documentation/gpio/sysfs.txt

"value" ... 读取为 0(低)或 1(高)。如果 GPIO 配置为输出,则可以写入该值;任何非零值都被视为高。

如果引脚可以配置为产生中断的中断并且它已被配置为产生中断(参见“边缘”的描述),您可以对该文件进行 poll(2) 并且 poll(2) 将在中断发生时返回触发。如果使用 poll(2),请设置事件 POLLPRI 和 POLLERR。如果使用 select(2),请在 exceptfds 中设置文件描述符。在 poll(2) 返回后,lseek(2) 到 sysfs 文件的开头并读取新值或关闭文件并重新打开以读取值。

“edge”……读作“none”、“rising”、“falling”或“both”。编写这些字符串以选择将使 poll(2) 对“值”文件返回的信号边沿。

只有当引脚可以配置为产生中断的输入引脚时,该文件才存在。

补充说明

注 1:使用该adb实用程序,我能够shell进入我的 DragonBoard 410C 并测试配置物理引脚 26,GPIO971,direction设置为inedge设置为rising。在与物理引脚 23、GPIO938 相连的面包板上使用一个简单的 LED 电路,并将物理引脚 26 的导线添加到由物理引脚 23 管理的 LED,我能够打开 LED,echo 1 > gpio938/value然后cat gpio971/value看到物理引脚 26 的值变高,读数为1. 然后我关闭了连接到物理引脚 23 的 LED echo 0 > gpio938/value,然后cat gpio971/value返回了0预期的值。

然而,这个实验并没有告诉我当 LED 打开和关闭时是否poll(2)会指示变化。gpio971/value

注 1a:我有一个 Native C++ JNI 函数的第一个版本来实现poll(2)服务调用,并且一直在用我的 DragonBoard 410C 对其进行测试。我看到的是该poll(2)函数立即返回POLLINPOLLERR设置在数组的revents成员中。struct pollfd

测试使用连接到面包板行的物理引脚 26,LED 的一条腿连接到物理引脚 23,我可以打开和关闭。当我尝试在 10000 毫秒超时的情况下打开轮询时,调用会立即返回 LED 是否亮起(引脚 26 值为 1)或未亮起(引脚 26 值为 0),同时设置了两个指示灯。

我的期望是,由于我已edge设置为rising,我应该只在 LED 未亮起时才能看到poll(2)返回,然后我将其打开或 10 秒过去了。

我正在继续我的调查,因为它让我感到震惊,我如何使用我在应用程序的 Kotlin 端编写的本机 C++ 函数可能存在问题。

注意 2:我尝试WatchService与我的 Kotlin 应用程序一起使用并遇到了一个错误,WatchService需要 API 级别 26,而我在 Android Studio 中的最低目标是 API 级别 22。看起来WatchService需要 Android 8.0 (Oreo) 而 DragonBoard 是 Android 5.1 (棒棒糖)所以WatchService我无法使用监视文件状态。

4

1 回答 1

1

我正在采用的方法是创建一个原生 C++ JNI 函数来提供一种实现poll(2) Linux 服务调用的方法。

我在开发和测试过程中遇到的一个有趣问题是poll()立即返回,而不是等待超时或 GPIO 输入引脚的电压。在 DragonBoard 410C 的 96Boards.org 论坛上发帖后,How to use poll() with sysfs interface to input GPIO pin to handle a switch press event之后,有人提出了一个可行的解决方案,在开始投票之前读取伪文件(2)。

为了使用这个功能,我需要某种 Kotlin 协程或侧线程,这样当主 UI 正在处理开始轮询 GPIO 输入引脚的按钮单击时,主 UI 线程不会被阻塞,直到函数返回 GPIO 事件或超时。

我还不能辨别如何做这样的协程,所以这仍然是一项正在进行的工作。经过一番思考,似乎某种事件侦听器架构将是最合适的方法。

然而,测试表明该功能pollPseudoFile()可以正常工作,方法是超时或返回一个值,该值/value是使用从 1.8v 电源(引脚 38)到设置为 GPIO 输入引脚的导线手动施加电压时的值a risingor伪文件falling中的设置。/edge

Native C++ JNI 函数的源代码如下。我将它与以下 Kotlin 源代码一起使用。

首先,在我的MainActivity.kt源文件中,我使用以下源提供了 Native C++ 库:

// See the StackOverFlow question with answer at URL:
//    https://stackoverflow.com/questions/36932662/android-how-to-call-ndk-function-from-kotlin
init {
    System.loadLibrary("pollfileservice")
}

external fun pollFileWithTimeOut(pathPseudo : String, timeOutMs : Int): Int
external fun pollGetLastRevents() : Int

接下来我在 Kotlin 源文件中使用这个函数Gpio.kt来实际对伪文件执行poll()服务调用。

class Gpio(pin: Int)  {
    private val pin : Int
    private val pinGpio : GpioFile = GpioFile()

    /*
     *  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"
    }


    //    ....... other source code in the Kotlin class Gpio


    fun pinPoll (timeMs: Int) : Int {
        val iStatus : Int = pinGpio.pollPseudoFile (MakeFileName(pin,  "/value"), timeMs)
        return iStatus
    }

上面的 Gpio 类在实际的 UI 按钮点击监听器中使用如下:

            val gpioProcessor = GpioProcessor()
            // Get reference of GPIO23.
            val gpioPin26 = gpioProcessor.pin26

            // Set GPIO26 as input.
            gpioPin26.pinIn()
            gpioPin26.pinEdgeRising()

            var xStatus: Int = gpioPin26.pinPoll(10000)
            val xvalue = gpioPin26.value

轮询文件服务.h

//
// Created by rchamber on 9/24/2020.
//

#ifndef MY_APPLICATION_POLLFILESERVICE_H
#define MY_APPLICATION_POLLFILESERVICE_H


class PollFileService {
private:
    int iValue;
    int fd;         /* file descriptor */

public:
    // See poll(2) man page at https://linux.die.net/man/2/poll
    static const int PollSuccess = 0;
    static const int PollTimeOut = 1;
    static const int PollErrorEFAULT = -1;
    static const int PollErrorEINTR  = -2;
    static const int PollErrorEINVAL = -3;
    static const int PollErrorENOMEM = -4;
    static const int PollErrorPOLLERR = -5;
    static const int PollErrorPOLLNVAL = -6;
    static const int PollErrorPOLLERRNVAL = -7;
    static const int PollErrorPOLLHUP = -8;
    static const int PollErrorPOLLERRDEFLT = -9;

    static const int PollErrorUNKNOWN = -100;

    static int iPollStatus;
    static int iPollRet;
    static int iPollRevents;

    PollFileService(const char *pathName = nullptr, int timeMilliSec = -1);
    ~PollFileService();

    int PollFileCheck (const char *pathName, int timeMilliSec = -1);
    int PollFileRead (const char *pathName = nullptr);
};

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollFileWithTimeOut (JNIEnv* pEnv, jobject pThis, jstring pKey, jint timeMS);

#endif //MY_APPLICATION_POLLFILESERVICE_H

轮询文件服务.cpp

//
// Created by rchamber on 9/24/2020.
//

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <errno.h>
#include <poll.h>

#include <jni.h>

#include "PollFileService.h"

int PollFileService::iPollStatus = 0;
int PollFileService::iPollRet = 0;
int PollFileService::iPollRevents = 0;

PollFileService::PollFileService(const char *pathName /* = nullptr */, int timeMilliSec /* = -1 */) : iValue(23), fd(-1)
{
    iPollStatus = 0;
    if (pathName) {
        fd = open (pathName, O_RDONLY);
    }
}

PollFileService::~PollFileService()
{
    if (fd >= 0) {
        close (fd);
        fd = -1;
    }
}

int PollFileService::PollFileCheck(const char *pathName, int timeMilliSec /* = -1 */)
{
    struct pollfd fdList[] = {
            {fd, POLLPRI | POLLERR, 0},
            {0}
        };
    nfds_t nfds = 1;
    unsigned char tempbuff[256] = {0};

    if (fd < 0 && pathName) {
        fd = open (pathName, O_RDONLY);
        fdList[0].fd = fd;
    }

    // with a edge triggered GPIO that we are going to use the poll(2)
    // function to wait on an event, we need to read from the
    // pin before we do the poll(2). If the read is not done then
    // the poll(2) returns with both POLLPRI and POLLERR set in the
    // revents member. however if we read first then do the poll2()
    // the poll(2) will wait for the event, input voltage change with
    // either a rising edge or a falling edge, depending on the setting
    // in the /edge pseudo file.
    ssize_t iCount = read (fdList[0].fd, tempbuff, 255);

    iPollStatus = PollErrorUNKNOWN;
    int iRet = poll(fdList, nfds, timeMilliSec);

    if (iRet == 0) {
        iPollStatus = PollTimeOut;
    } else if (iRet < 0) {
        switch (errno) {
            case EFAULT:
                iPollStatus = PollErrorEFAULT;
                break;
            case EINTR:
                iPollStatus = PollErrorEINTR;
                break;
            case EINVAL:
                iPollStatus = PollErrorEINVAL;
                break;
            case ENOMEM:
                iPollStatus = PollErrorENOMEM;
                break;
            default:
                iPollStatus = PollErrorUNKNOWN;
                break;
        }
    } else if (iRet > 0) {
        // successful call now determine what we should return.
        iPollRevents = fdList[0].revents; /* & (POLLIN | POLLPRI | POLLERR); */
        switch (fdList[0].revents & (POLLIN | POLLPRI | POLLERR /* | POLLNVAL | POLLHUP*/)) {
            case (POLLIN):                // value of 1, There is data to read.
            case (POLLPRI):               // value of 2, There is urgent data to read
            case (POLLOUT):               // , Writing now will not block.
            case (POLLIN | POLLPRI):      // value of 3
                iPollStatus = PollSuccess;
                break;

            // testing with a DragonBoard 410C indicates that we may
            // see the POLLERR indicator set in revents along with
            // the POLLIN and/or POLLPRI indicator set indicating there
            // is data to be read.
            // see as well poll(2) man page which states:
            //    POLLERR  Error condition (output only).
            case (POLLIN | POLLERR):                 // value of 9
            case (POLLPRI | POLLERR):                // value of 10
            case (POLLIN | POLLPRI | POLLERR):       // value of 11
                iPollStatus = PollSuccess;
                break;

            case (POLLHUP):               // , Hang up (output only).
                iPollStatus = PollErrorPOLLHUP;
                break;

            case (POLLERR):               // value of 8, Error condition (output only).
                iPollStatus = PollErrorPOLLERR;
                break;
            case (POLLNVAL):              // , Invalid request: fd not open (output only).
                iPollStatus = PollErrorPOLLNVAL;
                break;
            case (POLLERR | POLLNVAL):
                iPollStatus = PollErrorPOLLERRNVAL;
                break;

            default:
                iPollStatus = PollErrorPOLLERRDEFLT;
                break;
        }
    }

    return iPollStatus;
}

int PollFileService::PollFileRead (const char *pathName /* = nullptr */)
{
    char  buffer[12] = {0};
    int iRet = -1;

    if (fd < 0 && pathName) {
        fd = open (pathName, O_RDONLY);
    }
    int nCount = read (fd, buffer, 10);
    if (nCount > 0) {
        iRet = atoi (buffer);
    }

    return iRet;
}

// Check the specified file using the poll(2) service and
// return a status as follows:
//  -    0  -> poll(2) success indicating something is available
//  -    1  -> poll(2) failed with time out before anything available
//  -   -1  -> poll(2) error - EFAULT
//  -   -2  -> poll(2) error - EINTR
//  -   -3  -> poll(2) error - EINVAL
//  -   -4  -> poll(2) error - ENOMEM
//  -   -5  -> poll(2) error - POLLERR
//  -   -6  -> poll(2) error - POLLNVAL
//  -   -7  -> poll(2) error - POLLERR | POLLNVAL
//  -   -8  -> poll(2) error - POLLHUP
//  -   -9  -> poll(2) error - poll(2) revent indicator Unknown
//  - -100 -> poll(2) error - Unknown error
//
static int lastRevents = 0;

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollFileWithTimeOut (JNIEnv* pEnv, jobject pThis, jstring pKey, jint timeMS)
{
    char *pathName;
    int timeMilliSec;
    PollFileService  myPoll;

    const char *str = pEnv->GetStringUTFChars(pKey, 0);
    int  timeMSint = 10000; // timeMS;

#if 1
    int iStatus = myPoll.PollFileCheck(str, timeMSint);
#else
    int iStatus = myPoll.PollFileRead(str);
#endif

    pEnv->ReleaseStringUTFChars(pKey, str);

    lastRevents = myPoll.iPollRevents;

    return iStatus;
}

#if 0
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastStatus (JNIEnv* pEnv, jobject pThis) {
    return PollFileService::iPollStatus;
}
#endif

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastRevents (JNIEnv* pEnv, jobject pThis)
{
    return lastRevents;
}
于 2020-10-02T19:06:29.260 回答