平台
Android 11 + RK3566
概述
在应用层通过JNI操作主板上的GPIO.
一些小提示:
注意文件操作权限, 若在adb shell下可以成功操作, APP下操作失败, 注意排查下权限问题:
相关的几个文件 export和unexport, 默认情况下, 权限并没有给这么宽, 参考下面的修改, 以保证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
第二部分, 在成功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
代码
目录结构:
src └── main ├── AndroidManifest.xml ├── cpp │ ├── CMakeLists.txt │ └── jniapi.cpp ├── java │ └── com │ └── android │ └── apitester │ ├── Gpio.java │ └── utils │ └── JniApi.java ├── res │ ├── layout │ │ ├── activity_gpio.xml
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(); } }
activity_gpio.xml
<?xml version="1.0" encoding="utf-8"?> <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>
JniApi.java
package com.android.apitester.utils; public class JniApi { static { System.loadLibrary("apitester"); } public native int setGpio(int gpio, int value); }
jniapi.cpp
#include <stdio.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #include <assert.h> //#include <cutils/log.h> #include <string.h> #include <jni.h> #include <android/log.h> #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; }
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} )
扩展
关于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
3. 新增加JNI函数: 选中函数 ALT + ENTER
之后会有一个文件选择列表, 选中想要添加的位置即可自动生成代码, 相当方便