我的Android NDK之旅(四),android串口通信-mac+串口调试工具

转载请注明出处:(http://blog.csdn.net/qq_35071078/article/details/73065046
本篇博客主要内容是在mac上,使用串口调试工具等软件,来实现与android开发板的数据通信。

一些关于串口的知识

什么是串口

串口是计算机上一种非常通用设备通信的协议,不要与通用串行总线Universal Serial Bus(USB)混淆。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。

串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总常不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。

典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:
a. 波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率。例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
b. 数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
c. 停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
d. 奇偶校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。

/dev 目录下面的文件是什么

dev是设备(device)的英文缩写。/dev这个目录对所有的用户都十分重要。因为在这个目录中包含了所有linux(macOS也类似)系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和windows,dos操作系统不一样。它实际上是一个访问这些外部设备的端口。我们可以非常方便地去访问这些外部设备,和访问一个文件,一个目录没有任何区别。比如说我们的串口文件ttyS0就在这个目录下面详细请点击

串行端口终端(/dev/ttySn)

串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间这些串行端口设备通常被称为终端设备,因为 那时它的最大用途就是用来连接终端。这些串行端口所对应的设备名称是/dev/tts/0(或/dev/ttyS0), /dev/tts/1(或/dev/ttyS1)等,设备号分别是(4,0), (4,1)等,分别对应于DOS系统下的COM1、COM2等。若要向一个端口发送数据,可以在命令行上把标准输出重定向到这些特殊文件名上即可。例如, 在命令行提示符下键入:echo test > /dev/ttyS1会把单词”test”发送到连接在ttyS1(COM2)端口的设备上。所以说,我们的串口通信实际上也就是往这些文件中写入数据或者是接收数据。

什么是android串口通信

简单来说,就是在java层调用c或者是c++层的代码,来操控底层的串口文件,比如说ttyS0。因为串口通信主要是对串口文件的读写操作

什么是FileDescriptor

文件描述符,文件描述符类的实例作为一个不透明句柄到底层机器特定的结构表示一个打开的文件,一个开放的插座,或字节的另一个源或宿。对于一个文件描述符的主要实际用途是创建一个 FileInputStream或FileOutputStream遏制它。上面说了,android串口通信就是操作串口文件,但是串口文件不能直接操作,是要通过FileDescriptor这个对象来操作串口文件。android上的串口通信就是对这个对象进行操作。

串口通信示例

我的设备 mac + android开发板 + RS232usb转串口的线一根

这里写图片描述

首先,得下载usb转串口驱动下载和安装驱动请参考这篇博客(只需要参考安装驱动即可,不用管后面的安装SecureCRT串口调试工具),安装成功会在/dev下看到:

这里写图片描述

然后下载串口调试工具,这个是用来与android开发板进行通讯的,下载请轻参考这篇博客

串口调试工具用法很简单,首先点击Options来设置参数

这里写图片描述

接着,如果我们之前usb转串口驱动安装成功了的话,这里会显示有usbserial选项,选择这个,点击ok,再点击Connect,正常情况下会显示Connected,也就是连接成功,如果没有连接成功,那肯定是某些操作没有做好。

这里写图片描述

连接成功后就可以向android开发板发送数据了,不过现在我们还没有完成android的代码,所以发送了,android端也接收不到。

这里写图片描述

这里写图片描述

接下来就是android端代码的编写了,下面是最核心的代码。具体的代码点击下载

SerialPort.c

这个c文件是整个串口通信的关键,主要是用来打开串口和关闭串口,在百度上面搜索andoid串口通信可以发现基本上所有的示例都是用的这个文件或者说是代码,我也是用的这个,只不过稍微修改了点或者说是简化了点:

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>


#include "android/log.h"

static const char *TAG = "serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

/**
 * 获取相应波特率
 * @param baudrate 波特率
 * @return
 */
static speed_t getBaudrate(jint baudrate) {
    switch (baudrate) {
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 115200:
            return B115200;
        default:
            return -1;
    }
}

/*
 * Class:     android_serialport_SerialPort
 * Method:    打开串口,主要就是返回FileDescriptor这个对象,供java层调用。
 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
 */
JNIEXPORT jobject JNICALL Java_com_chenxin_testserialport_utils_SerialPort_open
        (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) {
    int fd;
    speed_t speed;
    jobject mFileDescriptor;

    /* 检查参数 */
    {
        speed = getBaudrate(baudrate);
        if (speed == -1) {
            /* TODO: throw an exception */
            LOGE("Invalid baudrate");
            return NULL;
        }
    }

    /* 打开设备 */
    {
        jboolean iscopy;
        const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
        LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
        fd = open(path_utf, O_RDWR | flags );//加上 O_NDELAY 参数,读取数据是异步的,不是阻塞的。目前没有加上此参数,所以说读取数据是阻塞的方式,具体加不加视情况而定。
        LOGD("open() fd = %d", fd);
        (*env)->ReleaseStringUTFChars(env, path, path_utf);
        if (fd == -1) {
            /* Throw an exception */
            LOGE("Cannot open port");
            /* TODO: throw an exception */
            return NULL;
        }

    }

    /* 设置参数 */
    {
        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;
        }
    }

    /* 创建一个 descriptor,用来操作这个串口文件 */
    {
        jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
        jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
        jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
        mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
        (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint) fd);
    }

    return mFileDescriptor;
}

/*
 * Class:     cedric_serial_SerialPort
 * Method:    关闭串口
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_chenxin_testserialport_utils_SerialPort_close
        (JNIEnv *env, jobject thiz) {
    jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
    jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

    jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");

    jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
    jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);
}

可以看到,这个段c层的代码只有两个方法,一个是打开串口,一个是关闭串口。打开串口就是打开的串口文件,比如说ttyS0,打开之后返回FileDescriptor这个对象。接下来就很简单了,只需要在java层中,声明这个native层的方法,然后调用,获取到FileDescriptor对象,最后只需要在java层操作FileDescriptor这个对象就可以实现对串口的数据读写操作了。如果串口成功的打开后,我们就可以用前面提到的串口调试工具来发送数据或者是接收数据,实现mac对anroid开发板的数据通讯。如果对android ndk这块还不是太了解的可以看看我前面的几篇博文
我的Android NDK之旅(三),使用cmake来构建jni
我的Android NDK之旅(二),使用ndk-build构建Jni
我的Android NDK之旅(一),不使用ndk-build命令来创建jni


示例代码下载

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值