edit | blame | history | raw

Android SDK开发入门文档

​ 首先在windows安装Android Studio,配置好环境,还要在虚拟机上使用同样的步骤安装Android Studio,虚拟机上安装的AS只有一个功能,就是要使用它的ndk交叉编译libgpiod动态库(在虚拟机上也可以不安装Android Studio,直接下载ndk来交叉编译,方法自行百度,本文档介绍第一种方法),其它工作都在windows上进行,交叉编译完成后,会生成gpio.h头文件和libgpiod.so动态库,在windows系统打开AS,配置项目的CMakeLists.txt文件,导入交叉编译好的libgpiod.so,再导入gpio.h头文件到cpp文件中,就能调用libgpiod库的接口。项目需要用到gpio18,gpio20,gpio22三个引脚,把这三个引脚空闲出来,再给gpiochip0赋予777权限,gpiochip0是管理GPIO0(一组引脚有32个,gpio0-32)这一组的gpio,要控制gpio18,gpio20,gpio22就需要有gpiochip0的操作权限。

总结以上,开发流程为:安装AS,配置AS环境,连接开发板, 空闲gpio引脚,开gpiochip0权限,使用NDK交叉编译libgpiod,设计UI界面,配置CMakeLists.txt,写JNI,最后编译运行


1 Android Studio下的 Java App 开发

1.1 软件安装

  • 下载地址:https://developer.android.com/?hl=zh-cn

image-20240424151334610

1.2 环境配置

  • 首先新建一个项目,选择C++

image-20240424151939556

  • image-20240424153108316

  • image-20240424153210907

  • 安装下载ndk

image-20240424153417702

image-20240424153457652

image-20240424153545138

image-20240424153619154

image-20240424153649866

image-20240424153707457

image-20240424153744305

  • 查看自己的项目结构

image-20240424154222459

image-20240424154240311

image-20240424154306250

1.3 硬件连接

image.png

  • 烧录软件下载

从凌云实验室文件服务器上下载并安装下面烧录软件。

​ 1、CP210x_VCP_Windows.zip 解压缩并安装开发板的Console调试串口驱动;

​ 2、DriverAssitant_v5.1.1.zip , 解压缩安装RK3568开发板驱动;

​ 3、RKDevTool_Release_v2.86.zip ,直接解压缩即可运行的 RK3568开发板烧录软件;

image.png

  • xshell软件,串口连接

image.png

image.png

电源充电日志

  • 在这两个窗口管理开发板连接

image-20240424200508707

1.4 UI界面设计

1.4.1 切换Project视图,代码文件名为activity_main.xml

image-20240424200849098

  • activity_main.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="76dp"
        android:text="ON/OFF"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Lamp" />

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginTop="180dp"
        android:visibility="visible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@android:drawable/presence_invisible" />

    <ImageView
        android:id="@+id/Lamp"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginTop="180dp"
        android:visibility="invisible"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@android:drawable/presence_online" />


    <TextView
        android:id="@+id/textView"
        android:layout_width="150dp"
        android:layout_height="35dp"
        android:gravity="center"
        android:text="RK3568-LED"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.532"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.04" />


</androidx.constraintlayout.widget.ConstraintLayout>

1.4.2 在java文件夹下新建一个HardControl类

  • 用于放函数接口

image-20240424201208856

image-20240424201247757

  • HardControl.java:
package com.example.myapplication;

public class HardControl {

    public static native int ledOpen();//打开设备
    public static native int ledCtrl_on(int which, int status);//打开led灯
    public static native int ledCtrl_off(int which, int status);//关闭led灯

    static
    {
        System.loadLibrary("myapplication");
        System.loadLibrary("gpiod");
    }
}

1.4.3 编写MainActivity.java

  • MainActivity.java相当于c语言的main函数

image-20240424202939763

  • MainActivity.java
package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;


public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    
    ImageView L1;
    Button bnt1;
    boolean isLightOn = false; // 指示灯状态
    HardControl hardControl = new HardControl();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bnt1 = findViewById(R.id.button1);//  按钮灯开

        hardControl.ledOpen();
        bnt1.setOnClickListener(this);
        L1 = (ImageView)this.findViewById(R.id.Lamp);
    }

    @Override
    public void onClick(View view) {


        int i=view.getId();

        if (i==bnt1.getId())
        {
            if(isLightOn)
            {
                L1.setVisibility(View.INVISIBLE);
                hardControl.ledCtrl_off(1,1);
                isLightOn = false;
            }
            else
            {
                L1.setVisibility(View.VISIBLE);
                hardControl.ledCtrl_on(1,0);
                isLightOn = true;
            }
        }
    }
        protected void onDestroy() {
        super.onDestroy();
        // 当Activity销毁时,关闭LED控制
        hardControl.ledCtrl_close();
    }

}

2 Android Framwork/JNI层的开发

2.1 交叉编译libgpiod

#下载安装相关编译工具
sudo apt update
sudo apt-get install autoconf-archive
sudo apt-get install autoconf automake libtool

2.1.1**下载源码然后解压**

  • 源码地址:https://github.com/brgl/libgpiod/tree/v1.6.x
