Android(Linux) usb串口通信连接,有轮子源码

系列文章目录

Android jni层开发 利用NDK定位崩溃crash 位置.



前言

首先Android也是一个Linux,所以Android的串口通信,几乎就是Linux的串口通信,代码几乎都可以通用,当然尽量用标准库里的函数,可以跨平台使用。


提示:以下是本篇文章正文内容,GitHub上面有源码提供,欢迎点赞、收藏、转载、指导,有写错误的点,欢迎纠正。源码:https://github.com/LaoTie0815/AndroidSerialPort.

什么是串口

在Linux系统中,“串口” 通常指的是串行端口(Serial Port),也被称为串口接口或COM口。串口是一种用于在计算机和外部设备之间进行串行数据传输的通信接口。它通过单一的数据线按照时间顺序一个位一个地传输数据,与并行通信相对,串口采用的是逐位传输的方式。

Linux系统中,串口通常由硬件设备和相应的驱动程序组成。串口接口通常以 “/dev/ttySx”(其中 x 是串口的编号)的形式表示。例如,第一个串口可能是 “/dev/ttyS0”,第二个是 “/dev/ttyS1”,依此类推。USB串口通常以 “/dev/ttyUSBx” 形式表示。

串口在Linux中被广泛用于连接各种外部设备,如串口终端、调制解调器、传感器、嵌入式系统等。通过串口,可以进行串行通信,传输数据以及与外部设备进行交互。在Linux系统中,用户可以使用各种工具和编程接口来管理和使用串口。

一、串口通信整体步骤图

在这里插入图片描述
整体步骤放在串口初始化函数里,读写在另外函数中

1.引入库

代码如下(示例):

#include "serial.h"
#include "mtc.h"
#include <fcntl.h>
#include <cstdio>
#include <termios.h>
#include <cstring>
#include <cerrno>
#include <sys/select.h>
#include <unistd.h>
#include <malloc.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>

2.初始化串口

/******************************************************************************
Function   : init
Description: initialize config and data.
Input      : device: the dev path.
             speed:  the uart transfer baudrate.
             stopBits: the uart transfer data stop bit.
             dataBits: the uart transfer data width bit.
             parity: does the transmitted data need parity? 0 is none, 1 is odd,2 is even.
Output     : none
Return     : success: 0
             failure: uart_open() failure return value ,or uart_set_attr() failure return value.
******************************************************************************/
int Serial::init(const char *device, const int &speed, const int &stop_bits, const int &data_bits, const int &parity) {
    this->m_uart.speed = speed;
    this->m_uart.stopBits = stop_bits;
    this->m_uart.dataBits = data_bits;
    this->m_uart.parity = parity;
    strcpy(this->m_uart.path, device);
    if(this->m_is_init) this->destroy();
    int ret = this->uartOpen(this->m_uart.path);
    LOGI("%s %d: The Serial init result: %d", __FUNCTION__, __LINE__, ret);
    if(ret) return ret;
    if((ret = pipe(this->m_uart.pipe_fd)) != 0) return ret;
    if((ret = this->uartSetAttr()) != 0) return ret;
    LOGI("%s %d: The Serial set attr result: %d", __FUNCTION__, __LINE__, ret);
    this->m_is_init = true;
    return 0;
}

二、打开串口

1、源码:

