android4.0下serial port给应用操作完成特殊定制
我们在开发中,串口也就是serialport或者叫uart用的是相当频繁的,很普通的接口了,今天为什么在这提出来呢?笔者前年完成了一款android4.0平台的车载平板产品,客户外接了一个DTV,我们在android这边通过GPIO模拟IR来控制DTV盒子的。客户前面也做了特殊的一些应用,可以通过wifi跟服务器连。服务器通过wifi网络发送控制命令给车载平板机器,但是客户反馈在wifi 在heavy WiFi trafic / interferences时,丢包率很高,达到50%以上,造成很多控制命令丢失。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/edsam49原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
其实通过网络传输,在网络堵塞的时候,丢包率高这是很正常的问题,怎么不能通过控制好发送命令再确认ack,如果约定时间内没收到就继续重发呢?不知道老外怎么想的。由于给他做的控制DTV的GPIO模拟IR接口很好用,很稳定,因此客户想通过串口去控制DTV,同时有些控制信息就走DVB网络,应该没那么堵塞,再通过DTV盒子通过串口回传给android车载平台机器,这样也确实是可行。由于目前产品早已经成形,客户也不想把通过串口的数据让我们来解析,所以我们就只要提供一条操作串口的库给上层就可以了,这样相对对我们来说也简单了,只要打通串口,上层能收发串口数据就可以了。
从android4.1起,android系统提供了操作串口的service,但是笔者以前是基于android4.0的,怎么办呢?参考android4.1以后的系统操作控制方式,主要思路还是提供一个操作串口的文件句柄给应用上层使用。但是笔者觉得不大好的,android提供的serial service没有提供关闭串口的接口,这样如果频繁关闭打开的话会存在很多句柄没关闭的情况。当然,如果只在service里面打开的话也没问题,如果在上层打开的话可能就会存在问题。下面笔者就带你一起游历这个过程吧!
第一步,肯定是要搞定驱动。串口驱动目前来说,每个平台基本都是做好了,直接做好配置打开就可以了,笔者使用的全志平台,配置如下:
;---------------------------------------------------------------------------------- ;uart configuration ;uart_type --- 2 (2 wire), 4 (4 wire), 8 (8 wire, full function) ;---------------------------------------------------------------------------------- [uart_para0] uart_used = 1 uart_port = 0 uart_type = 2 uart_tx =port:PB22<2><1><default><default> uart_rx =port:PB23<2><1><default><default>
第二步,确认串口设备文件的权限,需要有system的权限,所以笔者把它配置成666了。笔者使用的是打印串口做的测试,因此是ttyS0设备文件。
第三步,写一个JNI文件,提供一个库给应用调用了,因为我们不可能给他做一个系统service来使用了,以最小的代价来搞定,这样的话使用NDK也好,可以直接用一个apk就可以完成了。在这个JNI文件里,笔者提供了两个接口,一个open,一个close,刚好形成最小闭环循环。代码也不难,涉及一点JNI基本知识,如下:
extern "C" JNIEXPORT jobject JNICALL Java_com_jeavox_ttys_utils_SerialPort_open (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint flags) { int fd; speed_t speed; jobject mFileDescriptor; /* Check arguments */ { speed = getBaudrate(baudrate); if (speed == -1) { /* TODO: throw an exception */ LOGE("Invalid baudrate"); return NULL; } } /* Opening device */ { jboolean iscopy; const char *path_utf = env->GetStringUTFChars(path, NULL); LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); fd = open(path_utf, O_RDWR | flags); env->ReleaseStringUTFChars(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; } } /* 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_jeavox_ttys_utils_SerialPort_close (JNIEnv *env, jobject thiz) { jclass SerialPortClass = env->GetObjectClass(thiz); 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( thiz, mFdID); jint descriptor = env->GetIntField( mFd, descriptorID); close(descriptor); }
第四步,到应用层去接应JNI库,这里很重要的是使用了FileInputStream跟FileOutputStream。在FileOutputStream使用上有一点要注意,就是目前FileOutputStream提供的三个read接口都是阻塞的,会一直在那等数据,但是另外还提供了一个available接口,这个接口是用来提示有多少数据现在可以读取,这样我们去读的时候,就有目的性了,挺好的。
private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { 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 setters public InputStream getInputStream() { return mFileInputStream; }也是在一个thread里面去读数据的,
private class ReadThread extends Thread { @Override public void run() { super.run(); while (!isInterrupted()) { int size; try { byte[] buffer = new byte[64]; if (mInputStream == null) return; if(mInputStream.available() > 0 ){ size = mInputStream.read(buffer); } else{ continue; } if (size > 0) { onDataReceived(buffer, size); } } catch (IOException e) { e.printStackTrace(); return; } } } }
第五步,当然是起应用去call相应接口了。
mSerialPort = new SerialPort(new File("/dev/ttyS0"), 115200, 0);这边调用,也就是设置好设备文件已经波特率,其他flag先不传了。其他应用代码未贴,基本的界面都不难搞的。
第六步,就看看应用的效果吧!我从串口输入了一些字符,在应用上读出来显示出来。
整个过程还是不算难搞的,整个过程参考了网上一些大侠的代码,参考了android4.4的代码实现,同时得到了做应用的同事协助完成整个调试。在此看透这个问题,android4.1以后的serialservice只不过是把它写成一个系统service的形式供应用使用。总之,算交差了,通道建好了,客户自己爱怎么折腾就怎么折腾吧!