程序员探案之"被吃掉"的串口数据

2021-12-23 13:32:21 浏览数 (1)

2018电影春节档,唐人街探案居然拔得头筹,原因我分析了下,个人觉得 多数人都有一颗了解真相的心吧

程序员的世界里,总是充满着悬念,而带来悬念的就是挥之不去的bug 修复bug,就像侦办一起案件

我就蹭蹭热度 来个 程序员探案 系列吧

直击"案发"现场

前两天做嵌入式开发的一哥们在用ARM和一串口设备进行通信时, 碰到了诡异的问题,受尽折磨的他告诉我:

数据被"吃掉"了,还有人"调包"

"案情"分析

通过大量分析发送和接收的数据对比,看出了些端倪

数据被"吃掉"

程序在接收数据时 0x13,0x11总是收不到

数据被"调包"

串口发送方发0x0D,接收方收到0x0A 串口发送方发0x0A,接收方收到0x0D

找证据

从termios结构中找到有几个关键位设置对其有影响 c_iflag 中的INLCR,ICRNL,IXON,IXOFF,IXANY(具体含义参见下面表格宏说明)

c_iflag用于设置如何处理串口上接收到的数据,包含如下内容:

英文说明

中文说明

INPCK

Enable parity check

允许输入奇偶校验

IGNPAR

Ignore parity errors

忽略奇偶校验错误

PARMRK

Mark parity errors

标识奇偶校验错误

ISTRIP

Strip parity bits

去除字符的第8个比特

IXON

Enable software flow control (outgoing)

允许输出时对XON/XOFF流进行控制

IXOFF

Enable software flow control (incoming)

允许输入时对XON/XOFF流进行控制

IXANY

Allow any character to start flow again

输入任何字符将重启停止的输出

IGNBRK

Ignore break condition

忽略BREAK键输入

BRKINT

Send a SIGINT when a break condition is detected

如果设置了IGNBRK,BREAK键输入将被忽略

INLCR

Map NL to CR

将输入的NL换行转换成CR回车

IGNCR

Ignore CR

忽略输入的回车

ICRNL

Map CR to NL

将输入的回车转化成换行(如果IGNCR未设置的情况下)

IUCLC

Map uppercase to lowercase

将输入的大写字符转换成小写字符(非POSIX)

IMAXBEL

Echo BEL on input line too long

当输入队列满的时候开始响铃

c_oflag用于设置如何处理输出数据,包含如下内容:

英文说明

中文说明

OPOST

Postprocess output (not set = raw output)

是否处理(原始数据)

OLCUC

Map lowercase to uppercase

将输出的小写字符转换成大写字符(非POSIX)

ONLCR

Map NL to CR-NL

将输出的NL(换行)转换成CR(回车)及NL(换行)

OCRNL

Map CR to NL

将输出的CR(回车)转换成NL(换行)

NOCR

No CR output at column 0

第一行不输出回车符

ONLRET

NL performs CR function

不输出回车符

OFILL

Use fill characters for delay

发送填充字符以延迟终端输出

OFDEL

Fill character is DEL

以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL

NLDLY

Mask for delay time needed between lines

换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)

CRDLY

Mask for delay time needed to return carriage to left column

回车延迟,取值范围为:CR0、CR1、CR2和 CR3

TABDLY

Mask for delay time needed after TABs

水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3

BSDLY

Mask for delay time needed after BSs

空格输出延迟,可以取BS0或BS1

VTDLY

Mask for delay time needed after VTs

垂直制表符输出延迟,可以取VT0或VT1

FFDLY

Mask for delay time needed after FFs

换页延迟,可以取FF0或FF1

c_lflag用于设置本地模式,控制终端编辑功能,决定串口驱动如何处理输入字符,设置内容如下:

英文说明

中文说明

ISIG

Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals

当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号

ICANON

Enable canonical input (else raw)

使用标准输入模式

XCASE

Map uppercase lowercase (obsolete)

在ICANON和XCASE同时设置的情况下,终端只使用大写

ECHO

Enable echoing of input characters

显示输入字符

ECHOE

Echo erase character as BS-SP-BS

如果ICANON同时设置,ERASE将删除输入的字符

ECHOK

Echo NL after kill character

如果ICANON同时设置,KILL将删除当前行

ECHONL

