C语言操作ttyUSB设备时控制台输出不稳定的原因排查
串口AT命令输出不稳定问题排查
问题背景
编写了操作ttyUSB设备的C语言代码,编译无错误,但执行发送AT命令时,控制台输出不稳定,有时返回OK,有时无输出。
代码及编译配置
C语言代码
// C library headers #include <stdio.h> #include <string.h> #include <stdlib.h> // Linux headers #include <fcntl.h> // Contains file controls like O_RDWR #include <errno.h> // Error integer and strerror() function #include <termios.h> // Contains POSIX terminal control definitions #include <unistd.h> // write(), read(), close() // Custom headers //// klammern ////#include "some_functions.h" ////int write_log(char*); int main(int argc, char* argv[]) { if (argc < 2) { printf("Usage: %s <at-command>\n", argv[0]); exit(-1); } int serial_port = open("/dev/ttyUSB4", O_RDWR); // Check for errors if (serial_port < 0) { printf("Error %i from open: %s\n", errno, strerror(errno)); exit(1); } struct termios tty; // Read in existing settings, and handle any error if(tcgetattr(serial_port, &tty) != 0) { printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); return 1; } tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common) tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size tty.c_cflag |= CS8; // 8 bits per byte (most common) tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) tty.c_lflag &= ~ICANON; tty.c_lflag &= ~ECHO; // Disable echo tty.c_lflag &= ~ECHOE; // Disable erasure tty.c_lflag &= ~ECHONL; // Disable new-line echo tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX) // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX) tty.c_cc[VTIME] = 50; // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.; 5 sek ^= 50 tty.c_cc[VMIN] = 0; // Set in/out baud rate to be 9600 cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); // Save tty settings, also checking for error if (tcsetattr(serial_port, TCSANOW, &tty) != 0) { printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); return 1; } // Write to serial port //unsigned char msg[] = { 'a', 't', '+', 'c', 'g', 'p', 'a', 'd', 'd', 'r', '=', '1', '\r' }; unsigned int argv1_len = strlen(argv[1]); char msg[argv1_len+2]; // unsigned char strcpy(msg, argv[1]); ////write_log(msg); strcat(msg, "\r"); write(serial_port, msg, sizeof(msg)); // Allocate memory for read buffer, set size according to your needs char read_buf [256]; // Normally you wouldn't do this memset() call, but since we will just receive // ASCII data for this example, we'll set everything to 0 so we can // call printf() easily. memset(&read_buf, '\0', sizeof(read_buf)); // Read bytes. The behaviour of read() (e.g. does it block?, // how long does it block for?) depends on the configuration // settings above, specifically VMIN and VTIME int num_bytes = read(serial_port, &read_buf, sizeof(read_buf)); // n is the number of bytes read. n may be 0 if no bytes were received, and can also be -1 to signal an error. if (num_bytes < 0) { printf("Error reading: %s", strerror(errno)); close(serial_port); return 1; }else if (num_bytes == 0) { printf("0"); close(serial_port); return 1; } // Here we assume we received ASCII data, but you might be sending raw bytes (in that case, don't try and // print it to the screen like this!) //printf("Read %i bytes. Received message: %s", num_bytes, read_buf); printf("%s", read_buf); fflush(stdout); ////removeLeadingNewlines(read_buf); ////write_log(read_buf); close(serial_port); return 0; // success }
Makefile
WARNFLAGS = -W -Wall -Werror OPTFLAGS = -O3 DEBUGFLAGS = -ggdb3 -DDEBUG CFLAGS += $(WARNFLAGS) binaries = at_commander ifdef DEBUG CFLAGS += $(DEBUGFLAGS) else CFLAGS += $(OPTFLAGS) endif all: $(binaries) at_commander: some_functions.c clean: $(RM) *~ $(binaries) *.o
执行现象
root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at OK root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at OK root@OpenMPTCProuter:~/autostart# ./at_commander at OK root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at root@OpenMPTCProuter:~/autostart# ./at_commander at OK root@OpenMPTCProuter:~/autostart#
问题原因及修复方案
1. 错误的命令发送长度
代码中使用sizeof(msg)作为write的参数,而msg是变长数组,实际有效数据长度是strlen(msg)(命令内容+\r),sizeof(msg)会包含额外的一个字节(数组预留的空间),导致向设备发送了多余的空字节,干扰设备对AT命令的解析,这是响应不稳定的核心原因。
修复:将write调用改为:
write(serial_port, msg, strlen(msg));
2. 未清空串口缓冲区
每次打开串口后,没有清空输入输出缓冲区,可能残留之前的通信数据,导致读取到无效内容或干扰当前命令的响应。
修复:打开串口成功后,添加缓冲区清空操作:
tcflush(serial_port, TCIOFLUSH);
3. 单次读取无法保证获取完整响应
当前代码仅执行一次read,但串口设备的响应可能分批次到达(比如先返回命令回显,再返回OK),单次read可能仅读取到部分数据,或因数据未及时到达而读取失败。
修复:使用select配合循环读取,直到超时或获取到预期响应:
char read_buf[256] = {0}; int total_bytes = 0; fd_set read_fds; struct timeval timeout; while (1) { FD_ZERO(&read_fds); FD_SET(serial_port, &read_fds); timeout.tv_sec = 5; timeout.tv_usec = 0; int ret = select(serial_port + 1, &read_fds, NULL, NULL, &timeout); if (ret == -1) { perror("select error"); close(serial_port); return 1; } else if (ret == 0) { // 超时退出 break; } if (FD_ISSET(serial_port, &read_fds)) { int num_bytes = read(serial_port, read_buf + total_bytes, sizeof(read_buf) - total_bytes - 1); if (num_bytes < 0) { perror("read error"); close(serial_port); return 1; } else if (num_bytes == 0) { break; } total_bytes += num_bytes; read_buf[total_bytes] = '\0'; // 检测是否收到OK if (strstr(read_buf, "OK") != NULL) { break; } } } if (total_bytes > 0) { printf("%s", read_buf); fflush(stdout); } else { printf("No response received\n"); }
4. 可选:命令大小写适配
部分串口设备对AT命令大小写敏感,可将输入的命令转为大写后发送,避免因大小写问题导致响应异常:
// 转大写示例 for (int i = 0; msg[i]; i++) { msg[i] = toupper(msg[i]); }
内容的提问来源于stack exchange,提问作者mÄxZ
相关产品推荐
相关产品推荐

