android串口编程

最近在Android项目中要使用到串口编程,开始的时候为了省事,直接把以前在linux下用纯C写得串口程序封装成so库,再在JNI中调用so库,一点也没有问题。

虽说没有什么问题,总觉得在JAVA中使用纯C实现串口所有的操作很像是在“挂羊头卖狗肉”,而且也有点繁琐,想说JAVA应该把这些东西直接封装成API,于是在网上查资料,想找到类似于windows下的CreateFile的API接口,未果。

还好JAVA之中有个FileDescriptor类,可以把串口当作一个FileDescriptor,打开之后操作FileDescriptor就相当于操作串口。

不管是windows、linux、或是android操作系统,串口编程无非是以下几步:

  1. 打开串口
  2. 串口配置
  3. 串口操作(读写)
  4. 关闭串口

这是典型的流驱动设备,操作很简单,在C语言里无非就是open、write、read、close几个操作就能轻松搞定的东西。这跟文件的输入输出流其实是一个概念,所以在windows中可以用CreateFile操作串口,同样的道理,在java中可以使用FileDescriptor操作串口。

下面是android 串口编程的具体步骤,先创建一个串口类SerialPort:

[java] view plain copy

  1. private static final String TAG = "SerialPort";
  2. /*
  3. * Do not remove or rename the field mFd: it is used by native method close();
  4. */
  5. private FileDescriptor mFd;
  6. private FileInputStream mFileInputStream;
  7. private FileOutputStream mFileOutputStream;
  8. public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
  9. /* Check access permission */
  10. if (!device.canRead() || !device.canWrite()) {
  11. try {
  12. /* Missing read/write permission, trying to chmod the file */
  13. Process su;
  14. su = Runtime.getRuntime().exec("/system/bin/su");
  15. String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
  16. + "exit\n";
  17. su.getOutputStream().write(cmd.getBytes());
  18. if ((su.waitFor() != 0) || !device.canRead()
  19. || !device.canWrite()) {
  20. throw new SecurityException();
  21. }
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. throw new SecurityException();
  25. }
  26. }
  27. mFd = open(device.getAbsolutePath(), baudrate, flags);
  28. if (mFd == null) {
  29. Log.e(TAG, "native open returns null");
  30. throw new IOException();
  31. }
  32. mFileInputStream = new FileInputStream(mFd);
  33. mFileOutputStream = new FileOutputStream(mFd);
  34. }
  35. // Getters and setters
  36. public InputStream getInputStream() {
  37. return mFileInputStream;
  38. }
  39. public OutputStream getOutputStream() {
  40. return mFileOutputStream;
  41. }
  42. // JNI
  43. private native static FileDescriptor open(String path, int baudrate, int flags);
  44. public native void close();
  45. static {
  46. System.loadLibrary("serial_port");
  47. }

这个类中主要干了两件事情:
创建了打开串口和关闭串口的本地方法

[java] view plain copy

  1. private native static FileDescriptor open(String path, int baudrate, int flags);
  2. public native void close();

关联串口的文件描述符(FileDescriptor),并把文件的输入输出流与之关联,在代码之中

[java] view plain copy

  1. private FileDescriptor mFd;
  2. private FileInputStream mFileInputStream;
  3. private FileOutputStream mFileOutputStream;
  4. mFd = open(device.getAbsolutePath(), baudrate, flags);
  5. mFileInputStream = new FileInputStream(mFd);
  6. mFileOutputStream = new FileOutputStream(mFd);

这段代码的意思就是把串口打开,并取得串口的输入输出操作(读写操作)。

到了这里,有个问题,如何打开串口?

代码上可知,打开串口的操作用的是一个本地方法open,那只能在JNI中实现这个接口,下面是jni中的实现:

