本文主要是介绍Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Android Studio下NDK开发-Java与C混合编程(以硬件串口读写操作为例)
- 让Android Studio支持C++编译
- 新建支持C++的工程
- 新建工程分析
- cpp文件分析
- 调用cpp文件的MainActivity分析
- CMakeLists.txt文件分析
- 串口设备读写
- 修改一下cpp文件名字
- 修改CMakeLists.txt
- 新建SerialPort类
- 创建对象实现数据读写
写在前面:本文所用的硬件平台是天嵌的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 exceptionLOGD("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;//将数据位修改为8bitcfg.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);
}
上面代码中关于串口的配置参数如下:
- 波特率
波特率的参数格式上面代码中已经列出,这里就不再重复。 - 数据位
CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。注意,在设置数据位前须先使用CSIZE做位屏蔽。具体设置代码:
cfmakeraw(&cfg);// 设置串口数据位-----------------------------------------------//屏蔽其他标志cfg.c_cflag&=~CSIZE;//将数据位修改为8bitcfg.c_cflag |=CS8;//将修改后的termios数据设置到串口if (tcsetattr(fd, TCSANOW, &cfg)) {close(fd);//提示设置错误return env->NewStringUTF("Data bit setting failed");}
- 奇偶校验位
可设置参数列表如下,设置方法和数据为设置方法类似:
设 置 | 代 码 |
---|---|
无校验 | 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位 | cfg.c_cflag &= ~CSTOPB; |
2位 | cfg.c_cflag |= CSTOPB; |
- 数据流控制
可设置参数列表如下,设置方法和数据为设置方法类似:
设 置 | 代 码 |
---|---|
无流控 | 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 setterspublic 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;@Overrideprotected 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() {@Overridepublic 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];@Overridepublic 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上传提供参考。
这篇关于Android AS下的NDK开发-Java与C混合编程(以硬件串口读写操作为例)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!