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
相关产品推荐
相关产品推荐