Echo NL

如果ICANON同时设置,即使ECHO没有设置依然显示换行符

ECHOPRT

Echo erased character as character erased

如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)

TOSTOP

Send SIGTTOU for background output

向后台输出发送SIGTTOU信号

破案实战总结

结合上面的宏定义说明,对应修改配置如下,问题解决:

代码语言:javascript复制
opt.c_iflag &= ~(ICRNL | INLCR);
opt.c_iflag &= ~(IXON | IXOFF | IXANY);
opt.c_oflag &= ~(ONLCR | OCRNL); 

水落石出,最后给出我的串口配置实战

代码语言:javascript复制
/**
*打开串口
*/
int open_dev(char *dev_name)
{
    int fd = open(dev_name, O_RDWR | O_NOCTTY);

    if (-1 == fd)
    {
        perror("Can't open serial port");
        return -1;
    }
    else
        return fd;

}


/***设置串口通信速率和模式标识 
*@param  fd     类型 int  打开串口的文件句柄 
*@param  speed  类型 int  串口速度 
*@return  void*/
void set_speed(int fd, int speed)
{
    int i;
    int status;
    int speed_arr[] = {B921600, B460800, B230400, B115200, B57600,
                       B38400, B19200, B9600, B4800, B2400, B1200,
                       B300, B38400, B19200, B9600, B4800, B2400,
                       B1200, B300, };
    int name_arr[] = {, , , , , ,
                      , , , , , , , 
                      ,  , , , , , };
    struct termios opt;
    tcgetattr(fd, &opt);
    opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
    opt.c_oflag &= ~(ONLCR | OCRNL);
    opt.c_iflag &= ~(IXON);
    for ( i= ; i < sizeof(speed_arr) / sizeof(int); i  )
    {
        if (speed == name_arr[i])
        {
            tcflush(fd, TCIOFLUSH);
            cfsetispeed(&opt, speed_arr[i]);
            cfsetospeed(&opt, speed_arr[i]);
            status = tcsetattr(fd, TCSANOW, &opt);
            if (status != )
                perror("tcsetattr fd");
            return;
        }
        tcflush(fd,TCIOFLUSH);
    }
}


/**
*设置串口数据位,停止位和效验位
*@param fd 类型 int 打开的串口文件句柄*
*@param databits 类型 int 数据位 取值 为 7 或者8*
*@param stopbits 类型 int 停止位 取值为 1 或者2*
*@param parity 类型 int 效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{
    struct termios options;
    if ( tcgetattr( fd,&options) != )
    {
        perror("tcgetattr error");
        return -5;
    }
    options.c_cflag &= ~CSIZE;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;
    switch (databits) /*设置数据位数*/
    {
    case :
        options.c_cflag |= CS7;
        break;
    case :
        options.c_cflag |= CS8;
        break;
    default:
        fprintf(stderr,"unsupported data sizen");
        return -4;
    }
    switch (parity)
    {
    case 'n':
    case 'N':
        options.c_cflag &= ~PARENB; /* Clear parity enable */
        options.c_iflag &= ~INPCK; /* Enable parity checking */
        break;
    case 'o':
    case 'O':
        options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
        options.c_iflag |= INPCK; /* Disnable parity checking */
        break;
    case 'e':
    case 'E':
        options.c_cflag |= PARENB; /* Enable parity */
        options.c_cflag &= ~PARODD; /* 转换为偶效验*/
        options.c_iflag |= INPCK; /* Disnable parity checking */
        break;
    case 'S':
    case 's': /*as no parity*/
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        break;
    default:
        fprintf(stderr,"unsupported parityn");
        return -3;
    }
    /* 设置停止位*/
    switch (stopbits)
    {
    case :
        options.c_cflag &= ~CSTOPB;
        break;
    case :
        options.c_cflag |= CSTOPB;
        break;
    default:
        fprintf(stderr,"unsupported stop bitsn");
        return -2;
    }
    /* Set input parity option */
    if (parity != 'n')
        options.c_iflag |= INPCK;
    options.c_cc[VTIME] = ; // 15 seconds

    options.c_cc[VMIN] = ;

    tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
    if (tcsetattr(fd,TCSANOW,&options) != )
    {
        perror("setup serial options error");
        return -1;
    }
    return ;
}

0 人点赞