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


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信号

破案实战总结

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

1opt.c_iflag &= ~(ICRNL | INLCR);
2opt.c_iflag &= ~(IXON | IXOFF | IXANY);
3opt.c_oflag &= ~(ONLCR | OCRNL); 

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

  1/**
  2*打开串口
  3*/
  4int open_dev(char *dev_name)
  5{
  6    int fd = open(dev_name, O_RDWR | O_NOCTTY);
  7
  8    if (-1 == fd)
  9    {
 10        perror("Can't open serial port");
 11        return -1;
 12    }
 13    else
 14        return fd;
 15
 16}
 17
 18
 19/***设置串口通信速率和模式标识 
 20*@param  fd     类型 int  打开串口的文件句柄 
 21*@param  speed  类型 int  串口速度 
 22*@return  void*/
 23void set_speed(int fd, int speed)
 24{
 25    int i;
 26    int status;
 27    int speed_arr[] = {B921600, B460800, B230400, B115200, B57600,
 28                       B38400, B19200, B9600, B4800, B2400, B1200,
 29                       B300, B38400, B19200, B9600, B4800, B2400,
 30                       B1200, B300, };
 31    int name_arr[] = {921600, 460800, 230400, 115200, 57600, 38400,
 32                      19200, 9600, 4800, 2400, 1200, 300, 38400, 
 33                      19200,  9600, 4800, 2400, 1200, 300, };
 34    struct termios opt;
 35    tcgetattr(fd, &opt);
 36    opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
 37    opt.c_oflag &= ~(ONLCR | OCRNL);
 38    opt.c_iflag &= ~(IXON);
 39    for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
 40    {
 41        if (speed == name_arr[i])
 42        {
 43            tcflush(fd, TCIOFLUSH);
 44            cfsetispeed(&opt, speed_arr[i]);
 45            cfsetospeed(&opt, speed_arr[i]);
 46            status = tcsetattr(fd, TCSANOW, &opt);
 47            if (status != 0)
 48                perror("tcsetattr fd");
 49            return;
 50        }
 51        tcflush(fd,TCIOFLUSH);
 52    }
 53}
 54
 55
 56/**
 57*设置串口数据位,停止位和效验位
 58*@param fd 类型 int 打开的串口文件句柄*
 59*@param databits 类型 int 数据位 取值 为 7 或者8*
 60*@param stopbits 类型 int 停止位 取值为 1 或者2*
 61*@param parity 类型 int 效验类型 取值为N,E,O,,S
 62*/
 63int set_Parity(int fd,int databits,int stopbits,int parity)
 64{
 65    struct termios options;
 66    if ( tcgetattr( fd,&options) != 0)
 67    {
 68        perror("tcgetattr error");
 69        return -5;
 70    }
 71    options.c_cflag &= ~CSIZE;
 72    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 73    options.c_oflag &= ~OPOST;
 74    switch (databits) /*设置数据位数*/
 75    {
 76    case 7:
 77        options.c_cflag |= CS7;
 78        break;
 79    case 8:
 80        options.c_cflag |= CS8;
 81        break;
 82    default:
 83        fprintf(stderr,"unsupported data size\n");
 84        return -4;
 85    }
 86    switch (parity)
 87    {
 88    case 'n':
 89    case 'N':
 90        options.c_cflag &= ~PARENB; /* Clear parity enable */
 91        options.c_iflag &= ~INPCK; /* Enable parity checking */
 92        break;
 93    case 'o':
 94    case 'O':
 95        options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
 96        options.c_iflag |= INPCK; /* Disnable parity checking */
 97        break;
 98    case 'e':
 99    case 'E':
100        options.c_cflag |= PARENB; /* Enable parity */
101        options.c_cflag &= ~PARODD; /* 转换为偶效验*/
102        options.c_iflag |= INPCK; /* Disnable parity checking */
103        break;
104    case 'S':
105    case 's': /*as no parity*/
106        options.c_cflag &= ~PARENB;
107        options.c_cflag &= ~CSTOPB;
108        break;
109    default:
110        fprintf(stderr,"unsupported parity\n");
111        return -3;
112    }
113    /* 设置停止位*/
114    switch (stopbits)
115    {
116    case 1:
117        options.c_cflag &= ~CSTOPB;
118        break;
119    case 2:
120        options.c_cflag |= CSTOPB;
121        break;
122    default:
123        fprintf(stderr,"unsupported stop bits\n");
124        return -2;
125    }
126    /* Set input parity option */
127    if (parity != 'n')
128        options.c_iflag |= INPCK;
129    options.c_cc[VTIME] = 0; // 15 seconds
130
131    options.c_cc[VMIN] = 0;
132
133    tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
134    if (tcsetattr(fd,TCSANOW,&options) != 0)
135    {
136        perror("setup serial options error");
137        return -1;
138    }
139    return 0;
140}

原文发布于微信公众号 - chafezhou(gh_5b8f0c59b682)

原文发表时间:2018-03-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏生信宝典

Linux学习 - 常用和不太常用的实用awk命令

Linux学习系列文章是生信宝典最开始主推的一块,力图从一个新额视角帮助初学者快速入门Linux系统,熟悉Linux下的文件和目录,文件操作, 文件内容操作。而...

18410
来自专栏LinXunFeng的专栏

打造Moya便捷解析库,提供RxSwift拓展

1053
来自专栏为数不多的Android技巧

Android Studio你不知道的快捷键(二)

在Android Studio你不知道的快捷键(一)里面,主要讲述了一些窗口操作的快捷键还有补全参数提示等,这一篇会分享一些代码代码编辑的快捷键。(默认Keym...

862
来自专栏Android群英传

看ASM在代码中的强势插入

1453
来自专栏大内老A

.NET Core采用的全新配置系统[2]: 配置模型设计详解

在《.NET Core采用的全新配置系统[1]: 读取配置数据》中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.N...

1709
来自专栏LinXunFeng的专栏

打造Moya便捷解析库,提供RxSwift拓展

1、相信大家在使用Swift开发时,Moya是首选的网络工具,在模型解析这一块,Swift版模型解析的相关第三方库有很多,本人最习惯用的就是SwiftyJSON...

20111
来自专栏Android随笔

Android开发实践

网上大部分命名规范文章里,并不是以模块名开头的,可能是习惯不一样,也有可能我的做法是错误的。希望您能及时指正,谢谢! 把模块名称放在最前面,再配合Androi...

703
来自专栏数据结构与算法

BZOJ3261: 最大异或和(可持久化trie树)

582
来自专栏为数不多的Android技巧

Android Studio你不知道的调试技巧

写代码不可避免有Bug,通常情况下除了日志最直接的调试手段就是debug;那么你的调试技术停留在哪一阶段呢?仅仅是下个断点单步执行吗?或者你知道 Evaluat...

601
来自专栏情情说

深入浅出MyBatis:MyBatis插件及开发过程

上一篇介绍了 MyBatis解析和运行原理 ,包括SqlSessionFactory的构建和SqlSession的执行过程,其中,SqlSession包含四大对...

3226

扫码关注云+社区