Serial port select function use

The Select function is still relatively important in Socket programming, but for those who are new to Socket, they don't like to write programs with Select. They are just used to writing blocking programs such as connect, accept, recv, and recvfrom.
However, using Select can complete a program that works in a non-blocking manner. It can monitor the changes in the file descriptors we need to monitor-read and write or abnormal.

The so-called blocking mode block, as the name implies, is that the process or thread must wait for an event to occur when these functions are executed. If the event does not occur, the process or thread will be blocked and the function cannot return immediately.
The so-called non-blocking method non-block means that the process or thread does not have to wait for the event to occur when executing this function. Once the execution returns, the execution of the function is reflected by the difference in the return value. If the event occurs, it is the same as the blocking method. If the event does not occur, a code is returned to inform that the event did not occur, and the process or thread continues to execute, so the efficiency is higher.
Detailed explanation and example analysis of select function

Use of select function and case analysis

Header file

#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
OR
#include <sys/ioctl.h>
  •  

Define function

int select(int n, fd_set * readfds,fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);     
// select函数会不断修改timeout的值,所以每次循环都应该重新赋值

Function description
select() is used to wait for the status of the file descriptor to change. The parameter n represents the largest file descriptor plus 1. The parameters readfds, writefds and exceptfds are called descriptive phrases, which are used to return the read, write or exception conditions of the descriptor.

The following macros provide ways to deal with these three description phrases:

FD_CLR(inr fd,fd_set* set);-----------用来清除描述词组 set 中相关 fd 的位
FD_ISSET(int fd,fd_set *set);---------用来测试描述词组 set 中相关 fd 的位是否为真
FD_SET(int fd,fd_set*set);------------用来设置描述词组 set 中相关 fd 的位  //添加描述符
FD_ZERO(fd_set *set); ---------------用来清除描述词组 set 的全部位  //每次循环都要清空集合,否则不能检测描述符变化
  • The parameter timeout is the structure timeval, which is used to set the wait time of select(). Its structure is defined as follows:
struct timeval
{
	time_t tv_sec;
	time_t tv_usec;
};

struct timeval *timeout is the timeout period of select, this parameter is very important, it can make select in three states:

  • First, if NULL is passed in as a formal parameter, that is, no time structure is passed in, that is, select is placed in a blocking state, and it must wait until a file descriptor in the monitoring file descriptor set changes;
  • Second, if the time value is set to 0 seconds and 0 milliseconds, it becomes a pure non-blocking function, no matter whether the file descriptor has changed, it will immediately return to continue execution, the file will return 0 if there is no change, and a positive value if there is change ;
  • Third, the value of timeout is greater than 0, which is the waiting timeout time, that is, select blocks within the timeout time, and returns when an event arrives within the timeout time, otherwise it must return after the timeout, and the return value is the same as above.

The following is a common program fragment

fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)		{......}

error code

  • If the execution is successful, it will return the number of changed status of the file descriptor;
  • If it returns 0, it means that the timeout time has passed before the descriptor status changes;
  • When an error occurs, it returns -1. The cause of the error is stored in errno. At this time, the values ​​of the parameters readfds, writefds, exceptfds and
    timeout become unpredictable.

In-depth understanding of the select function

The key to a deep understanding of the select model is to understand fd_set. For the convenience of explanation, we take the length of fd_set as 1 byte, and each bit in fd_set can correspond to a file descriptor fd. Then a 1-byte long fd_set can correspond to 8 fd at most.

  1. Execute fd_set set; FD_ZERO(&set); then set is expressed in bits as 0000,0000.
  2. If fd = 5, after executing FD_SET(fd,&set), set becomes 0001, 0000 (the fifth position is 1)
  3. If fd=2 and fd=1 are added again, then set becomes 0001.0011
  4. Execute select(6,&set,0,0,0) to block waiting
  5. If a readable event occurs on both fd=1 and fd=2, select returns, and set becomes 0000, 0011 at this time. When no readable event occurs, fd = 5
    is cleared.

Serial port select function use

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定义  
#include <termios.h>//终端控制定义  
#include <errno.h>
char* device="/dev/ttyUSB0";

int serial_fd = 0;

struct termios options;

//default config with fd--ycc1
void defaultConfigWithFd()
{
    //串口主要设置结构体termios <termios.h>
    //struct termios options;

    /**1. tcgetattr函数用于获取与终端相关的参数。
    *参数fd为终端的文件描述符,返回的结果保存在termios结构体中
    */
    tcgetattr(serial_fd, &options);
    /**2. 修改所获得的参数*/
    options.c_cflag |= (CLOCAL | CREAD);//设置控制模式状态,本地连接,接收使能
    options.c_cflag &= ~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位
    options.c_cflag &= ~CRTSCTS;//无硬件流控
    options.c_cflag |= CS8;//8位数据长度
    options.c_cflag &= ~CSTOPB;//1位停止位
    options.c_iflag |= IGNPAR;//无奇偶检验位
    options.c_oflag = 0; //输出模式
    options.c_lflag = 0; //不激活终端模式
    cfsetospeed(&options, B115200);//设置波特率

    /**3. 设置新属性,TCSANOW:所有改变立即生效*/
    tcflush(serial_fd, TCIFLUSH);//溢出数据可以接收,但不读
    tcsetattr(serial_fd, TCSANOW, &options);
}
//open serial--ycc1
int openSerial(char* device)
{
    serial_fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (serial_fd < 0) {
        perror("open err");
        return -1;
    }
    return serial_fd;
}

