Android 串口支持奇偶校验及Android串口基础


本年度在uniapp的插件开发中,涉及到了打印机/扫描仪都与串口有关,刚好朋友在使用官方的serialport时遇到奇偶校验问题,并给了一个相关问题的文章( 让android_serialport_api支持奇偶校验、数据位、停止位等参数),本文以此为基础,保留了被去掉都flags。并作相关的源码解析。

android_serialport_api(官方下载地址http://code.google.com/p/android-serialport-api/)。由于没有代理,使用了https://github.com/cepr/android-serialport-api/tree/master

代码数量少,简单,通过jni调用c底层方法读写linux的串口文件。android_seiralport_api仅支持自定义波特率,其他三个常用的串口参数(奇偶校验、数据位、停止位)只能使用默认值。

原始代码SerialPort.java中的构造函数:

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

原始代码SerialPort.c中的打开串口的函数:

JNIEXPORT jobject JNICALL Java_com_cosin_serialport_SerialPort_open (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)

多数情况下可能没问题,如果项目要求采用不同于默认值的串口策略。需要改(很少)部分代码,让这个类库支持奇偶校验、数据位、停止位这三个常用参数。

思路就是增加一个构造函数,向后兼容
同时为了避免冲突,重写定义了so的名称和java的包名

编译好的包

已经编译打包好的aar文件下载

java 文件修改

  1. 保留原构造函数 SerialPort(File device, int baudrate, int flags)
  2. 新增需要的构造函数public SerialPort(File device, int baudrate, int parity, int dataBits, int stopBit,int flags)
    增加了parity,dataBits,stopBit 3个整形参数,并让原构造函数以默认参数调用新函数。
  3. 增加open函数的参数。同新构造函数增加的参数。
/*
 * Copyright 2009 Cedric Priscal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package cn.lizii.serialport;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.util.Log;

public class SerialPort {
    
    

	private static final String TAG = "SerialPort";

	/*
	 * Do not remove or rename the field mFd: it is used by native method close();
	 */
	private FileDescriptor mFd;
	private FileInputStream mFileInputStream;
	private FileOutputStream mFileOutputStream;

	public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException{
    
    
		this(device,baudrate,0,0,0,flags);
	}
	public SerialPort(File device, int baudrate, int parity, int dataBits, int stopBit,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");
				String cmd = "chmod 666 " + 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);
		mFd = open(device.getAbsolutePath(), baudrate, parity, dataBits, stopBit,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 InputStream getInputStream() {
    
    
		return mFileInputStream;
	}

	public OutputStream getOutputStream() {
    
    
		return mFileOutputStream;
	}

	// JNI
	private native static FileDescriptor open(String path, int baudrate, int parity, int dataBits, int stopBit,int flags);
	public native void close();
	static {
    
    
		System.loadLibrary("lzserial_port");
	}
}

SerialPort.c修改

这部分主要是jni的部分,代码也不多,主要open和close两个函数。本次修改的是open中的代码。
相比前文提到的大佬的修改,这里保留了flags和日志输出。极大限度增加了端口的属性设置和错误定位

/*
 * Copyright 2009-2011 Cedric Priscal
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "SerialPort.h"

#include "android/log.h"
static const char *TAG="serial_port";
#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 -1;
	}
}

static void throwException(JNIEnv *env, const char *name, const char *msg)
{
    
    
    jclass cls = (*env)->FindClass(env, name);
    /* if cls is NULL, an exception has already been thrown */
    if (cls != NULL) {
    
    
        (*env)->ThrowNew(env, cls, msg);
    }

    /* free the local ref */
    (*env)->DeleteLocalRef(env, cls);
}

/*
 * Class:     cn_lizii_serialport_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;IIIII)Ljava/io/FileDescriptor;
 */
JNIEXPORT jobject JNICALL Java_cn_lizii_serialport_SerialPort_open
  (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint parity, jint dataBits,jint stopBit, jint flags)

