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串口通信要点:
- 参数配置:正确设置波特率、数据位、停止位、校验位
- 交叉连接:TX连接RX,确保正确接线
- 异步通信:理解起始位和停止位的作用
- 协议解析:根据设备协议解析数据
- 错误处理:添加超时和异常处理机制
下一章我们将学习PWM与ADC,实现模拟信号的输出和采集。
最后更新:2026-03-27