如何以JNI方式实现安卓APP控制GPIO?

发表时间:2024-06-04 16:11


本文档提供了在 Android 10 设备上通过应用程序(App)控制通用输入输出(GPIO)的详细指南。这涵盖了从创建 gpio驱动到App 配置 以及 SELinux 策略以允许特定访问的所有必要步骤。

1.驱动实现  

添加创建gpio控制驱动bsp\kernel\kernel4.14\drivers\gpio\gpio_led.c,并添加好对应的Makfile编译












































































































































































































































#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include    #include#include#include#include#include#include#include#include#include
#define GPIO_HIGH _IO('L', 0)#define GPIO_LOW _IO('L', 1)
#define LED_ON 1#define LED_OFF 0#define SIMPIE_LED_MAX 4
//============================== Upper interface value ==============================//// 驱动模块名称定义#define MODULE_NAME "gpio_led"          // 驱动模块的名字#define MISC_NAME "gpio_led_device"     // 用于注册为“misc”设备的名字
// 模块函数接口定义,供上层应用调用的接口。通过MM_DEV_MAGIC区分不同系统接口,通过_IO()加上自己的编号作为接口number。#define MM_DEV_MAGIC 'N'
// LED 控制命令#define RFID_IO1 _IO(MM_DEV_MAGIC, 93)#define RFID_IO2 _IO(MM_DEV_MAGIC, 130)#define RFID_IO3 _IO(MM_DEV_MAGIC, 121)#define RFID_LED _IO(MM_DEV_MAGIC, 138)
static int major;static struct class *cls;
// GPIO 描述数组struct gpio_desc *led_gpio[SIMPIE_LED_MAX];
// cat命令将调用该函数static ssize_t gpio_value_show(struct device *dev, struct device_attribute *attr, char *buf){    return sprintf(buf, "%d\n", gpiod_get_value(led_gpio[0]));}
// echo命令将调用该函数static ssize_t gpio_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len){    pr_err("[vanxoak]%c\n", buf[0]);    if ('0' == buf[0])    {        gpiod_direction_output(led_gpio[0], 0);        pr_err("[vanxoak]: _%s_ :gpio off\n", __func__);    }    else if ('1' == buf[0])    {        gpiod_direction_output(led_gpio[0], 1);        pr_err("[vanxoak]: _%s_ :gpio on\n", __func__);    }    else        pr_err("I only support 0 or 1 to ctrl gpio on or off\n");    pr_err("[vanxoak]gpio_value_store\n");    return len;}
// 定义一个名为gpio_led的设备属性static DEVICE_ATTR(gpio_led, 0664, gpio_value_show, gpio_value_store);
// 提供给上层控制的接口long gpio_led_ioctl(struct file *file, unsigned int cmd, unsigned long arg){    switch (cmd)    {    case RFID_LED:        gpiod_direction_output(led_gpio[0], arg);        break;    case RFID_IO1:        gpiod_direction_output(led_gpio[1], arg);        break;    case RFID_IO2:        gpiod_direction_output(led_gpio[2], arg);        break;    case RFID_IO3:        gpiod_direction_output(led_gpio[3], arg);        break;
    default:        pr_err("[vanxoak] %s default: break\n", __func__);        break;        }    return 0;}
struct file_operations gpio_led_ops = {    .owner = THIS_MODULE,    .unlocked_ioctl = gpio_led_ioctl,};
// LED灯初始化static int simpie_led_init(struct platform_device *pdev){    int ret = 0;    int i;
    // 申请gpio设备    led_gpio[0] = devm_gpiod_get(&pdev->dev, "led0", GPIOD_OUT_LOW);    led_gpio[1] = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);    led_gpio[2] = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);    led_gpio[3] = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);
    for (i = 0; i < SIMPIE_LED_MAX; i++)    {        if (IS_ERR(led_gpio[i]))        {            ret = PTR_ERR(led_gpio[i]);            return ret;        }        // 输出初始电平        ret = gpiod_direction_output(led_gpio[i], LED_OFF);    }
    device_create_file(&pdev->dev, &dev_attr_gpio_led);    return ret;}
