android4.0下serial port给应用操作完成特殊定制

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设备文件。

 android4.0下serial port给应用操作完成特殊定制_第1张图片

     第三步,写一个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.0下serial port给应用操作完成特殊定制_第2张图片

      整个过程还是不算难搞的,整个过程参考了网上一些大侠的代码,参考了android4.4的代码实现,同时得到了做应用的同事协助完成整个调试。在此看透这个问题,android4.1以后的serialservice只不过是把它写成一个系统service的形式供应用使用。总之,算交差了,通道建好了,客户自己爱怎么折腾就怎么折腾吧!

 

你可能感兴趣的:(android4.0下serial port给应用操作完成特殊定制)