Android NDK開發(fā)中jni配置及調(diào)用GPIO

一直以來做項(xiàng)目都是直接加載生成好的so文件,然后調(diào)用相關(guān)的封裝方法來實(shí)現(xiàn)外接硬件的調(diào)用。平常我們使用android通過串口與單片機(jī)進(jìn)行通訊,只需要對(duì)接RS232的串口通訊協(xié)議就可以了,因?yàn)槲覀儾恍枰苯域?qū)動(dòng)底層的硬件。但是最近是需求通過Android 開發(fā)板上的I2C總線直接加裝一塊定制的開發(fā)板去驅(qū)動(dòng)一些底層硬件,例如直接控制LED燈、驅(qū)動(dòng)馬達(dá)等動(dòng)作。這時(shí)候就需要使用到GPIO。

1.首先我們創(chuàng)建一個(gè)項(xiàng)目,新建一個(gè)GPIOControl.java文件


img1.png

2.寫入自己需要的調(diào)用方法

public class GPIOControl {
    static
    {
        System.loadLibrary("GPIOControl");
    }


    // JNI
    public static native int nativeReadGpio(String path);
    public static native int nativeWriteGpio(String path, String value);
}

3.點(diǎn)擊build重構(gòu)下項(xiàng)目


img2.png

4.使用Terminal命令窗口創(chuàng)建.h頭文件


img3.png
cd app/build/intermediates/classes/debug  //跳到指定的文件夾下

javah -jni com.zhawenqing.gpiodemo.GPIOControl    //編譯c的頭文件,jni后面是文件路徑

5.在app/build/intermediates/classes/debug目錄下找到生成的.h文件復(fù)制,右鍵app新建一個(gè)jni文件夾放置.h文件。并且需要把jni文件夾移動(dòng)到app目錄下否則會(huì)報(bào)錯(cuò),具體報(bào)錯(cuò)請(qǐng)往下看


img4.png
img5.png
img6.png
img7.png

6.在jni文件夾新建GPIOControl.c文件并實(shí)現(xiàn).h文件里面的方法


img8.png

具體實(shí)現(xiàn)方法的相關(guān)代碼,注意里面方法的方法名要與生成的.h里面的方法名必須保持一致
Java_com_zhawenqing_retrofit_gpio_GPIOControl_nativeReadGpio。需要將GPIOControl中封裝方法添加完整。

下面是GPIOControl.java文件

package com.zhawenqing.gpiodemo;

/**
 * Created by Administrator on 2018/7/27.
 */

public class GPIOControl {
    // JNI
    public static native int nativeReadGpio(String path);
    public static native int nativeWriteGpio(String path, String value);

    private static final String  mPathstr      = "/sys/class/gpio/gpio";
    private static final String  mDirectDir     = "/direction";
    private static final String  mValueDir     = "/value";

    public static int writeGpioValue(int num, int value)    //設(shè)置gpio輸出電平,value=0為低電平,value=1為高電平,num為gpio口序列號(hào)
    {
        String dataPath = composePinPath(num).concat(mValueDir);

        return nativeWriteGpio(dataPath, Integer.toString(value));
    }

    public static int readGpioValue(int num)
    {
        String dataPath = composePinPath(num).concat(mValueDir);

        return nativeReadGpio(dataPath);
    }

    public static int writeGpioDirection(int num, int value)    //設(shè)置gpio口輸入輸出狀態(tài),value=0為輸入,value=1為輸出,num為gpio口序列號(hào)
    {
        String dataPath = composePinPath(num).concat(mDirectDir);
        String direct;
        if(value == 0)
        {
            direct = "in";
        }
        else if(value == 1)
        {
            direct = "out";
        }
        else
        {
            return -1;
        }
        return nativeWriteGpio(dataPath, direct);
    }

    public static int readGpioDirection(int num)
    {
        String dataPath = composePinPath(num).concat(mDirectDir);

        return nativeReadGpio(dataPath);
    }

    private static String composePinPath(int num)
    {
        String  numstr;
        numstr = Integer.toString(num);
        return mPathstr.concat(numstr);
    }

    static
    {
        System.loadLibrary("GPIOControl");
    }
}

下面是GPIOControl.c文件

//
// Created by Administrator on 2018/7/27.
//
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>

#include "com_zhawenqing_retrofit_gpio_GPIOControl.h"

#include "android/log.h"
static const char *TAG="GpioDemo";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

JNIEXPORT jint JNICALL Java_com_zhawenqing_retrofit_gpio_GPIOControl_nativeReadGpio(JNIEnv *env, jclass thiz, jstring path)
{
    if(path == NULL)
    {
        return -1;
    }
    const char *chars = (*env)->GetStringUTFChars(env, path, NULL);
    int ret = readData(chars);
    (*env)->ReleaseStringUTFChars(env, path, chars);
    return ret;
}

JNIEXPORT jint JNICALL Java_com_zhawenqing_retrofit_gpio_GPIOControl_nativeWriteGpio(JNIEnv *env, jclass thiz, jstring path, jstring value)
{
    if(path == NULL)
    {
        return -1;
    }
    const char *chars = (*env)->GetStringUTFChars(env, path, NULL);
    const char *valueStr = (*env)->GetStringUTFChars(env, value, NULL);
    int ret = writeData(valueStr, strlen(valueStr), chars);
    (*env)->ReleaseStringUTFChars(env, path, chars);
    (*env)->ReleaseStringUTFChars(env, value, valueStr);
    return ret;
}

