You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:15:55