// 驱动入口static int gpio_led_probe(struct platform_device *pdev){    int ret = 0;    pr_err("[vanxoak]gpio_led_probe start...\n");
    // LED灯gpio初始化及输出配置    ret = simpie_led_init(pdev);   
    pr_err("[vanxoak]gpio_led_probe end...\n");
    return 0;}
// 绑定设备static struct of_device_id gpio_led_match_table[] = {    {.compatible = "yz,gpio-led"},    {}};
static int gpio_led_remove(struct platform_device *pdev){    pr_err("[vanxoak]gpio_led_remove...\n");    return 0;}
static struct platform_driver gpio_led_driver = {    .driver = {        .name = MODULE_NAME,        .owner = THIS_MODULE,        .of_match_table = gpio_led_match_table,    },    .probe = gpio_led_probe,    .remove = gpio_led_remove,};
// gpio初始化入口static int gpio_led_init(void){    struct device *mydev;
    pr_err("[vanxoak]gpio_led_init start...\n");    platform_driver_register(&gpio_led_driver);
    major = register_chrdev(0, "gpiotest", &gpio_led_ops);
    // 创建gpio_led_class设备    cls = class_create(THIS_MODULE, "gpio_led_class");
    // 在gpio_led_class设备目录下创建一个gpio_led_device属性文件    mydev = device_create(cls, 0, MKDEV(major, 0), NULL, MISC_NAME);    if (sysfs_create_file(&(mydev->kobj), &dev_attr_gpio_led.attr))    {            return -1;    }
    return 0;}
static void gpio_led_exit(void){    pr_err("[vanxoak]gpio_led_exit...\n");    platform_driver_unregister(&gpio_led_driver);
    device_destroy(cls, MKDEV(major, 0));    class_destroy(cls);    unregister_chrdev(major, "gpiotest");}
module_init(gpio_led_init);module_exit(gpio_led_exit);
MODULE_DESCRIPTION("Device_create Driver");MODULE_LICENSE("GPL");
设备树配置 gpio_led: yz,gpio-led {                status = "disabled";                compatible = "yz,gpio-led";                led0-gpio = <&ap_gpio 138 GPIO_ACTIVE_HIGH>;                led1-gpio = <&ap_gpio 93 GPIO_ACTIVE_HIGH>;                led2-gpio = <&ap_gpio 130 GPIO_ACTIVE_HIGH>;                led3-gpio = <&ap_gpio 121 GPIO_ACTIVE_HIGH>;};

配置好上面gpio驱动后重新编译更新kernel 可以在/dev目录下找到对应的设备文件

"/dev/gpio_led_device",通过读写设备文件就可以操作gpio了。

1.2创建 Native 库  

1.2.1设置 JNI 方法  

在 App 中定义 JNI 方法以实现与 GPIO 设备的交互。   

















