• Android通过JNI操作GPIO


    平台

      Android 11 + RK3566

    概述

    在应用层通过JNI操作主板上的GPIO.
    一些小提示:

    • 注意文件操作权限, 若在adb shell下可以成功操作, APP下操作失败, 注意排查下权限问题:
      相关的几个文件 exportunexport, 默认情况下, 权限并没有给这么宽, 参考下面的修改, 以保证APP能有正确认的操作权限.
    rk3566:/sys/class/gpio # ll /sys/class/gpio
    -rw-rw-rw-  1 root root 4096 2022-07-20 10:36 export
    -rw----rw-  1 root root 4096 2022-07-20 10:19 unexport
    
    • 1
    • 2
    • 3

    第二部分, 在成功export后, 对应的节点下的权限, 下面对应的是gpio93代码中需要用到的两个文件节点:

    ll /sys/class/gpio/gpio93/                                                              
    total 0
    -rw-rw-rw- 1 root root 4096 2022-07-20 10:36 direction
    -rw-rw-rw- 1 root root 4096 2022-07-20 10:36 value
    
    • 1
    • 2
    • 3
    • 4

    代码

    目录结构:

    src
    └── main
        ├── AndroidManifest.xml
        ├── cpp
        │   ├── CMakeLists.txt
        │   └── jniapi.cpp
        ├── java
        │   └── com
        │       └── android
        │           └── apitester
        │               ├── Gpio.java
        │               └── utils
        │                   └── JniApi.java
        ├── res
        │   ├── layout
        │   │   ├── activity_gpio.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    GPIO.java

    package com.android.apitester;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.Switch;
    import android.widget.Toast;
    
    import com.android.apitester.utils.JniApi;
    
    public class Gpio extends Activity implements View.OnClickListener {
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_gpio);
    		findViewById(R.id.btGpio).setOnClickListener(this);
    	}
    
    	@Override
    	public void onClick(View view) {
    		JniApi api = new JniApi();
    		int gpio = Integer.valueOf(((EditText)findViewById(R.id.etGpio)).getText().toString());
    		int val = ((Switch)findViewById(R.id.swValue)).isChecked() ? 1 : 0;
    		int res = api.setGpio(gpio, val);
    		String result = "SUCCESS";
    		if(res == -1){
    			result = "OPEN EXPORT failed";
    		}else if(res == -2){
    			result = "open DIRECTION failed";
    		}else if(res == -3){
    			result = "open VALUE failed";
    		}else if(res == -4){
    			result = "write VALUE failed";
    		}else if(res == -5){
    			result = "open UNEXPORT failed";
    		}
    
    		Toast.makeText(this, result, Toast.LENGTH_LONG).show();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    activity_gpio.xml

    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <EditText android:id="@+id/etGpio"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:gravity="center_horizontal"
            android:singleLine="true"
            android:maxLines="1"/>
        <Switch android:id="@+id/swValue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="GPIO High/Low:"/>
    
        <Button android:id="@+id/btGpio"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="OK"/>
    LinearLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    JniApi.java

    package com.android.apitester.utils;
    
    public class JniApi {
    	static {
    		System.loadLibrary("apitester");
    	}
    	public native int setGpio(int gpio, int value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    jniapi.cpp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    //#include 
    #include 
    #include 
    #include 
    
    #define LOG_TAG "ApiTester"
    #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,  LOG_TAG, __VA_ARGS__))
    #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN,  LOG_TAG, __VA_ARGS__))
    #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
    #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
    
    //
    // Created by anson on 2022/7/20.
    //
    
    
    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_android_apitester_utils_JniApi_setGpio(JNIEnv *env, jobject thiz, jint gpio, jint value) {
        char direction_file[64];
        char value_file[64];
        const char* export_file = "/sys/class/gpio/export";
        const char* unexport_file = "/sys/class/gpio/unexport";
        LOGD("setGpio gpio:%d to value:%d", gpio, value);
    
        //export gpio.
        FILE *export_fp=fopen(export_file,"w");
        if(!export_fp){
            LOGE("open file %s failed", export_file);
            return -1;
        }
        fprintf(export_fp,"%d", gpio);
        fclose(export_fp);
    
        //set direction output
        sprintf(direction_file,"/sys/class/gpio/gpio%d/direction", gpio);
        FILE *direction_fd=fopen(direction_file,"w");
        if(!direction_fd){
            LOGE("open file %s failed", direction_file);
            return -2;
        }
        fprintf(direction_fd,"out");
        fclose(direction_fd);
    
        //write value
        sprintf(value_file,"/sys/class/gpio/gpio%d/value", gpio);
        int value_fd = open(value_file, O_RDWR);
        if(value_fd <= 0){
            LOGE("open file %s failed", value_file);
            return -3;
        }
    
        ssize_t size = write(value_fd, value == 0 ? "0" : "1", 1);
        if(size <= 0){
            LOGE("write gpio %s failed", value_file);
            close(value_fd);
            return -4;
        }
    
        //unexport gpio
        FILE *unexport_fd = fopen(unexport_file,"w");
        if(!unexport_fd){
            LOGE("open file %s failed", unexport_file);
            return -5;
        }
        fprintf(unexport_fd, "%d", gpio);
        fclose(unexport_fd);
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    CMakeLists.txt

    
    # For more information about using CMake with Android Studio, read the
    # documentation: https://d.android.com/studio/projects/add-native-code.html
    
    # Sets the minimum version of CMake required to build the native library.
    
    cmake_minimum_required(VERSION 3.10.2)
    
    # Declares and names the project.
    
    project("apitester")
    
    # file(GLOB native_srcs "src/main/cpp/*.cpp" "src/main/cpp/dalvik/*.cpp" "src/main/cpp/art/*.cpp" "src/main/cpp/art/*.S")
    
    # Creates and names a library, sets it as either STATIC
    # or SHARED, and provides the relative paths to its source code.
    # You can define multiple libraries, and CMake builds them for you.
    # Gradle automatically packages shared libraries with your APK.
    
    add_library( # Sets the name of the library.
                 apitester
    
                 # Sets the library as a shared library.
                 SHARED
    
                 # Provides a relative path to your source file(s).
            jniapi.cpp )
    
    # Searches for a specified prebuilt library and stores the path as a
    # variable. Because CMake includes system libraries in the search path by
    # default, you only need to specify the name of the public NDK library
    # you want to add. CMake verifies that the library exists before
    # completing its build.
    
    find_library( # Sets the name of the path variable.
                  log-lib
    
                  # Specifies the name of the NDK library that
                  # you want CMake to locate.
                  log )
    
    # Specifies libraries CMake should link to your target library. You
    # can link multiple libraries, such as libraries you define in this
    # build script, prebuilt third-party libraries, or system libraries.
    
    target_link_libraries( # Specifies the target library.
                           apitester
    
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    扩展

    关于Studio 中创建JNI

    创建JNI比较简单: 模块名, 右键 > Add C++ to Module
    在这里插入图片描述

    伴随而来的一些问题
    1. CMake中版本兼容问题, 更改为已安装的版本 3.10.2, 修改build.gradle和 CMakeLists.txt
    在这里插入图片描述
    2. 创建后, 在Android视图下, 只有一个CPP文件夹
    在这里插入图片描述
    其它的问题现象还有:
    打开CPP文件:android studio this file does not belong to any project target等.
    不管如何sync或增加/修改文件, 甚至重启Studio也不能解决, 始终看不到CPP目录下的文件.

    最后解决:
    找到项目目录下的setttings.gradle

    注释当前的模块 -> sync now
    去掉模块注释 -> sync now
    
    • 1
    • 2

    3. 新增加JNI函数: 选中函数 ALT + ENTER
    在这里插入图片描述
    之后会有一个文件选择列表, 选中想要添加的位置即可自动生成代码, 相当方便

  • 相关阅读:
    [Pandas] 按轴标签.loc VS 按数字索引.iloc
    【ROS】初识ROS
    android_logcat_4.4
    数据科学与机器学习案例之WiFi定位系统的位置预测
    逆向进阶,利用 AST 技术还原 JavaScript 混淆代码
    Godot快速精通-从看懂英文文档开始-翻译插件
    从0备战蓝桥杯:找出只出现一次的数字,数单身狗
    Kotlin 协程 - 生命周期 Job
    Go 复合数据类型之结构体与自定义类型
    轻量级网络整理及其在Yolov5上的实现
  • 原文地址:https://blog.csdn.net/ansondroider/article/details/125887503