RXTX 中文文档

RXTX 串口使用示例

本文档为 RXTX 官方 SerialPortInstructions.txt 的中文翻译版本。 适用于 Linux 初学者学习使用 Java 读写串口的入门指南。


目录

  1. 简介
  2. 查找串口
  3. 查看端口权限
  4. 简单的读写程序
  5. 常用波特率
  6. 故障排除
  7. 相关资源
  8. 高级封装示例

1. 简介

RXTX 为 Java 程序提供了访问计算机串口的能力。本文档将帮助你:

💡 什么是串口?
串口(Serial Port)是一种逐位发送数据的通信接口,常用于:


2. 查找串口

Linux 上的串口设备文件

/dev/ttyS0   - 第一个串口 (通常对应 Windows 的 COM1)
/dev/ttyS1   - 第二个串口 (COM2)
/dev/ttyS2   - 第三个串口 (COM3)
...

📝 什么是 /dev/ttyS*?
在 Linux 中,设备以文件形式存在于 /dev 目录下。ttyS 代表 UART 串口设备。

USB 串口适配器

如果你使用的是 USB 转串口适配器,设备文件名通常是:

/dev/ttyUSB0   # 最常见的 USB 转串口芯片
/dev/ttyUSB1
/dev/ttyACM0   # ACM 设备,通常是手机的调制解调器接口

📝 为什么 USB 串口是 ttyUSB 而不是 ttyS?
USB 转串口芯片(如 CH340、PL2303、FTDI)在内核中映射为不同的驱动,产生的设备名以 ttyUSB 开头。

查看可用端口

# 方法一:列出所有 tty 设备
$ ls -la /dev/tty*

# 方法二:只列出串口
$ ls -la /dev/ttyS*

# 方法三:查看设备列表(需要 root)
$ dmesg | grep tty

📝 dmesg 是什么?
dmesg 显示内核启动信息,插入 USB 设备后会看到新设备的检测信息。

Windows 上的端口

COM1, COM2, COM3, ...

在 Java 中使用:

String portName = "COM1";          // Windows
String portName = "/dev/ttyS0";    // Linux
String portName = "/dev/ttyUSB0";  // Linux (USB 适配器)

3. 查看端口权限

查看当前权限

$ ls -l /dev/ttyS0
crw-rw---- 1 root uucp 4, 64 2009-08-10 12:00 /dev/ttyS0

权限说明:

📝 什么是字符设备?
Linux 中设备分为字符设备(逐字符 I/O,如串口)和块设备(按块读写,如硬盘)。串口是字符设备。

修改权限

临时修改(重启后失效):

# 允许所有用户读写(开发环境可用)
$ sudo chmod 666 /dev/ttyS0

永久修改(推荐):

将用户添加到 dialout 或 uucp 组:

# Ubuntu/Debian - 串口通常属于 dialout 组
$ sudo usermod -a -G dialout 用户名

# Fedora/RHEL - 串口通常属于 uucp 组
$ sudo usermod -a -G uucp 用户名

⚠️ 修改后需要重新登录(或重启会话)才能使组权限生效!

📝 为什么要用组权限而不是 chmod 777?
chmod 666 让所有人可读写,但不够安全。正确的做法是让用户加入设备所属的组(dialout/uucp),这样只有授权用户才能访问串口。


4. 简单的读写程序

4.1 添加 RXTX 到项目

Maven 依赖:

<dependency>
    <groupId>org.rxtx</groupId>
    <artifactId>rxtx</artifactId>
    <version>2.2</version>
</dependency>

或者手动安装:

  1. 下载 RXTX 二进制包
  2. RXTXcomm.jar 添加到 classpath
  3. 将对应平台的本地库放到 Java 库路径:
    • Linux: librxtxSerial.so
    • Windows: rxtxSerial.dll

📝 native library(本地库)是什么?
RXTX 由两部分组成:

两者缺一不可!

4.2 完整的串口通信示例

