2019年1月10日星期四

借用网上的Linux串口初始化代码要当心

入职新公司以来,我开发的几乎每个Linux程序都要用到串口。我开发第一个程序的时候,在网上阅读了大量的代码,并根据这些代码写了一个通用的初始化函数,以后的程序都调用这个函数进行初始化。在这期间,还发现了网上流传的代码存在的一个问题,详见《串口通信中的INPCK和ISTRIP标志位》。然而近期有人在使用我的程序时,还是发现了一个问题,所有流经我的程序的数据中的0x0D全部变成了0x0A.
网上一搜就知道了原因,原来是我初始化的时候没有关闭ICRNL标志位。我猜测Linux的串口架构沿用自Unix系统,那个年代串口主要用于远程登录和传输文本,设计上主要考虑的是方便人机交互;而现在在工业自动化领域,串口主要用来传输二进制数据,更希望数据准确传输。
这个问题也说明,网上找的代码,一定要自己研究明白再据为己有。现在有很多软件开发人员,随口就是去Github上搜代码,这样做未必是好事。
如下是我的初始化函数,已经修改了我上面提到的0x0D变成0x0A的问题,但不知道是否还隐藏着其他问题。供大家参考。
/*初始化串口*/
int init_serial(unsigned char com_sn, unsigned char spd, unsigned char data_bit, 
    unsigned char parity, unsigned char stop_bit)
{
    int  com_fd;              /*串口fd*/
    char com_device_name[16]; /*串口设备名*/
struct termios options;
    
    sprintf(com_device_name, "/dev/ttyS%d", com_sn);
#ifdef RS485_NONBLOCK
    com_fd = open(com_device_name, O_RDWR | O_NOCTTY | O_NDELAY); 
#else
    com_fd = open(com_device_name, O_RDWR | O_NOCTTY);
#endif 
    if (com_fd < 0) 
    {
    printf("Open the serial port %d error!\n", com_sn);
        control_led(LED_ERR, ON);
        return -1;
    }

tcgetattr(com_fd, &options);

    /*设置串口波特率*/
speed_t speed = B0;
    switch (spd)
    {
        case 0x96:
            speed = B9600;
            break;
        case 0x38:
            speed = B38400;
            break;
    }
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);

/*
* Enable the receiver and set local mode
*/
options.c_cflag |= (CLOCAL | CREAD);

switch (parity)
    {
    case 'N':
        options.c_cflag &= ~PARENB;    //关闭校验  
        options.c_iflag &= ~INPCK;
        break;

    case 'E':
    options.c_cflag |= PARENB;       //使能校验
        options.c_iflag |= INPCK;
        options.c_cflag &= ~PARODD;      //关闭奇校验,也就相当于打开了偶校验
        break;

    case 'O':
        options.c_cflag |= PARENB;       //使能校验
        options.c_iflag |= INPCK;
        options.c_cflag |= PARODD;
        break;

    default:
        /*do nothing*/;
    }
    
    if (stop_bit == 1)
    {
        options.c_cflag &= ~CSTOPB;      //关闭第二个校验位,也就相当于只打开一个校验位
}

    options.c_cflag &= ~CSIZE;    
    if (data_bit == 8)
    {
    options.c_cflag |= CS8;          //8位数据位
    }

/*
* Disable hardware flow control
*/
options.c_cflag &= ~CRTSCTS;

/*
* Choosing raw input
*/
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

/*
* Disable software flow control
*/
options.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);

/*
* Choosing raw output
*/
options.c_oflag &= ~OPOST;

    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 1;

    tcsetattr(com_fd, TCSANOW, &options);

    return com_fd;
}

没有评论:

发表评论