UART串口通信 #

一、UART基础概念 #

1.1 UART概述 #

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种异步串行通信协议。

text
┌─────────────────────────────────────────────────────────┐
│                    UART通信连接                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│     ┌─────────┐              ┌─────────┐              │
│     │  设备A  │              │  设备B  │              │
│     │         │              │         │              │
│     │   TX ───┼──────────────┼─── RX   │              │
│     │         │              │         │              │
│     │   RX ───┼──────────────┼─── TX   │              │
│     │         │              │         │              │
│     │  GND ───┼──────────────┼─── GND  │              │
│     │         │              │         │              │
│     └─────────┘              └─────────┘              │
│                                                         │
│     TX: 发送端 (Transmit)                               │
│     RX: 接收端 (Receive)                                │
│     GND: 地线                                           │
│                                                         │
│     注意:TX连接RX,交叉连接                            │
│                                                         │
└─────────────────────────────────────────────────────────┘

1.2 UART特性 #

特性 说明
通信方式 异步串行
线路数量 2线(TX、RX)+ GND
数据格式 起始位 + 数据位 + 校验位 + 停止位
波特率 常用9600、115200等
双工模式 全双工

1.3 数据帧格式 #

text
空闲状态(高电平)
    ────────────────┐
                    │ 起始位
                    ▼
    ────────────────┐
                    │ D0  D1  D2  D3  D4  D5  D6  D7 │ P │ 停止位
                    └───┴───┴───┴───┴───┴───┴───┴───┴───┴───────
                     └────────── 数据位(8位)──────────┘ └ 校验位

起始位: 1位,低电平
数据位: 5-9位,通常8位
校验位: 0或1位(奇校验、偶校验、无校验)
停止位: 1或2位,高电平

1.4 常见波特率 #

波特率 应用场景
9600 传统设备、低速通信
19200 中速通信
38400 中速通信
57600 中高速通信
115200 高速通信、调试
230400 高速通信
460800 超高速通信

二、Raspberry Pi串口配置 #

2.1 启用串口 #

bash
# 启用串口
sudo raspi-config
# 选择 Interface Options -> Serial Port
# Login shell: No
# Serial port hardware enabled: Yes

# 或手动配置
sudo nano /boot/config.txt
# 添加
enable_uart=1

# 重启
sudo reboot

2.2 查看串口设备 #

bash
# 查看串口设备
ls -la /dev/tty*

# 常见串口设备:
# /dev/ttyAMA0 - 硬件串口(蓝牙占用)
# /dev/ttyS0   - mini串口
# /dev/ttyUSB0 - USB转串口设备

# 查看串口属性
stty -F /dev/ttyS0 -a

2.3 串口权限配置 #

bash
# 将用户添加到dialout组
sudo usermod -a -G dialout $USER

# 注销并重新登录生效

# 验证权限
groups $USER

三、Java串口编程 #

3.1 添加依赖 #

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>com.fazecast</groupId>
        <artifactId>jSerialComm</artifactId>
        <version>2.10.4</version>
    </dependency>
</dependencies>

3.2 基本串口操作 #

java
package com.example.uart;

import com.fazecast.jSerialComm.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;

public class UARTBasicDemo {

    public static void main(String[] args) {
        SerialPort[] ports = SerialPort.getCommPorts();
        
        System.out.println("可用串口列表:");
        for (int i = 0; i < ports.length; i++) {
            System.out.printf("%d: %s (%s)%n", 
                i, ports[i].getSystemPortName(), ports[i].getDescriptivePortName());
        }
        
        if (ports.length == 0) {
            System.out.println("未找到可用串口");
            return;
        }
        
        SerialPort port = ports[0];
        
        port.setComPortParameters(115200, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
        port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0);
        
        if (!port.openPort()) {
            System.out.println("无法打开串口: " + port.getSystemPortName());
            return;
        }
        
        System.out.println("串口已打开: " + port.getSystemPortName());
        System.out.println("波特率: " + port.getBaudRate());
        System.out.println("数据位: " + port.getNumDataBits());
        System.out.println("停止位: " + port.getNumStopBits());
        
        try {
            OutputStream out = port.getOutputStream();
            InputStream in = port.getInputStream();
            
            String message = "Hello UART!\n";
            out.write(message.getBytes());
            System.out.println("发送: " + message.trim());
            
            byte[] buffer = new byte[1024];
            int bytesRead = in.read(buffer);
            if (bytesRead > 0) {
                String response = new String(buffer, 0, bytesRead);
                System.out.println("接收: " + response.trim());
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            port.closePort();
            System.out.println("串口已关闭");
        }
    }
}

3.3 串口事件监听 #

java
package com.example.uart;

import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialPort.SerialPortEvent;

public class UARTEventListener {

