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

Linux C++串口设备读取问题:read()无数据时未返回0

Arduino与树莓派串口通信中read()函数无数据时不返回0的问题

我在实现Arduino和树莓派之间的数据传输,其他功能都正常,但read(usb, &buf, 1);在没有可用数据时始终不返回0。根据函数说明:

Read NBYTES into BUF from FD. Return the
number read, -1 for errors or 0 for EOF.

但实际运行中从未出现返回0的情况。

Arduino端代码

#include <Arduino.h>

bool blinking = false;
bool led_on = false;
int target_time;

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB
  }
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  char c;
  
  if (Serial.available() > 0) {
    c = Serial.read();
    switch (c) {
    case '0':
      // stop blinking
      blinking = false;
      if (led_on) {
        digitalWrite(LED_BUILTIN, LOW);
      }
      Serial.write("A", 1);
      break;
    case '1':
      // start blinking
      if (blinking == false) {
        blinking = true;
        digitalWrite(LED_BUILTIN, HIGH);
        led_on = true;
        target_time = millis() + 100; // turn off in 1 tenth of a second (100 milliseconds)
      }
      Serial.write("ABC\0", 4);
      break;
    default:
      Serial.write("E", 1);
      break;    
    }
  } else if (blinking) {
    if (millis() >= target_time) {
      if (led_on) {
        digitalWrite(LED_BUILTIN, LOW);
        led_on = false;
        target_time = millis() + 100; // turn on in 1 tenth of a second (100 milliseconds)
      } else {
        digitalWrite(LED_BUILTIN, HIGH);
        led_on = true;
        target_time = millis() + 100; // turn off in 1 tenth of a second (100 milliseconds)
      }
    }
  }
}

树莓派端相关函数代码

int readWhole(int usb, char *response){
    int n = 0,
        spot = 0;
    char buf = '\0';

    memset(response, '\0', sizeof response);

    while (1) {
        cout << "read\n";
        n = read(usb, &buf, 1);    
        cout << "n = " << n << "\n"; 
        usleep(100 * 1000);
        if (n == -1) {
            if (errno != EAGAIN) {
                fprintf(stderr, "read() failed: (%d) %s\n", errno, strerror(errno));
                return -1;
            }
            // errno == EAGAIN, loop around and read again
        } else {
            if (n == 1) {
                sprintf( &response[spot], "%c", buf );
                spot += n;
                cout << "got something: " << buf << "\n";
            }else{ // 0 == End Of File
                break;
            }
            // read 0 bytes, loop around an read again
        }        
    }
    return spot;
}

实际输出结果

read
n = -1
read
n = 1
got something: A
read
n = 1
got something: B
read
n = 1
got something: C
read
n = 1
got something: 
read
n = -1
read
n = -1
read

问题原因

  1. EOF的触发逻辑:read()返回0的唯一场景是串口连接被彻底关闭(比如Arduino断电、USB物理断开),而非当前无数据可读。如果你的串口是设置为非阻塞模式,无数据时read()会返回-1并将errno设为EAGAIN,这正是你看到的输出。
  2. 无效字符干扰:Arduino代码中发送了"ABC\0",其中的空字符\0会被树莓派正常读取,所以出现了got something: 的空输出,这并不是EOF。

解决方法

方法1:通过超时判定无数据状态

利用EAGAIN的特性,设置连续多次读取都返回EAGAIN时,判定为当前没有更多数据,主动退出循环:

int readWhole(int usb, char *response){
    int n = 0,
        spot = 0;
    char buf = '\0';
    int retry_count = 0;
    const int MAX_RETRY = 3; // 连续3次无数据则停止读取

    memset(response, '\0', sizeof(response));

    while (1) {
        cout << "read\n";
        n = read(usb, &buf, 1);    
        cout << "n = " << n << "\n"; 
        usleep(100 * 1000);
        if (n == -1) {
            if (errno != EAGAIN) {
                fprintf(stderr, "read() failed: (%d) %s\n", errno, strerror(errno));
                return -1;
            }
            retry_count++;
            if (retry_count >= MAX_RETRY) {
                break; // 连续无数据,退出循环
            }
        } else {
            retry_count = 0; // 读到数据重置计数器
            if (n == 1) {
                // 可选:过滤掉空字符,避免无效存储
                if (buf != '\0') {
                    response[spot] = buf;
                    spot += 1;
                    cout << "got something: " << buf << "\n";
                }
            }else{
                break; // 仅连接关闭时会触发
            }
        }        
    }
    return spot;
}

方法2:改用明确的结束符协议

修改Arduino的输出逻辑,不发送空字符,改用换行符\n作为数据结束标记,树莓派读到结束符就停止读取:

  • Arduino端修改发送代码:
// 替换原Serial.write("ABC\0", 4);
Serial.write("ABC\n", 4);
// 其他发送也统一加结束符:Serial.write("A\n", 2);
  • 树莓派端修改读取逻辑:
int readWhole(int usb, char *response){
    int n = 0,
        spot = 0;
    char buf = '\0';

    memset(response, '\0', sizeof(response));

    while (1) {
        cout << "read\n";
        n = read(usb, &buf, 1);    
        cout << "n = " << n << "\n"; 
        usleep(100 * 1000);
        if (n == -1) {
            if (errno != EAGAIN) {
                fprintf(stderr, "read() failed: (%d) %s\n", errno, strerror(errno));
                return -1;
            }
        } else {
            if (n == 1) {
                if (buf == '\n') {
                    break; // 读到结束符,停止读取
                }
                response[spot] = buf;
                spot += 1;
                cout << "got something: " << buf << "\n";
            }else{
                break;
            }
        }        
    }
    return spot;
}

方法3:切换为阻塞模式读取

如果程序不需要同时处理其他任务,可以在打开串口时设置为阻塞模式(默认可能为阻塞),此时read()会一直等待直到有数据到来,但无数据时会卡住程序,适合简单场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 20:26:02