import gnu.io.*;  // RXTX 使用的包名,与 Sun 的 javax.comm 兼容

import java.io.*;
import java.util.*;

public class SerialTest {

    private static final String PORT_NAMES = "/dev/ttyS0";

    public static void main(String[] args) {
        CommPortIdentifier portId = null;
        Enumeration<?> portEnum = CommPortIdentifier.getPortIdentifiers();

        // 步骤 1:查找端口
        while (portEnum.hasMoreElements()) {
            CommPortIdentifier currPortId =
                (CommPortIdentifier) portEnum.nextElement();
            if (currPortId.getName().equals(PORT_NAMES)) {
                portId = currPortId;
                break;
            }
        }

        if (portId == null) {
            System.out.println("找不到端口: " + PORT_NAMES);
            System.out.println("提示:检查端口名称是否正确,是否有权限访问");
            return;
        }

        try {
            // 步骤 2:打开端口(超时设置为 2 秒)
            // open() 是阻塞的,如果端口被占用,会等待超时时间
            SerialPort serialPort = (SerialPort) portId.open("SerialTest", 2000);

            // 步骤 3:配置串口参数
            serialPort.setSerialPortParams(
                9600,                      // 波特率:每秒 9600 位
                SerialPort.DATABITS_8,      // 数据位:8 位
                SerialPort.STOPBITS_1,      // 停止位:1 位
                SerialPort.PARITY_NONE       // 校验位:无
            );

            // 步骤 4:设置流控(默认通常是无,这里示例如何设置)
            // serialPort.setFlowControlMode(
            //     SerialPort.FLOWCONTROL_NONE);

            // 步骤 5:获取输入/输出流
            InputStream in = serialPort.getInputStream();
            OutputStream out = serialPort.getOutputStream();

            // 步骤 6:发送数据
            out.write("Hello Serial Port!".getBytes());
            out.flush();  // 立即发送,不要等缓冲区满

            // 步骤 7:读取数据(简单轮询方式)
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = in.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));
            }

            // 步骤 8:关闭端口(重要!记得释放资源)
            in.close();
            out.close();
            serialPort.close();

        } catch (PortInUseException e) {
            System.out.println("端口被占用!请关闭其他使用该端口的程序");
        } catch (UnsupportedCommOperationException e) {
            System.out.println("串口参数不支持!检查波特率等设置是否正确");
        } catch (IOException e) {
            System.out.println("I/O 错误: " + e.getMessage());
        }
    }
}

📝 串口参数详解

两端必须完全一致才能正常通信!

4.3 带事件监听的示例

上面的例子使用轮询方式(不断检查是否有数据)。更高效的方式是使用事件监听

import gnu.io.*;

public class SerialEventListener implements SerialPortEventListener {

    private SerialPort serialPort;
    private InputStream in;