/******************************************************************************
Function   : uartOpen
Description: open the uart
Input      : device: dev path.
Output     : none
Return     : success: 0
             failure: ERR_ALREADY_INIT: already init.
                      ERR_CANNOT_OPEN: can't open serial port,may not have permission.
                      ERR_CANNOT_FCNTL: catn't set fcntl.
******************************************************************************/
int Serial::uartOpen(const char *device) {
#ifdef _WIN32
    HANDLE pCom = CreateFileA(device,
                              GENERIC_READ | GENERIC_WRITE, //支持读写
                              0,                //独占方式,串口不支持共享
                              nullptr,          //安全属性指针,默认值为NULL
                              OPEN_EXISTING,    //打开现有的串口文件
                              0,                //0:同步方式,FILE_FLAG_OVERLAPPED:异步方式
                              nullptr);         //用于复制文件句柄,默认值为NULL,对串口而言该参数必须置为NULL);
    if(pCom==HANDLE(-1))
        return ERR_CANNOT_OPEN;
    if(!SetupComm(pCom,4096,4096))
        return ERR_CANNOT_OPEN;
#else
//    if(this->m_is_connect)
//        return ERR_ALREADY_INIT; //already init.
    if(this->m_is_init)
        return ERR_ALREADY_INIT;
    int fd;
    fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
    LOGD("device:%s, fd:%d", device, fd);
//    fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (-1 == fd) {
        perror("Can't Open Serial Port");
        return ERR_CANNOT_OPEN;
    }
    if (fcntl(fd, F_SETFL, 0) < 0) {//设为阻塞状态
        perror("fcntl setfl failed!\n");
        return ERR_CANNOT_FCNTL;
    } else {
        printf("fcntl=%d\n", fcntl(fd, F_SETFL, 0));
    }
    this->m_uart.fd = fd;
#endif
    return 0;
}

2、open函数解析

功能描述:用于打开或创建文件,成功则返回文件描述符,否则返回-1,open返回的文件描述符一定是最小的未被使用的描述符

/*
 * pass_object_size serves two purposes here, neither of which involve __bos: it
 * disqualifies this function from having its address taken (so &open works),
 * and it makes overload resolution prefer open(const char *, int) over
 * open(const char *, int, ...).
 */
__BIONIC_FORTIFY_INLINE
int open(const char* const __pass_object_size pathname, int flags)
        __overloadable
        __clang_error_if(__open_modes_useful(flags), "'open' " __open_too_few_args_error) {
#if __ANDROID_API__ >= __ANDROID_API_J_MR1__
    return __open_2(pathname, flags);
#else
    return __open_real(pathname, flags);
#endif /* __ANDROID_API__ >= __ANDROID_API_J_MR1__ */
}

参数解释:
pathname:文件路径名,串口在Linux中被看做是一个文件
flags:一些文件模式选择,有如下几个参数可以设置

  • O_RDONLY 只读模式
  • O_WRONLY 只写模式
  • O_RDWR 读写模式
    上面三个参数在设置的时候必须选择其中一个!!!下面的是可选的
  • O_APPEND 每次写操作都写入文件的末尾
  • O_CREAT 如果指定文件不存在,则创建这个文件
  • O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
  • O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
  • O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
  • O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
    下面三个常量同样是选用的,他们用于同步输入输出
  • O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
  • O_RSYNC 读(read)等待所有写入同一区域的写操作完成后再进行
  • O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O对于串口的打开操作,必须使用
  • O_NOCTTY 参数,它表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务的一个输入(比如键盘终止信号等)都会影响进程。
  • O_NDELAY表示不关心DCD信号所处的状态(端口的另一端是否激活或者停止)。

3、fcntl 函数解析

功能描述:根据文件描述词来操作文件的特性,返回-1代表出错

/**
 * [fcntl(3)](http://man7.org/linux/man-pages/man2/fcntl.2.html) performs various operations
 * on file descriptors.
 *
 * The return value depends on the operation.
 */
int fcntl(int __fd, int __cmd, ...);

参数说明:
fd:文件描述符
cmd:命令参数
fcntl函数有5种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD).
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
    具体使用见链接: http://www.cnblogs.com/lonelycatcher/archive/2011/12/22/2297349.html.

三、设置串口属性

******************************************************************************
Function   : uartSetAttr
Description: set the IO fd's attr of uart.
Input      : none
Output     : none
Return     : success: 0
             failure: ERR_CANNOT_SET_ATTR: failed.
