Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例)

版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/u012902367/article/details/84797093


写在前面:本文所用的硬件平台是天嵌的E9开发板,烧的是安卓6.0.1系统,E9平台的如何烧录镜像等操作这里不作讲解,单纯当做一个有串口接口的Android设备来使用。当然一般的Android手机的硬件设备都可以用来操作,自己的手机具体有哪些硬件设备可以操作可以在系统的/dev目录下查看,串口、显示屏和触摸屏等都在目录中可以找到(截图只有一部分),这里默认读者已经熟悉Android系统和应用开发、了解C语言基本文件操作。

在这里插入图片描述

让Android Studio支持C++编译

在这里插入图片描述
在这里插入图片描述
选择NDK安装的路径:
在这里插入图片描述

新建支持C++的工程

在这里插入图片描述
然后一直下一步直到Finish:
在这里插入图片描述
工程新建好后在目录结构中比一般的Android工程多几个文件,其中如下是最为重要的:
在这里插入图片描述

新建工程分析

cpp文件分析

如上新建的工程可以直接编译运行到手机里面,只在中间显示了一串符串:
在这里插入图片描述
这一串字符来自native-lib.cpp文件:
在这里插入图片描述
这个文件对函数名做一个简单介绍:

extern "C" 
JNIEXPORT jstring JNICALL
Java_com_test_ndk_serialdemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

对C、C++比较熟悉的应该知道第一行的作用。
第二行说明这个函数的返回值是string。
把函数名字分割成如下4个部分:
在这里插入图片描述
1: 每个函数最前面固定写一个Java
2: 将包名中的“.”换成“_”
在这里插入图片描述
3: 要调用c++文件的类名,c++文件会被编译成.so库,java通过加载库来调用c++函数,这里这个cpp文件会被编译成libnative–lib.so,这个名字也是由3部分组成,lib+native-lib+so,中间那个才是库名。
4: 最后一部分就是函数名字。
这个函数的作用就是返回字符串"Hello from C++"

调用cpp文件的MainActivity分析

在这里插入图片描述
9~11行:加载有cpp文件编译成的native-lib库。
25行:用关键字native修饰,说明库里面有一个名为stringFromJNI的函数。
函数的调用就在19行,把返回值显示在TextView上面。

CMakeLists.txt文件分析

这个文件有英文注释,描述比较清楚也就不再翻译,主要是表述了cpp文件名、编译过后生成的库名和最后需要链接的库名字。

串口设备读写

修改一下cpp文件名字

在这里插入图片描述
文件内容如下:

#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <jni.h>

#include <android/log.h>

static const char *TAG = "seril";

#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

// 获取波特率枚举类型
static speed_t getBaudrate(jint baudrate) {
    switch (baudrate) {
        case 0:
            return B0;
        case 50:
            return B50;
        case 75:
            return B75;
        case 110:
            return B110;
        case 134:
            return B134;
        case 150:
            return B150;
        case 200:
            return B200;
        case 300:
            return B300;
        case 600:
            return B600;
        case 1200:
            return B1200;
        case 1800:
            return B1800;
        case 2400:
            return B2400;
        case 4800:
            return B4800;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
        case 115200:
            return B115200;
        case 230400:
            return B230400;
        case 460800:
            return B460800;
        case 500000:
            return B500000;
        case 576000:
            return B576000;
        case 921600:
            return B921600;
        case 1000000:
            return B1000000;
        case 1152000:
            return B1152000;
        case 1500000:
            return B1500000;
        case 2000000:
            return B2000000;
        case 2500000:
            return B2500000;
        case 3000000:
            return B3000000;
        case 3500000:
            return B3500000;
        case 4000000:
            return B4000000;
        default:
            return (speed_t) -1;
    }
}