{
    
    
	int fd;
	speed_t speed;
	jobject mFileDescriptor;

	/* Check arguments */
	{
    
    
		speed = getBaudrate(baudrate);
		if (speed == -1) {
    
    
        		    throwException(env, "java/lang/IllegalArgumentException", "Invalid baudrate");
        			return NULL;
        		}
        		if (parity <0 || parity>2) {
    
    
        		    throwException(env, "java/lang/IllegalArgumentException", "Invalid parity");
                	return NULL;
        		}
                if (dataBits <5 || dataBits>8) {
    
    
        		    throwException(env, "java/lang/IllegalArgumentException", "Invalid dataBits");
                    return NULL;
                }
                if (stopBit <1 || stopBit>2) {
    
    
        		    throwException(env, "java/lang/IllegalArgumentException", "Invalid stopBit");
                    return NULL;
                }
	}

	/* Opening device */
	{
    
    
		jboolean iscopy;
		const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
		LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
		fd = open(path_utf, O_RDWR | flags);
		LOGD("open() fd = %d", fd);
		(*env)->ReleaseStringUTFChars(env, path, path_utf);
		if (fd == -1)
		{
    
    
			/* Throw an exception */
			LOGE("Cannot open port");
			throwException(env, "java/io/IOException", "Cannot open port");
			return NULL;
		}
	}


	/* Configure device */
    	{
    
    
    		struct termios cfg;
    		if (tcgetattr(fd, &cfg))
    		{
    
    
    			close(fd);
    		    throwException(env, "java/io/IOException", "tcgetattr() failed");
    			return NULL;
    		}

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

    		/* More attribute set */
    		switch (parity) {
    
    
    		    case 0: break;
    		    case 1: cfg.c_cflag |= PARENB; break;
    		    case 2: cfg.c_cflag &= ~PARODD; break;
    		}
    		switch (dataBits) {
    
    
    		    case 5: cfg.c_cflag |= CS5; break;
    		    case 6: cfg.c_cflag |= CS6; break;
    		    case 7: cfg.c_cflag |= CS7; break;
    		    case 8: cfg.c_cflag |= CS8; break;
    		}
    		switch (stopBit) {
    
    
    		    case 1: cfg.c_cflag &= ~CSTOPB; break;
    		    case 2: cfg.c_cflag |= CSTOPB; break;
    		}
            int rc = tcsetattr(fd, TCSANOW, &cfg);
            /*
    		if (rc)
    		{
    			close(fd);
    		    throwException(env, "java/io/IOException", strcat("tcsetattr() failed: ", rc));
    			return NULL;
    		}
    		*/
    	}

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

	return mFileDescriptor;
}

/*
 * Class:     cedric_serial_SerialPort
 * Method:    close
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_cn_lizii_serialport_SerialPort_close
  (JNIEnv *env, jobject thiz)
{
    
    
	jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
	jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

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

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

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

具体改动

  1. 包名修改为了cn.lizii.serialport,jni的函数签名也随之变化,使用的时候需要配套使用。
  2. 新增构造函数,增加了3个参数。
  3. open函数增加参数,增加了3个参数。
    private native static FileDescriptor open(String path, int baudrate, int parity, int dataBits, int stopBit,int flags);
    native 函数open参数变化:
    mFd = open(device.getAbsolutePath(), baudrate, parity, dataBits, stopBit,flags);
  4. 添加三个参数,并修改函数体,处理了这三个传入数值:
		/* More attribute set */
		switch (parity) {
    
    
		    case 0:
		        cfg.c_cflag &= ~PARENB;
		        cfg.c_cflag &= ~INPCK;
		        break;
		    case 1:
		        cfg.c_cflag |= PARENB;
		        cfg.c_cflag |= PARODD;
		        cfg.c_cflag |= INPCK;
		        break;
		    case 2:
		        cfg.c_cflag |= PARENB;
		        cfg.c_cflag &= ~PARODD;
		        cfg.c_cflag |= INPCK;
		        break;
		}
 
		cfg.c_cflag &= ~CSIZE;
		switch (dataBits) {
    
    
		    case 5: cfg.c_cflag |= CS5; break;
		    case 6: cfg.c_cflag |= CS6; break;
		    case 7: cfg.c_cflag |= CS7; break;
		    case 8: cfg.c_cflag |= CS8; break;
		}
 
		switch (stopBit) {
    
    
		    case 1: cfg.c_cflag &= ~CSTOPB; break;
		    case 2: cfg.c_cflag |= CSTOPB; break;
		}
 
		tcsetattr(fd, TCSANOW, &cfg);

jni编译

我们改了包名之后,用javah导出对应的jni函数签名,并同步到c文件。改完之后之后,使用ndk-build命令编译,对应此项目,windows和mac都很容易,下载ndk,解压后cd到jni目录执行ndk-build,如果命令识别不了,直接把ndk路径加上:ndkpath/ndk-build即可,当然也可以将ndkpath添加到环境变量path中。
这里要注意,高版本(ndk-r21e)的ndk已经不支持armeabi架构,因此修改Application.mk文件

APP_ABI := armeabi-v7a arm64-v8a x86

即去掉armeabi,增加arm64-v8a
so库名称加了lz前缀(SerialPort.java和Android.mk)。

Android 串口基本操作流程

  1. 在jni中打开一个文件描述,并设置相关的属性
  2. java层面通过文件描述获得一个输入输出流
  3. 用输入流读取串口的数据
  4. 用输出流向串口写入数据

关键代码

更多可从termios.h了解

  1. fd = open(path_utf, O_RDWR | flags);
    从这个地方可以看出,fd是一个文件“指针”,path_utf为串口地址(端口),O_RDWR 为文件操作模式,flags可以增加额外的操作模式,因此flags是有用的。只是多数情况我们用不上。
  2. tcgetattr(fd, &cfg)
    获取文件属性
  3. cfmakeraw(&cfg);
  4. cfsetispeed(&cfg, speed);
    设置写入速度
  5. cfsetospeed(&cfg, speed);
    设置输出速度
  6. cfg.c_cflag
    相关的校验都是修改cfg.c_cflag 完成

后续补充相关的详细定义

猜你喜欢

转载自blog.csdn.net/lanlangaogao/article/details/131585178