    public static void main(String[] args) throws InterruptedException {
        SerialPort port = SerialPort.getCommPort("/dev/ttyS0");
        port.setComPortParameters(115200, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
        port.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 0, 0);
        
        if (!port.openPort()) {
            System.out.println("无法打开串口");
            return;
        }
        
        port.addDataListener(new SerialPortDataListener() {
            @Override
            public int getListeningEvents() {
                return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
            }
            
            @Override
            public void serialEvent(SerialPortEvent event) {
                if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
                    return;
                }
                
                byte[] buffer = new byte[port.bytesAvailable()];
                int bytesRead = port.readBytes(buffer, buffer.length);
                
                String data = new String(buffer, 0, bytesRead);
                System.out.printf("[%d bytes] %s%n", bytesRead, data.trim());
            }
        });
        
        System.out.println("串口监听已启动,按Ctrl+C退出");
        
        while (true) {
            Thread.sleep(1000);
        }
    }
}

3.4 串口工具类 #

java
package com.example.uart;

import com.fazecast.jSerialComm.SerialPort;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class UARTManager {

    public static class Config {
        public String portName;
        public int baudRate = 115200;
        public int dataBits = 8;
        public int stopBits = SerialPort.ONE_STOP_BIT;
        public int parity = SerialPort.NO_PARITY;
        public int timeoutMs = 1000;
    }
    
    private SerialPort port;
    private InputStream inputStream;
    private OutputStream outputStream;
    private volatile boolean listening = false;
    private Thread listenThread;
    private final List<Consumer<byte[]>> dataListeners = new ArrayList<>();
    
    public static List<String> listPorts() {
        List<String> portNames = new ArrayList<>();
        for (SerialPort p : SerialPort.getCommPorts()) {
            portNames.add(p.getSystemPortName());
        }
        return portNames;
    }
    
    public boolean open(Config config) {
        port = SerialPort.getCommPort(config.portName);
        port.setComPortParameters(config.baudRate, config.dataBits, 
            config.stopBits, config.parity);
        port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 
            config.timeoutMs, 0);
        
        if (!port.openPort()) {
            return false;
        }
        
        inputStream = port.getInputStream();
        outputStream = port.getOutputStream();
        return true;
    }
    
    public void close() {
        stopListening();
        if (port != null && port.isOpen()) {
            port.closePort();
        }
    }
    
    public boolean isOpen() {
        return port != null && port.isOpen();
    }
    
    public void write(byte[] data) throws IOException {
        if (outputStream != null) {
            outputStream.write(data);
            outputStream.flush();
        }
    }
    
    public void write(String data) throws IOException {
        write(data.getBytes());
    }
    
    public void writeLine(String line) throws IOException {
        write(line + "\n");
    }
    
    public byte[] read(int maxBytes) throws IOException {
        if (inputStream == null) return null;
        
        byte[] buffer = new byte[maxBytes];
        int bytesRead = inputStream.read(buffer);
        
        if (bytesRead > 0) {
            byte[] result = new byte[bytesRead];
            System.arraycopy(buffer, 0, result, 0, bytesRead);
            return result;
        }
        return null;
    }
    
    public String readLine() throws IOException {
        if (inputStream == null) return null;
        
        StringBuilder sb = new StringBuilder();
        int c;
        while ((c = inputStream.read()) != -1) {
            if (c == '\n') {
                break;
            }
            if (c != '\r') {
                sb.append((char) c);
            }
        }
        return sb.length() > 0 ? sb.toString() : null;
    }
    
    public void addDataListener(Consumer<byte[]> listener) {
        dataListeners.add(listener);
    }
    
    public void startListening() {
        if (listening) return;
        
        listening = true;
        listenThread = new Thread(() -> {
            byte[] buffer = new byte[1024];
            while (listening && port.isOpen()) {
                try {
                    if (port.bytesAvailable() > 0) {
                        int bytesRead = inputStream.read(buffer);
                        if (bytesRead > 0) {
                            byte[] data = new byte[bytesRead];
                            System.arraycopy(buffer, 0, data, 0, bytesRead);
                            for (Consumer<byte[]> listener : dataListeners) {
                                listener.accept(data);
                            }
                        }
                    } else {
                        Thread.sleep(10);
                    }
                } catch (Exception e) {
                    if (listening) {
                        e.printStackTrace();
                    }
                }
            }
        });
        listenThread.setDaemon(true);
        listenThread.start();
    }
    
    public void stopListening() {
        listening = false;
        if (listenThread != null) {
            listenThread.interrupt();
        }
    }
    
    public SerialPort getPort() {
        return port;
    }
}

