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复制1opt.c_iflag &= ~(ICRNL | INLCR);
2opt.c_iflag &= ~(IXON | IXOFF | IXANY);
3opt.c_oflag &= ~(ONLCR | OCRNL);
水落石出,最后给出我的串口配置实战
代码语言:javascript复制 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 sizen");
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 parityn");
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 bitsn");
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}