NDK & JNI方式读写Android系统的GPIO
大家都知道Android系统是一种基于Linux的自由及开放源码的操作系统,所以读写GPIO也可以直接用Linux那一套export/unexport方法,本文将介绍如何使用NDK jni方式来读写Android系统的GPIO。
1. 软硬件环境:
- Android Studio
- RK3288 Android开发板
2. Android Studio安装NDK
打开Android Studio 中的SDK Manager,菜单Tools -> Android -> SDK Manager
选中NDK,点击OK就可以安装NDK了,安装时间需要几分钟到十几分钟
3. 创建Android工程
(1) 创建Android工程jnigpio,然后添加一个新的java类GPIOControl.java
代码如下:
public class GPIOControl {
static {
System.loadLibrary("GPIOControl");
}
public final static native int exportGpio(int gpio);
public final static native int setGpioDirection(int gpio, int direction);
public final static native int readGpioStatus(int gpio);
public final static native int writeGpioStatus(int gpio, int value);
public final static native int unexportGpio(int gpio);
}
(2) Build -> Rebuild Project
(3) 生成jni的头文件
Android Studio Terminal中输入如下命令
cd app/src/main/java/
javah -d ../jni com.zhuang.jnigpio.GPIOControl
生成jni头文件如下图:
(4) jni目录下创建GPIOControl.c程序
代码如下:
//
// Created by Gavin Zhuang on 05/12/2017.
//
#include <jni.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <android/log.h>
#include "com_zhuang_jnigpio_GPIOControl.h"
#define TAG "jni_gpio"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
#define IN 0
#define OUT 1
#define LOW 0
#define HIGH 1
#define BUFFER_MAX 3
#define DIRECTION_MAX 48
/*
* Class: com_zhuang_jnigpio_GPIOControl
* Method: exportGpio
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_zhuang_jnigpio_GPIOControl_exportGpio(JNIEnv *env, jobject instance, jint gpio)
{
char buffer[BUFFER_MAX];
int len;
int fd;
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0) {
LOGE("Failed to open export for writing!\n");
return(0);
}
len = snprintf(buffer, BUFFER_MAX, "%d", gpio);
if (write(fd, buffer, len) < 0) {
LOGE("Fail to export gpio!\n");
return 0;
}
close(fd);
return 1;
}
/*
* Class: com_zhuang_jnigpio_GPIOControl
* Method: setGpioDirection
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zhuang_jnigpio_GPIOControl_setGpioDirection(JNIEnv *env, jobject instance, jint gpio, jint direction)
{
static const char dir_str[] = "in\0out";
char path[DIRECTION_MAX];
int fd;
snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", gpio);
fd = open(path, O_WRONLY);
if (fd < 0) {
LOGE("failed to open gpio direction for writing!\n");
return 0;
}
if (write(fd, &dir_str[direction == IN ? 0 : 3], direction == IN ? 2 : 3) < 0) {
LOGE("failed to set direction!\n");
return 0;
}
close(fd);
return 1;
}
/*
* Class: com_zhuang_jnigpio_GPIOControl
* Method: readGpioStatus
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_zhuang_jnigpio_GPIOControl_readGpioStatus(JNIEnv *env, jobject instance, jint gpio)
{
char path[DIRECTION_MAX];
char value_str[3];
int fd;
snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/value", gpio);
fd = open(path, O_RDONLY);
if (fd < 0) {
LOGE("failed to open gpio value for reading!\n");
return -1;
}
if (read(fd, value_str, 3) < 0) {
LOGE("failed to read value!\n");
return -1;
}
close(fd);
return (atoi(value_str));
}
/*
* Class: com_zhuang_jnigpio_GPIOControl
* Method: writeGpioStatus
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zhuang_jnigpio_GPIOControl_writeGpioStatus(JNIEnv *env, jobject instance, jint gpio, jint value)
{
static const char values_str[] = "01";
char path[DIRECTION_MAX];
int fd;
snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/value", gpio);
fd = open(path, O_WRONLY);
if (fd < 0) {
LOGE("failed to open gpio value for writing!\n");
return 0;
}
if (write(fd, &values_str[value == LOW ? 0 : 1], 1) < 0) {
LOGE("failed to write value!\n");
return 0;
}
close(fd);
return 1;
}
/*
* Class: com_zhuang_jnigpio_GPIOControl
* Method: unexportGpio
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_zhuang_jnigpio_GPIOControl_unexportGpio(JNIEnv *env, jobject instance, jint gpio)
{
char buffer[BUFFER_MAX];
int len;
int fd;
fd = open("/sys/class/gpio/unexport", O_WRONLY);
if (fd < 0) {
LOGE("Failed to open unexport for writing!\n");
return 0;
}
len = snprintf(buffer, BUFFER_MAX, "%d", gpio);
if (write(fd, buffer, len) < 0) {
LOGE("Fail to unexport gpio!");
return 0;
}
close(fd);
return 1;
}
(5) 修改项目下的gradle.properties修改app下的build.gradle
在项目下的gradle.properties添加:
android.useDeprecatedNdk=true
在defaultConfig中添加ndk内容:
ndk{
moduleName "GPIOControl" //so文件: lib+moduleName+.so
ldLibs "log", "z", "m" // 添加android日志引用
abiFilters "armeabi", "armeabi-v7a", "x86" //cpu的类型
}
(6) Rebuild Project 生成so文件
so文件在app>build>intermediates>ndk>debug>lib>目录下,如图:
(7) 方法调用
// 定义GPIO输入输出方式和高低电平
public final static int GPIO_DIRECTION_IN = 0;
public final static int GPIO_DIRECTION_OUT = 1;
public final static int GPIO_VALUE_LOW = 0;
public final static int GPIO_VALUE_HIGH = 1;
例如,输出高低电平变化信号(如:IO口接LED电路,闪烁发光)
调用流程跟直接linux下文件方式控制gpio类似,先export该管脚,然后设定输入输出方式,最后不用的时候要unexport该管脚。
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String gpioPinStr = mGPIOEdit.getEditableText().toString();
if (TextUtils.isEmpty(gpioPinStr)){
return;
}
mStartButton.setEnabled(false);
final int gpioPin = Integer.valueOf(gpioPinStr);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
mIsFlashing = true;
try {
GPIOControl.exportGpio(gpioPin);
GPIOControl.setGpioDirection(gpioPin, GPIOControl.GPIO_DIRECTION_OUT);
boolean flag = true;
while (mIsFlashing){
GPIOControl.writeGpioStatus(gpioPin, flag ? GPIOControl.GPIO_VALUE_HIGH : GPIOControl.GPIO_VALUE_LOW);
flag = !flag;
Thread.sleep(1000);
}
GPIOControl.unexportGpio(gpioPin);
} catch (Exception ex){
}
}
});
thread.start();
}
});
读取gpio电平状态,按键按下为高电平,松开为低电平,如下是读取该管脚的日志:
12-05 10:09:22.624: W/CallSettingFragment(2551): GpioRead value; 262; 1
12-05 10:09:22.724: W/CallSettingFragment(2551): GpioRead value; 262; 1
12-05 10:09:22.824: W/CallSettingFragment(2551): GpioRead value; 262; 1
12-05 10:09:22.925: W/CallSettingFragment(2551): GpioRead value; 262; 0
12-05 10:09:23.025: W/CallSettingFragment(2551): GpioRead value; 262; 0
12-05 10:09:23.126: W/CallSettingFragment(2551): GpioRead value; 262; 0
12-05 10:09:23.228: W/CallSettingFragment(2551): GpioRead value; 262; 0
注意事项:该控制GPIO需要su权限,可以试着用adb shell去修改 /sys/class/gpio/export 和 /sys/class/gpio/unexport 的读写权限。如:chmod 666 /sys/class/export