//close
void uart_close()
{
    close(serial_fd);
}

//打开串口并初始化设置  
int init_serial(char* device)
{
    if (openSerial(device) < 0) {
        perror("open error");
        return -1;
    }
    defaultConfigWithFd();
    return 0;
}

//***ycc --bb
//set baudrate
void set_baudrate(int speed) {
    int i;
    int speed_arr[] = {B38400, B19200, B9600, B4800, B2400, B1200, B300,
                       B38400, B19200, B9600, B4800, B2400, B1200, B300};
    int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400,
                      1200, 300};
    //设置串口输入波特率和输出波特率
    for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
        if (speed == name_arr[i]) {
            cfsetispeed(&options, speed_arr[i]);
            cfsetospeed(&options, speed_arr[i]);
        }
    }
}
//set device
void set_device(char* device)
{
    uart_close();
    serial_fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
}
//set data bits
int set_data_bits(int databits)
{
    //设置数据位
    options.c_cflag &= ~CSIZE; //屏蔽其他标志位
    switch (databits)
    {
        case 5    :
            options.c_cflag |= CS5;
            break;
        case 6    :
            options.c_cflag |= CS6;
            break;
        case 7    :
            options.c_cflag |= CS7;
            break;
        case 8:
            options.c_cflag |= CS8;
            break;
        default:
            fprintf(stderr,"Unsupported data size/n");
            return -1;
    }
}
//set flow control
void set_flow_ctrl(int flow_ctrl)
{
    //设置数据流控制
    switch(flow_ctrl)
    {

        case 0 ://不使用流控制
            options.c_cflag &= ~CRTSCTS;
            break;

        case 1 ://使用硬件流控制
            options.c_cflag |= CRTSCTS;
            break;
        case 2 ://使用软件流控制
            options.c_cflag |= IXON | IXOFF | IXANY;
            break;
    }
}
//set parity bits
void set_parity_bits(int parity)
{
    //设置校验位
    switch (parity)
    {
        case 'n':
        case 'N': //无奇偶校验位。
            options.c_cflag &= ~PARENB;
            options.c_iflag &= ~INPCK;
            break;
        case 'o':
        case 'O'://设置为奇校验    
            options.c_cflag |= (PARODD | PARENB);
            options.c_iflag |= INPCK;
            break;
        case 'e':
        case 'E'://设置为偶校验  
            options.c_cflag |= PARENB;
            options.c_cflag &= ~PARODD;
            options.c_iflag |= INPCK;
            break;
        case 's':
        case 'S': //设置为空格 
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~CSTOPB;
            break;
        default:
            fprintf(stderr,"Unsupported parity/n");
    }
}
//set stop bits
void set_stop_bits(int stopbits)
{
    // 设置停止位 
    switch (stopbits)
    {
        case 1:
            options.c_cflag &= ~CSTOPB;
            break;
        case 2:
            options.c_cflag |= CSTOPB;
            break;
        default:
            fprintf(stderr,"Unsupported stop bits/n");
    }
}
//***ycc --ee

/** 
*串口发送数据 
*@fd:串口描述符 
*@data:待发送数据 
*@datalen:数据长度
*/
int uart_send(char *data, int datalen)
{
    int len = 0;
    len = write(serial_fd, data, datalen);//实际写入的长度
    if(len == datalen) {
        return len;
    } else {
        tcflush(serial_fd, TCOFLUSH);//TCOFLUSH刷新写入的数据但不传送
        return -1;
    }

    return 0;
}

/**
*串口接收数据
*要求启动后,在pc端发送ascii文件
*/
int uart_recv(char *data, int datalen)
{
    int len=0, ret = 0;
    fd_set fs_read;
    struct timeval tv_timeout;

    FD_ZERO(&fs_read);
    FD_SET(serial_fd, &fs_read);
    tv_timeout.tv_sec  =2;     // (10*20/115200+5);
    tv_timeout.tv_usec = 0;

    while(FD_ISSET(serial_fd,&fs_read))
    {

        FD_ZERO(&fs_read);
        FD_SET(serial_fd,&fs_read);
        ret = select(serial_fd+1, &fs_read, NULL, NULL, &tv_timeout);
        printf("ret = %d\n", ret);
        //如果返回0,代表在描述符状态改变前已超过timeout时间,错误返回-1

        if(FD_ISSET(serial_fd, &fs_read)) {
            len = read(serial_fd, data, datalen);
            printf("len = %d\n", len);
            if(-1==len) {
                return -1;
            }
        } else {
            perror("select");
        }
    }
    return 0;
}

void testSend()
{
    char buf[]="yccyccyccy";
    init_serial("/dev/tty");

    uart_send(buf, sizeof(buf));
}
void testRsv()
{
    init_serial("/dev/tty");
    char buf1[11];
    uart_recv(buf1, sizeof(buf1));
    printf("uart receive %s\n", buf1);
}
int main(int argc, char **argv)
{
    testSend();
    //testRsv();
    return 0;
}

 

Guess you like

Origin blog.csdn.net/star871016/article/details/108550363