小毛 CSDN

点击上方“CSDN”,选择“置顶公众号”

关键时刻,第一时间送达!

作者 | 小毛

责编 | 唐小引

直击“案发”现场

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

数据被“吃掉”了,还有人“调包”!


“案情”分析


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

数据被“吃掉”:

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

数据被“调包”:

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

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

找证据

从 termios 结构中,我们找到有几个关键位设置对其有影响:

  • c_iflag 中的 INLCR,ICRNL,IXON,IXOFF,IXANY(具体含义参见下面表格宏说明)。

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

英文说明中文说明
INPCKEnable parity check允许输入奇偶校验
IGNPARIgnore parity errors忽略奇偶校验错误
PARMRKMark parity errors标识奇偶校验错误
ISTRIPStrip parity bits去除字符的第8个比特
IXONEnable software flow control (outgoing)允许输出时对XON/XOFF流进行控制
IXOFFEnable software flow control (incoming)允许输入时对XON/XOFF流进行控制
IXANYAllow any character to start flow again输入任何字符将重启停止的输出
IGNBRKIgnore break condition忽略BREAK键输入
BRKINTSend a SIGINT when a break condition is detected如果设置了IGNBRK,BREAK键输入将被忽略
INLCRMap NL to CR将输入的NL换行转换成CR回车
IGNCRIgnore CR忽略输入的回车
ICRNLMap CR to NL将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLCMap uppercase to lowercase将输入的大写字符转换成小写字符(非POSIX)
IMAXBELEcho BEL on input line too long当输入队列满的时候开始响铃

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

英文说明中文说明
OPOSTPostprocess output (not set = raw output)是否处理(原始数据)
OLCUCMap lowercase to uppercase将输出的小写字符转换成大写字符(非POSIX)
ONLCRMap NL to CR-NL将输出的NL(换行)转换成CR(回车)及NL(换行)
OCRNLMap CR to NL将输出的CR(回车)转换成NL(换行)
NOCRNo CR output at column 0第一行不输出回车符
ONLRETNL performs CR function不输出回车符
OFILLUse fill characters for delay发送填充字符以延迟终端输出
OFDELFill character is DEL以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL
NLDLYMask for delay time needed between lines换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
CRDLYMask for delay time needed to return carriage to left column回车延迟,取值范围为:CR0、CR1、CR2和 CR3
TABDLYMask for delay time needed after TABs水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
BSDLYMask for delay time needed after BSs空格输出延迟,可以取BS0或BS1
VTDLYMask for delay time needed after VTs垂直制表符输出延迟,可以取VT0或VT1
FFDLYMask for delay time needed after FFs换页延迟,可以取FF0或FF1

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

英文说明中文说明
ISIGEnable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANONEnable canonical input (else raw)使用标准输入模式
XCASEMap uppercase \lowercase (obsolete)在ICANON和XCASE同时设置的情况下,终端只使用大写
ECHOEnable echoing of input characters显示输入字符
ECHOEEcho erase character as BS-SP-BS如果ICANON同时设置,ERASE将删除输入的字符
ECHOKEcho NL after kill character如果ICANON同时设置,KILL将删除当前行
ECHONLEcho NL如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRTEcho erased character as character erased如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOPSend SIGTTOU for background output向后台输出发送SIGTTOU信号


破案实战总结


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

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

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

/**
*打开串口
*/

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[] = {921600, 460800, 230400, 115200, 57600, 38400,
                     19200, 9600, 4800, 2400, 1200, 300, 38400,
                     19200,  9600, 4800, 2400, 1200, 300, };
   struct termios opt;
   tcgetattr(fd, &opt);
   opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
   opt.c_oflag &= ~(ONLCR | OCRNL);
   opt.c_iflag &= ~(IXON);
   for ( i= 0; 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 != 0)
               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) != 0)
   {
       perror("tcgetattr error");
       return -5;
   }
   options.c_cflag &= ~CSIZE;
   options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
   options.c_oflag &= ~OPOST;
   switch (databits) /*设置数据位数*/
   {
   case 7:
       options.c_cflag |= CS7;
       break;
   case 8:
       options.c_cflag |= CS8;
       break;
   default:
       fprintf(stderr,"unsupported data size\n");
       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 parity\n");
       return -3;
   }
   /* 设置停止位*/
   switch (stopbits)
   {
   case 1:
       options.c_cflag &= ~CSTOPB;
       break;
   case 2:
       options.c_cflag |= CSTOPB;
       break;
   default:
       fprintf(stderr,"unsupported stop bits\n");
       return -2;
   }
   /* Set input parity option */
   if (parity != 'n')
       options.c_iflag |= INPCK;
   options.c_cc[VTIME] = 0; // 15 seconds
   options.c_cc[VMIN] = 0;
   tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
   if (tcsetattr(fd,TCSANOW,&options) != 0)
   {
       perror("setup serial options error");
       return -1;
   }
   return 0;
}

本文为作者投稿,版权归原作者所有。

  征稿啦!

CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。

如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。


Forwarded from Official Account

chafezhou
chafezhou
Learn More