Android通过JNI操作GPIO

本文详细介绍了在Android 11+ RK3566平台上,如何通过JNI在应用层操作GPIO。首先强调了文件权限的重要性,特别是在adb shell和APP中操作的差异。接着,展示了代码实现,包括C++的jniapi.cpp和Java的JniApi.java,以及AndroidManifest.xml和布局文件。在遇到问题时,提到了 Studio 中创建JNI的步骤和可能遇到的文件归属问题的解决方案。最后,给出了CMakeLists.txt的配置示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

平台

  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

第二部分, 在成功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
在这里插入图片描述
之后会有一个文件选择列表, 选中想要添加的位置即可自动生成代码, 相当方便

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值