# SerralTool **Repository Path**: jackso/SerralTool ## Basic Information - **Project Name**: SerralTool - **Description**: android串口通讯google有个开源修改的串口调试工具,ATM出钞模块。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2019-10-16 - **Last Updated**: 2024-11-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SerralTool #### 介绍 android串口通讯google有个开源修改的串口调试工具,ATM出钞模块。 最近业余时间有个android项目需要和硬件进行通信,通讯方式都是通过串口,android串口通讯google有个开源的demo 和很多人一样我也是通过下载这个demo进行开发和研究的。 [google android串口通讯开源demo地址](https://code.google.com/archive/p/android-serialport-api/) 串口通讯中我们可能会要设置校验位、速率、停止位等参数,但一般情况下还是不用设置的,只需要设置波特率就行。google提供的demo中就只提供一个波特率的设置其他的参数一并没有提供。 **在使用google 源码的时候一定要注意 jni 当中.h和.c文件中的方法命名的规则,是java关键字+包名+类名+方法名。一开始我没注意,程序报错走了好多弯路。所以画图具体解释下。** ![image](https://images.gitee.com/uploads/images/2019/1016/151244_19d5f2c1_1013670.png "554879-20161022170140498-146798985.png") android串口通讯说到底其实还是linux串口通讯。我们jni中的c代码其实就是操作linux中提供的串口文件。一开始并不知道这个原理,只是后来在网上找如何设置校验位、速率等问题时才明白的。 比如打开串口 fd = open("/dev/ttyS3", O_RDWR | O_NOCTTY | O_NDELAY); 其实就是打开linux设备中串口文件。串口文件都是以ttys开头 如ttyS3等。这个fd很重要,应该就是代表当前串口文件对象。只要这里打开成功了,C代码其他地方对串口的操作以及配置都是基于这个fd来进行的。 在adb 中可以用 cd dev来查看是否有串口文件。如果看到了 ttyS3等这样的文件说明目前设备上有串口否则没有找到串口设备。 adb 中通过命令向串口发送数据:echo -e "AT\r\n">/dev/ttyS3 顺便说下 如何在模拟器中使用电脑上的串口 这个只能使用android自带的模拟器。 一直想如果第三方模拟器能加载电脑的串口设备就好了。 emulator @模拟器名称 -qemu -serial COM1 // 这个命令会自动启动安卓模拟器 adb shell su //使用非系统进程通过su命令可以将自身提权到root权限代码 ls //查看设备目录,是否有ttyS3这个设备 cd /dev //进入设备号 chmod 777 ttyS3 //提示权限 串口设备文件 ttyS3 废话不多说,下面附上完整的代码。 注:代码是google的源代码+网上找的一些参考资料=现在的一个整合代码。 注意C和h代码中的数据类型,尽量以jint、jstring等来定义。(jint相当于java的int...)因为要被java调用,所以做好这样定义 .h代码(用于jni方法声明 即要在java中调用的方法声明)注意包名+类名+方法名 SerialPort.h ``` /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class android_serialport_api_SerialPort */ #ifndef _Included_android_serialport_api_SerialPort #define _Included_android_serialport_api_SerialPort #ifdef __cplusplus extern "C" { #endif /* * Class: android_serialport_api_SerialPort * Method: open * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; */ JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open (JNIEnv *, jclass, jstring, jint, jint); /* * Class: android_serialport_api_SerialPort * Method: close * Signature: ()V */ JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif ``` .C代码(用于jni方法的具体实现代码本例中串口操作的所有代码)注意包名+类名+方法名 **注意C代码中 写一个方法,一定要写到调用地方的前面。或者前面写声明;即 A要调用B方法,B方法代码必须要在A方法前面。由于平时写java或者c#这些代码是不需要这样的写法。一时切换到C代码可能容易忘记。** ``` #include #include #include #include #include #include #include #include #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) int fd; 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; } } /** * 设置串口数据,校验位,速率,停止位 * @param nBits 类型 int数据位 取值 位7或8 * @param nEvent 类型 char 校验类型 取值N ,E, O,,S * @param mStop 类型 int 停止位 取值1 或者 2 */ int set_opt(jint nBits, jchar nEvent, jint nStop) { LOGE("set_opt:nBits=%d,nEvent=%c,nSpeed=%d,nStop=%d", nBits, nEvent, nStop); LOGE("set_opt:nStop=%d", nStop); struct termios newtio; if(tcgetattr(fd, & newtio) != 0) { LOGE("setup serial failure"); return -1; } bzero( & newtio, sizeof(newtio)); //c_cflag标志可以定义CLOCAL和CREAD,这将确保该程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。CLOCAL和CREAD通常总是被是能的 newtio.c_cflag |= CLOCAL | CREAD; switch(nBits) //设置数据位数 { case 7: LOGE("设置数据位数==7"); newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS7; break; case 8: LOGE("设置数据位数==8"); newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS8; break; default: newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS8; LOGE("设置数据位数==默认=8"); break; } switch(nEvent) //设置校验位 { case 'o': case 'O': newtio.c_cflag |= (PARODD | PARENB); newtio.c_iflag |= INPCK; LOGE("设置校验位O奇校验位"); break; case 'e': case 'E': newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; newtio.c_iflag |= INPCK; LOGE("设置校验位E偶校验位"); break; case 'N': case 'n': newtio.c_cflag &= ~PARENB; //清除校验位 LOGE("设置校验位N"); break; default: newtio.c_cflag &= ~PARENB; //清除校验位 LOGE("设置校验位默认N"); break; } switch(nStop) //设置停止位 { case 1: newtio.c_cflag &= ~CSTOPB; break; case 2: newtio.c_cflag |= CSTOPB; break; default: newtio.c_cflag &= ~CSTOPB; // LOGW("nStop:%d,invalid param", nStop); break; } newtio.c_cc[VTIME] = 0;//设置等待时间 newtio.c_cc[VMIN] = 0;//设置最小接收字符 tcflush(fd, TCIFLUSH); if(tcsetattr(fd, TCSANOW, & newtio) != 0) { LOGE("options set error"); return -1; } LOGE("options set success"); return 1; } /* * Class: android_serialport_SerialPort * Method: open * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; */ JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint databits, jint stopbits, jchar parity) { speed_t speed; jobject mFileDescriptor; /*波特率 */ { speed = getBaudrate(baudrate); if (speed == -1) { /* TODO: throw an exception */ LOGE("Invalid baudrate"); return NULL; } } /* Opening device */ { jint flags = 0; 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 | O_NONBLOCK); //fd=fd; LOGD("open() fd = %d", fd); (*env)->ReleaseStringUTFChars(env, path, path_utf); if (fd == -1) { /* Throw an exception */ LOGE("Cannot open port"); /* TODO: throw an exception */ return NULL; } } /* Configure device */ { struct termios cfg; LOGD("Configuring serial port"); if (tcgetattr(fd, &cfg)) { LOGE("tcgetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } cfmakeraw(&cfg); //设置波特率 cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) { LOGE("tcsetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } //配置校验位 停止位等等 set_opt(databits, parity, stopbits); } /* Create a corresponding file descriptor */ { jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()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_android_1serialport_1api_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); } ``` java代码 调用jni提供的方法进行串口操作 ``` /* * 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 android_serialport_api; import android.util.Log; 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; 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 dataBits, int stopBits, char parity) throws SecurityException, IOException { // mFd = open(device.getAbsolutePath(), baudrate, flags); mFd = open(device.getAbsolutePath(), baudrate, dataBits, stopBits, parity); if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { // mFd = open(device.getAbsolutePath(), baudrate, flags); mFd = open(device.getAbsolutePath(), baudrate, 8, 2, 'E'); 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; } /** * * @param path 串口号 * @param baudrate 波特率 * @param dataBits 起始位 * @param stopBits 停止位 取值1 或者 2 * @param parity 校验类型 取值N ,E, O,,S * @return */ private native static FileDescriptor open(String path, int baudrate, int dataBits, int stopBits, char parity); public native void close(); static { System.loadLibrary("serial_port"); } } ```