在 Linux 系统里,我们可以借助 select
、poll
或者 epoll
这些 I/O 多路复用机制达成串口数据读取的触发方式。这些机制能够让程序在特定文件描述符(像串口设备文件描述符)有数据可读时得到通知,进而进行数据读取操作,而不是像轮询方式那样持续调用 read
函数。
示例代码(使用 select
实现)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
// 设置串口波特率
void SetSpeed(int fd, int speed) {
struct termios options;
tcgetattr(fd, &options);
switch (speed) {
case 115200:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
// 可以添加更多波特率设置
default:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
}
tcsetattr(fd, TCSANOW, &options);
}
// 设置串口参数
void SetParity(int fd, int databits, int stopbits, char parity) {
struct termios options;
tcgetattr(fd, &options);
// 设置数据位
options.c_cflag &= ~CSIZE;
switch (databits) {
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
options.c_cflag |= CS8;
break;
}
// 设置奇偶校验位
switch (parity) {
case 'n':
case 'N':
options.c_cflag &= ~PARENB;
options.c_cflag &= ~PARODD;
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB);
break;
case 'e':
case 'E':
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
default:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~PARODD;
break;
}
// 设置停止位
switch (stopbits) {
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
options.c_cflag &= ~CSTOPB;
break;
}
tcsetattr(fd, TCSANOW, &options);
}
int main(void) {
int fd;
unsigned char buf[300];
unsigned short len;
fd = open("/dev/ttyS1", O_RDWR);
if (fd == -1) {
perror("can't open serial\n");
return -1;
}
SetParity(fd, 8, 1, 'n');
SetSpeed(fd, 115200);
fd_set readfds;
struct timeval timeout;
while (1) {
// 清空文件描述符集
FD_ZERO(&readfds);
// 将串口文件描述符加入读文件描述符集
FD_SET(fd, &readfds);
// 设置超时时间
timeout.tv_sec = 0;
timeout.tv_usec = 20000;
// 调用 select 函数等待事件发生
int activity = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (activity < 0) {
perror("select error");
break;
} else if (activity > 0) {
// 检查是否是串口文件描述符有数据可读
if (FD_ISSET(fd, &readfds)) {
len = read(fd, buf, 100);
if (len > 0) {
write(fd, buf, len);
}
}
}
}
close(fd);
return 0;
}
代码解释
SetSpeed
函数:此函数用于设置串口的波特率,它借助tcsetattr
函数来配置串口的输入和输出波特率。SetParity
函数:该函数用于设置串口的数据位、停止位和奇偶校验位,同样是通过tcsetattr
函数来完成串口参数的配置。main
函数:- 打开串口设备文件
/dev/ttyS1
,并且设置串口参数。 - 构建一个
fd_set
类型的文件描述符集readfds
,把串口文件描述符fd
添加到该集合中。 - 设定一个超时时间
timeout
,防止select
函数一直阻塞。 - 调用
select
函数等待事件发生,若有数据可读,select
函数会返回大于 0 的值。 - 利用
FD_ISSET
宏检查是否是串口文件描述符有数据可读,若有则调用read
函数读取数据,然后将数据回写到串口。
- 打开串口设备文件
通过这种方式,程序就无需持续轮询 read
函数,而是在有数据到达时才进行读取操作。