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

本文主要是介绍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);
}

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

  1. 波特率
    波特率的参数格式上面代码中已经列出,这里就不再重复。
  2. 数据位
    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");}
  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 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混合编程(以硬件串口读写操作为例)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093204

相关文章

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Spring 依赖注入与循环依赖总结

《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Spring 三级缓存解决循环依赖1. 创建UserService原始对象2. 将原始对象包装成工

Java中如何正确的停掉线程

《Java中如何正确的停掉线程》Java通过interrupt()通知线程停止而非强制,确保线程自主处理中断,避免数据损坏,线程池的shutdown()等待任务完成,shutdownNow()强制中断... 目录为什么不强制停止为什么 Java 不提供强制停止线程的能力呢?如何用interrupt停止线程s

SpringBoot请求参数传递与接收示例详解

《SpringBoot请求参数传递与接收示例详解》本文给大家介绍SpringBoot请求参数传递与接收示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录I. 基础参数传递i.查询参数(Query Parameters)ii.路径参数(Path Va

SpringBoot路径映射配置的实现步骤

《SpringBoot路径映射配置的实现步骤》本文介绍了如何在SpringBoot项目中配置路径映射,使得除static目录外的资源可被访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一... 目录SpringBoot路径映射补:springboot 配置虚拟路径映射 @RequestMapp

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映