异步I/O+termios编程实现串口接收解释

termios是面向所有终端设备的。
termios 结构体:

   tcflag_t c_iflag;      /* input modes */
   tcflag_t c_oflag;      /* output modes */
   tcflag_t c_cflag;      /* control modes */
   tcflag_t c_lflag;      /* local modes */
   cc_t     c_cc[NCCS];   /* special characters */

终端的三种模式:

规范模式(命令行的形式):
所有输入基于行进行处理。在用户输入一个行结束符(回车符、EOF等)之前,系统调用read()函数读不到用户输入的任何字符。其次,除了EOF之外的行结束符与普通字符一样会被read()函数读取到缓冲区中。一次调用read()只能读取一行数据。
非规范模式:
所有输入时即时有效的,不需要用户另外输入行结束符。
原始模式:
是一种特殊的非规范模式,所有的输入数据以字节为单位被处理。即有一个字节输入时,触发输入有效。

但是串口并不仅仅只扮演着人机交互的角色(数据以字符的形式传输、也就数说传输的数据其实字符对应的 ASCII 编码值);串口本就是一种数据串行传输接口,通过串口可以与其他设备或传感器进行数据传输、通信,譬如很多 sensor 就使用了串口方式与主机端进行数据交互。那么在这种情况下,我们就得使用原始模式,意味着通过串口传输的数据不应进行任何特殊处理、不应将其解析成 ASCII 字符。

终端控制API函数

tcgetattr      取属性(termios结构)
tcsetattr      设置属性(termios结构)
cfgetispeed    得到输入速度
cfgetospeed    得到输出速度
cfsetispeed    设置输入速度
cfsetospeed    设置输出速度
tcdrain        等待所有输出都被传输
tcflow         挂起传输或接收
tcflush        刷清未决输入和/或输出
tcsendbreak    送BREAK字符
tcgetpgrp      得到前台进程组ID
tcsetpgrp      设置前台进程组ID
cfmakeraw    将终端设置成原始模式
cfsetspeed   设置输入输出速度

需要注意的地方:

fd = open(device,O_RDWR|O_NOCTTY|O_NDELAY);
  1. O_NONBLOCK/如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
    2.O_NOCTTY 如果pathname指的是终端设备,则不将此设备分配作为此进程的控制终端。(个人的理解是只有read和write才能对指定此终端设备进行通信)。
    3.O_NONBLOCK和O_NDELAY几乎相同,它们的差别在于设立O_NDELAY会使I/O函式马上回传0,但是又衍生出一个问题,因为读取到档案结尾时所回传的也是0,这样无法得知是哪中情况;因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。
    4.fd是int类型,device是char*指针,eg:“/dev/ttyUSB0”。
tcgetattr(fd, &old_cfg)
  1. 获取终端设备的参数,保存至old_cfg中,old_cfg是自己设定的全局变量,类型是struct termios。old_cfg的作用,退出主循环后,还要将终端设备的工作模式恢复成规范模式(命令行模式解析成ASCII码)。
tcsetattr(fd, TCSANOW, &old_cfg);
close(fd);
  1. 中间的参数是配置立即生效
cfmakeraw(&new_cfg);
  1. 将终端设备设置为原始模式
new_cfg.c_cflag |= CREAD;	//接收模式
cfsetspeed(&new_cfg,speed)

1.设置输入输出baud率,speed是speed_t类型的

new_cfg.c_cflag &= ~CSIZE; //清空数据位控制字
new_cfg.c_cflag |= CS8;		//数据位八位
new_cfg.c_cflag &= ~PARENB;	//配置为无校验
new_cfg.c_iflag &= ~INPCK;		//配置为无校验
new_cfg.c_cflag &= ~CSTOPB;	//一个停止位,如果是或上就是两个停止位
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;

在对接收字符和等待时间没有特别要求的情况下,可以将 MIN 和 TIME 设置为 0,这样则在任何情况下 read()调用都会立即返回,此时对串口的 read 操作会设置为非阻塞方式

接下来是配置使用异步I/O,类似于使用中断。

