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 ;
}