声明:如果项目对串口的读取频率要求不高,请参考 serialPort api 框架的串口通信文章。该文章使用的是 Linux 读取串口设备文件的方式,工具为 Android Studio 3.3。
首先创建一个支持 c++ 的项目,在 cpp 文件夹下创建 include 头文件夹。新建一个头文件,如下图:
注意:这里的头文件最好和源文件名称保持一致(规范,利人利己)。
这里是代码,如果有不了解的地方请留言,有必要的地方都有注释(为什么看起来像 Java 写法?别在意,笔者就是一个写 Java 的。注释为什么是这么蹩脚的英文?大家能看懂就好):
//
// Created by BevisWang on 2018/7/17.
//
#ifndef BLOGPROJECT_DEV_UART_H
#define BLOGPROJECT_DEV_UART_H
#include
#include "android/log.h"
typedef unsigned char BYTE;
static const char *DEV_UART_TAG = "UartLib";
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, DEV_UART_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, DEV_UART_TAG, fmt, ##args)
/** define states. */
#define FALSE 0
#define TRUE 1
/** define device path. */
#define UART_DEVICE_4 "/dev/ttyS4"
#define UART_DEVICE_6 "/dev/ttyS6"
/** Serial port config param. */
struct UartConfig {
int baudrate; // read speed
int databits; // one of 7,8
int stopbits; // one of 1,2
int parity; // one of N,E,O,S
};
/** Serial port device UART class. */
class DevUart {
private:
const char *path; // device path
int fd; // device
bool isClose; // is close fd
/**
* @brief Set serial port speed (baudrate).
* @param fd type int device file
* @param speed type int serial port baurate
* @return is success
*/
int setSpeed(int fd, int speed);
/**
* @brief Set data bits,stop bits and parity.
* @param fd type int device file
* @param databits type int data bits,one of 7,8
* @param stopbits type int stop bits,one of 1,2
* @param parity type int parity,one of N,E,O,S
*/
int setParity(int fd, int databits, int stopbits, int parity);
public:
DevUart();
/**
* @brief Construction method.
* @param path serial port device path
*/
DevUart(const char *path);
/**
* @brief Open serial port device.
* @param config serial port device config param
* @return is success
*/
int openUart(UartConfig config);
/**
* @brief Read device data.
* @param data read data
* @param timeval read time
* @return data length
*/
int readData(BYTE *data,int size);
/**
* @brief Write serial port data.
* @param data serial port data
* @return is success
*/
int writeData(BYTE *data, int len);
/** @brief Close serial port device. */
void closeUart();
/**
* @brief Get baudrate by int.
* @param baudrate int
* @return baudrate
*/
speed_t getBaudrate(int baudrate);
};
#endif //BLOGPROJECT_DEV_UART_H
#include
#include
#include
#include "include/DevUart.h"
/// serial port operator class.
/// \author BevisWang
/// \param path
DevUart::DevUart() {}
DevUart::DevUart(const char *path) {
DevUart::path = path;
}
speed_t DevUart::getBaudrate(int 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 -1;
}
}
int DevUart::setSpeed(int fd, int speed) {
speed_t b_speed;
struct termios cfg;
b_speed = getBaudrate(speed);
if (tcgetattr(fd, &cfg)) {
LOGE("tcgetattr invocation method failed!");
close(fd);
return FALSE;
}
cfmakeraw(&cfg);
cfsetispeed(&cfg, b_speed);
cfsetospeed(&cfg, b_speed);
if (tcsetattr(fd, TCSANOW, &cfg)) {
LOGE("tcsetattr invocation method failed!");
close(fd);
return FALSE;
}
return TRUE;
}
int DevUart::setParity(int fd, int databits, int stopbits, int parity) {
struct termios options;
if (tcgetattr(fd, &options) != 0) {
LOGE("The method tcgetattr exception!");
return FALSE;
}
options.c_cflag &= ~CSIZE;
switch (databits) /* Set data bits */
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
LOGE("Unsupported data size!");
return FALSE;
}
switch (parity) {
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* Set odd checking */
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* Transformation even checking */
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
LOGE("Unsupported parity!");
return FALSE;
}
/* 设置停止位*/
switch (stopbits) {
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
LOGE("Unsupported stop bits!");
return FALSE;
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush(fd, TCIFLUSH);
options.c_cc[VTIME] = 150; /* Set timeout to 15 seconds */
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd, TCSANOW, &options) != 0) {
LOGE("The method tcsetattr exception!");
return FALSE;
}
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* Input */
options.c_oflag &= ~OPOST; /* Output */
return TRUE;
}
int DevUart::openUart(UartConfig config) {
LOGD("Open device!");
isClose = false;
fd = open(path, O_RDWR);
if (fd < 0) {
LOGE("Error to read %s port file!", path);
return FALSE;
}
if (!setSpeed(fd, config.baudrate)) {
LOGE("Set Speed Error!");
return FALSE;
}
if (!setParity(fd, config.databits, config.stopbits, config.parity)) {
LOGE("Set Parity Error!");
return FALSE;
}
LOGD("Open Success!");
return TRUE;
}
int DevUart::readData(BYTE *data, int size) {
int ret, retval;
fd_set rfds;
ret = 0;
if (isClose) return 0;
for (int i = 0; i < size; i++) {
data[i] = static_cast<char>(0xFF);
}
FD_ZERO(&rfds); // Clear device file desc.
FD_SET(fd, &rfds); // Reset device file desc.
// TODO Async operation. Thread blocking.
if (FD_ISSET(fd, &rfds)) {
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
retval = select(fd + 1, &rfds, NULL, NULL, NULL);
if (retval == -1) {
LOGE("Select error!");
} else if (retval) {
LOGD("This device has data!");
ret = static_cast<int>(read(fd, data, static_cast(size)));
} else {
LOGE("Select timeout!");
}
}
if (isClose) close(fd);
return ret;
}
int DevUart::writeData(BYTE *data, int len) {
int result;
result = static_cast<int>(write(fd, data, static_cast(len)));
LOGE("Write result:%d", result);
return TRUE;
}
void DevUart::closeUart() {
LOGD("Close device!");
isClose = true;
}
文件创建位置同上,注意保证头文件和源文件名称一致,不然容易乱套。
//
// Created by BevisWang on 2018/7/17.
//
#ifndef BLOGPROJECT_DEV_UART_H
#define BLOGPROJECT_DEV_UART_H
#include
#include "DevUart.h"
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_beviswang_blogproject_ndk_UART4Operator_openPort(
JNIEnv *env, jobject thiz, jstring path, jint baudRate, jint dataBits, jint stopBits,
jchar parity);
JNIEXPORT jbyteArray JNICALL
Java_com_beviswang_blogproject_ndk_UART4Operator_readPort(JNIEnv *env, jobject thiz, jint maxSize);
JNIEXPORT void JNICALL
Java_com_beviswang_blogproject_ndk_UART4Operator_writePort(JNIEnv *env, jobject thiz, jbyteArray data);
JNIEXPORT void JNICALL
Java_com_beviswang_blogproject_ndk_UART4Operator_closePort(JNIEnv *env, jobject thiz);
JNIEXPORT jint JNICALL Java_com_beviswang_blogproject_ndk_UART6Operator_openPort(
JNIEnv *env, jobject thiz, jstring path, jint baudRate, jint dataBits, jint stopBits,
jchar parity);
JNIEXPORT jbyteArray JNICALL
Java_com_beviswang_blogproject_ndk_UART6Operator_readPort(JNIEnv *env, jobject thiz, jint maxSize);
JNIEXPORT void JNICALL
Java_com_beviswang_blogproject_ndk_UART6Operator_writePort(JNIEnv *env, jobject thiz, jbyteArray data);
JNIEXPORT void JNICALL
Java_com_beviswang_blogproject_ndk_UART6Operator_closePort(JNIEnv *env, jobject thiz);
#ifdef __cplusplus
}
#endif
#endif //BLOGPROJECT_DEV_UART_H
#include
#include "include/UartLib.h"
///
/// @author BevisWang
///
DevUart devUart4;
DevUart devUart6;
////////////////////////////////// UART4 ////////////////////////////////////////
jint Java_com_beviswang_blogproject_ndk_UART4Operator_openPort(
JNIEnv *env, jobject thiz, jstring path, jint baudRate, jint dataBits, jint stopBits,
jchar parity) {
try {
UartConfig config;
config = UartConfig();
config.baudrate = baudRate;
config.databits = dataBits;
config.stopbits = stopBits;
config.parity = parity;
devUart4 = DevUart(env->GetStringUTFChars(path, 0));
devUart4.openUart(config);
} catch (char *exception) {
LOGE("Open device is error! Message:%s", exception);
return FALSE;
}
return TRUE;
}
jbyteArray Java_com_beviswang_blogproject_ndk_UART4Operator_readPort(
JNIEnv *env, jobject thiz, jint maxSize) {
BYTE buf[maxSize];
int len;
len = devUart4.readData(buf, maxSize);
if(len < 1) return NULL;
jbyteArray byteArray;
jbyte *bytes = reinterpret_cast(buf);
byteArray = env->NewByteArray(len);
env->SetByteArrayRegion(byteArray, 0, len, bytes);
return byteArray;
}
void Java_com_beviswang_blogproject_ndk_UART4Operator_writePort(
JNIEnv *env, jobject thiz, jbyteArray data) {
jbyte *array = env->GetByteArrayElements(data, 0);
BYTE *bytes = reinterpret_cast(array);
devUart4.writeData(bytes, 0);
}
void Java_com_beviswang_blogproject_ndk_UART4Operator_closePort(
JNIEnv *env, jobject thiz) {
devUart4.closeUart();
devUart4 = NULL;
}
////////////////////////////////// UART6 ////////////////////////////////////////
jint Java_com_beviswang_blogproject_ndk_UART6Operator_openPort(
JNIEnv *env, jobject thiz, jstring path, jint baudRate, jint dataBits, jint stopBits,
jchar parity) {
try {
UartConfig config;
config = UartConfig();
config.baudrate = baudRate;
config.databits = dataBits;
config.stopbits = stopBits;
config.parity = parity;
devUart6 = DevUart(env->GetStringUTFChars(path, 0));
devUart6.openUart(config);
} catch (char *exception) {
LOGE("Open device is error! Message:%s", exception);
return FALSE;
}
return TRUE;
}
jbyteArray Java_com_beviswang_blogproject_ndk_UART6Operator_readPort(
JNIEnv *env, jobject thiz, jint maxSize) {
BYTE buf[maxSize];
int len;
len = devUart6.readData(buf, maxSize);
if(len < 1) return NULL;
jbyteArray byteArray;
jbyte *bytes = reinterpret_cast(buf);
byteArray = env->NewByteArray(len);
env->SetByteArrayRegion(byteArray, 0, len, bytes);
return byteArray;
}
void Java_com_beviswang_blogproject_ndk_UART6Operator_writePort(
JNIEnv *env, jobject thiz, jbyteArray data) {
jbyte *array = env->GetByteArrayElements(data, 0);
BYTE *bytes = reinterpret_cast(array);
devUart6.writeData(bytes, 0);
}
void Java_com_beviswang_blogproject_ndk_UART6Operator_closePort(
JNIEnv *env, jobject thiz) {
devUart6.closeUart();
devUart6 = NULL;
}
根据以上接入函数名 Java_com_beviswang_blogproject_ndk_UART6Operator 和
Java_com_beviswang_blogproject_ndk_UART4Operator 创建本地 Java 类,结构如下图:
UART4Operator 和 UART6Operator 完全一样,改下路径即可,故只贴其中一个类的代码(Kotlin?不习惯就直接用 Java 代替,只要衔接一下本地方法就可以了):
package com.beviswang.blogproject.ndk
import java.io.Closeable
/**
* @author BevisWang
*/
interface IUARTOperator : Closeable {
fun open(path: String, baudRate: Int = 9600, dataBits: Int = 8,
stopBits: Int = 1, parity: Char = 'N')
fun write(data: ByteArray)
/**
* I/O operation.
* @param maxSize max array size
*/
fun read(maxSize: Int = 256): ByteArray?
}
package com.beviswang.blogproject.ndk
import android.util.Log
/**
* UART4 operator.
* @author BevisWang
*/
class UART4Operator : IUARTOperator {
override fun open(path: String, baudRate: Int, dataBits: Int, stopBits: Int, parity: Char) {
val result = openPort(path, baudRate, dataBits, stopBits, parity)
if (result != 1) {
Log.e("UART4","Serial port open failed!")
}
}
override fun write(data: ByteArray) {
writePort(data)
}
override fun read(maxSize: Int): ByteArray? {
return readPort(maxSize)
}
override fun close() {
closePort()
}
/**
* 打开并设置串口数据位,停止位和效验位
* @param path 设备路径
* @param baudRate 波特率
* @param dataBits 数据位,取值为7或者8
* @param stopBits 停止位,取值为1或者2
* @param parity 效验类型 取值为N,E,O,S
* @return is success
*/
private external fun openPort(path: String = PORT_PATH, baudRate: Int = 9600,
dataBits: Int = 8, stopBits: Int = 1, parity: Char = 'N'): Int
/**
* 开始读取串口数据
* @param maxSize 最大读取空间
* @return 读取到的串口数据
*/
private external fun readPort(maxSize: Int): ByteArray?
/**
* The data to write to serial port device.
* @param data write data
*/
private external fun writePort(data: ByteArray)
/** Close serial port device. */
private external fun closePort()
companion object {
private const val PORT_PATH = "/dev/ttyS4"
init {
System.loadLibrary("uart_lib")
}
}
}
CMake 脚本真的方便了许多,以下为脚本代码(知道大概的用法就好,自动创建的脚本文件有说明):
cmake_minimum_required(VERSION 3.4.1)
add_library(uart_lib SHARED src/main/cpp/UartLib.cpp)
add_library(dev_uart SHARED src/main/cpp/DevUart.cpp)
find_library(log-lib log )
target_link_libraries(dev_uart ${log-lib} )
target_link_libraries(uart_lib dev_uart ${log-lib} )
以上,实在不懂的请留言,再次声明,本文章只是为了提升读取速度。在 serialPort api 框架中,使用的是 java 方法读取文件,速度实在慢(普通的 Android 设备性能跟不上,这里指的并非手机和平板)。说下笔者的项目大概情况,笔者的项目是车载设备,需要接入 OBD ,但是 OBD 串口数据发送频率非常之快,并且在车上每个操作的同时,也会实时返回串口信息,每条数据几乎都是实时的。因此,serialPort api 框架无法满足项目要求,只好重新使用 c++ 读取。