int readData(const char * filePath)
{
    int fd;
    int value;
    fd = open(filePath, O_RDWR);
    if(fd < 0){
        return -1;
    }
    char valueStr[32];
    memset(valueStr, 0, sizeof(valueStr));
    read(fd, (void *)valueStr, sizeof(valueStr) - 1);
    char *end;
    if(strncmp(valueStr,"in",2) == 0)
    {
        value = 0;
    }
    else if(strncmp(valueStr,"out",3) == 0)
    {
        value = 1;
    }
    else
    {
        value = strtol(valueStr, &end, 0);
        if(end == valueStr){
            close(fd);
            return -1;
        }
    }

    close(fd);
    return value;
}

int writeData(const char * data,int count,const char * filePath)
{
    int fd;
    fd = open(filePath, O_RDWR);
    if(fd < 0)
    {
        return -1;
    }
    int ret = write(fd, data, count);
    close(fd);
    return 0;
}

7.配置并打開ndk,在項(xiàng)目的gradle.properties文件中添加android.useDeprecatedNdk=true


img9.png

8.打開項(xiàng)目中l(wèi)ocal.properties文件,確認(rèn)是否配置ndk


img10.png

9.在app的build.gradle文件中添加ndk配置,這個(gè)需要具體根據(jù)開發(fā)用的Android板CPU框架,moudleName這個(gè)自定義的,保持和新建的方法封裝庫名稱一致

ndk {
            moduleName "GPIOControl"
            //選擇要添加的對(duì)應(yīng)cpu類型的.so庫。
            abiFilters 'armeabi'
        }
img11.png
img12.png

10.配置ndk-build
點(diǎn)擊file->setting->tools->external tools 點(diǎn)擊+新建一個(gè)tool


img13.png

具體配置如下:

Program:C:\Users\Administrator\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

Working directory: E:\svn\FaceSystem\faceDoor\app\

點(diǎn)擊OK,完成ndk-build配置

11.在jni文件夾創(chuàng)建.mk文件,讓ndk-build動(dòng)態(tài)生成so庫


img14.png

12..mk文件語法

1> LOCAL_PATH := $(call my-dir)
每個(gè)Android.mk文件必須以定義LOCAL_PATH為開始,它用于在開發(fā)tree中查找源文件,宏my-dir 則由Build System提供,返回包含Android.mk的目錄路徑。

2> include $(CLEAR_VARS)
CLEAR_VARS 變量由Build System提供。并指向一個(gè)指定的GNU Makefile,由它負(fù)責(zé)清理很多LOCAL_xxx.

3> LOCAL_MODULE模塊必須定義,以表示Android.mk中的每一個(gè)模塊。名字必須唯一且不包含空格

4> LOCAL_SRC_FILES變量必須包含將要打包如模塊的C/C++ 源碼,不必列出頭文件,build System 會(huì)自動(dòng)幫我們找出依賴文件。

application.mk文件代碼:

APP_ABI := armeabi armeabi-v7a x86

Android.mk文件代碼:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
TARGET_PLATFORM := android-23
LOCAL_MODULE := GPIOControl
LOCAL_SRC_FILES := GPIOControl.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

13.mk文件創(chuàng)建好后,我們重新ndk-build一下,我們將會(huì)成功編譯好so庫,將會(huì)自動(dòng)生成一個(gè)obj文件夾

14.還需要手動(dòng)創(chuàng)建一個(gè)jniLibs文件夾,將我們手動(dòng)編譯完成的so庫復(fù)制,如果讀取不到j(luò)niLibs文件夾里面的資源,需要

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

對(duì)于以上的問題,如果出現(xiàn)以下錯(cuò)誤

Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.  Please switch to a supported build system.
  Consider using CMake or ndk-build integration. For more information, go to:
   https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
   To get started, you can use the sample ndk-build script the Android
   plugin generated for you at:
   E:\SwimmingSpace\ndktest\build\intermediates\ndk\debug\Android.mk
  Alternatively, you can use the experimental plugin:
   https://developer.android.com/r/tools/experimental-plugin.html
  To continue using the deprecated NDK compile for another 60 days, set 
  android.deprecatedNdkCompileLease=1515138691572 in gradle.properties

這種錯(cuò)誤是由于啟動(dòng)ndk時(shí)出錯(cuò),在gradle.properties文件里把a(bǔ)ndroid.useDeprecatedNdk去掉換成android.deprecatedNdkCompileLease=1515138691572就行了。具體后面的值需要根據(jù)錯(cuò)誤提示中的提示值填入就可以了。

調(diào)用GPIO

GpioControl.writeGpioDirection(192, 1); //設(shè)置PG0為輸出
int nDirect = GpioControl.readGpioDirection(192); //讀取PG0的輸入輸出狀態(tài)
GpioControl.writeGpioValue(192, 1); //PG0為輸出時(shí),設(shè)置為高電平
int nValue = GpioControl.readGpioValue(192); //讀取PG0的輸出電平

具體PG口對(duì)應(yīng)的序列號(hào)需要參考廠家的規(guī)格書上的定義。我這邊是PG0對(duì)應(yīng)192,以此類推。

最后如果出現(xiàn)奇特的編譯器錯(cuò)誤,那么就嘗試從頭開始,每完成一個(gè)步驟都盡量rebuild一下項(xiàng)目。NDK開發(fā)中jni的配置是一門玄學(xué),需要靜下心來慢慢探索屬于自己的方法。我也是折騰了一天才能總結(jié)出以上步驟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容