publicclassNativeClass {          


    static {                  try {                      System.loadLibrary("jni_gpiocontrol");                      Log.d("NativeClass", "Native library loaded successfully.");                  } catch (UnsatisfiedLinkError e) {                      Log.e("NativeClass", "Failed to load native library: " + e.getMessage());                      // throw new RuntimeException("Failed to load native library", e);                  }              }              // 声明本地方法              publicnativeintcontrolGPIO(int cmd, long arg);          }

1.2.2实现 Native 方法  

在app/src/main目录下创建一个cpp文件夹(如果你的项目是用Kotlin编写的,这个步骤仍然适用,因为JNI是用C/C++实现的)。将你的libjni_gpiocontrol.cpp文件放到这个cpp目录中。

注意事项:确保本地方法签名正确,Java方法签名和本地(C/C++)方法实现之间必须完全匹配。



































































#include#include#include#include#include#include#include#include
#define MM_DEV_MAGIC 'N'#define RFID_LED _IO(MM_DEV_MAGIC, 138)#define RFID_IO1 _IO(MM_DEV_MAGIC, 93)#define RFID_IO2 _IO(MM_DEV_MAGIC, 130)#define RFID_IO3 _IO(MM_DEV_MAGIC, 121)   
#define DEVICE_PATH "/dev/gpio_led_device"#define LOG_TAG "GPIOControl"

extern "C" JNIEXPORT jint JNICALLJava_com_example_gpio_NativeClass_controlGPIO(JNIEnv *env, jobject obj, jint cmd, jlong arg) {
    int device_fd;    long ioctl_result;    unsigned int ioctl_cmd = cmd;
    // Open the device file    device_fd = open(DEVICE_PATH, O_RDWR);    if (device_fd < 0) {        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Could not open device: %s", strerror(errno));        return -1;    }
    // Translate cmd to appropriate ioctl command based on input    switch (cmd) {        case 138:            ioctl_cmd = RFID_LED;            break;        case 93:            ioctl_cmd = RFID_IO1;            break;        case 130:            ioctl_cmd = RFID_IO2;            break;        case 121:            ioctl_cmd = RFID_IO3;            break;        default:            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Invalid command");            close(device_fd);            return -1;    }
    // Send an ioctl to the device    ioctl_result = ioctl(device_fd, ioctl_cmd, arg);    if (ioctl_result < 0) {        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to call ioctl: %s", strerror(errno));            close(device_fd);        return -1;    }
    // Close the device    close(device_fd);
    return 0;}

1.2.3编译 Native 库  

使用 CMake 或 ndk-build 工具编译你的 native 代码为共享库(.so 文件)。

添加app\src\main\cpp\CMakeLists.txt










cmake_minimum_required(VERSION 3.4.1)project("gpiotest")add_library(jni_gpiocontrol SHARED libjni_gpiocontrol.cpp)

find_library( log-lib log )
target_link_libraries(jni_gpiocontrol                       ${log-lib} )  

1.2.4调用 Native 方法

通过 JNI 接口在 App 中调用实现的 native 方法以控制 GPIO。
















public class MainActivity extends AppCompatActivity {    private NativeClass nativeClass;
    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);
        nativeClass = new NativeClass();
        // 示例:打开LED            int result = nativeClass.controlGPIO(138, 1);        // 根据result处理结果    }}

2. SELinux 配置  

由于直接访问硬件设备在 Android 中受到 SELinux 策略的限制,需要修改 SELinux 策略以允许 App 访问 GPIO 设备文件。

定义设备类型:为 GPIO 设备定义一个新的 SELinux 类型(如 gpio_led_device_t)。

在SDK_dir/device/sprd/sharkle/common/sepolicy/device.te 添加



# 定义新的设备类型type gpio_led_device_t, dev_type;

分配文件上下文:为 GPIO 设备文件分配新定义的 SELinux 类型。

SDK_dir/device/sprd/sharkle/common/sepolicy/file_contexts中添加


/dev/gpio_led_device u:object_r:gpio_led_device_t:s0

授予权限:在 SELinux 策略中添加规则,允许 App 访问 GPIO 设备。

SDK_dir/device/sprd/sharkle/common/sepolicy/system_app.te



# 允许 system_app 访问 gpio_led_deviceallow system_app gpio_led_device_t:chr_file { read write };

重新编译 SELinux 策略:对更改的 SELinux 策略进行编译,并将其部署到设备上。这一步骤的目的是将自定义的安全策略更改应用到Android构建系统的预设SELinux策略中,确保在编译系统镜像时,这些更改会被包含进去。



cp system/sepolicy/public/app.te system/sepolicy/prebuilts/api/29.0/public/app.tecp system/sepolicy/private/coredomain.te system/sepolicy/prebuilts/api/29.0/private/coredomain.te

3. 测试与部署  

测试 App:在具有所需硬件支持的 Android 10 设备上测试 App。确保 App 能成功加载 native 库,并能通过 JNI 调用控制 GPIO。

SELinux 策略测试:验证 SELinux 策略更改是否允许 App 无障碍地访问 GPIO 设备。

问题排查:如果遇到访问被拒绝的情况,请检查 SELinux 审计日志以确定是否需要进一步调整策略。

3.1注意事项  

安全性:在修改 SELinux 策略以增加访问权限时,务必小心谨慎,避免引入安全漏洞。

设备兼容性:确保你的实现考虑到了不同设备可能存在的硬件和配置差异。

文档和维护:适当记录你的设计和实现过程,包括 JNI 接口、native 代码和 SELinux 策略更改,以便于未来的审计和维护。

通过遵循以上步骤,你可以在遵守 Android 安全模型的同时,实现 App 对 GPIO 的有效控制。

             



文章列表
新年伊始,喜讯传来!武汉万象奥科凭借过去一年在RK系列嵌入式技术方案开发、市场推广等方面的努力和积累,从Rockchip众多合作伙伴中脱颖而出,荣获瑞芯微“2024年度优秀合作奖”!这一荣誉的获得,是对武汉万象奥科技术实力和服务水平的充分肯定,更是双方合作、共赢的最佳见证。过去一年,万象奥科与瑞芯微紧密合作,成功推出并量产了搭载瑞芯微RK3588、RK3576、RK3562、RK3506等芯...
`overlayroot` 是一种使用 OverlayFS 实现的功能,可将根文件系统挂载为只读,并通过一个临时的写层实现对文件系统的修改。这种方法非常适合嵌入式设备或需要保持系统文件完整性和安全性的场景。下文以 RK3568 平台为例,介绍制作 overlayroot 的详细步骤。1. 制作精简文件系统ramdisk1.1   环境准备1. 目标系统:确保系统支持 OverlayFS(内核...
RK3506是一款高性能三核Cortex-A7处理器,内部集成Cortex-M0核心, 适合于工业控制、工业通信、人机交互等应用场景。为方便开发、使用,万象奥科推出HD-RK3506G-MINI单板机,仅需99元即可入手。单板机接口包含:以太网、RS232/RS485、CAN总线、液晶屏,支持接口扩展!产品特点:  多核异构3xCortex-A7+Cortex-M0,低功耗、高实时  外设接...
RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,...
RK3506单板机(卡片电脑)是一款高性能三核Cortex-A7处理器,内部集成Cortex-M0核心,RK3506单板机具有接口丰富、实时性高、显示开发简单、低功耗及多系统支持等特点,非常适合于工业控制、工业通信、人机交互等应用场景。 多核异构3xCortex-A7+Cortex-M0 外设接口丰富,板载网络、串口、CAN总线 支持Buildroot、Yocto系统,支持AMP混合部署 支...
作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!
UGFC是Unified Gold Finger Core Board的缩写(意指:统一接口定义金手指核心板),为武汉万象奥科电子有限公司基于企业标准定义的一种针对嵌入式、低功耗、通用型的小型计算机模块标准,采用204Pin金手指连接器,基于ARM架构的MPU平台,主要面向泛工业领域的数据采集、边缘数据处理、接口通讯与人机交互等应用场景。
1. 概念SMARC(Smart Mobility ARChitecture,智能移动架构)是一种通用的小型计算机模块定义,基于ARM和X86技术的模块化计算机低功耗嵌入式架构平台,旨在满足低功耗、低成本和高性能的应用需求。这些模块通常使用与平板电脑和智能手机中相似的ARM SOC,或其他低功耗SOC和CPU。  图片(314 Pin金手指)2. 起源SMARC最初名为ULP-COM,主要发...
网络时间协议NTP(Network TimeProtocol)是用于互联网中时间同步的标准互联网协议,可以把计算机的时间同步到某些时间标准。NTP对于我们产品来说有什么用呢,简单的讲,当你的设备时间不准确了,你可以接入到互联网,从网上同步一下时间,非常方便。
在 Android 开发中,源码管理是一项至关重要且颇具挑战性的任务。面对包含数百个 git 库的 Android 源码,如何高效地进行下载、管理和协作开发成为了开发者们必须攻克的难题!
武汉万象奥科电子有限公司
服务热线:027-59218026
销售邮箱:sales@vanxoak.com
主营产品:ARM核心板、ARM工控板
工业网关、软硬件定制设计
地址:武汉东湖新技术开发区大学园路长城园路8号海容基孵化园B5

扫描二维码
关注公众号
产品及方案咨询
专业专注   合作共赢
移动电话:181 2058 0511 (湖北)
移动电话:175 7010 9551 (华南、华中)
移动电话:133 0386 9667 (华东、华北)
移动电话:187 3812 7320 (其他)