    public void initialize(SerialPort serialPort) {
        this.serialPort = serialPort;
        try {
            in = serialPort.getInputStream();
            serialPort.addEventListener(this);  // 注册监听器
            serialPort.notifyOnDataAvailable(true);  // 开启数据到达通知
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.DATA_AVAILABLE:
                // 有数据可读
                try {
                    while (in.available() > 0) {
                        int data = in.read();
                        System.out.print((char) data);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;

            case SerialPortEvent.OE:
                // Overrun Error - 数据覆盖
                System.out.println("溢出错误");
                break;

            case SerialPortEvent.PE:
                // Parity Error - 校验错误
                System.out.println("校验错误");
                break;

            case SerialPortEvent.FE:
                // Framing Error - 帧错误
                System.out.println("帧错误");
                break;

            case SerialPortEvent.BI:
                // Break Interrupt - 中断信号
                System.out.println("收到中断信号");
                break;
        }
    }
}

📝 为什么用事件监听而不是轮询?
轮询会浪费 CPU 资源(一直检查)。事件监听只在有数据时才通知,更高效、更省电。


5. 常用波特率

波特率 适用场景 说明
1200 老式调制解调器 很慢但可靠
9600 GPS、单片机、老设备 最常用
19200 工业设备 中速
38400 高速工业设备
57600 较少使用
115200 高速通信、微控制器 Arduino 默认
921600 超高速通信 需要好线缆

💡 选择波特率的建议


6. 故障排除

问题:提示 “No serial ports found!”

检查步骤:

  1. 设备是否正确连接?(USB 适配器要插紧)
  2. 设备驱动是否安装?(dmesg | tail 查看)
  3. 当前用户是否有端口访问权限?(ls -l /dev/ttyUSB0

问题:可以发送但无法接收数据

可能原因:

  1. 另一程序正在使用该端口(检查锁文件 /var/lock/
  2. 波特率或数据格式不匹配
  3. 硬件连接问题(接线错误、松动)
  4. 线缆质量问题(长距离需要带屏蔽的线)

问题:数据全是乱码

检查项:

  1. 波特率是否匹配?(最常见原因!)
  2. 数据位、停止位、校验位是否正确?
  3. 两端电压是否匹配?(RS-232 vs TTL)
  4. 是否需要交叉连接?(TX 接 RX)

📝 TX/RX 交叉连接
串口连接时,一方的 TX(发送)要接另一方的 RX(接收):


7. 相关资源


8. 高级封装示例

以下是一个更完整的串口操作封装示例,包含参数配置、工具类和测试代码:

8.1 串口参数封装

package com.pilot.ioserver.basic.pbl.port.serialPort;

import gnu.io.SerialPort;

/**
 * 串口参数
 */
public final class SerialPortParameter {

    /**
 * 串口名称(COM0、COM1、COM2等等)
 */
    private String serialPortName;
    /**
 * 波特率
 * 默认:115200
 */
    private int baudRate;
    /**
 * 数据位 默认8位
 * 可以设置的值:SerialPort.DATABITS_5、SerialPort.DATABITS_6、SerialPort.DATABITS_7、SerialPort.DATABITS_8
 * 默认:SerialPort.DATABITS_8
 */
    private int dataBits;
    /**
 * 停止位
 * 可以设置的值:SerialPort.STOPBITS_1、SerialPort.STOPBITS_2、SerialPort.STOPBITS_1_5
 * 默认:SerialPort.STOPBITS_1
 */
    private int stopBits;
    /**
 * 校验位
 * 可以设置的值:SerialPort.PARITY_NONE、SerialPort.PARITY_ODD、SerialPort.PARITY_EVEN、SerialPort.PARITY_MARK、SerialPort.PARITY_SPACE
 * 默认:SerialPort.PARITY_NONE
 */
    private int parity;

    public SerialPortParameter(String serialPortName) {
        this.serialPortName = serialPortName;
        this.baudRate = 115200;
        this.dataBits = SerialPort.DATABITS_8;
        this.stopBits = SerialPort.STOPBITS_1;
        this.parity = SerialPort.PARITY_NONE;
    }

    public SerialPortParameter(String serialPortName, int baudRate) {
        this.serialPortName = serialPortName;
        this.baudRate = baudRate;
        this.dataBits = SerialPort.DATABITS_8;
        this.stopBits = SerialPort.STOPBITS_1;
        this.parity = SerialPort.PARITY_NONE;
    }

    public String getSerialPortName() {
        return serialPortName;
    }

    public void setSerialPortName(String serialPortName) {
        this.serialPortName = serialPortName;
    }

    public int getBaudRate() {
        return baudRate;
    }

    public void setBaudRate(int baudRate) {
        this.baudRate = baudRate;
    }

    public int getDataBits() {
        return dataBits;
    }

    public void setDataBits(int dataBits) {
        this.dataBits = dataBits;
    }

    public int getStopBits() {
        return stopBits;
    }

    public void setStopBits(int stopBits) {
        this.stopBits = stopBits;
    }

    public int getParity() {
        return parity;
    }

    public void setParity(int parity) {
        this.parity = parity;
    }
}

8.2 串口工具类

package com.pilot.ioserver.basic.pbl.port.serialPort;

import gnu.io.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

/**
 * 串口工具类
 */
public class SerialPortUtil {

    /**
 * 获得系统可用的端口名称列表(COM0、COM1、COM2等等)
 *
 * @return List<String>可用端口名称列表
 */

    @SuppressWarnings("unchecked")
    public static List<String> getSerialPortList() {
        List<String> systemPorts = new ArrayList<>();
        //获得系统可用的端口
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();//获得端口的名字
 systemPorts.add(portName);
        }
        return systemPorts;
    }

    /**
 * 打开串口
 *
 * @param serialPortName 串口名称
 * @return SerialPort 串口对象
 * @throws NoSuchPortException 对应串口不存在
 * @throws PortInUseException 串口在使用中
 * @throws UnsupportedCommOperationException 不支持操作操作
 */
    public static SerialPort openSerialPort(String serialPortName)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialPortParameter parameter = new SerialPortParameter(serialPortName);
        return openSerialPort(parameter);
    }

    /**
 * 打开串口
 *
 * @param serialPortName 串口名称
 * @param baudRate 波特率
 * @return SerialPort 串口对象
 * @throws NoSuchPortException 对应串口不存在
 * @throws PortInUseException 串口在使用中
 * @throws UnsupportedCommOperationException 不支持操作操作
 */
    public static SerialPort openSerialPort(String serialPortName, int baudRate)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialPortParameter parameter = new SerialPortParameter(serialPortName, baudRate);
        return openSerialPort(parameter);
    }

    /**
 * 打开串口
 *
 * @param serialPortName 串口名称
 * @param baudRate 波特率
 * @param timeout 串口打开超时时间
 * @return SerialPort 串口对象
 * @throws NoSuchPortException 对应串口不存在
 * @throws PortInUseException 串口在使用中
 * @throws UnsupportedCommOperationException 不支持操作操作
 */
    public static SerialPort openSerialPort(String serialPortName, int baudRate, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialPortParameter parameter = new SerialPortParameter(serialPortName, baudRate);
        return openSerialPort(parameter, timeout);
    }

    /**
 * 打开串口
 *
 * @param parameter 串口参数
 * @return SerialPort 串口对象
 * @throws NoSuchPortException 对应串口不存在
 * @throws PortInUseException 串口在使用中
 * @throws UnsupportedCommOperationException 不支持操作操作
 */
    public static SerialPort openSerialPort(SerialPortParameter parameter)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        return openSerialPort(parameter, 2000);
    }

