zoukankan      html  css  js  c++  java
  • Android Studio的串口通讯开发

    基于android-serialport-api实现

    前言

    软件代码写久了,总会对嵌入式开发感兴趣,因为软件的东西写来写去看不见摸不着,而嵌入式硬件开发,可以捣鼓一些机械设备玩,电子感应灯,遥控车啥的,这也就是传说中的创客啊!后面索性买了一套单片机教程《手把手教你学51单片机》和板子学习,作为入门书来说,确实挺不错,配合上官方的板子,把单片机的基础原理都将的挺透彻,其中还包括了C语言的讲解,为嵌入式开发打下基础。

    共花了2个月把单片机学了一遍,最后觉得也没有想象中的难,因为其实到头来还是像在使用工具,只是知识体系会有些不一样,主要就是基于C/汇编语言通过单片机对寄存器、定时器、每个引脚的高低电平的使用,然后参考硬件手册,对每个硬件进行操作,以达到想要实现的效果。而如果想要实现自己的最小系统,或者弄出一些好玩的东西,还要自己根据物力电气原理选原件、画PCB板子,这就是硬件工程师要干的事情了。

    刚好公司项目里基于Android板子开发了一些串口通讯的应用,得益于之前单片机的学习,没有遇到太多困难,记录分享一下。

    RS232标准接口

    也就是PC电脑上所说的COM口,RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平。

    正常情况下,PC台式主机机箱都会有一个RS232的通讯接口(别和VGI的口搞错啦!),而目前笔记本几乎不会带有了,所以都是用USB转接口。

    UART

    及Universal Asynchronous Receiver Transmitter:通用异步收发器,通常ARM嵌入式板子都会集成此接口

    UART有4个pin(VCC, GND, RX, TX), 用的TTL电平, 低电平为0(0V),高电平为1(3.3V或以上)。

    RS232与UART转接

    通常嵌入式里所说的串口,是指UART口,但硬件众多,大多数都是基于RS232和UART,有时候这两种口之间需要通讯,最主要不同的其实也就是电平不一样,所以需要转接口,某宝上MAX3232种类繁多。

    下载 NDK 和构建工具

    由于是使用JNI直接进行串口设备的读写,所以需要下载工具。android studio版本务必要2.2以上
    运行SDK Manager,下载3个工具,CMake、LLDB、NDK
    PS:如果下载进度很慢,请使用国内镜像

    创建支持 C/C++ 的新项目



    在 Customize C++ Support 选项卡中。你有下面几种方式来自定义你的项目:

    1. C++ Standard:点击下拉框,可以选择标准 C++,或者选择默认 CMake 设置的 Toolchain Default 选项。
    2. Exceptions Support:如果你想使用有关 C++ 异常处理的支持,就勾选它。勾选之后,Android Studio 会在 module 层的 build.gradle 文件中的 cppFlags 中添加 -fexcetions 标志。
    3. Runtime Type Information Support:如果你想支持 RTTI,那么就勾选它。勾选之后,Android Studio 会在 module 层的 build.gradle 文件中的 cppFlags 中添加 -frtti 标志。


    新建成功之后,相对于以前的项目目录,多了几个地方。

    1. cpp 目录存放你所有 native code 的地方,包括源码,头文件,预编译项目等。对于新项目,Android Studio 创建了一个 C++ 模板文件:native-lib.cpp,并且将该文件放到了你的 app 模块的 src/main/cpp/ 目录下。这份模板代码提供了一个简答的 C++ 函数:stringFromJNI(),该函数返回一个字符串:”Hello from C++”。
    2. External Build Files 目录是存放 CMake 或 ndk-build 构建脚本的地方。有点类似于 build.gradle 文件告诉 Gradle 如何编译你的 APP 一样,CMake 和 ndk-build 也需要一个脚本来告知如何编译你的 native library。对于一个新的项目,Android Studio 创建了一个 CMake 脚本:CMakeLists.txt,并且将其放到了你的 module 的根目录下。
    3. gradle的脚本下也加入了externalNativeBuild字段来显示指定CMake。

    PS:如果现有项目需要添加C/C++ 代码,则参考项目模板添加相应文件即可。

    编译C/C++代码

    CMakeLists.txt

    1. # Sets the minimum version of CMake required to build the native
    2. # library. You should either keep the default value or only pass a
    3. # value of 3.4.0 or lower.
    4. cmake_minimum_required(VERSION 3.4.1)
    5. # Creates and names a library, sets it as either STATIC
    6. # or SHARED, and provides the relative paths to its source code.
    7. # You can define multiple libraries, and CMake builds it for you.
    8. # Gradle automatically packages shared libraries with your APK.
    9. add_library( # Sets the name of the library.
    10. serial_port
    11. # Sets the library as a shared library.
    12. SHARED
    13. # Provides a relative path to your source file(s).
    14. # Associated headers in the same location as their source
    15. # file are automatically included.
    16. src/main/cpp/SerialPort.c )
    17. # Searches for a specified prebuilt library and stores the path as a
    18. # variable. Because system libraries are included in the search path by
    19. # default, you only need to specify the name of the public NDK library
    20. # you want to add. CMake verifies that the library exists before
    21. # completing its build.
    22. find_library( # Sets the name of the path variable.
    23. log-lib
    24. # Specifies the name of the NDK library that
    25. # you want CMake to locate.
    26. log )
    27. # Specifies libraries CMake should link to your target library. You
    28. # can link multiple libraries, such as libraries you define in the
    29. # build script, prebuilt third-party libraries, or system libraries.
    30. target_link_libraries( # Specifies the target library.
    31. serial_port
    32. # Links the target library to the log library
    33. # included in the NDK.
    34. ${log-lib} )

    SerialPort.h

    1. /* DO NOT EDIT THIS FILE - it is machine generated */
    2. #include <jni.h>
    3. /* Header for class android_serialport_api_SerialPort */
    4. #ifndef _Included_android_serialport_api_SerialPort
    5. #define _Included_android_serialport_api_SerialPort
    6. #ifdef __cplusplus
    7. extern "C" {
    8. #endif
    9. /*
    10. * Class: android_serialport_api_SerialPort
    11. * Method: open
    12. * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
    13. */
    14. JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
    15. (JNIEnv *, jclass, jstring, jint, jint);
    16. /*
    17. * Class: android_serialport_api_SerialPort
    18. * Method: close
    19. * Signature: ()V
    20. */
    21. JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
    22. (JNIEnv *, jobject);
    23. #ifdef __cplusplus
    24. }
    25. #endif
    26. #endif

    SerialPort.c

    1. /*
    2. * Copyright 2009-2011 Cedric Priscal
    3. *
    4. * Licensed under the Apache License, Version 2.0 (the "License");
    5. * you may not use this file except in compliance with the License.
    6. * You may obtain a copy of the License at
    7. *
    8. * http://www.apache.org/licenses/LICENSE-2.0
    9. *
    10. * Unless required by applicable law or agreed to in writing, software
    11. * distributed under the License is distributed on an "AS IS" BASIS,
    12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13. * See the License for the specific language governing permissions and
    14. * limitations under the License.
    15. */
    16. #include <termios.h>
    17. #include <unistd.h>
    18. #include <sys/types.h>
    19. #include <sys/stat.h>
    20. #include <fcntl.h>
    21. #include <string.h>
    22. #include <jni.h>
    23. #include "SerialPort.h"
    24. #include "android/log.h"
    25. static const char *TAG="serial_port";
    26. #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
    27. #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
    28. #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
    29. static speed_t getBaudrate(jint baudrate)
    30. {
    31. switch(baudrate) {
    32. case 0: return B0;
    33. case 50: return B50;
    34. case 75: return B75;
    35. case 110: return B110;
    36. case 134: return B134;
    37. case 150: return B150;
    38. case 200: return B200;
    39. case 300: return B300;
    40. case 600: return B600;
    41. case 1200: return B1200;
    42. case 1800: return B1800;
    43. case 2400: return B2400;
    44. case 4800: return B4800;
    45. case 9600: return B9600;
    46. case 19200: return B19200;
    47. case 38400: return B38400;
    48. case 57600: return B57600;
    49. case 115200: return B115200;
    50. case 230400: return B230400;
    51. case 460800: return B460800;
    52. case 500000: return B500000;
    53. case 576000: return B576000;
    54. case 921600: return B921600;
    55. case 1000000: return B1000000;
    56. case 1152000: return B1152000;
    57. case 1500000: return B1500000;
    58. case 2000000: return B2000000;
    59. case 2500000: return B2500000;
    60. case 3000000: return B3000000;
    61. case 3500000: return B3500000;
    62. case 4000000: return B4000000;
    63. default: return -1;
    64. }
    65. }
    66. /*
    67. * Class: android_serialport_SerialPort
    68. * Method: open
    69. * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
    70. */
    71. JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
    72. (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
    73. {
    74. int fd;
    75. speed_t speed;
    76. jobject mFileDescriptor;
    77. /* Check arguments */
    78. {
    79. speed = getBaudrate(baudrate);
    80. if (speed == -1) {
    81. /* TODO: throw an exception */
    82. LOGE("Invalid baudrate");
    83. return NULL;
    84. }
    85. }
    86. /* Opening device */
    87. {
    88. jboolean iscopy;
    89. const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
    90. LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
    91. fd = open(path_utf, O_RDWR | flags);
    92. LOGD("open() fd = %d", fd);
    93. (*env)->ReleaseStringUTFChars(env, path, path_utf);
    94. if (fd == -1)
    95. {
    96. /* Throw an exception */
    97. LOGE("Cannot open port");
    98. /* TODO: throw an exception */
    99. return NULL;
    100. }
    101. }
    102. /* Configure device */
    103. {
    104. struct termios cfg;
    105. LOGD("Configuring serial port");
    106. if (tcgetattr(fd, &cfg))
    107. {
    108. LOGE("tcgetattr() failed");
    109. close(fd);
    110. /* TODO: throw an exception */
    111. return NULL;
    112. }
    113. cfmakeraw(&cfg);
    114. cfsetispeed(&cfg, speed);
    115. cfsetospeed(&cfg, speed);
    116. //此处设置校验位
    117. //cfg.c_cflag……
    118. if (tcsetattr(fd, TCSANOW, &cfg))
    119. {
    120. LOGE("tcsetattr() failed");
    121. close(fd);
    122. /* TODO: throw an exception */
    123. return NULL;
    124. }
    125. }
    126. /* Create a corresponding file descriptor */
    127. {
    128. jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
    129. jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
    130. jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
    131. mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
    132. (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
    133. }
    134. return mFileDescriptor;
    135. }
    136. /*
    137. * Class: cedric_serial_SerialPort
    138. * Method: close
    139. * Signature: ()V
    140. */
    141. JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
    142. (JNIEnv *env, jobject thiz)
    143. {
    144. jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
    145. jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
    146. jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    147. jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
    148. jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
    149. jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
    150. LOGD("close(fd = %d)", descriptor);
    151. close(descriptor);
    152. }

    最后java在需要的地方调用

    1. // JNI
    2. private native static FileDescriptor open(String path, int baudrate, int flags);
    3. public native void close();
    4. static {
    5. Log.i(TAG, "loadLibrary..............");
    6. System.loadLibrary("serial_port");
    7. }

    串口通讯原理

    使用到了linux系统函数,是相对底层的开发,所以一大堆标志位,位运算,看着有些晕,函数的具体使用方法需要查询手册
    1、打开串口

    1. fd = open(path_utf, O_RDWR | flags);

    其中
    O_RDWR 读写方式打开
    O_NOCTTY 不允许进程管理串口
    O_NDELAY 非阻塞

    2、写串口

    1. n = write(fd, "ATZ ", 4);

    n实际写入个数

    3、设置串口为非阻塞方式

    1. fcntl(fd, F_SETFL, FNDELAY);

    4、设置串口为阻塞方式:

    1. fcntl(fd, F_SETFL, 0);

    5、读串口:

    1. res = read(fd,buf,len);

    6、关闭串口

    1. Close(fd);

    注:示例代码中通过JAVA的FileDescriptor类包装了数据流,所以读写操作是在上层完成的。

    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;

    关于校验位

    示例代码默认是没有添加任何校验位的,一般情况设备的基础通讯协议都是
    波特率:9600
    校验位:无
    数据位:8
    停止位:1位
    至于具体的指令协议,根据设备提供商文档自行写控制代码即可。
    校验位设置代码如下:
    No parity (8N1):

    1. cfg.c_cflag &= ~PARENB
    2. cfg.c_cflag &= ~CSTOPB
    3. cfg.c_cflag &= ~CSIZE;
    4. cfg.c_cflag |= CS8;

    Even parity (7E1):

    1. cfg.c_cflag |= PARENB
    2. cfg.c_cflag &= ~PARODD
    3. cfg.c_cflag &= ~CSTOPB
    4. cfg.c_cflag &= ~CSIZE;
    5. cfg.c_cflag |= CS7;

    Odd parity (7O1):

    1. cfg.c_cflag |= PARENB
    2. cfg.c_cflag |= PARODD
    3. cfg.c_cflag &= ~CSTOPB
    4. cfg.c_cflag &= ~CSIZE;
    5. cfg.c_cflag |= CS7;

    Space parity is setup the same as no parity (7S1):

    1. cfg.c_cflag &= ~PARENB
    2. cfg.c_cflag &= ~CSTOPB
    3. cfg.c_cflag &= ~CSIZE;
    4. cfg.c_cflag |= CS8;
    5. //cfg.c_cflag |= PARENB | CS8 | CMSPAR;

    Mark parity is simulated by using 2 stop bits (7M1):

    1. cfg.c_cflag &= ~PARENB;
    2. cfg.c_cflag |= CSTOPB;
    3. cfg.c_cflag &= ~CSIZE;
    4. cfg.c_cflag |= CS7;
    5. //cfg.c_cflag |= PARENB | CS8 | CMSPAR |PARODD;

    1.even 每个字节传送整个过程中bit为1的个数是偶数个(校验位调整个数)
    2.odd 每个字节穿送整个过程中bit为1的个数是奇数个(校验位调整个数)
    3.noparity没有校验位
    4.space 校验位总为0
    5.mark 校验位总为1;

    HexString与Bytes的转换

    默认情况下串口读出来的数据是byte[],有时候需要转换16进制的字符串进行指令识别

    1. /**
    2. * Created by lixin(178078114@qq.com) on 2016/12/17.
    3. */
    4. public class HexUtils {
    5. /**
    6. * 把字节数组转换成16进制字符串
    7. *
    8. * @param bArray
    9. * @return
    10. */
    11. public static String bytesToHexString(byte[] src) {
    12. StringBuilder stringBuilder = new StringBuilder("");
    13. if (src == null || src.length <= 0) {
    14. return null;
    15. }
    16. for (int i = 0; i < src.length; i++) {
    17. int v = src[i] & 0xFF;
    18. String hv = Integer.toHexString(v);
    19. if (hv.length() < 2) {
    20. stringBuilder.append(0);
    21. }
    22. stringBuilder.append(hv);
    23. }
    24. return stringBuilder.toString().toUpperCase();
    25. }
    26. /**
    27. * 把16进制字符串转换成字节数组
    28. *
    29. * @param hex
    30. * @return
    31. */
    32. public static byte[] hexStringToByte(String hex) {
    33. int len = (hex.length() / 2);
    34. byte[] result = new byte[len];
    35. char[] achar = hex.toCharArray();
    36. for (int i = 0; i < len; i++) {
    37. int pos = i * 2;
    38. result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
    39. }
    40. return result;
    41. }
    42. private static byte toByte(char c) {
    43. byte b = (byte) "0123456789ABCDEF".indexOf(c);
    44. return b;
    45. }
    46. }

    最终的demo如图,选择对了参数和串口设备,即可调试了!

    参考

    http://gqdy365.iteye.com/blog/2188906
    http://wl9739.github.io/2016/09/21/%E5%9C%A8-Android-Studio-2-2-%E4%B8%AD%E6%84%89%E5%BF%AB%E5%9C%B0%E4%BD%BF%E7%94%A8-C-C-md/

    demo示例:
    链接:http://pan.baidu.com/s/1pLc1JVt 密码:h1zt

  • 相关阅读:
    Annotation
    GIT的作用以及Versioncontrol为什么要用GIT
    Http协议
    人工智能的可怕与不可怕
    Makefile简易教程
    Node.js 学习笔记之一:学习规划 & 认知 Node.js
    《大教堂与集市》的启示 — 软件工程的另一种选择
    Git简易教程
    谈谈买书与读书
    clang编译器简介
  • 原文地址:https://www.cnblogs.com/leestar54/p/6604636.html
Copyright © 2011-2022 走看看