extern "C"
JNIEXPORT jobject JNICALL
Java_com_test_ndk_ndkdemo_SerialPort_open(JNIEnv *env, jclass type, jstring path_, jint baudrate, jint flags) {
    int fd;
    speed_t speed;
    jobject mFileDescriptor;
   const char *path = env->GetStringUTFChars(path_, 0);
   // Check arguments
   {
       speed = getBaudrate(baudrate);
       if (speed == -1) {
           LOGD("Invalid baudrate");
           return NULL;
       }
   }

   // Opening device
   {
       LOGD("Opening serial port %s with flags 0x%x", path, O_RDWR | flags);
       fd = open(path, O_RDWR | flags);
       LOGD("open() fd = %d", fd);

       env->ReleaseStringUTFChars(path_, path);

       if (fd == -1) {
           // Throw an exception
           LOGD("Cannot open port");
           return NULL;
       }
   }

   // Configure device
   {
       struct termios cfg;
       LOGD("Configuring serial port");
       if (tcgetattr(fd, &cfg)) {
           LOGD("tcgetattr() failed");
           close(fd);
           return NULL;
       }

       cfmakeraw(&cfg);
       cfsetispeed(&cfg, speed);
       cfsetospeed(&cfg, speed);

       if (tcsetattr(fd, TCSANOW, &cfg)) {
           LOGD("tcsetattr() failed");
           close(fd);
           return NULL;
       }

       //获得串口指向配置结构的指针
       cfmakeraw(&cfg);
       // 设置串口数据位-----------------------------------------------
       //屏蔽其他标志
       cfg.c_cflag&=~CSIZE;
       //将数据位修改为8bit
       cfg.c_cflag |=CS8;
       //将修改后的termios数据设置到串口中
       if (tcsetattr(fd, TCSANOW, &cfg)) {
           close(fd);
           return env->NewStringUTF("uart data num set error");
       }

       //获得串口指向配置结构的指针
       cfmakeraw(&cfg);
       // 设置串口校验位-----------------------------------------------
       cfg.c_cflag &= ~PARENB;
       //将修改后的termios数据设置到串口中
       if (tcsetattr(fd, TCSANOW, &cfg)) {
           close(fd);
           return env->NewStringUTF("uart cheack set error");
       }

       //获得串口指向配置结构的指针
       cfmakeraw(&cfg);
       // 设置串口流控-----------------------------------------------
       cfg.c_cflag &= ~CRTSCTS;
       //将修改后的termios数据设置到串口中
       if (tcsetattr(fd, TCSANOW, &cfg)) {
           close(fd);
           return env->NewStringUTF("Data bit setting failed");
       }
   }


   // Create a corresponding file descriptor
   {
       jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
       jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, "<init>", "()V");
       jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I");
       mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor);
       env->SetIntField(mFileDescriptor, descriptorID, (jint) fd);
   }
    return mFileDescriptor;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_test_ndk_ndkdemo_SerialPort_close(JNIEnv *env, jobject instance) {
    jclass SerialPortClass = env->GetObjectClass(instance);
    jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor");

    jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");

    jobject mFd = env->GetObjectField(instance, mFdID);
    jint descriptor = env->GetIntField(mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);
}

上面代码中关于串口的配置参数如下:

  1. 波特率
    波特率的参数格式上面代码中已经列出,这里就不再重复。
  2. 数据位
    CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。注意,在设置数据位前须先使用CSIZE做位屏蔽。具体设置代码:
	cfmakeraw(&cfg);
	// 设置串口数据位-----------------------------------------------
	//屏蔽其他标志
	cfg.c_cflag&=~CSIZE;
  	//将数据位修改为8bit
  	cfg.c_cflag |=CS8;
  	//将修改后的termios数据设置到串口
  	if (tcsetattr(fd, TCSANOW, &cfg)) {
           close(fd);
           //提示设置错误
           return env->NewStringUTF("Data bit setting failed");
    }
  1. 奇偶校验位
    可设置参数列表如下,设置方法和数据为设置方法类似:
设 置 代 码
无校验 cfg.c_cflag &= ~PARENB;
奇校验 cfg.c_cflag |= (PARODD | PARENB);
偶校验 cfg.c_cflag &= ~ PARENB; cfg.c_cflag &= ~PARODD;
空格 cfg.c_cflag &= ~PARENB; cfg.c_cflag &= ~CSTOPB;
  1. 停止位
    里面停止位只允许有如下两种:
设 置 代 码
1位 cfg.c_cflag &= ~CSTOPB;
2位 cfg.c_cflag |= CSTOPB;
  1. 数据流控制
    可设置参数列表如下,设置方法和数据为设置方法类似:
设 置 代 码
无流控 cfg.c_cflag &= ~CRTSCTS
奇校验 cfg.c_cflag |= CRTSCTS
偶校验 cfg.c_cflag |= IXON | IXOFF | IXANY

修改CMakeLists.txt

在这里插入图片描述

新建SerialPort类

在这里插入图片描述
SerialPort类内容如下:

package com.test.ndk.ndkdemo;

