首页 > 解决方案 > 使用 C 语言读取串行端口(USB-RS232)的问题

问题描述

祝大家有美好的一天,

我刚刚开始为我的研究编程,它需要使用通过串行端口(USB 到 RS232,使用 /dev/ttyUSB0)连接到计算机的横向机构(类似于只能在 3 轴 XYZ 上移动的机械臂)。计算机的操作系统是 Linux 14.04,由于内核原因,我不能使用更高级的版本,而且我在这个遍历臂的程序上使用 C 语言。

我一直在使用的这个代码是从 2000 年开始开发的,但是近年来这个程序没有被使用过。自从我开始做这个项目以来,我遇到了几个问题,但最后我总是撞到同一堵墙,这是程序内部造成的一个永恒循环,因为 C 的 read() 函数无法正常读取来自串行端口的数据。

/* For 232cOUT() and 232cIN() */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include "traverse.h"

rs232cIN(mojiin,status)
unsigned char *mojiin;
unsigned char *status;{
     int fdi,c,res,j,w;
     struct termios oldtio,newtio;
     struct termios oldscr;
     printf("ok0i\n");
     fdi = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
     if (fdi <0) {perror(MODEMDEVICE); exit(-1); }

    tcgetattr(fdi,&oldtio); /* save current port settings --> For tty device */
    tcgetattr(1,&oldscr);  /* save current port settings --> For display device */
    printf("fdi=%d\n",fdi);
    printf("ok1i\n");
    bzero(&newtio, sizeof(newtio));
    newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD | CSTOPB;
    newtio.c_iflag = IGNPAR | ICRNL;  
    newtio.c_oflag = 0;
    printf("ok2i\n");
    /* set input mode (non-canonical, no echo,...) */
    newtio.c_lflag = 0;

    newtio.c_cc[VTIME]    = 5;   /* inter-character timer unused */
    newtio.c_cc[VMIN]     = 0;   /* blocking read until 5 chars received */

    tcflush(fdi, TCIFLUSH);
    tcsetattr(fdi,TCSANOW,&newtio);
    tcsetattr(1,TCSANOW,&newtio); /* stdout settings like modem settings */

    printf("ok3i\n");
    res = read(fdi,mojiin,256);   /* return after 5 chars have been input */        
    printf("res=%d\n",res);
     printf("ok4i\n");
    mojiin[res]=0;              /* so we can printf... */
    #if 1
         printf("Data import Good!!\n\r");
         printf("Imported-MOJIRETSU:%c\n\r",mojiin[0]);   
         printf("Imported-MOJISU:%d\n\r", res);   
     #endif
     status[0]=mojiin[0];       
     //sleep(1);       /*  this command is very important */
     usleep(SLEEPUSEC2);
     tcsetattr(fdi,TCSANOW,&oldtio);
     tcsetattr(1,TCSANOW,&oldscr);

     close(fdi); 
     }

也许这段代码与我在网上找到的其他代码相似,开放端口也与这个相似,但不是 read() 函数是 write()。

问题出在读取功能上,使用当前代码我无法读取来自 RS232 的任何值。计算机可以向RS232发送一个ASCII码,但是当它需要接收时,没有返回值,作为程序运行的例子就是这个代码,程序运行了几个循环,但程序卡在loopin1中。

读取的结果是 res、MOJIRETSU 和 MOJISU。如果你检查 fdi=3 这意味着它正在从 RS232 端口读取一些东西,但是当它到达 res 时,值为 0,MOJIRETSU 不显示任何值,MOJISU 它等于 0。从 MOJISU 它创建稍后在状态标志上生成错误的状态。

==========  [Y_AXIS] Traversing START!! ========== 
Delta(Traverse) -->0.000000 
loopin1ok0o 
ok1o 
ok2o 
MOJISU=4 
ok3o 
     ZR 

MOJIOUT=ZR 
Sending OK 

ok4o 
ok0i 
fdi=3 
ok1i 
ok2i 
ok3i 
    res=0 
         ok4i 
             Data import Good!! 
Imported-MOJIRETSU: 
Imported-MOJISU:0 
status=0 
STATUS ERROR 0x0 !!!! 
STATUS FLAG=-4 
ok0o 
ok1o 
ok2o 
MOJISU=4 
ok3o 
     ZR 

MOJIOUT=ZR 
Sending OK 

ok4o 
ok0i 
fdi=3 
ok1i 
ok2i 
ok3i 
    res=0 
         ok4i 
             Data import Good!! 
Imported-MOJIRETSU: 
Imported-MOJISU:0 
status=0 
STATUS ERROR 0x0 !!!! 
STATUS FLAG=-4 
^C 

正如您从循环中看到的那样,有一条错误消息导致同一部分重复并一直重复。最奇怪的是,如果我取消这个错误,这意味着我只是忽略了错误生成的循环修改其他代码:信号继续到其他循环,直到它最终移动遍历机制!即使它仍然发送与循环显示的完全相同的错误。

你可能会问,不使用循环工作,但问题是错误消息是指示遍历机制是否达到其所在轨道的限制的标志,如果它没有警告我可能会破坏遍历机制!所以我绝对需要这个反馈循环来运行它。

系统以非规范方式工作,我已将 VTIME 调整为多个值,并且不会改变输出的状态。如果我更改了 VMIN,那么程序就会卡住,只有关闭终端才能停止它。

真的很奇怪,即使没有循环发送错误,遍历机制也可以工作。因此,如果有人知道如何操作 read() 函数,我真的需要建议,这样我才能真正获得 res、MOJIRETSU y MOJISU 的值,从而在不删除此反馈循环的情况下操作遍历机制。

标签: cserial-portubuntu-14.04communicationtermios

解决方案


您的串行终端初始化使用了糟糕的编码实践。将 termios 结构清零不符合 POSIX。

您对 termios 结构进行归零指的是什么?

只需在您的代码中搜索“零”,您就会发现有问题的语句:

    bzero(&newtio, sizeof(newtio));

有关初始化串行终端的正确方法,请参阅正确设置终端模式POSIX 操作系统的串行编程指南。

几年前该代码也可以使用,因为之前的研究中使用了遍历机制,最近它无法使用该代码移动。

这就是写得不好的代码的问题。它不是便携式的,并且可能无法在不同的系统或不同的日子工作。


问题出在读取功能上,使用当前代码我无法读取来自 RS232 的任何值。

与注释或文本描述不匹配的代码会在某种程度上阻碍分析。
但是您的代码存在三个问题是显而易见的。

如前所述,串行终端初始化不符合 POSIX。

该日志表明您在一些神秘的输出例程和您发布以供审查的输入例程之间循环。
输入例程的每次执行都会在串行终端上执行open()、初始化和close()序列。大概输出例程(尚未发布)在串行终端上执行类似的open()、初始化和close()序列。

串行终端的这种重复打开、初始化和关闭的顺序是非常低效的,并且对于适当的程序设计来说完全没有必要。
这是第二个问题。

第三个问题是前一个问题的直接后果。
作为读取初始化的一部分,您对可能在接收缓冲区中的任何数据执行显式丢弃:

    tcflush(fdi, TCIFLUSH);

您后续的read()(无论您如何配置 VMIN 和 VTIME)只能返回刷新操作后收到的数据。
在串行终端未打开和/或在刷新操作之前接收到的任何和所有数据都会丢失到您的程序中。

如果您的程序在启动时只打开并初始化串行终端一次以进行读写,那么第三个问题就会消失。


推荐阅读