四、实际应用示例 #

4.1 GPS模块通信 #

java
package com.example.uart.device;

import com.example.uart.UARTManager;
import java.util.HashMap;
import java.util.Map;

public class GPSModule {

    private final UARTManager uart;
    private GPSData lastData;
    
    public static class GPSData {
        public double latitude;
        public double longitude;
        public double altitude;
        public double speed;
        public double course;
        public int satellites;
        public String time;
        public String date;
        public boolean valid;
    }
    
    public GPSModule(String portName) {
        this.uart = new UARTManager();
        
        UARTManager.Config config = new UARTManager.Config();
        config.portName = portName;
        config.baudRate = 9600;
        
        if (!uart.open(config)) {
            throw new RuntimeException("无法打开GPS串口");
        }
        
        uart.addDataListener(this::parseNMEA);
        uart.startListening();
    }
    
    private void parseNMEA(byte[] data) {
        String sentence = new String(data).trim();
        
        if (sentence.startsWith("$GPGGA")) {
            parseGPGGA(sentence);
        } else if (sentence.startsWith("$GPRMC")) {
            parseGPRMC(sentence);
        }
    }
    
    private void parseGPGGA(String sentence) {
        String[] parts = sentence.split(",");
        if (parts.length < 15) return;
        
        GPSData gps = new GPSData();
        
        try {
            gps.time = parts[1];
            gps.latitude = parseCoordinate(parts[2], parts[3]);
            gps.longitude = parseCoordinate(parts[4], parts[5]);
            gps.valid = !parts[6].equals("0");
            gps.satellites = Integer.parseInt(parts[7]);
            gps.altitude = Double.parseDouble(parts[9]);
            
            lastData = gps;
            
        } catch (Exception e) {
            // 解析错误
        }
    }
    
    private void parseGPRMC(String sentence) {
        String[] parts = sentence.split(",");
        if (parts.length < 12) return;
        
        try {
            if (lastData == null) {
                lastData = new GPSData();
            }
            
            lastData.time = parts[1];
            lastData.valid = parts[2].equals("A");
            lastData.latitude = parseCoordinate(parts[3], parts[4]);
            lastData.longitude = parseCoordinate(parts[5], parts[6]);
            lastData.speed = Double.parseDouble(parts[7]) * 1.852;
            lastData.course = Double.parseDouble(parts[8]);
            lastData.date = parts[9];
            
        } catch (Exception e) {
            // 解析错误
        }
    }
    
    private double parseCoordinate(String coord, String dir) {
        if (coord.isEmpty()) return 0;
        
        double degrees = Double.parseDouble(coord.substring(0, 2));
        double minutes = Double.parseDouble(coord.substring(2));
        
        double decimal = degrees + minutes / 60.0;
        
        if (dir.equals("S") || dir.equals("W")) {
            decimal = -decimal;
        }
        
        return decimal;
    }
    
    public GPSData getData() {
        return lastData;
    }
    
    public void close() {
        uart.close();
    }
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println("GPS模块测试");
        
        GPSModule gps = new GPSModule("/dev/ttyUSB0");
        
        while (true) {
            GPSData data = gps.getData();
            if (data != null && data.valid) {
                System.out.printf("位置: %.6f, %.6f%n", data.latitude, data.longitude);
                System.out.printf("海拔: %.1f m%n", data.altitude);
                System.out.printf("速度: %.1f km/h%n", data.speed);
                System.out.printf("卫星数: %d%n", data.satellites);
                System.out.println("---");
            }
            Thread.sleep(1000);
        }
    }
}

4.2 Modbus RTU通信 #

java
package com.example.uart.protocol;

import com.example.uart.UARTManager;
import java.util.zip.CRC32;

public class ModbusRTU {

    private final UARTManager uart;
    private final int timeout;
    