[cpp] view plain copy

  1. static speed_t getBaudrate(jint baudrate)
  2. {
  3. switch(baudrate) {
  4. case 0: return B0;
  5. case 50: return B50;
  6. case 75: return B75;
  7. case 110: return B110;
  8. case 134: return B134;
  9. case 150: return B150;
  10. case 200: return B200;
  11. case 300: return B300;
  12. case 600: return B600;
  13. case 1200: return B1200;
  14. case 1800: return B1800;
  15. case 2400: return B2400;
  16. case 4800: return B4800;
  17. case 9600: return B9600;
  18. case 19200: return B19200;
  19. case 38400: return B38400;
  20. case 57600: return B57600;
  21. case 115200: return B115200;
  22. case 230400: return B230400;
  23. case 460800: return B460800;
  24. case 500000: return B500000;
  25. case 576000: return B576000;
  26. case 921600: return B921600;
  27. case 1000000: return B1000000;
  28. case 1152000: return B1152000;
  29. case 1500000: return B1500000;
  30. case 2000000: return B2000000;
  31. case 2500000: return B2500000;
  32. case 3000000: return B3000000;
  33. case 3500000: return B3500000;
  34. case 4000000: return B4000000;
  35. default: return -1;
  36. }
  37. }
  38. /*
  39. * Class:     android_serialport_SerialPort
  40. * Method:    open
  41. * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
  42. */
  43. JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
  44. (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
  45. {
  46. int fd;
  47. speed_t speed;
  48. jobject mFileDescriptor;
  49. /* Check arguments */
  50. {
  51. speed = getBaudrate(baudrate);
  52. if (speed == -1) {
  53. /* TODO: throw an exception */
  54. LOGE("Invalid baudrate");
  55. return NULL;
  56. }
  57. }
  58. /* Opening device */
  59. {
  60. jboolean iscopy;
  61. const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
  62. LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
  63. fd = open(path_utf, O_RDWR | flags);
  64. LOGD("open() fd = %d", fd);
  65. (*env)->ReleaseStringUTFChars(env, path, path_utf);
  66. if (fd == -1)
  67. {
  68. /* Throw an exception */
  69. LOGE("Cannot open port");
  70. /* TODO: throw an exception */
  71. return NULL;
  72. }
  73. }
  74. /* Configure device */
  75. {
  76. struct termios cfg;
  77. LOGD("Configuring serial port");
  78. if (tcgetattr(fd, &cfg))
  79. {
  80. LOGE("tcgetattr() failed");
  81. close(fd);
  82. /* TODO: throw an exception */
  83. return NULL;
  84. }
  85. cfmakeraw(&cfg);
  86. cfsetispeed(&cfg, speed);
  87. cfsetospeed(&cfg, speed);
  88. if (tcsetattr(fd, TCSANOW, &cfg))
  89. {
  90. LOGE("tcsetattr() failed");
  91. close(fd);
  92. /* TODO: throw an exception */
  93. return NULL;
  94. }
  95. }
  96. /* Create a corresponding file descriptor */
  97. {
  98. jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
  99. jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
  100. jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
  101. mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
  102. (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
  103. }
  104. return mFileDescriptor;
  105. }
  106. /*
  107. * Class:     cedric_serial_SerialPort
  108. * Method:    close
  109. * Signature: ()V
  110. */
  111. JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
  112. (JNIEnv *env, jobject thiz)
  113. {
  114. jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
  115. jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
  116. jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
  117. jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
  118. jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
  119. jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
  120. LOGD("close(fd = %d)", descriptor);
  121. close(descriptor);
  122. }

在Java_android_1serialport_1api_SerialPort_open接口中,能发现一些熟悉的东西:

[cpp] view plain copy

  1. fd = open(path_utf, O_RDWR | flags);
  2. tcgetattr(fd, &cfg)
  3. cfmakeraw(&cfg);
  4. cfsetispeed(&cfg, speed);
  5. cfsetospeed(&cfg, speed);

[cpp] view plain copy

  1. ...

这些都是在linux下的串口编程,分别是打开串口设备节点,配置数据流控、波特率等,这些东西其实不是重点,重点是如何把打开的串口设备与FileDescriptor关联,即把fd封装到FileDescriptor中去:

[cpp] view plain copy

  1. /* Create a corresponding file descriptor */
  2. {
  3. jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
  4. jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
  5. jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
  6. mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
  7. (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
  8. }
  9. return mFileDescriptor;

上面的代码简单解释来说就是mFileDescriptor与fd关联并返回给JAVA层。

至此,其实串口的JAVA封装已经完成,这个类其实已经可以完成串口的所有操作(打开,配置,读写,关闭),接下来的事情只是逻辑层面的事情了。

时间: 2024-10-15 02:56:02

android串口编程的相关文章

Android界面编程——导航栏及菜单(六)

Android界面编程--导航栏及菜单 2.7导航栏及菜单 2.7.1  ActionBar ActionBar是Android3.0(API 11)开始增加的新特性,ActionBar出现在活动窗口的顶部,可以显示标题.icon.Actions按钮.可交互View,可实现应用程序级的导航,如图2.7-1所示 图2.7-1 其中 1. App icon: 主要用于展示App的Logo,如果当前界面不是一级界面,还可以展示返回航. 2.View Control: 用于切换不同的视图或者展示非交互信

第6章 Android驱动编程

第6章  Android驱动编程 通过介绍本章设备驱动.字符设备驱动编程.GPIO驱动程序实例和4*4扫描键盘驱动等内容,熟练掌握了Android驱动编程.Android内核内核模块编程中包括设备驱动和内核模块.模块相关命令.Android内核内核模块编程和内核模块实例程序.Android内核中采用可加载的模块化设计,一般情况下编译的Android内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中.模块相关命令中lsmod列出了当前系统中加载的模块,rmmood用于当前模块卸载,in

Android多线程编程(一)——多线程基础

什么是进程 一个进程是一个独立(self contained)的运行环境,它可以看作一个程序或者一个应用. 什么是线程 而线程是进程中执行的一个任务,Java运行环境是一个包含了不同累和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. Android线程 Android的线程,实际上和Java的多线程编程并没有什么本质上的不同.当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如

Android网络编程网上文章总结

关于网络编程,网上也有许多好的文章,这里我就选了几篇觉得不错的归纳到了一起,仅供参考 Android网络编程概述 首先,应该了解的几个问题: 1)Android平台网络相关API接口 a) java.net.*(标准Java接口) java.net.*提供与联网有关的类,包括流.数据包套接字(socket).Internet协议.常见Http处理等.比如:创建URL,以及URLConnection/HttpURLConnection对象.设置链接参数.链接到服务器.向服务器写数据.从服务器读取数

Android网络编程系列 一 TCP/IP协议族

在学习和使用Android网路编程时,我们接触的仅仅是上层协议和接口如Apache的httpclient或者Android自带的httpURlconnection等等.对于这些接口的底层实现我们也有必要进一步的了解,这就要我们了解网络通信层了,提到网络通信层不得不说起ISO-OSI的七层协议经典架构,如图所示: 上图的左边部分就是osi架构模型了, ISO/OSI模型,即开放式通信系统互联参考模型(Open System Interconnection Reference Model),是国际标

《Qt on Android核心编程》勘误

我的第一本技术书籍<Qt on Android核心编程>已经出版上市,书中难免疏漏,存在一些文字和技术上的问题,特记录在此. 本文会不停更新,拿到书的朋友发现问题可以回复本文,我回将问题更新到博文中.提前感谢各位啦. <Qt on Android核心编程>的试读样章在这里,点击下载.购买链接在下面: china-pub 亚马逊 京东1 京东2 淘宝惊喜价 编排说明 本文将结合章节和问题发现的先后顺序来编排,具体是酱紫的:每个章节单列出来作为本文的一节,在该节下按时间顺序列出发现的问

《Qt on Android核心编程》介绍

<Qt on Android核心编程>终于尘埃落定,付梓印刷了. 封面 看看封面的效果吧,历经几版,最终就成了这个样子. 看下封皮: 这是立体版效果: 章节内容简介 第 1 章 欢迎来到Qt的世界,让我们看看Qt是什么,能给我们带来什么,又有谁在使用Qt.我们要约会的Qt on Android,它缘起何处,有着怎样曲折婉转的历史,如今的小模样能否让我们爱之如狂-- 第 2 章 当你遇见了合适的人,开始筹划第一次约会,又是兴奋又是惆怅,要不要买花,去哪里买礼物,穿什么衣服,洒什么香水,带什么应对

基础篇:6.Android数据库编程---SQLite

简介: 在Android平台上,继承了一个嵌入式关系型数据库---SQLite.SQLite具有跨平台特性,可以在所有主要的操作系统上运行.SQLite通过独占性和共享锁实现独立事务处理,因此多个进程可以在同一时间从同一数据库读取数据,但只有一个可以写入数据,在进行写操作之前,必须先获得独占锁.另一方面,SQLite采取动态数据类型,当某个值插入数据库时,SQLite会检查它的类型,如果该类型与所关联的列不匹配,SQLite则会进行强制转换.SQLite支持以下几种数据类型:NULL(空值).I

Linux 程序设计学习笔记----终端及串口编程基础之概念详解

转载请注明出处,谢谢! linux下的终端及串口的相关概念有: tty,控制台,虚拟终端,串口,console(控制台终端)详解 部分内容整理于网络. 终端/控制台 终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念. 1.终端 一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源. 终端有字符哑终端和图形终端两种. 控制台是另一种人机接口, 不通过终端与主机相连, 而是通过显示卡-显示器和键盘接口分别与主机相连, 这是人控制主机的第一人机接口.