int flag;
flag = fcntl(fd, F_GETFL);             //先获取原来的 flag
flag |= O_ASYNC;                         //将 O_ASYNC 标志添加到  flag 
fcntl(fd, F_SETFL, flag);               //重新设置  flag
fcntl(fd, F_SETOWN, getpid());  

为文件描述符设置异步 I/O 事件的接收进程,也就是设置异步 I/O 的所有者。

fcntl(fd, F_SETSIG, SIGRTMIN);  

SIGRTMIN编号是34,小于34的都是不可靠信号。
SIGRTMAX编号是64号。
指定实时信号SIGRTMIN作为异步I/O通知信号。 SIGRTMIN->(SIG-REAL-TIME-MINIMUM)是实时信号
注意: F_SETSIG要使用#define _GNU_SOURCE 宏定义

struct sigaction act;
act.sa_sigaction = io_handler; //sa_sigaction是个函数指针,指向相应的处理函数
act.sa_flags = SA_SIGINFO; 
sigemptyset(&act.sa_mask); 
sigaction(SIGRTMIN, &act, NULL);

下面是例程代码

#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE宏
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <sys/ioctl.h> 
#include <errno.h> 
#include <string.h> 
#include <signal.h> 
#include <termios.h>

typedef struct uart_cfg_t
{
    unsigned int baudrate;
    unsigned char databit;
    char parity;
    unsigned char stopbit;
}
uart_cfg;

static struct termios old_cfg;
static int fd;
char *device = "/dev/ttyUSB0";

static int uart_init(const char *device);
static int uart_config(const uart_cfg *cfg);
static void async_io_init(void);
static void io_handler(int sig,siginfo_t *info, void *context);

int main(int argc, char *argv[])
{
    uart_cfg cfg = {0};
    int ret;
    uart_init(device);
    cfg.baudrate = 115200;
    ret = uart_config(&cfg);
    if(ret)
    {
        tcsetattr(fd, TCSANOW, &old_cfg);
        close(fd);
        exit(EXIT_FAILURE);
    }
    async_io_init();
    while(1)
    {
        sleep(1);
    }
    tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置 
    close(fd); 
    exit(EXIT_SUCCESS);

}


/*异步IO初始化函数*/
static void async_io_init(void)
{
    struct sigaction sig;
    int flag;

    /*使能异步I/O*/            /**/
    flag = fcntl(fd,F_GETFL); /*使能串口的异步I/O功能*/
    flag |= O_ASYNC;
    fcntl(fd,F_SETFL,flag);
    /*设置异步I/O的所有者*/
    fcntl(fd,F_SETOWN,getpid());
    /*为实时信号SIGRTMIN作为异步I/O信号*/
    fcntl(fd,F_SETSIG,SIGRTMIN);

    sig.sa_sigaction = io_handler;
    sig.sa_flags = SA_SIGINFO;
    sigisemptyset(&sig.sa_mask);
    sigaction(SIGRTMIN,&sig,NULL);
}

/*UART配置*/
static int uart_config(const uart_cfg *cfg)
{
    struct termios new_cfg = {0};
    speed_t speed;
    cfmakeraw(&new_cfg);
    new_cfg.c_cflag |= CREAD;

    switch(cfg->baudrate)
    {
        case 9600: speed = B9600; 
        break;
        case 38400: speed = B38400;
        break;
        case 57600: speed = B57600;
        break;
        case 115200: speed = B115200;
        break;
        default: 
            speed = B115200;
            printf("default baud rate: 115200\n");
            break;
    }
if(0 > cfsetspeed(&new_cfg,speed))
{
    fprintf(stderr,"cfsetspeed error: %s\n)",strerror(errno));
}
    new_cfg.c_cflag &= ~CSIZE;
    new_cfg.c_cflag |= CS8;
    new_cfg.c_cflag &= ~PARENB;
    new_cfg.c_iflag &= ~INPCK;
    new_cfg.c_cflag &= ~CSTOPB;

    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN] = 0;

    if(0 > tcflush(fd,TCIOFLUSH))
    {
        fprintf(stderr,"tcflush error:%s\n",strerror(errno));
        return -1;
    }

    if(0 > tcsetattr(fd,TCSANOW,&new_cfg))
    {
        fprintf(stderr,"tcsetattr error: %s\n",strerror(errno));
        return -1;
    }
    return 0;
}

