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

C语言串口通信异常:首次请求后无法正确读取仪器数据

串口通信后续请求数据偏移问题解决方案

问题背景

在Mac OSX M1 Max平台使用XCode开发C语言串口程序,向仪器发送d\r查询指令时,首次能收到正确响应 2.31<0D><0A>,但后续发送相同指令时,读取到的是偏移错误数据.31<0D><0A>。仪器串口参数为9600波特率、无校验、8位数据位、1位停止位,已通过SerialTools验证通信正常,且同类历史程序可稳定运行,怀疑问题与串口缓冲区或termios配置相关。

问题代码

#define TERMINAL    "/dev/cu.usbserial-4013420"
#define DISPLAY_STRING 1

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <sys/stat.h>
#include <sys/types.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    tty.c_lflag |= ICANON | ISIG;  /* canonical input */
    tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
    tty.c_lflag |= (ICANON | ECHO | ECHOE);

        tty.c_iflag &= ~IGNCR;  /* preserve carriage return */
        tty.c_iflag &= ~INPCK;
        tty.c_iflag &= ~(INLCR | ICRNL | IMAXBEL);
        tty.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */
        tty.c_oflag &= ~OPOST;
 tty.c_cc[VEOF] = 0x0A;
    
    
    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }

   
    return 0;
}


int main(int argc, const char * argv[])
{
    char *portname = TERMINAL;
    int fd;
    size_t wlen;            // use size_t for unsigned int
    int tester;
    char *xstr = "d\r";
    size_t xlen = strlen(xstr);
    unsigned char buff[10]={0};
    size_t rdlen2;
    
    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 9600, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B9600);

    
    /* simple output  write, then read and repeat...*/
   
    printf("xstr is %s\n", xstr);
    
    wlen = write(fd, xstr, xlen);
    
    rdlen2 = read(fd, buff, sizeof(buff));

    wlen = write(fd, xstr, xlen);

    rdlen2 = read(fd, buff, sizeof(buff));

    wlen = write(fd, xstr, xlen);

    rdlen2 = read(fd, buff, sizeof(buff));

    wlen = write(fd, xstr, xlen);

    rdlen2 = read(fd, buff, sizeof(buff));

    wlen = write(fd, xstr, xlen);

    rdlen2 = read(fd, buff, sizeof(buff));
   

    close(fd);
}

问题分析

  1. 规范模式冲突:代码中重复设置ICANON(规范模式)并启用ECHO,同时将VEOF设为换行符,导致系统对串口数据进行自动缓冲和字符转换,破坏了仪器返回数据的完整性。
  2. 读写逻辑不严谨:发送指令后直接读取,未确保数据完全发送完成,也未清空缓冲区残留数据,导致后续读取被历史数据干扰。
  3. 无超时与完整性校验:单次读取无法保证获取完整响应,也没有超时机制,容易出现数据截断。

解决方案

1. 修正termios配置(改用原始模式)

仪器返回固定格式的ASCII数据,适合用原始模式(非规范模式)处理,避免系统自动转换:

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* 忽略modem控制 */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;                 /* 8位数据位 */
    tty.c_cflag &= ~PARENB;             /* 无校验 */
    tty.c_cflag &= ~CSTOPB;             /* 1位停止位 */
    tty.c_cflag &= ~CRTSCTS;            /* 无硬件流控 */

    /* 禁用规范模式,启用原始模式 */
    tty.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE | ECHONL | IEXTEN);
    /* 禁用输入转换,保留原始字符 */
    tty.c_iflag &= ~(IGNCR | INPCK | INLCR | ICRNL | IMAXBEL | IXON | IXOFF | IXANY);
    /* 禁用输出转换,直接发送原始数据 */
    tty.c_oflag &= ~OPOST;

    /* 设置读取超时:1秒超时,最少读取1个字符 */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

2. 完善读写逻辑

int main(int argc, const char * argv[])
{
    char *portname = TERMINAL;
    int fd;
    size_t wlen;
    char *xstr = "d\r";
    size_t xlen = strlen(xstr);
    unsigned char buff[10]={0};
    size_t rdlen;
    int i;

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }

    if (set_interface_attribs(fd, B9600) < 0) {
        close(fd);
        return -1;
    }

    /* 初始化时清空输入缓冲区 */
    tcflush(fd, TCIFLUSH);

    printf("xstr is %s\n", xstr);

    for (i = 0; i < 5; i++) {
        memset(buff, 0, sizeof(buff)); // 每次循环清空接收缓冲区
        wlen = write(fd, xstr, xlen);
        if (wlen != xlen) {
            printf("Write error: wrote %zu bytes out of %zu\n", wlen, xlen);
            continue;
        }
        /* 等待所有数据发送到串口硬件 */
        tcdrain(fd);

        /* 循环读取直到获取完整响应或超时 */
        rdlen = 0;
        while (rdlen < sizeof(buff)-1) {
            ssize_t ret = read(fd, buff + rdlen, sizeof(buff)-1 - rdlen);
            if (ret < 0) {
                printf("Read error: %s\n", strerror(errno));
                break;
            } else if (ret == 0) {
                printf("Timeout, read %zu bytes\n", rdlen);
                break;
            }
            rdlen += ret;
            /* 检查是否收到结束符<0D><0A> */
            if (rdlen >=2 && buff[rdlen-2] == 0x0D && buff[rdlen-1] == 0x0A) {
                break;
            }
        }

        /* 打印原始字节和字符串形式的响应 */
        printf("Received %zu bytes: ", rdlen);
        for (size_t j=0; j<rdlen; j++) {
            printf("0x%02X ", buff[j]);
        }
        printf("| %.*s\n", (int)rdlen, buff);
    }

    close(fd);
    return 0;
}

关键修改说明

  • 禁用规范模式,改用原始模式,避免系统对串口数据的自动处理
  • 设置VMIN=1和VTIME=10,确保读取时能及时获取数据且不会无限等待
  • 每次循环前清空接收缓冲区,避免残留数据干扰
  • 使用tcdrain确保所有发送数据已输出到串口硬件
  • 循环读取直到检测到结束符<0D><0A>,保证获取完整响应

内容的提问来源于stack exchange,提问作者Markys

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 10:14:55