Linux下C语言串口通信异常:需先运行Python脚本才可正常工作
串口通信异常问题:C程序需先运行Python脚本才能正常接收数据
问题场景
Linux系统启动后直接运行C语言串口读取程序,无法接收数据;但先运行一段仅打开并关闭串口的Python脚本后,再启动C程序就能正常接收数据。
相关代码
C语言程序
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <termios.h> #include <time.h> #define BUFFER_SIZE 1024 volatile sig_atomic_t stop_flag = 0; void handle_signal(int signum) { printf("Caught signal %d\n", signum); if (signum == SIGINT || signum == SIGTERM) { printf("Stopping...\n"); stop_flag = 1; printf("Stopped\n"); } printf("Exiting signal handler\n"); } void cleanup(int serial_fd, FILE *outputFile) { printf("Cleaning up...\n"); close(serial_fd); fclose(outputFile); printf("Cleaned up\n"); } int main(int argc, char *argv[]) { printf("argc: %d\n", argc); for (int i = 0; i < argc; i++) { printf("argv[%d]: %s\n", i, argv[i]); } if (argc != 7) { printf("Usage: %s <serial_port> <output_file> <baud_rate> <parity> <data_bits> <stop_bits>\n", argv[0]); return EXIT_FAILURE; } const char *serialPortName = argv[1]; const char *outputFileName = argv[2]; int baudRate = atoi(argv[3]); char parity = argv[4][0]; int dataBits = atoi(argv[5]); int stopBits = atoi(argv[6]); // Open the serial port int serial_fd = open(serialPortName, O_RDWR | O_NOCTTY | O_NONBLOCK); if (serial_fd == -1) { perror("Error opening serial port"); return EXIT_FAILURE; } // Set serial port parameters struct termios tty; memset(&tty, 0, sizeof(tty)); if (tcgetattr(serial_fd, &tty) != 0) { perror("Error getting serial port attributes"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } cfsetospeed(&tty, baudRate); cfsetispeed(&tty, baudRate); tty.c_cflag |= (CLOCAL | CREAD); tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; // Set parity if (parity == 'N') { tty.c_cflag &= ~PARODD; // No parity } else if (parity == 'E') { tty.c_cflag |= PARODD; // Even parity } else if (parity == 'O') { tty.c_cflag |= (PARENB | PARODD); // Odd parity } // Set data bits tty.c_cflag &= ~CSIZE; if (dataBits == 8) { tty.c_cflag |= CS8; } else if (dataBits == 7) { tty.c_cflag |= CS7; } else { fprintf(stderr, "Invalid number of data bits\n"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } // Set stop bits if (stopBits == 1) { tty.c_cflag &= ~CSTOPB; // 1 stop bit } else if (stopBits == 2) { tty.c_cflag |= CSTOPB; // 2 stop bits } else { fprintf(stderr, "Invalid number of stop bits\n"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) { perror("Error setting serial port attributes"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } if (access(outputFileName, F_OK) != -1) { // check for the file already exists, if yes, rename to filename_old_current_time char oldFileName[256]; strcpy(oldFileName, outputFileName); strcat(oldFileName, "_old_"); time_t now = time(NULL); struct tm *t = localtime(&now); char timeStr[256]; strftime(timeStr, sizeof(timeStr) - 1, "%Y%m%d_%H%M%S", t); strcat(oldFileName, timeStr); rename(outputFileName, oldFileName); } // Open the output file FILE *outputFile = fopen(outputFileName, "wb"); if (!outputFile) { perror("Error opening output file"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } // Register signal handler for graceful termination signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); printf("Starting...\n"); char buffer[BUFFER_SIZE]; while (!stop_flag) { ssize_t bytesRead = read(serial_fd, buffer, sizeof(buffer)); if (bytesRead > 0) { fwrite(buffer, 1, bytesRead, outputFile); fflush(outputFile); } else if (bytesRead < 0) { perror("Error reading from serial port"); cleanup(serial_fd, outputFile); return EXIT_FAILURE; } else { // No data read } } printf("Stopping...\n"); // Cleanup and close resources before exiting cleanup(serial_fd, outputFile); // Reset signal handlers to default behavior signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); return EXIT_SUCCESS; }
Python脚本
import serial import time port_name = "/dev/ttyXR0" baud_rate = 2000000 serial_port = serial.Serial(port_name, baud_rate) if serial_port: try: print("opened") except KeyboardInterrupt: pass finally: serial_port.close()
原因分析
Python的pySerial库在打开串口时,会完成很多C代码遗漏的初始化操作,这些操作修正了串口的硬件或驱动状态,让后续C程序能正常工作:
- 握手信号与硬件激活:pySerial默认会配置RTS/CTS、DTR/DSR等控制信号(比如拉低DTR),部分串口设备(尤其是工业级或定制UART)需要这些信号触发接收就绪状态,而你的C代码完全未处理这类信号。
- 完整的termios参数初始化:pySerial会将
termios所有字段重置为合理默认值,比如:- 禁用规范模式、回显、信号捕获:
c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) - 禁用软件流控制与输入预处理:
c_iflag &= ~(IXON | IXOFF | IXANY)、c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL) - 设置原始输出模式:
c_oflag &= ~OPOST
你的C代码仅初始化了c_cflag的部分参数,其他字段可能保留系统启动后的无效值,导致串口无法正确解析输入数据。
- 禁用规范模式、回显、信号捕获:
- 缓冲区清空:pySerial打开串口时会自动清空输入输出缓冲区(
tcflush),而C代码未做此操作,系统启动后残留的缓冲区数据或错误状态会干扰后续读取。 - 波特率的正确映射:Python脚本中用的2Mbps波特率,pySerial会自动映射为Linux
termios对应的B2000000宏;而C代码直接传入数值,若系统不支持这种写法,会导致波特率配置错误。
解决方案
修改C代码,补充以下缺失的初始化步骤:
配置原始模式,禁用不必要的处理:
// 禁用规范模式、回显、信号捕获 tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 禁用软件流控制与输入预处理 tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // 设置原始输出模式 tty.c_oflag &= ~OPOST;控制信号配置(根据设备手册调整):
如果设备需要DTR信号激活,添加以下代码:int status; ioctl(serial_fd, TIOCMGET, &status); status &= ~TIOCM_DTR; // 拉低DTR,若需要拉高则改为 |= TIOCM_DTR ioctl(serial_fd, TIOCMSET, &status);清空串口缓冲区:
tcflush(serial_fd, TCIOFLUSH); // 清空输入输出缓冲区修正波特率映射:
将输入的波特率数值映射为termios对应的宏:speed_t baud; switch(baudRate) { case 9600: baud = B9600; break; case 115200: baud = B115200; break; case 2000000: baud = B2000000; break; // 补充其他需要支持的波特率 default: fprintf(stderr, "Unsupported baud rate\n"); cleanup(serial_fd, NULL); return EXIT_FAILURE; } cfsetospeed(&tty, baud); cfsetispeed(&tty, baud);验证配置生效:
在tcsetattr后重新读取串口参数并打印,确认所有配置正确应用。
内容的提问来源于stack exchange,提问作者Hareendran MG
相关产品推荐
相关产品推荐

