SerialPort 是一个用于Jetson Nano/Linux平台的C++串口通信库,封装了Linux系统底层的POSIX串口API,提供了简洁易用的接口。特别适用于Jetson Nano与STM32、Arduino等微控制器的串口通信。
- ✅ 支持标准波特率(9600-4000000)
- ✅ 8位数据位、无校验、1位停止位(8N1)
- ✅ 同步读写操作
- ✅ 超时控制
- ✅ 字符串和二进制数据传输
- ✅ 完善的错误处理
your_project/
├── include/
│ └── SerialPort.h # 头文件
├── src/
│ ├── SerialPort.cpp # 实现文件
│ └── main.cpp # 你的主程序
└── CMakeLists.txt # CMake构建文件
#include "SerialPort.h"
#include <iostream>
int main() {
// 1. 创建串口对象
SerialPort serial;
// 2. 打开串口
if (!serial.open("/dev/ttyTHS1", 115200)) {
std::cerr << "打开串口失败: " << serial.getLastError() << std::endl;
return -1;
}
// 3. 发送数据
serial.writeString("Hello STM32!");
// 4. 接收数据
std::string reply = serial.readLine(1000); // 等待1秒
if (!reply.empty()) {
std::cout << "收到: " << reply << std::endl;
}
// 5. 关闭串口
serial.close();
return 0;
}cmake_minimum_required(VERSION 3.10)
project(SerialDemo)
set(CMAKE_CXX_STANDARD 11)
include_directories(include)
add_executable(demo
src/main.cpp
src/SerialPort.cpp
)# 编译
mkdir build && cd build
cmake ..
make
# 设置权限并运行
sudo chmod 777 /dev/ttyTHS1
sudo ./demoSerialPort(); // 构造函数,初始化文件描述符为-1
~SerialPort(); // 析构函数,自动关闭串口/**
* 打开串口
* @param portName 串口设备路径,如 "/dev/ttyTHS1"
* @param baudRate 波特率,常用值:9600, 115200, 921600等
* @return true:成功 false:失败
*/
bool open(const std::string& portName, int baudRate = 115200);
/**
* 关闭串口
*/
void close();
/**
* 检查串口是否已打开
* @return true:已打开 false:未打开
*/
bool isOpen() const;/**
* 发送原始字节数据
* @param data 数据缓冲区指针
* @param length 数据长度
* @return 实际发送的字节数,-1表示失败
*/
int write(const uint8_t* data, int length);
/**
* 发送字符串
* @param str 要发送的字符串
* @param addNewline 是否自动添加换行符(\n)
* @return 实际发送的字节数
*/
int writeString(const std::string& str, bool addNewline = true);
// 使用示例
serial.writeString("Hello"); // 发送 "Hello"
serial.writeString("Hello", true); // 发送 "Hello\n"
serial.writeString("Hello", false); // 发送 "Hello" (不添加换行)/**
* 读取数据到缓冲区
* @param buffer 接收缓冲区
* @param maxLength 最大读取长度
* @param timeoutMs 超时时间(毫秒),-1表示无限等待
* @return 实际读取的字节数,0表示超时,-1表示错误
*/
int read(uint8_t* buffer, int maxLength, int timeoutMs = 100);
/**
* 读取一行数据(直到遇到换行符\n)
* @param timeoutMs 超时时间(毫秒)
* @return 读取到的字符串(不含换行符)
*/
std::string readLine(int timeoutMs = 100);
// 使用示例
uint8_t buf[128];
int len = serial.read(buf, sizeof(buf), 500); // 等待最多500ms
if (len > 0) {
// 处理接收到的数据
}
std::string line = serial.readLine(1000); // 读取一行,等待1秒/**
* 清空输入输出缓冲区
*/
void flush();
/**
* 获取最后一次错误信息
* @return 错误描述字符串
*/
std::string getLastError() const;#include "SerialPort.h"
#include <iostream>
#include <thread>
int main() {
SerialPort serial;
// 打开串口
if (!serial.open("/dev/ttyTHS1", 115200)) {
std::cerr << "打开失败" << std::endl;
return 1;
}
std::cout << "串口已打开" << std::endl;
// 发送数据
serial.writeString("AT\r\n"); // 发送AT指令
// 接收响应
std::string response = serial.readLine(1000);
std::cout << "响应: " << response << std::endl;
// 连续通信
for (int i = 0; i < 5; i++) {
std::string msg = "Message " + std::to_string(i);
serial.writeString(msg);
std::string reply = serial.readLine(500);
if (!reply.empty()) {
std::cout << "收到: " << reply << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
serial.close();
return 0;
}#include "SerialPort.h"
#include <cstdint>
// 定义通信协议结构体
#pragma pack(1)
struct MotorCommand {
uint8_t header; // 帧头: 0xAA
uint8_t cmd; // 命令: 0x01
int16_t left_speed; // 左轮速度
int16_t right_speed; // 右轮速度
uint8_t checksum; // 校验和
uint8_t footer; // 帧尾: 0x55
};
struct SensorData {
uint8_t header; // 帧头: 0xAA
uint8_t cmd; // 命令: 0x02
uint16_t distance; // 距离 (mm)
uint16_t voltage; // 电压 (mV)
uint8_t checksum; // 校验和
uint8_t footer; // 帧尾: 0x55
};
#pragma pack()
// 计算校验和
uint8_t calcChecksum(const uint8_t* data, int len) {
uint8_t sum = 0;
for (int i = 0; i < len; i++) sum += data[i];
return sum;
}
int main() {
SerialPort serial;
serial.open("/dev/ttyTHS1", 115200);
// 发送电机控制命令
MotorCommand cmd;
cmd.header = 0xAA;
cmd.cmd = 0x01;
cmd.left_speed = 500;
cmd.right_speed = 300;
cmd.footer = 0x55;
cmd.checksum = calcChecksum((uint8_t*)&cmd,
sizeof(cmd) - sizeof(cmd.checksum) - sizeof(cmd.footer));
serial.write((uint8_t*)&cmd, sizeof(cmd));
// 接收传感器数据
uint8_t buffer[64];
int len = serial.read(buffer, sizeof(buffer), 1000);
if (len >= sizeof(SensorData)) {
SensorData* sensor = (SensorData*)buffer;
if (sensor->header == 0xAA && sensor->footer == 0x55) {
std::cout << "距离: " << sensor->distance << " mm" << std::endl;
std::cout << "电压: " << sensor->voltage << " mV" << std::endl;
}
}
serial.close();
return 0;
}#include "SerialPort.h"
#include <thread>
#include <atomic>
#include <iostream>
std::atomic<bool> running(true);
// 接收线程
void receiveThread(SerialPort* serial) {
while (running) {
std::string line = serial->readLine(100);
if (!line.empty()) {
std::cout << "[接收] " << line << std::endl;
}
}
}
// 发送线程
void sendThread(SerialPort* serial) {
int count = 0;
while (running) {
std::string msg = "Data " + std::to_string(count++);
serial->writeString(msg);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
SerialPort serial;
if (!serial.open("/dev/ttyTHS1", 115200)) {
std::cerr << "打开失败" << std::endl;
return 1;
}
std::cout << "串口已打开,开始通信..." << std::endl;
// 启动收发线程
std::thread recvThread(receiveThread, &serial);
std::thread sendTh(sendThread, &serial);
// 等待用户输入退出
std::cout << "按回车键退出..." << std::endl;
std::cin.get();
running = false;
sendTh.join();
recvThread.join();
serial.close();
return 0;
}错误: 打开串口失败: Permission denied
解决:
# 临时解决
sudo chmod 777 /dev/ttyTHS1
# 永久解决(推荐)
sudo usermod -a -G dialout $USER
# 然后注销重新登录错误: 打开串口失败: No such file or directory
解决:
# 检查可用的串口设备
ls -l /dev/ttyTHS*
ls -l /dev/ttyUSB*
# Jetson Nano通常使用 /dev/ttyTHS1可能原因:
- 波特率不匹配
- 接线问题(TX-RX接反)
- 没有共地(GND未连接)
解决:
// 确保两边的波特率一致
serial.open("/dev/ttyTHS1", 115200); // STM32也必须是115200可能原因:
- 设备未发送数据
- 超时时间太短
解决:
// 增加超时时间
std::string data = serial.readLine(5000); // 等待5秒
// 或者使用循环接收
while (true) {
uint8_t buf[1];
int n = serial.read(buf, 1, 100);
if (n > 0) {
// 处理数据
}
}解决: 使用带校验的协议
// 发送端添加校验和
uint8_t checksum = 0;
for (int i = 0; i < dataLen; i++) {
checksum += data[i];
}
packet[dataLen] = checksum; // 附加校验和
// 接收端验证
uint8_t calc = 0;
for (int i = 0; i < dataLen; i++) {
calc += packet[i];
}
if (calc == packet[dataLen]) {
// 数据正确
}适用于调试和简单控制:
命令格式: CMD:参数\r\n
示例:
"MOTOR:100,200\r\n" // 设置电机速度
"SERVO:1,90\r\n" // 设置舵机角度
"GET:SENSOR\r\n" // 请求传感器数据
响应格式:
"OK:距离=150mm\r\n"
"ERROR:无效命令\r\n"
适用于可靠的数据传输:
[帧头][命令][数据长度][数据...][校验和][帧尾]
帧头: 0xAA 0x55 (2字节)
命令: 1字节 (0x01=电机, 0x02=传感器, 0x03=配置)
数据长度: 1字节 (后续数据长度)
数据: 变长 (根据命令定义)
校验和: 1字节 (从命令到数据的累加和)
帧尾: 0x5B (1字节)
// 协议定义
#define FRAME_HEADER1 0xAA
#define FRAME_HEADER2 0x55
#define FRAME_FOOTER 0x5B
// 发送数据包
bool sendPacket(SerialPort& serial, uint8_t cmd,
const uint8_t* data, uint8_t len) {
std::vector<uint8_t> packet;
packet.push_back(FRAME_HEADER1);
packet.push_back(FRAME_HEADER2);
packet.push_back(cmd);
packet.push_back(len);
uint8_t checksum = cmd + len;
for (int i = 0; i < len; i++) {
packet.push_back(data[i]);
checksum += data[i];
}
packet.push_back(checksum);
packet.push_back(FRAME_FOOTER);
return serial.write(packet.data(), packet.size()) == packet.size();
}
// 接收数据包
bool receivePacket(SerialPort& serial, uint8_t* cmd,
uint8_t* data, uint8_t* len) {
uint8_t buf[256];
int n = serial.read(buf, sizeof(buf), 100);
// 查找帧头
for (int i = 0; i < n - 1; i++) {
if (buf[i] == FRAME_HEADER1 && buf[i+1] == FRAME_HEADER2) {
if (i + 5 >= n) return false; // 数据不足
*cmd = buf[i+2];
*len = buf[i+3];
// 验证数据完整性
int totalLen = 5 + *len; // 头2 + cmd1 + len1 + 数据 + 校验和1 + 尾1
if (i + totalLen > n) return false;
// 复制数据
memcpy(data, &buf[i+4], *len);
// 验证校验和
uint8_t checksum = buf[i+4+*len];
uint8_t calc = *cmd + *len;
for (int j = 0; j < *len; j++) {
calc += data[j];
}
// 验证帧尾
if (checksum == calc &&
buf[i+5+*len] == FRAME_FOOTER) {
return true;
}
}
}
return false;
}| 函数参数 | 实际波特率 |
|---|---|
| 9600 | 9600 |
| 19200 | 19200 |
| 38400 | 38400 |
| 57600 | 57600 |
| 115200 | 115200 |
| 230400 | 230400 |
| 460800 | 460800 |
| 921600 | 921600 |
| 1000000 | 1,000,000 |
| 2000000 | 2,000,000 |
| 3000000 | 3,000,000 |
| 4000000 | 4,000,000 |
- v1.0.0 (2024-01): 初始版本
- 基础串口通信功能
- 支持标准波特率
- 同步读写操作
如有问题,请检查:
- 串口设备是否存在 (
ls /dev/tty*) - 权限是否正确 (
ls -l /dev/ttyTHS1) - 接线是否正确 (TX-RX交叉连接)
- 波特率是否匹配
- GND是否连接