    public ModbusRTU(String portName, int baudRate, int timeoutMs) {
        this.uart = new UARTManager();
        this.timeout = timeoutMs;
        
        UARTManager.Config config = new UARTManager.Config();
        config.portName = portName;
        config.baudRate = baudRate;
        config.timeoutMs = timeoutMs;
        
        if (!uart.open(config)) {
            throw new RuntimeException("无法打开Modbus串口");
        }
    }
    
    public int[] readHoldingRegisters(int slaveId, int startAddress, int quantity) 
            throws Exception {
        byte[] request = new byte[8];
        request[0] = (byte) slaveId;
        request[1] = 0x03;
        request[2] = (byte) ((startAddress >> 8) & 0xFF);
        request[3] = (byte) (startAddress & 0xFF);
        request[4] = (byte) ((quantity >> 8) & 0xFF);
        request[5] = (byte) (quantity & 0xFF);
        
        int crc = calculateCRC(request, 0, 6);
        request[6] = (byte) (crc & 0xFF);
        request[7] = (byte) ((crc >> 8) & 0xFF);
        
        uart.write(request);
        
        Thread.sleep(50);
        
        int responseLength = 3 + quantity * 2 + 2;
        byte[] response = uart.read(responseLength);
        
        if (response == null || response.length < responseLength) {
            throw new Exception("响应超时");
        }
        
        int responseCRC = calculateCRC(response, 0, responseLength - 2);
        int receivedCRC = (response[responseLength - 1] & 0xFF) << 8 | 
                          (response[responseLength - 2] & 0xFF);
        
        if (responseCRC != receivedCRC) {
            throw new Exception("CRC校验失败");
        }
        
        int[] registers = new int[quantity];
        for (int i = 0; i < quantity; i++) {
            registers[i] = ((response[3 + i * 2] & 0xFF) << 8) | 
                           (response[4 + i * 2] & 0xFF);
        }
        
        return registers;
    }
    
    public void writeSingleRegister(int slaveId, int address, int value) 
            throws Exception {
        byte[] request = new byte[8];
        request[0] = (byte) slaveId;
        request[1] = 0x06;
        request[2] = (byte) ((address >> 8) & 0xFF);
        request[3] = (byte) (address & 0xFF);
        request[4] = (byte) ((value >> 8) & 0xFF);
        request[5] = (byte) (value & 0xFF);
        
        int crc = calculateCRC(request, 0, 6);
        request[6] = (byte) (crc & 0xFF);
        request[7] = (byte) ((crc >> 8) & 0xFF);
        
        uart.write(request);
    }
    
    public boolean[] readCoils(int slaveId, int startAddress, int quantity) 
            throws Exception {
        byte[] request = new byte[8];
        request[0] = (byte) slaveId;
        request[1] = 0x01;
        request[2] = (byte) ((startAddress >> 8) & 0xFF);
        request[3] = (byte) (startAddress & 0xFF);
        request[4] = (byte) ((quantity >> 8) & 0xFF);
        request[5] = (byte) (quantity & 0xFF);
        
        int crc = calculateCRC(request, 0, 6);
        request[6] = (byte) (crc & 0xFF);
        request[7] = (byte) ((crc >> 8) & 0xFF);
        
        uart.write(request);
        
        Thread.sleep(50);
        
        int byteCount = (quantity + 7) / 8;
        int responseLength = 3 + byteCount + 2;
        byte[] response = uart.read(responseLength);
        
        if (response == null || response.length < responseLength) {
            throw new Exception("响应超时");
        }
        
        boolean[] coils = new boolean[quantity];
        for (int i = 0; i < quantity; i++) {
            int byteIndex = 3 + i / 8;
            int bitIndex = i % 8;
            coils[i] = (response[byteIndex] & (1 << bitIndex)) != 0;
        }
        
        return coils;
    }
    
    private int calculateCRC(byte[] data, int offset, int length) {
        int crc = 0xFFFF;
        
        for (int i = offset; i < offset + length; i++) {
            crc ^= (data[i] & 0xFF);
            
            for (int j = 0; j < 8; j++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc >>= 1;
                }
            }
        }
        
