一口Linux

文章数:1382 被阅读:1966155

账号入驻

Linux下应用层操作UART的四种方式

最新更新时间:2023-06-22
    阅读数:
点击左上方蓝色“一口Linux”,选择“设为星标


作者: 亚洲程序员盟主

串口文件

在linux中,针对所有的周边设备都提供了设备文件供用户访问,所以如果要访问串口,只要打开相关的设备文件即可。

在LInux下串口文件是位于/dev下的

    • COM1串口一为/dev/ttyS0

    • COM2串口2为/dev/ttyS1

或者

  • COM1串口一为/dev/ttyUSB0

  • COM2串口2为/dev/ttyUSB1

命令查询串口:

~$ ls /dev/ttyS*
/dev/ttyS0 /dev/ttyS12 /dev/ttyS16 /dev/ttyS2 /dev/ttyS23 /dev/ttyS27 /dev/ttyS30 /dev/ttyS6
/dev/ttyS1 /dev/ttyS13 /dev/ttyS17 /dev/ttyS20 /dev/ttyS24 /dev/ttyS28 /dev/ttyS31 /dev/ttyS7
/dev/ttyS10 /dev/ttyS14 /dev/ttyS18 /dev/ttyS21 /dev/ttyS25 /dev/ttyS29 /dev/ttyS4 /dev/ttyS8
/dev/ttyS11 /dev/ttyS15 /dev/ttyS19 /dev/ttyS22 /dev/ttyS26 /dev/ttyS3 /dev/ttyS5 /dev/ttyS9


方法1:轮询

1. 打开串口

fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open serial port");
return -1;
}

2. 配置串口

 tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &options);

其中,tcgetattr 和 tcsetattr 函数用于获取和设置串口参数。cfsetispeed 和 cfsetospeed 函数用于设置串口的输入和输出波特率,这里设置为 115200。options.c_cflag 表示控制标志位,用于配置串口控制参数,具体含义如下:

  • CLOCAL:忽略调制解调器的状态线,只允许本地使用串口。

  • CREAD:允许从串口读取数据。

  • PARENB:启用奇偶校验。&= ~PARENB则为禁用校验。

  • CSTOPB:使用两个停止位而不是一个。&= ~CSTOPB停止位为1。

  • CSIZE:表示字符长度的位掩码。在这里设置为 0,表示使用默认的 8 位数据位。

  • CS8:表示使用 8 位数据位。

  • CRTSCTS:启用硬件流控制,即使用 RTS 和 CTS 状态线进行流控制。

在示例程序中,我们将 CLOCAL 和 CREAD 标志位置为 1,表示允许本地使用串口,并允许从串口读取数据。我们将 PARENB、CSTOPB 和 CRTSCTS 标志位都设置为 0,表示不启用奇偶校验、使用一个停止位和禁用硬件流控制。最后,我们将 CSIZE 标志位设置为 0,然后将 CS8 标志位设置为 1,以表示使用 8 位数据位。

3. 读写

read(fd, buf, sizeof(buf)); // 返回接收个数
write(fd, buf, strlen(buf)); // 返回发送长度,负值表示发送失败

4. 关闭串口

close(fd);

完整示例

int open_port(const char *port)
{
int fd;
struct termios options;

// 打开串口设备
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open_port: Unable to open serial port");
return -1;
}

// 配置串口参数
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &options);

return fd;
}

int main()
{
int fd;
char buf[255];
int n;

// 打开串口设备
fd = open_port("/dev/ttyUSB0");
if (fd == -1) {

printf("open err\n");
exit(1);
}
while (1)
{
// 读取串口数据
n = read(fd, buf, sizeof(buf));
if (n > 0) {
printf("Received: %.*s\n", n, buf);
}

// 发送串口数据
strcpy(buf, "Hello, world!\n");
n = write(fd, buf, strlen(buf));
if (n < 0) {
perror("write failed\n");
}
usleep(10 * 1000);
}

// 关闭串口设备
close(fd);
printf("close uart\n");

return 0;
}

方法2:中断读取示例

上面给出的串口示例是使用轮询的方式读取串口数据,这种方式在某些场景下可能会占用大量 CPU 资源。实际上,对于 Linux 系统来说,还可以使用中断方式接收串口数据,这样可以大大减少 CPU 的占用率,并且能够更快地响应串口数据。

要使用中断方式接收串口数据,可以使用 select 函数来监听串口文件描述符的可读事件。当串口数据可读时,select 函数将返回,并且可以调用 read 函数来读取串口数据。这种方式可以避免轮询操作,只有在串口数据可读时才会执行读取操作,因此能够减少 CPU 的占用率。

以下是一个简单的使用中断方式接收串口数据的示例程序:

#include 
#include
#include
#include
#include
#include

int main() {
int fd;
struct termios options;
fd_set rfds;

// 打开串口设备
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open");
return -1;
}