/*UART初始化*/
static int uart_init(const char *device)
{
    fd = open(device,O_RDWR|O_NOCTTY|O_NDELAY);
    if(0 > fd)
    {
        fprintf(stderr, "open error : %s: %s\n",device,strerror(errno));
        return -1;        
    }

    if(0 > tcgetattr(fd, &old_cfg))
    {
        fprintf(stderr, "tcgetattr error : %s\n",strerror(errno));
        close(fd);
        return -1;
    }
    return 0;
}

static void io_handler(int sig,siginfo_t *info, void *context)
{
    unsigned char buf[128] = {0};
    int ret;
    int n;

    if(SIGRTMIN != sig)
    {
        return;
    }

    if(POLL_IN == info->si_code)
    {
        ret = read(fd,buf,128);
        for(n = 0;n < ret ;n++)
        {
//            printf("0x%hhx ",buf[n]);
            printf("%hhc",buf[n]);
        }
    }
}

注意:
ret = read(fd,buf,128);
ret返回的值是读取到接收缓冲区中的数据长度。
sig.sa_flags = SA_SIGINFO;
SA_SIGINFO控制字代表sa_sigaction 成员有效,sa_handler成员无效。
sa_sigaction和sa_handler都是函数指针。
区别是sa_sigaction的参数信息更多。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Linux平台上,使用C++语言结合termios实现多线程串口通信是可行的。下面是一个简单的示例代码,演示了如何使用多线程和termios库在Linux上进行串口通信。 ```cpp #include <iostream> #include <fcntl.h> #include <termios.h> #include <pthread.h> // 串口设备文件描述符 int serial_fd; // 串口通信线程函数 void* serialThread(void*) { while (true) { // 读取串口数据 char buffer[256]; ssize_t bytesRead = read(serial_fd, buffer, sizeof(buffer)); if (bytesRead > 0) { // 处理接收到的数据 std::string data(buffer, bytesRead); // 处理接收到的数据 // ... // 如果需要在子线程中发送数据,可以调用 write(serial_fd, data.c_str(), data.length()); // 退出线程的条件 if (data == "exit") { break; } } } pthread_exit(NULL); } int main() { // 打开串口设备 serial_fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY); if (serial_fd == -1) { std::cerr << "Failed to open serial port" << std::endl; return 1; } // 配置串口参数 struct termios serialOptions; tcgetattr(serial_fd, &serialOptions); cfsetispeed(&serialOptions, B9600); cfsetospeed(&serialOptions, B9600); serialOptions.c_cflag &= ~PARENB; serialOptions.c_cflag &= ~CSTOPB; serialOptions.c_cflag &= ~CSIZE; serialOptions.c_cflag |= CS8; tcsetattr(serial_fd, TCSANOW, &serialOptions); // 创建串口通信线程 pthread_t thread; int result = pthread_create(&thread, NULL, serialThread, NULL); if (result != 0) { std::cerr << "Failed to create serial thread" << std::endl; return 1; } // 主线程中发送数据 while (true) { std::string data; std::cin >> data; // 发送数据到串口 write(serial_fd, data.c_str(), data.length()); // 退出主线程的条件 if (data == "exit") { break; } } // 等待串口通信线程结束 pthread_join(thread, NULL); // 关闭串口设备 close(serial_fd); return 0; } ``` 上述示例代码使用了pthread库创建了一个串口通信线程,其中包含了串口的读取操作。在主线程中,你可以从终端输入数据,并通过串口发送出去。在串口通信线程中,你可以通过read函数读取串口数据来接收来自外部设备的数据。 请注意,上述示例代码仅用于演示多线程串口通信的基本概念,并没有完整的错误处理和数据处理逻辑。在实际应用中,你可能需要添加更多的代码来处理异常情况和实现特定的通信协议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值