平台
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
之后会有一个文件选择列表, 选中想要添加的位置即可自动生成代码, 相当方便