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的形式供应用使用。总之,算交差了,通道建好了,客户自己爱怎么折腾就怎么折腾吧!