使用 select 进行 UART 通信的注意事项

引言

UART(Universal Asynchronous Receiver/Transmitter)是一种用于异步串行通信的硬件协议,常用于计算机和外设之间的数据交换。在嵌入式系统中,UART 通信非常常见,用于连接传感器、微控制器、调制解调器等设备。为了实现高效的 UART 通信,通常需要使用非阻塞式 I/O 操作,这时候 select 函数就派上用场了。

UART 通信中的 select 函数

select 函数允许我们同时监控多个文件描述符,并在这些文件描述符变为可读、可写或发生错误时通知程序。这样可以避免程序在等待 I/O 操作时被阻塞,提高系统的响应速度和效率。

select 函数的工作原理

  • 监控文件描述符select 接受一组文件描述符,并监控这些文件描述符的状态。
  • 阻塞等待:可以设置阻塞等待或非阻塞等待,直到文件描述符变为可读、可写或发生错误,或者达到超时时间。
  • 返回事件select 返回后,程序可以检查哪些文件描述符变为可读、可写或发生错误,并进行相应的处理。

使用 select 进行 UART 通信的注意事项

在使用 select 进行 UART 通信时,有几个关键点需要注意:

  1. 正确初始化文件描述符集

每次调用 select 之前,需要重新初始化文件描述符集(fd_set),因为 select 调用会修改这个集合:

FD_ZERO(&rfds);
FD_SET(fd, &rfds);
  1. 设置超时值

在每次调用 select 之前,需要设置超时值(struct timeval)。同样,select 会修改这个结构体,因此每次调用前都需要重新设置:

struct timeval tv;
tv.tv_sec = timeout_us / 1000000;
tv.tv_usec = timeout_us % 1000000;
  1. 处理 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
    }
}
  1. 避免文件描述符泄漏

确保在程序结束或不再需要文件描述符时关闭它们,防止资源泄漏:

close(fd);
  1. 多线程环境下的文件描述符使用

如果在多线程环境下使用文件描述符,需要确保对文件描述符的操作是线程安全的。使用互斥锁(mutex)保护对文件描述符的操作,避免竞态条件。

  1. 检查文件描述符的有效性

在调用 select 之前检查文件描述符是否有效,确保文件描述符在整个生命周期中是有效的:

int check_fd_valid(int fd) {
    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
  1. 处理边界情况

处理 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 select
    ota_info("uart_read: fd=%d, timeout_us=%d, thread_id=%ld", fd, timeout_us, pthread_self());

    // Check if fd is valid
    if (!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 failed
        ota_info("select() failed. ret %d, errno %d, %m", ret, errno);
        return -1;
    } else if (ret == 0) {
        // Timeout occurred without any file descriptor becoming ready
        ota_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 read
            ota_info("read() failed. errno %d, %m", errno);
            return -1;
        } else if (bytesRead == 0) {
            // EOF reached or no data available
            ota_info("read() returned 0, no data available or EOF reached.");
            return 0;
        }
        // Return actual bytes read
        ota_info("read() succeeded. bytesRead=%ld, thread_id=%ld", bytesRead, pthread_self());
        return bytesRead;
    } else {
        // This branch should not be reachable given the checks above
        ota_info("select() returned with no file descriptor ready.");
        return 0;
    }
}

int main() {
    // Example usage
    int fd = 0; // Example file descriptor, should be initialized properly
    uint8_t buffer[256];
    int len = 256;
    int timeout_us = 5000000; // 5 seconds

    int 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 通信。

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值