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


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 条评论
登录 后参与评论

相关文章

来自专栏小狼的世界

使用YUI3创建Popup弹出层

很多互联网的项目应用中,弹出层出现的越来越多,使用YUI3可以快速的实现制作弹出层的效果。

841
来自专栏Golang语言社区

Go Channel 详解

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

2582
来自专栏Java帮帮-微信公众号-技术文章全总结

深入浅出爬虫之道: Python、Golang与GraphQuery的对比

本文将分别使用 Python ,Golang 以及 GraphQuery 来解析某网站的 素材详情页面 ,这个页面的特色是具有清晰的数据结构,但是DOM结构不够...

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

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

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

1162
来自专栏Django中文社区

Django模板标签regroup的妙用

在使用 Django 开发时,有时候我们需要在模板中按对象的某个属性分组显示一系列数据。例如博客文章按照时间归档分组显示文章列表(示例效果请看我的博客的归档页面...

3306
来自专栏zhangdd.com

ceph性能测试

该工具的语法为:rados bench -p <pool_name> <seconds> <write|seq|rand> -b <block size> -t...

2372
来自专栏前端达人

2018年最全面的前端面试题都在这里了

意义:根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好地解析。 注意: 1.尽可能少的...

4.9K7
来自专栏.NET后端开发

Highcharts使用指南

摘要 Highcharts图表控件是目前使用最为广泛的图表控件。本文将从零开始逐步为你介绍Highcharts图表控件。通过本文,你将学会如何配置Highcha...

3215
来自专栏非著名程序员

仿苹果数字键盘以及判断信用卡有效期的Editext

这次带来一个小小的信用卡有效期规则的Editext,额外赠送内置数字键盘的开发 首先来看下需求: 1) 月份数字: λ 数字输入0:后一位数字可输入...

2235
来自专栏雨过天晴

原 荐 PHP 在 Console 模式下的

1731

扫码关注云+社区

领取腾讯云代金券