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

ESP-IDF中结合select与fgets读取UART行数据的FD与FILE*转换

在ESP-IDF中结合select与fgets实现UART整行读取的解决方案

文件描述符与FILE*的相互转换

要解决你的问题,核心是掌握两个标准C库函数的用法:

  • 从文件描述符转FILE*:使用fdopen()函数,它可以将已打开的文件描述符关联到一个标准I/O流。用法示例:

    int fd = open("/dev/uart0", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("open uart failed");
        return -1;
    }
    FILE* fp = fdopen(fd, "r+"); // 模式要和open的权限匹配,这里r+对应O_RDWR
    if (!fp) {
        perror("fdopen failed");
        close(fd);
        return -1;
    }
    

    注意:一旦用fdopen关联后,只能用fclose()关闭流,它会自动关闭对应的文件描述符,不要再调用close(fd),否则会导致双重释放错误。

  • 从FILE*转文件描述符:使用fileno()函数,直接从已初始化的FILE*中提取对应的文件描述符:

    FILE* fp = fopen("/dev/uart0", "r+");
    if (!fp) {
        perror("fopen failed");
        return -1;
    }
    int fd = fileno(fp);
    if (fd < 0) {
        perror("fileno failed");
        fclose(fp);
        return -1;
    }
    

结合select与fgets的完整实现示例

下面是贴合ESP-IDF环境的代码,实现用select监听UART就绪,再用fgets读取整行数据:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <errno.h>

#define UART_DEV_PATH "/dev/uart0"
#define BUF_SIZE 128

int main() {
    // 打开UART设备获取文件描述符
    int fd = open(UART_DEV_PATH, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0) {
        perror("Failed to open UART");
        return -1;
    }

    // 将fd转换为FILE*,用于fgets
    FILE* uart_fp = fdopen(fd, "r+");
    if (!uart_fp) {
        perror("Failed to convert fd to FILE*");
        close(fd);
        return -1;
    }

    // 设置流为行缓冲,确保fgets能及时读取到整行
    setvbuf(uart_fp, NULL, _IOLBF, 0);

    fd_set read_fds;
    struct timeval timeout;
    char buf[BUF_SIZE];

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(fd, &read_fds);

        // 设置select超时时间,这里设为5秒
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        // 等待UART可读
        int ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);
        if (ret < 0) {
            perror("select error");
            break;
        } else if (ret == 0) {
            printf("Select timeout\n");
            continue;
        }

        // 检测到UART有数据,用fgets读取整行
        if (FD_ISSET(fd, &read_fds)) {
            if (fgets(buf, BUF_SIZE, uart_fp) != NULL) {
                // 处理读取到的行数据,注意buf末尾包含换行符
                printf("Received line: %s", buf);
            } else {
                // 读取错误或到达EOF
                if (ferror(uart_fp)) {
                    perror("fgets error");
                } else {
                    printf("UART EOF\n");
                }
                break;
            }
        }
    }

    // 关闭流,同时会关闭对应的fd
    fclose(uart_fp);
    return 0;
}

关键注意事项

  • 缓冲设置:默认情况下,FILE*是全缓冲模式,这可能导致select检测到数据后,fgets无法立即读取到内容。通过setvbuf(uart_fp, NULL, _IOLBF, 0)设置为行缓冲,这样当遇到换行符或缓冲区满时,数据会被刷新,更适合整行读取的场景。
  • 非阻塞模式:open时加上O_NONBLOCK可以避免fgets在没有数据时阻塞,但我们已经用select来等待就绪,所以这里的非阻塞主要是防止意外的阻塞情况。
  • 错误处理:每个系统调用和库函数的返回值都要检查,避免忽略错误导致问题难以排查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 10:23:21