    /**
 * 打开串口
 *
 * @param parameter 串口参数
 * @param timeout 串口打开超时时间
 * @return SerialPort串口对象
 * @throws NoSuchPortException 对应串口不存在
 * @throws PortInUseException 串口在使用中
 * @throws UnsupportedCommOperationException 不支持操作操作
 */
    public static SerialPort openSerialPort(SerialPortParameter parameter, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        //通过端口名称得到端口
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(parameter.getSerialPortName());
        //打开端口,(自定义名字,打开超时时间)
        CommPort commPort = portIdentifier.open(parameter.getSerialPortName(), timeout);
        //判断是不是串口
        if (commPort instanceof SerialPort) {
            SerialPort serialPort = (SerialPort) commPort;
            //设置串口参数(波特率,数据位8,停止位1,校验位无)
 serialPort.setSerialPortParams(parameter.getBaudRate(), parameter.getDataBits(), parameter.getStopBits(), parameter.getParity());
            System.out.println("开启串口成功,串口名称:" + parameter.getSerialPortName());
            return serialPort;
        } else {
            //是其他类型的端口
            throw new NoSuchPortException();
        }
    }

    /**
 * 关闭串口
 *
 * @param serialPort 要关闭的串口对象
 */
    public static void closeSerialPort(SerialPort serialPort) {
        if (serialPort != null) {
 serialPort.close();
            System.out.println("关闭了串口:" + serialPort.getName());
        }
    }

