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

串口文件

在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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <pthread.h>

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;
}

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

### 回答1: MT7688是一款高度集成的无线芯片,广泛应用于物联网设备中。对于MT7688应用开发的调用,主要包括以下几个方面: 1. 开发环境的搭建:首先,需要搭建好开发环境,确保开发工具链、编译器等软件的正确安装。MT7688一般支持OpenWrt操作系统,开发者可以通过官方提供的软件包进行安装。 2. 熟悉MT7688的功能特性:了解MT7688的硬件结构和功能特性是调用的前提。MT7688支持Wi-Fi、蓝牙、以太网等多种通信方式,且可与其他外围设备进行串口、GPIO等接口的交互。 3. 编写应用程序:开发者可以根据自己的需求,使用C、C++等编程语言编写应用程序。可以利用MT7688的通信能力进行网络传输、数据采集等操作。开发者也可以通过编写驱动程序来控制MT7688与外围设备的交互。 4. 调试和测试:开发完成后,需要对应用程序进行调试和测试,确保其正常运行和稳定性。可以利用串口调试工具、网络调试工具等进行调试,同时也要考虑系统的安全性、稳定性等方面。 总之,MT7688应用开发的调用需要开发者具备一定的硬件知识和编程技能,熟悉MT7688的功能特性,并能灵活运用所学知识进行程序开发和调试。 ### 回答2: MT7688是一款高度集成的应用处理器,常用于物联网设备和嵌入式系统的应用开发。在MT7688应用开发中,我们可以使用以下几种调用方式: 1. 应用层API调用:MT7688提供了一系列的API接口,开发者可以直接调用这些接口实现各种功能。例如,可以使用UART API来实现串口通信,使用GPIO API来控制IO口,使用WiFi API来实现无线网络连接等。 2. Linux系统调用:MT7688运行的是Linux操作系统,开发者可以使用标准的Linux系统调用来实现应用功能。例如,可以使用read和write系统调用来进行文件读写,使用socket系统调用来进行网络通信等。 3. 外部接口调用:MT7688支持多种外部接口,开发者可以通过这些接口与外部设备进行通信。例如,可以通过SPI接口与外部传感器进行数据交换,通过I2C接口读取外部设备的状态等。 4. 中断处理:MT7688支持中断机制,开发者可以在中断服务函数中实现各种异步事件的处理。例如,可以通过GPIO中断来处理外部设备的状态变化,通过定时器中断来实现定时任务等。 总而言之,MT7688应用开发的调用方式多种多样,开发者可以根据具体需求选择适合的调用方式来实现应用功能。无论是应用层API调用、Linux系统调用、外部接口调用还是中断处理,都可以实现MT7688应用的高效开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BinaryStarXin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值