git clone https://github.com/brgl/libgpiod/tree/v1.6.x
unzip libgpiod-1.6.x.zip

image.png

2.1.2 运行脚本

cd libgpiod-1.6.x
bash autogen.sh   #运行脚本,会生成configure这个文件

image.png

2.1.3 设置交叉编译环境变量

  • 首先安装AS,我的安装目录为/home/af/Android/

(重点,环境变量没设置对交叉编译会失败)

export NDK=/home/af/Android/Sdk/ndk/26.2.11394342 # 根据自己的NDK路径设置
#export TOOLCHAIN=${NDK}/toolchains/llvm/prebuilt/darwin-x86_64
export TOOLCHAIN=${NDK}/toolchains/llvm/prebuilt/linux-x86_64
#export TARGET=armv7a-linux-androideabi
#export TARGET=i686-linux-android
#export TARGET=x86_64-linux-android
export TARGET=aarch64-linux-android #TARGET=aarch64-linux-android 是我们选择的交叉编译的目标架构,根据需要选择不通的架构
export API=29
export AR=${TOOLCHAIN}/bin/llvm-ar #打包器,用于库操作,可以通过该工具从一个库中删除或者增加目标代码模块。
export CC=${TOOLCHAIN}/bin/${TARGET}${API}-clang # 编译器,对C源文件进行编译处理,生成汇编文件。
export AS=${CC} #汇编器,将汇编文件生成目标文件(汇编文件使用的是指令助记符, AS将它翻译成机器码)
export CXX=${TOOLCHAIN}/bin/${TARGET}${API}-clang++
export LD=${TOOLCHAIN}/bin/ld #链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者是可执行文件。
export RANLIB=${TOOLCHAIN}/bin/llvm-ranlib
export STRIP=${TOOLCHAIN}/bin/llvm-strip # 以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码。

image.png

"aarch64-linux-android29-clang" 是一个基于 Clang 编译器的工具链,用于在 Android 平台上编译 ARMv8 架构的代码。它的主要特点包括:

1、支持使用 LLVM/Clang 编译器进行编译,具有优秀的代码优化能力和兼容性;
2、支持 ARMv8 架构指令集,可以生成针对 ARMv8 架构的优化代码;
3、针对 Android 平台进行了优化,可以使用 Android 平台上的系统库和头文件;
4、适用于 Android 9.0 及以上版本(API level 29)。
5、使用该工具链可以在 Android 平台上编译出高效、稳定、兼容性好的 ARMv8 架构代码,适用于开发 Android 平台上的应用程序和系统级组件。

image-20240424194233263

2.1.4 在源码目录下运行上一步生成的configure脚本,生成Makefile文件

sudo ./configure CC=${CC} CXX=${CXX} RANLIB=${RANLIB} AR=${AR} STRIP=${STRIP} --prefix=/home/af/android_libgpiod --host=${TARGET}

# --prefix指定安装目录

image.png

2.1.5 使用make命令,运行Makefile文件,再安装

sudo make
sudo make install
  • 我的库文件和头文件安装到了这里,就是之前--prefix指定的目录

image.png

  • 我们只要生成的头文件和动态库,拷贝这两个文件

image.png

  • 用file命令查看文件类型,看到这个输出表示交叉编译成功,否则重新编译

image.png


2.2 java加载交叉编译的libgpiod.so

2.2.1 新建C++项目

image.png

2.2.2 新建目录,将头文件和动态库复制进去,目录结构如图所示

  • 安卓设备架构为arm64-v8a,就新建为arm64-v8a

image.png

2.2.3 修改CMakeLists.txt文件,导入、链接库

########导入libgpiod###########

#在CMakeLists.txt文件中添加一个导入库(imported library)的声明,
#IMPORTED: 这是一个可选的参数,表示库是一个导入库(imported library),即该库不是由CMake构建生成的,而是由外部提供的。
add_library(libgpiod
        SHARED
        IMPORTED)

set_target_properties( libgpiod
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libgpiod.so)

target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        ${log-lib}
        libgpiod
        )

2.2.4 修改build.gradle.kts文件,添加如下内容

image.png

2.2.5 导入加载libgpiod库

image.png


2.3 JNI编写

ledOpen ledCtrl_on ledCtrl_off是JNI接口,鼠标选中函数名,按alt+enter键,点击Create JNI...,然后会在cpp文件里自动生成JNI函数接口名,在里面写代码

image-20240424202503561

image-20240424203947453

  • myapplication.cpp (记得导入头文件include/gpiod.h)