// 配置串口参数
tcgetattr(fd, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
tcsetattr(fd, TCSANOW, &options);

while (1) {
// 使用 select 函数监听串口文件描述符的可读事件
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);

// 读取串口数据
char buf[256];
int n = read(fd, buf, sizeof(buf));
if (n > 0) {
printf("Received data: %.*s\n", n, buf);
}
}

// 关闭串口设备
close(fd);

return 0;
}

需要注意的是,在使用中断方式接收串口数据时,需要对串口文件描述符设置为非阻塞模式,以便在 select 函数返回时立即读取串口数据。可以使用 fcntl 函数来设置文件描述符的标志位,如下所示:

// 设置串口文件描述符为非阻塞模式
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

方法3:信号的方式接收数据

#include 
#include
#include
#include
#include
#include

int fd;

void sigio_handler(int sig) {
char buf[256];
int n = read(fd, buf, sizeof(buf));
if (n > 0) {
printf("Received data: %.*s\n", n, buf);
}
}

int main() {
struct termios options;
struct sigaction sa;

// 打开串口设备
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open");
return -1;
}

// 配置串口参数
tcgetattr(fd, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
tcsetattr(fd, TCSANOW, &options);

// 设置串口文件描述符为异步通知模式
/* 将串口文件描述符设置为当前进程的拥有者,从而接收该文件描述符相关的信号。*/
fcntl(fd, F_SETOWN, getpid());
int flags = fcntl(fd, F_GETFL, 0); // 先获取当前配置, 下面只更改O_ASYNC标志
/* 将串口文件描述符设置为非阻塞模式,从而允许该文件描述符异步地接收数据和信号。*/
fcntl(fd, F_SETFL, flags | O_ASYNC);

// 设置 SIGIO 信号的处理函数
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
/* 设置了 SIGIO 信号的处理函数为 sigio_handler,从而在该信号被触发时读取串口数据并进行处理。*/
sigaction(SIGIO, &sa, NULL);

while (1) {
// 等待 SIGIO 信号
sleep(1);
}

// 关闭串口设备
close(fd);

return 0;
}

上述代码中,使用了 fcntl 函数将串口文件描述符设置为异步通知模式,并使用 SIGIO 信号来通知程序串口数据已经可读。当程序接收到 SIGIO 信号时,会调用 sigio_handler 函数来读取并处理串口数据。


在这段代码中, sigemptyset(&sa.sa_mask); 的作用是将信号处理函数在执行时要屏蔽的信号集合清空,即将其设置为空集。

每个进程都有一个信号屏蔽字,它表示了当前被阻塞的信号集合。当一个信号被阻塞时,它将被加入到信号屏蔽字中,而当信号被解除阻塞时,它将被从信号屏蔽字中移除。如果信号处理函数在执行时需要屏蔽其他的信号,则可以使用 sigaddset 等函数将需要屏蔽的信号添加到信号屏蔽字中。但是,在本例中,我们需要处理的信号是SIGIO,它通常不需要被屏蔽,因此我们使用 sigemptyset 函数将信号屏蔽字清空,以确保在处理SIGIO信号时不会屏蔽任何其他信号。

在Linux系统中,使用 sigaction 函数注册信号处理函数时,可以设置一些标志来指定信号处理的行为。例如,可以使用 SA_RESTART 标志来指定当系统调用被信号中断时自动重启该系统调用。在本例中,由于我们并不需要设置任何标志,因此将 sa.sa_flags 字段设置为0即可。这表示信号处理函数不需要任何特殊的行为,只需要按照默认的方式处理信号即可。

方法4:使用线程接收串口数据:

#include 
#include
#include
#include
#include
#include

void *read_thread(void *arg) {
int fd = *(int *)arg;
char buf[256];
int n;

while (1) {
// 读取串口数据
n = read(fd, buf, sizeof(buf));
if (n > 0) {
printf("Received data: %.*s\n", n, buf);
}
}

return NULL;
}

int main() {
int fd;
struct termios options;
pthread_t tid;

// 打开串口设备
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open");
return -1;
}

// 配置串口参数
tcgetattr(fd, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
tcsetattr(fd, TCSANOW, &options);

// 创建读取线程
if (pthread_create(&tid, NULL, read_thread, &fd) != 0) {
perror("pthread_create");
return -1;
}

while (1) {
// 主线程的其他处理逻辑
sleep(1);
}

// 关闭串口设备
close(fd);

return 0;
}

上述代码中,创建了一个读取线程,不断读取串口数据并进行处理。主线程可以在读取线程运行的同时进行其他处理逻辑。

这是一口君的新书,感谢大家支持!

end


一口Linux


关注,回复【 1024 】海量Linux资料赠送

精彩文章合集


文章推荐

【专辑】 ARM
【专辑】 粉丝问答
专辑 linux 入门
专辑 计算机网络
专辑 Linux驱动

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: TI培训

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved