文章目录
- 引言
- UART 通信中的 `select` 函数
- `select` 函数的工作原理
- 使用 `select` 进行 UART 通信的注意事项
- 示例代码
引言
UART(Universal Asynchronous Receiver/Transmitter)是一种用于异步串行通信的硬件协议,常用于计算机和外设之间的数据交换。在嵌入式系统中,UART 通信非常常见,用于连接传感器、微控制器、调制解调器等设备。为了实现高效的 UART 通信,通常需要使用非阻塞式 I/O 操作,这时候 select
函数就派上用场了。
UART 通信中的 select
函数
select
函数允许我们同时监控多个文件描述符,并在这些文件描述符变为可读、可写或发生错误时通知程序。这样可以避免程序在等待 I/O 操作时被阻塞,提高系统的响应速度和效率。
select
函数的工作原理
- 监控文件描述符:
select
接受一组文件描述符,并监控这些文件描述符的状态。 - 阻塞等待:可以设置阻塞等待或非阻塞等待,直到文件描述符变为可读、可写或发生错误,或者达到超时时间。
- 返回事件:
select
返回后,程序可以检查哪些文件描述符变为可读、可写或发生错误,并进行相应的处理。
使用 select
进行 UART 通信的注意事项
在使用 select
进行 UART 通信时,有几个关键点需要注意:
- 正确初始化文件描述符集
每次调用 select
之前,需要重新初始化文件描述符集(fd_set
),因为 select
调用会修改这个集合:
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
- 设置超时值
在每次调用 select
之前,需要设置超时值(struct timeval
)。同样,select
会修改这个结构体,因此每次调用前都需要重新设置:
struct timeval tv;
tv.tv_sec = timeout_us / 1000000;
tv.tv_usec = timeout_us % 1000000;
- 处理
select
的返回值
检查 select
的返回值以确定是否有文件描述符变为可读、可写或出错:
int ret = select(fd + 1, &rfds, NULL, NULL, &tv);
if (ret == -1) {// Handle error
} else if (ret == 0) {// Handle timeout
} else {if (FD_ISSET(fd, &rfds)) {// Handle readable data}
}
- 避免文件描述符泄漏
确保在程序结束或不再需要文件描述符时关闭它们,防止资源泄漏:
close(fd);
- 多线程环境下的文件描述符使用
如果在多线程环境下使用文件描述符,需要确保对文件描述符的操作是线程安全的。使用互斥锁(mutex)保护对文件描述符的操作,避免竞态条件。
- 检查文件描述符的有效性
在调用 select
之前检查文件描述符是否有效,确保文件描述符在整个生命周期中是有效的:
int check_fd_valid(int fd) {return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
- 处理边界情况
处理 read
返回 0
的情况,这通常表示文件结束或没有数据可用:
ssize_t bytesRead = read(fd, buff, len);
if (bytesRead == -1) {// Handle read error
} else if (bytesRead == 0) {// Handle EOF or no data
}
示例代码
以下是综合了上述注意事项的改进代码示例:
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <stdarg.h>
#include <fcntl.h>
#include <pthread.h>void ota_info(const char *fmt, ...) {va_list args;va_start(args, fmt);vprintf(fmt, args);printf("\n");va_end(args);
}int check_fd_valid(int fd) {return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}int uart_read(int fd, uint8_t *buff, int len, int timeout_us)
{int ret;struct timeval tv;fd_set rfds;memset(buff, 0, len);// Debug log to print fd value before selectota_info("uart_read: fd=%d, timeout_us=%d, thread_id=%ld", fd, timeout_us, pthread_self());// Check if fd is validif (!check_fd_valid(fd)) {ota_info("Invalid file descriptor: %d", fd);return -1;}FD_ZERO(&rfds);FD_SET(fd, &rfds);memset(&tv, 0, sizeof(tv));tv.tv_sec = timeout_us / 1000000;tv.tv_usec = timeout_us % 1000000;ret = select(fd + 1, &rfds, NULL, NULL, &tv);if (ret == -1) {// Select itself failedota_info("select() failed. ret %d, errno %d, %m", ret, errno);return -1;} else if (ret == 0) {// Timeout occurred without any file descriptor becoming readyota_info("select() timed out.");return 0; // Indicate timeout}if (FD_ISSET(fd, &rfds)) {ssize_t bytesRead = read(fd, buff, len);if (bytesRead == -1) {// Error during readota_info("read() failed. errno %d, %m", errno);return -1;} else if (bytesRead == 0) {// EOF reached or no data availableota_info("read() returned 0, no data available or EOF reached.");return 0;}// Return actual bytes readota_info("read() succeeded. bytesRead=%ld, thread_id=%ld", bytesRead, pthread_self());return bytesRead;} else {// This branch should not be reachable given the checks aboveota_info("select() returned with no file descriptor ready.");return 0;}
}int main() {// Example usageint fd = 0; // Example file descriptor, should be initialized properlyuint8_t buffer[256];int len = 256;int timeout_us = 5000000; // 5 secondsint result = uart_read(fd, buffer, len, timeout_us);if (result > 0) {printf("Read %d bytes\n", result);} else if (result == 0) {printf("Timeout or no data available\n");} else {printf("Error occurred\n");}return 0;
}
通过遵循这些注意事项,可以确保在使用 select
函数进行 UART 通信时,代码更加健壮和可靠,减少出现难以复现的错误的可能性。希望这篇博客能帮助你在项目中更好地使用 select
函数进行 UART 通信。