```cpp
#include <jni.h>
#include
#include <android/log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/select.h>
#include "include/gpiod.h"

// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
// static {
// System.loadLibrary("myapplication");
// }
//
// Or, in MainActivity.kt:
// companion object {
// init {
// System.loadLibrary("myapplication")
// }
// }

const char *chipname = "gpiochip0"; //GPIO1
struct gpiod_chip *chip;
unsigned int line_num ;
struct gpiod_line *line18 ;
unsigned int line_num_18 = 18; // 偏移GPIO 20 GPIO1_20
int ret;
bool isused;
const char *chip_name;

int set_led_value(int status)
{
__android_log_print(ANDROID_LOG_DEBUG, "函数:set_led_value", "开始设置0/1控制亮灭:%d",status);

  //设置GPIO18的值
  ret = gpiod_line_set_value(line18, status);//设置输出状态
  if (ret < 0) {
      __android_log_print(ANDROID_LOG_DEBUG, "Set On/Off", "Set line output failed. val:%d!",status);

      return 0;
  }

  ret = gpiod_line_get_value(line18);
  __android_log_print(ANDROID_LOG_DEBUG, "GPIO18的值", "GPIO18的值为:%d!",ret);

  if (ret == 0)
  {
      __android_log_print(ANDROID_LOG_DEBUG, "--LED状态--", "状态为:--打开!--");
  }
  else
  {
      {
          __android_log_print(ANDROID_LOG_DEBUG, "--LED状态--", "状态为:--关闭!--");
      }
  }

  return 0;

}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_HardControl_ledOpen(JNIEnv *env, jclass clazz) {

  // TODO: implement ledOpen()

// __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "------native ledOpen-------");

  //1、OPEN:打开chipgpio0
  chip = gpiod_chip_open_by_name(chipname);

  if (!chip) {
      __android_log_print(ANDROID_LOG_INFO, "Open chipgpio0", "Open chip by name failed. name: %s\n", chipname);

      return 0;
  }
  __android_log_print(ANDROID_LOG_DEBUG, "Open chipgpio0", "Successfully opened chip:%s",chipname);

  //2、获取打开的设备名
  chip_name = gpiod_chip_name(chip);
  __android_log_print(ANDROID_LOG_DEBUG, "gpiod_chip_name", "The GPIO chip name as represented in the kernel: %s",chip_name);

  //3、获取打开的chipgpio0的引脚个数
  line_num = gpiod_chip_num_lines(chip);
  __android_log_print(ANDROID_LOG_DEBUG, "gpiod_chip_num_lines", "Line number:%u",line_num);

  //4、获取GPIO18引脚句柄
  line18 = gpiod_chip_get_line(chip,line_num_18);
  if ( !line18 )
  {
      __android_log_print(ANDROID_LOG_DEBUG, "gpiod_chip_get_line", "Get line failed. line_num: %u",line_num_18);

      return 0;
  }

  //5、判断GPIO18是否被占用
  isused = gpiod_line_is_used(line18);
  if (isused)
  {
      // GPIO线正在被使用
      __android_log_print(ANDROID_LOG_DEBUG, "GPIO18 isused", "GPIO18线正在被使用");
      return 0;
  }
  else
  {
      // GPIO线没有被使用
      __android_log_print(ANDROID_LOG_DEBUG, "GPIO18 is not used", "GPIO18线没有被使用");
  }

  //6、设置GPIO18为输出模式
  ret = gpiod_line_request_output(line18,NULL ,1);
  if (ret < 0) {
      __android_log_print(ANDROID_LOG_DEBUG, "设置为输出模式", "输出模式设置失败");

      return 0;
  }
  __android_log_print(ANDROID_LOG_DEBUG, "设置为输出模式", "输出模式设置成功!");

  return 2;

}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_HardControl_ledCtrl_1on(JNIEnv *env, jclass clazz, jint which,
jint status) {
// TODO: implement ledCtrl_on()

  __android_log_print(ANDROID_LOG_DEBUG, "ledCtrl_on", "status 值:%d",status);

  set_led_value(status);

  return 0;

}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_HardControl_ledCtrl_1off(JNIEnv *env, jclass clazz, jint which,
jint status) {
// TODO: implement ledCtrl_off()

  __android_log_print(ANDROID_LOG_DEBUG, "ledCtrl_off", "status 值:%d",status);

  set_led_value(status);

  return 0;

}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_HardControl_ledCtrl_1close(JNIEnv *env, jclass clazz) {
// TODO: implement ledCtrl_close()

  set_led_value(1);

  gpiod_line_release(line18);

  gpiod_chip_close(chip);

}
```

2.3.1 gpiochip0开权限

  • app默认是以普通用户运行,但/dev/gpiochip0的权限默认为为600,需要修改为666才能控制,修改之后重新烧录镜像

5ac496ce35018b82d2d5904618df8d6

2.3.2 编译运行程序

image-20240424205135864

  • 可以通过Logcat查看运行日志

image-20240424205243326

  • app界面,点击控制led灯亮灭

image-20240424205329016

  • 运行结果

image-20240426164804753image-20240426165118487

image-20240426165136395


附:烧录镜像

  • image目录下,运行android脚本

image-20240424165102012

image-20240424165151676

  • 打开软件,空白处右键,导入配置

image-20240424191638112

  • 双击这些空白格,把镜像路径设置一下,Loader就是选取.bin结尾的文件,其它的对应文件名

image-20240424191830634

  • 点击切换

image-20240424191916718

  • 点击高级功能,进入Maskrom

image-20240424191944870

  • 点击执行

image-20240513110704858

  • 下载完成后会自动重启,再点复位按键重启

image-20240424192103815

1c1e089b4a0c2f428091852a4df7795