******************************************************************************/
int Serial::uartSetAttr() {
    struct termios ios;
    if (tcgetattr(this->m_uart.fd, &ios) != 0) {
        perror("can't setup serial device.");
        this->uartClose();
        return ERR_CANNOT_SET_ATTR;
    }
    speed_t speed = getBaudrate(this->m_uart.speed);
    cfmakeraw(&ios);
    //设置速率
    cfsetispeed(&ios, speed);
    cfsetospeed(&ios, speed);
    //设置字符
    ios.c_cflag |= CLOCAL | CREAD;
    ios.c_cflag &= ~CSIZE;

    //设置数据位
    switch (this->m_uart.dataBits) {
        case 5:
            ios.c_cflag |= CS5;
            break;
        case 6:
            ios.c_cflag |= CS6;
            break;
        case 7:
            ios.c_cflag |= CS7;
            break;
        case 8:
            ios.c_cflag |= CS8;
            break;
        default:
            ios.c_cflag |= CS8;
            break;
    }

    //设置奇偶校验
    switch (this->m_uart.parity) {
        case 0:
            ios.c_cflag &= ~PARENB;
            break;
        case 1:
            ios.c_cflag |= PARENB;
            ios.c_cflag |= PARODD;
            ios.c_iflag |= (INPCK | ISTRIP);
            break;
        case 2:
            ios.c_iflag |= (INPCK | ISTRIP);
            ios.c_cflag |= PARENB;
            ios.c_cflag &= ~PARODD;
            break;
        default:
            ios.c_cflag &= ~PARENB;
            break;
    }

    //设置停止位
    switch (this->m_uart.stopBits) {
        case 1:
            ios.c_cflag &= ~CSTOPB;
            break;
        case 2:
            ios.c_cflag |= CSTOPB;
            break;
        default:
            ios.c_cflag &= ~CSTOPB;
            break;
    }

    if ((tcsetattr(this->m_uart.fd, TCSANOW, &ios)) != 0) {
        perror("serial set error.");
        return ERR_CANNOT_SET_ATTR;
    }
    return tcflush(this->m_uart.fd,TCIOFLUSH);
}

讲解这片代码之前,我们要先研究一下termios的数据结构。最小的termios结构的典型定义如下:

struct termios {
  tcflag_t c_iflag;
  tcflag_t c_oflag;
  tcflag_t c_cflag;
  tcflag_t c_lflag;
  cc_t c_line;
  cc_t c_cc[NCCS];
};

上面五个结构成员名称分别代表:

  • c_iflag:输入模式
  • c_oflag:输出模式
  • c_cflag:控制模式
  • c_lflag:本地模式
  • c_cc[NCCS]:特殊控制模式

五种模式的参数说明见博客 http://blog.csdn.net/querdaizhi/article/details/7436722.
tcgetattr可以初始化一个终端对应的termios结构,tcgetattr函数原型如下:

/**
 * [tcgetattr(3)](http://man7.org/linux/man-pages/man3/tcgetattr.3.html)
 * reads the configuration of the given terminal.
 *
 * Returns 0 on success and returns -1 and sets `errno` on failure.
 */
int tcgetattr(int __fd, struct termios* __t) __INTRODUCED_IN(21);

这个函数调用把低昂前终端接口变量的值写入termios_p参数指向的结构。如果这些值其后被修改了,可以通过调用函数tcsetattr来重新配置。
再看我们的代码,我们修改字符大小的代码为
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
c_cflag代表控制模式

  • CLOCAL 含义为忽略所有调制解调器的状态行,这个目的是为了保证程序不会占用串口。
  • CREAD 代表启用字符接收器,目的是是的能够从串口中读取输入的数据。
  • CS5/6/7/8 表示发送或接收字符时使用5/6/7/8比特。
  • CSTOPB 表示每个字符使用两位停止位。
  • HUPCL 表示关闭时挂断调制解调器。
  • PARENB:启用奇偶校验码的生成和检测功能。
  • PARODD:只使用奇校验而不使用偶校验。
    c_iflag代表输入模式
  • BRKINT:当在输入行中检测到一个终止状态时,产生一个中断。
  • TGNBRK:忽略输入行中的终止状态。
  • TCRNL:将接受到的回车符转换为新行符。
  • TGNCR:忽略接受到的新行符。
  • INLCR:将接受到的新行符转换为回车符。
  • IGNPAR:忽略奇偶校检错误的字符。
  • INPCK:对接收到的字符执行奇偶校检。
  • PARMRK:对奇偶校检错误作出标记。
  • ISTRIP:将所有接收的字符裁减为7比特。
  • IXOFF:对输入启用软件流控。
  • IXON:对输出启用软件流控。
    c_cc 特殊的控制字符