        return crc;
    }
    
    public void close() {
        uart.close();
    }
    
    public static void main(String[] args) throws Exception {
        ModbusRTU modbus = new ModbusRTU("/dev/ttyUSB0", 9600, 1000);
        
        System.out.println("读取保持寄存器...");
        int[] registers = modbus.readHoldingRegisters(1, 0, 10);
        
        for (int i = 0; i < registers.length; i++) {
            System.out.printf("寄存器%d: %d%n", i, registers[i]);
        }
        
        modbus.close();
    }
}

4.3 AT命令通信 #

java
package com.example.uart.device;

import com.example.uart.UARTManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ATCommandModem {

    private final UARTManager uart;
    
    public ATCommandModem(String portName, int baudRate) {
        this.uart = new UARTManager();
        
        UARTManager.Config config = new UARTManager.Config();
        config.portName = portName;
        config.baudRate = baudRate;
        config.timeoutMs = 5000;
        
        if (!uart.open(config)) {
            throw new RuntimeException("无法打开Modem串口");
        }
    }
    
    public String sendCommand(String command) throws Exception {
        uart.writeLine(command);
        
        StringBuilder response = new StringBuilder();
        long startTime = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - startTime < 5000) {
            String line = uart.readLine();
            if (line != null) {
                response.append(line).append("\n");
                
                if (line.equals("OK") || line.equals("ERROR") || 
                    line.startsWith("+CME ERROR") || line.startsWith("+CMS ERROR")) {
                    break;
                }
            }
        }
        
        return response.toString();
    }
    
    public boolean testConnection() throws Exception {
        String response = sendCommand("AT");
        return response.contains("OK");
    }
    
    public String getIMEI() throws Exception {
        String response = sendCommand("AT+GSN");
        Pattern pattern = Pattern.compile("(\\d{15})");
        Matcher matcher = pattern.matcher(response);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }
    
    public String getSIMStatus() throws Exception {
        String response = sendCommand("AT+CPIN?");
        if (response.contains("+CPIN: READY")) {
            return "READY";
        } else if (response.contains("+CPIN: SIM PIN")) {
            return "SIM_PIN";
        } else if (response.contains("+CPIN: SIM PUK")) {
            return "SIM_PUK";
        }
        return "UNKNOWN";
    }
    
    public int getSignalStrength() throws Exception {
        String response = sendCommand("AT+CSQ");
        Pattern pattern = Pattern.compile("\\+CSQ: (\\d+),(\\d+)");
        Matcher matcher = pattern.matcher(response);
        if (matcher.find()) {
            int rssi = Integer.parseInt(matcher.group(1));
            return -113 + rssi * 2;
        }
        return 0;
    }
    
    public boolean sendSMS(String number, String message) throws Exception {
        sendCommand("AT+CMGF=1");
        
        uart.write("AT+CMGS=\"" + number + "\"\r");
        Thread.sleep(100);
        
        uart.write(message + (char) 26);
        
        String response = sendCommand("");
        return response.contains("+CMGS:");
    }
    
    public void close() {
        uart.close();
    }
    
    public static void main(String[] args) throws Exception {
        ATCommandModem modem = new ATCommandModem("/dev/ttyUSB2", 115200);
        
        System.out.println("测试连接: " + modem.testConnection());
        System.out.println("IMEI: " + modem.getIMEI());
        System.out.println("SIM状态: " + modem.getSIMStatus());
        System.out.println("信号强度: " + modem.getSignalStrength() + " dBm");
        
        modem.close();
    }
}

五、串口调试技巧 #

5.1 命令行调试 #

bash
# 使用minicom
sudo apt install minicom
sudo minicom -s
# 配置串口设备和参数

# 使用screen
screen /dev/ttyUSB0 115200

# 使用stty配置
stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb

# 直接读写
echo "Hello" > /dev/ttyUSB0
cat /dev/ttyUSB0

5.2 常见问题排查 #

问题 可能原因 解决方案
无法打开串口 权限不足 添加用户到dialout组
乱码 波特率不匹配 检查双方波特率设置
数据丢失 缓冲区溢出 增加读取频率或使用流控
通信中断 线路问题 检查TX/RX连接

六、总结 #

UART串口通信要点:

  1. 参数配置:正确设置波特率、数据位、停止位、校验位
  2. 交叉连接:TX连接RX,确保正确接线
  3. 异步通信:理解起始位和停止位的作用
  4. 协议解析:根据设备协议解析数据
  5. 错误处理:添加超时和异常处理机制

下一章我们将学习PWM与ADC,实现模拟信号的输出和采集。

最后更新:2026-03-27