为何每次运行C++程序时termios串口设置无法重置?
问题分析与修复方案
核心问题排查
你的程序出现异常的主要原因有两点:
- termios配置顺序完全错误:你先手动修改了
tty结构体参数,再调用tcgetattr(sp, &tty),这会用串口当前属性覆盖你之前的修改,导致配置根本没生效。 - 读取逻辑未处理行拼接:即使串口配置正确,
read()调用也不一定能一次返回完整的GPS日志行,直接把单次读取内容当作完整行处理,必然引发解析错误。
另外,pygpsclient退出后可能未恢复串口规范模式,而你的配置代码因顺序错误无法修正该状态,所以必须插拔设备重置串口。
具体修复步骤
1. 修正termios配置顺序
必须先调用tcgetattr获取当前串口属性,再基于该结构体修改参数,最后调用tcsetattr生效。同时关闭GPS串口不需要的回显、流控等功能。
修改后的configPort函数:
void configPort(int sp) { struct termios tty; // 先获取当前串口属性,这一步必须放在最前面 if (tcgetattr(sp, &tty) != 0) { std::cerr << "Error getting serial port attributes" << std::endl; return; } // 重置所有标志位,清除残留配置 cfmakeraw(&tty); // 恢复规范模式(按行读取) tty.c_lflag |= ICANON; // 配置波特率 cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); // 配置行结束符(GPS日志以换行符结尾) tty.c_cc[VEOL] = '\n'; tty.c_cc[VMIN] = 1; // 至少读取1字节 tty.c_cc[VTIME] = 0; // 无超时 // 禁用不需要的功能:回显、流控等 tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ISIG); tty.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL); tty.c_oflag &= ~OPOST; // 应用配置,TCSANOW立即生效,TCSAFLUSH清空缓冲区 if (tcsetattr(sp, TCSANOW | TCSAFLUSH, &tty) != 0) { std::cerr << "Error setting serial port attributes." << std::endl; return; } }
2. 修复读取逻辑,实现行缓存拼接
添加类成员缓存字符串,每次读取内容先追加到缓存,分割出完整行后再调用回调处理。
修改后的Read_Class完整代码:
class Read_Class { private: std::string line_buffer; // 缓存不完整的行内容 public: using LineCallback = std::function<void(std::string &)>; int openSerial() { // 添加O_NOCTTY(不将串口设为控制终端)和O_NONBLOCK(非阻塞模式) const int serial_port = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK); if (serial_port < 0) { std::cout << "serial port: " << serial_port << std::endl; std::cerr << "Error opening serial port." << std::endl; } else { fcntl(serial_port, F_SETFL, 0); // 恢复阻塞模式,配合poll使用 } return serial_port; } // 上面修改后的configPort函数放在这里 void readLogs(int sp, LineCallback callback) { const int buff_size = 200; char buffer[buff_size]; memset(buffer, '\0', buff_size); struct pollfd fds[1]; fds[0].fd = sp; fds[0].events = POLLIN; while (true) { int pollResult = poll(fds, 1, 200); if (pollResult > 0 && (fds[0].revents & POLLIN)) { ssize_t bytesRead = read(sp, buffer, sizeof(buffer)-1); // 留1字节给终止符 if (bytesRead > 0) { buffer[bytesRead] = '\0'; // 确保字符串终止 line_buffer += buffer; // 追加到缓存 // 分割并处理完整的行 size_t newline_pos; while ((newline_pos = line_buffer.find('\n')) != std::string::npos) { std::string line = line_buffer.substr(0, newline_pos+1); // 包含换行符 if (!line.empty() && line[0] == '$') // 判断是否为NMEA语句 { callback(line); } // 移除已处理的行,保留剩余不完整内容 line_buffer = line_buffer.substr(newline_pos+1); } memset(buffer, '\0', buff_size); } } else if (pollResult == -1) { std::cerr << "Poll error occurred." << std::endl; break; } } } };
3. 简化main函数逻辑
确保回调函数逻辑简洁,若不需要sc_dt可直接移除捕获列表:
int main() { Read_Class rc; const int serial = rc.openSerial(); rc.configPort(serial); if (serial >= 0) { rc.readLogs(serial, [](std::string &line) { std::cout << line << std::endl; }); } close(serial); return 0; }
验证方法
修改后无需插拔GPS设备,直接运行程序:
- 执行
stty -F /dev/ttyUSB0查看串口属性,确认参数与配置一致。 - 程序应稳定输出完整的NMEA格式GPS日志行,不再出现单字节读取导致的崩溃。
内容的提问来源于stack exchange,提问作者MBI
相关产品推荐
相关产品推荐