cfsetispeed和cfsetospeed用来设置输入输出的波特率,函数模型如下:

/**
 * [cfsetispeed(3)](http://man7.org/linux/man-pages/man3/cfsetispeed.3.html)
 * sets the terminal input baud rate.
 *
 * Returns 0 on success and returns -1 and sets `errno` on failure.
 */
int cfsetispeed(struct termios* __t, speed_t __speed) __INTRODUCED_IN(21);

/**
 * [cfsetospeed(3)](http://man7.org/linux/man-pages/man3/cfsetospeed.3.html)
 * sets the terminal output baud rate.
 *
 * Returns 0 on success and returns -1 and sets `errno` on failure.
 */
int cfsetospeed(struct termios* __t, speed_t __speed) __INTRODUCED_IN(21);

参数说明:
struct termios *termptr:指向termios结构的指针
speed_t speed:需要设置的波特率
返回值:成功返回0,否则返回-1
这样,所有的初始化操作我们就完成了。


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为了在Android设备和其他设备之间进行串口通信,可以使用Android USB Host API和USB串口转换器。下面是一个简单的工具类,可以实现Android设备和串口设备之间的通信。 ```java import android.content.Context; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.util.Log; import java.util.HashMap; import java.util.Iterator; public class UsbSerialUtil { private static final String TAG = "UsbSerialUtil"; private static final int TIMEOUT = 1000; private static final int BAUD_RATE = 9600; private UsbManager mUsbManager; private UsbDevice mDevice; private UsbDeviceConnection mConnection; private UsbInterface mInterface; private UsbEndpoint mEndpoint; public UsbSerialUtil(Context context) { mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); } public void openSerialPort() { HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while (deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); if (device.getVendorId() == VENDOR_ID && device.getProductId() == PRODUCT_ID) { mDevice = device; break; } } if (mDevice == null) { Log.e(TAG, "Device not found"); return; } mInterface = mDevice.getInterface(0); mEndpoint = mInterface.getEndpoint(0); mConnection = mUsbManager.openDevice(mDevice); mConnection.claimInterface(mInterface, true); mConnection.controlTransfer(0x21, 34, 0, 0, null, 0, TIMEOUT); mConnection.controlTransfer(0x21, 32, 0, 0, new byte[]{(byte) (BAUD_RATE & 0xff), (byte) ((BAUD_RATE >> 8) & 0xff), (byte) ((BAUD_RATE >> 16) & 0xff), (byte) ((BAUD_RATE >> 24) & 0xff)}, 4, TIMEOUT); } public void closeSerialPort() { mConnection.releaseInterface(mInterface); mConnection.close(); } public void send(String data) { byte[] bytes = data.getBytes(); mConnection.bulkTransfer(mEndpoint, bytes, bytes.length, TIMEOUT); } public String receive() { byte[] buffer = new byte[64]; int bytes = mConnection.bulkTransfer(mEndpoint, buffer, buffer.length, TIMEOUT); return new String(buffer, 0, bytes); } } ``` 其中,VENDOR_ID和PRODUCT_ID可以根据实际的串口转换器进行设置,BAUD_RATE设置为串口设备的波特率。使用时,可以先调用openSerialPort()方法打开串口,然后使用send()方法发送数据,使用receive()方法接收数据,最后调用closeSerialPort()方法关闭串口。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值