    /**
 * 向串口发送数据
 *
 * @param serialPort 串口对象
 * @param data 发送的数据
 */
    public static void sendData(SerialPort serialPort, byte[] data) {
        OutputStream os = null;
        try {
            //获得串口的输出流
 os = serialPort.getOutputStream();
 os.write(data);
 os.flush();
        } catch (IOException e) {
 e.printStackTrace();
        } finally {
            try {
                if (os != null) {
 os.close();
                }
            } catch (IOException e) {
 e.printStackTrace();
            }
        }
    }

    /**
 * 从串口读取数据
 *
 * @param serialPort 要读取的串口
 * @return 读取的数据
 */
    public static byte[] readData(SerialPort serialPort) {
        InputStream is = null;
        byte[] bytes = null;
        try {
            //获得串口的输入流
 is = serialPort.getInputStream();
            //获得数据长度
            int bufflenth = is.available();
            while (bufflenth != 0) {
                //初始化byte数组
 bytes = new byte[bufflenth];
 is.read(bytes);
 bufflenth = is.available();
            }
        } catch (IOException e) {
 e.printStackTrace();
        } finally {
            try {
                if (is != null) {
 is.close();
                }
            } catch (IOException e) {
 e.printStackTrace();
            }
        }
        return bytes;
    }

    /**
 * 给串口设置监听
 *
 * @param serialPort serialPort 要读取的串口
 * @param listener SerialPortEventListener监听对象
 * @throws TooManyListenersException 监听对象太多
 */
    public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) throws TooManyListenersException {
        //给串口添加事件监听
 serialPort.addEventListener(listener);
        //串口有数据监听
 serialPort.notifyOnDataAvailable(true);
        //中断事件监听
 serialPort.notifyOnBreakInterrupt(true);
    }

}

8.3 测试代码

package com.pilot.ioserver.basic.pbl.port.serialPort;

import gnu.io.*;
import org.junit.Test;

import java.util.List;
import java.util.TooManyListenersException;

/**
 * 串口测试代码
 */
public class SerialPortUtilTest {

    /**
 * 测试获取串口列表
 */
    @Test
    public void getSystemPortList() {

        List<String> portList = SerialPortUtil.getSerialPortList();
        System.out.println(portList);

    }

    /**
 * 测试串口打开,读,写操作
 */
    @Test
    public void serialPortAction() {
        try {
            final SerialPort serialPort = SerialPortUtil.openSerialPort("COM2", 115200);
            //启动一个线程每2s向串口发送数据,发送1000次hello
            new Thread(() -> {
                int i = 1;
                while (i < 1000) {
                    String s = "hello";
                    byte[] bytes = s.getBytes();
                    SerialPortUtil.sendData(serialPort, bytes);//发送数据
 i++;
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
 e.printStackTrace();
                    }
                }
            }).start();
            //设置串口的listener
            SerialPortUtil.setListenerToSerialPort(serialPort, event -> {
                //数据通知
                if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                    byte[] bytes = SerialPortUtil.readData(serialPort);
                    System.out.println("收到的数据长度:" + bytes.length);
                    System.out.println("收到的数据:" + new String(bytes));
                }
            });
            try {
                // sleep 一段时间保证线程可以执行完
                Thread.sleep(3 * 30 * 1000);
            } catch (InterruptedException e) {
 e.printStackTrace();
            }
        } catch (NoSuchPortException | PortInUseException | UnsupportedCommOperationException | TooManyListenersException e) {
 e.printStackTrace();
        }
    }

}

本指南翻译自 RXTX 官方 SerialPortInstructions.txt。 原文作者:Vaibhav Andleigh / Athena andleigh@mit.edu


📎 文档导航

← 上一篇 🏠 首页 下一篇 →
安装指南 返回文档首页 移植指南