import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class SerialPort {
    static {
        System.loadLibrary("uart");
    }

    private static final String TAG = "SerialPort";
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;
    private FileDescriptor mFd;

    public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

        /* Check access permission */
        if (!device.canRead() || !device.canWrite()) {
            try {
                /* Missing read/write permission, trying to chmod the file */
                Process su;
                su = Runtime.getRuntime().exec("/system/bin/su"); // 切换root用户
                String cmd = "chmod 777 " + device.getAbsolutePath() + "\n"
                        + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                if ((su.waitFor() != 0) || !device.canRead()
                        || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }

        mFd = open(device.getAbsolutePath(), baudrate, flags);
        if (mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        }
        mFileInputStream = new FileInputStream(mFd); // 获取串口输入流
        mFileOutputStream = new FileOutputStream(mFd); // 获取串口输出流
    }
    // Getters and setters
    public FileInputStream getInputStream() {
        return mFileInputStream;
    }
    public FileOutputStream getOutputStream() {
        return mFileOutputStream;
    }

    // cpp文件中的两个方法
    private native static FileDescriptor open(String path, int baudrate, int flags);
    public native void close();
}

顺便说一句,linux(Android也是linux)里面所有设备都是以文件形式呈现,操作文件即操作硬件接口。

创建对象实现数据读写

在MainActivity创建SerialPort对象,并获取SerialPort中的输入输出stream即可实现串口读写。界面中有一个Button,一个TextView。
MainActivity代码如下

package com.test.ndk.ndkdemo;

import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private SerialPort mSerialPort;
    private FileInputStream mInputStream;
    private FileOutputStream mOutputStream;

    private Button send;
    private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = findViewById(R.id.sample_text);
        send = findViewById(R.id.send);
        // 点击按钮发送2个字节数据
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                byte[] data = new byte[2];
                data[0] = 5;
                data[1] = 6;
                try {
                    mOutputStream.write(data);
                    mOutputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        // 打开串口设备并获取输入输出流
        try {
        // 不同的设备串口的设备名字不一样,比如A8开发板的串口文件名字是s3c2410_serial2...,E9开发板提供了四个串口接口,串口1到串口4,串口1的文件名为ttySAC0,用于debug不开放.
        // ttySAC1到ttySAC3对应串口2~4,每个串口的硬件位置查看硬件原理图就可以知道,位置都在“/dev”下。
            mSerialPort = new SerialPort(new File("/dev/ttySAC3"), 115200, 0); // 打开连接协调器串口,波特率115200,无奇偶校验位
            mInputStream = mSerialPort.getInputStream(); // 获取串口输入流,用于读取串口数据
            mOutputStream = mSerialPort.getOutputStream(); // 获取串口输出流,用于通过串口发送数据
        } catch (IOException e) {
            System.out.println("无法打开串口,ttySAC3");
            e.printStackTrace();
        }
        new Thread(new ReadThread()).start();
    }
    /**
     * 数据读取线程
     */
    private class ReadThread extends Thread {
        private int size;
        private byte[] buffer = new byte[512];
        @Override
        public void run() {
            while (true) {
                if (mInputStream == null) {
                    try {
                        // A8串口:s3c2410_serial2...要用什么对应原理图
                        // E9串口: ttySAC0(调试接口),ttySAC1、ttySAC2、ttySAC3具体位置看原理图
                        mSerialPort = new SerialPort(new File("/dev/ttySAC3"), 115200, 0); // 打开连接协调器串口,波特率115200,无奇偶校验位
                        mInputStream = mSerialPort.getInputStream(); // 获取串口输入流,用于读取串口数据
                        mOutputStream = mSerialPort.getOutputStream(); // 获取串口输出流,用于通过串口发送数据
                    } catch (Exception e) {
                        System.out.println("无法打开串口");
                        SystemClock.sleep(3000);
                    }
                }
                else
                {
                    try {  // buffer数组就是读取到的内容,size就是读取到的数量
                        size = mInputStream.read(buffer, 0, buffer.length); // 从协调器串口读取数据
                        if (size > 0) {
                            Log.d("seril",size+"");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

E9开发版拓展接口图。如果读者用的A8或者2440等设备需要查看自己板子的原理图,并在“/dev”路径下找到相应的文件
在这里插入图片描述
最后将demo上传提供参考。

猜你喜欢

转载自blog.csdn.net/u012902367/article/details/84797093