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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory