【问题标题】:Working with linux serial port in C, Not able to get full data在 C 中使用 linux 串行端口,无法获取完整数据
【发布时间】:2016-12-19 14:13:41
【问题描述】:

我正在使用用 C 编写的 Linux 串行端口。下面是我的 UART 设置

 int fd;
 struct termios tty_attributes;
 fd = open(comport, O_RDWR | O_NOCTTY | O_SYNC | O_NONBLOCK );

if(fd < 0)
{
    perror("open comport error.\n");
    exit(EXIT_FAILURE);
}
else
 {

    if(tcgetattr(fd, &tty_attributes) == -1)
    {
        perror("tcgetattr termios function error.\n");
        exit(EXIT_FAILURE);
    }

    tty_attributes.c_lflag = 0;
    tty_attributes.c_oflag = 0;
    tty_attributes.c_iflag = 0;
    tty_attributes.c_cflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
    tty_attributes.c_cflag |= CS8;
    tty_attributes.c_cflag |= CLOCAL;
    tty_attributes.c_cflag &= ~CREAD;
    tty_attributes.c_oflag &= ~OPOST;
    tty_attributes.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    tty_attributes.c_cc[VMIN] = SIZE_STR_FRAME;
    cfsetospeed(&tty_attributes, BAUDRATE);       //setting communication speed and other attributes
    cfsetispeed(&tty_attributes, BAUDRATE);
    tcflush(fd, TCIOFLUSH);
    tcsetattr(fd, TCSANOW, &tty_attributes);     //change immediately
    return fd;
}

}

下面是我阅读框架的代码

char* frame_read(int fd)
{
    char *ret = NULL;
    int read_ret_val;
    struct timeval time_val;
    if (fd < 0)
    {
        printf("Before read over comm channel, channel must be initialize\n");
        exit(EXIT_FAILURE);
    }
    memset(frame, 0, SIZE);
    fd_set rfds;        //read file discriptors
    int return_val;
    FD_SET(fd, &rfds);

    setReceiveMode(fd, TRUE);
    tcflush(fd, TCIFLUSH);
    tcflush(fd, TCOFLUSH);    //flush previous values
    return_val = select((fd) + 1, &rfds, NULL, NULL, &time_val);
    if (return_val == -1)
    {
        perror("select");
        exit(EXIT_FAILURE);
    }

    else if (return_val)
    {
        usleep(100 * 1000);
        read_ret_val = read(fd, frame, SIZE);
        if (read_ret_val < 0)
        {
            perror("read");
            exit(EXIT_FAILURE);
        }
        ret = frame;
        //printf("inside else if of read\n");
    }
}

我有一个 gps 模块与 UART 连接,当我使用 minicom 检查时,我得到了全帧,但是当我通过 uart 接收(使用此代码)时,我只得到前 16 个字节。 谁能指出我的错误。? 这里波特是 9600 ,帧是 64 字节,大小是 64 字节。,我占用的缓冲区也是 64 字节。如有格式错误请见谅。

我的 main.c 文件

int main(int argc, char *argv[])
{
  int i=0,j=0;
  char *readb;
  unsigned char data[34];
  static int fd = -1;
  struct struct_base_gps *gps;
  int command=0;
  char COMM_PORTNAME[13];
  strcpy( COMM_PORTNAME, argv[1] );// give the first port number for GPS receiving
  if((fd = init_comm_channel(COMM_PORTNAME)) < 0 )
  {
       exit(EXIT_FAILURE);
       printf("port is not opened\n");
  }
  else
  {

     printf("port is open for communication:\n");
     readb = frame_read(fd);
     for (i=0;i<=34;i++)
      {
          data[i] = *(readb +j);
         printf("the data is %x\n",data[i]);
         j++;
     }
  }
  close (fd);

}

对于 SIZE 是 #定义尺寸 64 和框架是 字符帧[64];

感谢您的反馈,我已经更新了代码。

还更新了我在终端和程序上获取的框架图片。可能会更清楚。

Received the data from UART by program

minicom recived

【问题讨论】:

  • 与你的问题无关,但使用前需要清除描述符集rfds。集合基本上是一个包含数组的结构,如果您不首先在集合上使用FD_ZERO,则该数据将未初始化,因此具有indeterminate 值。 time_val同样的问题,必须初始化为你想要的超时时间。
  • 至于您的问题,您需要创建一个Minimal, Complete, and Verifiable Example 并展示给我们。包括您如何调用这些函数以及您使用的变量的定义。例如,frame 是什么? SIZE 是什么?
  • 最后,通过串行通信发送的数据是streaming。没有固定的消息边界或数据包。如果你需要,你需要自己实现它。这也意味着对read 的一次调用我没有读取完整的消息(“帧”),或者它可能读取更多 而不是单个消息(如果有多个缓冲)。您需要循环阅读,直到收到至少一条消息,并且能够在单个 read 调用中处理多条消息(并且最后一条消息可能是部分消息)。

标签: c linux serial-port


【解决方案1】:

试试

    memset(&tty_attributes,0,sizeof(tty_attributes));
    tty_attributes.c_iflag=0;
    tty_attributes.c_oflag=0;
    tty_attributes.c_cflag=CS8|CREAD|CLOCAL;
    tty_attributes.c_lflag=0;
    tty_attributes.c_cc[VMIN]=1;
    tty_attributes.c_cc[VTIME]=5;

【讨论】:

【解决方案2】:

看着The Man

返回值

成功时,返回读取的字节数(零表示结束 文件),并且文件位置由这个数字提前。 是 如果此数字小于字节数,则不是错误 要求;这可能会发生,例如,因为更少的字节 现在实际可用(也许是因为我们接近尾声- 文件,或者因为我们正在从管道或终端读取),或者 因为 read() 被信号打断了。另见注释。

强调我的

因此,您不能指望一次读取就可以检索到整个帧。 您应该循环直到收到所有预期的字符,例如:

int total_rec = 0;
char temp[SIZE];
while( total_rec < SIZE )
{
    read_ret_val = read(fd, temp, SIZE);
    if (read_ret_val != -1)
    {
       if ( (total_rec + read_ret_val) >= SIZE)
       { 
           read_ret_val = SIZE - total_rec;
       }
       memcpy(&frame[total_rec], temp, read_ret_val);
       total_rec += read_ret_val;
    }
    else
    {
       perror("error reading serial line: ");
    }
}

【讨论】:

  • :- 感谢您的反馈,所以我应该循环 read 直到收到预期的字符?
  • 看我的小例子。
  • 是的,我会按照这个修改我的
  • 是的,我会按照这个修改我的。我可以在这里给+1。因为我无法回答,(低分:-))。谢谢
  • 答案不完整,因为终端是非阻塞模式打开的。循环将检索更多数据,但会浪费 CPU 周期。 IE。程序正忙于轮询系统获取数据。
【解决方案3】:

大多数 GPS 模块和设备的串行接口通常会逐行向您发送数据。为此,您可以使用已明确禁用的规范模式。

manual 中所述的规范模式

在规范模式下:

输入是逐行提供的。输入行分隔符之一时,输入行可用(NL、EOL、EOL2;或 EOF 在 行首)。 除了 EOF 的情况外,行分隔符包含在 read(2) 返回的缓冲区中。

我发布代码来设置串行接口速度和启用规范模式的奇偶校验:

int set_interface_attribs(int fd, int speed, int parity) 
{
  // setup based on stty < /dev/ttyACM0 (cfg.txt) output which
  // worked for ABSniffer in pyserial implementation
  // otherwise module responded only once for every two prompts
  struct termios tty;
  int rc;
  memset(&tty, 0, sizeof tty);
  if (tcgetattr(fd, &tty) != 0) 
  {
    log_info("error from tcgetattr %s\r\n", strerror(errno));
    return -1;
  }

  rc = cfsetospeed(&tty, speed);
  if (rc == - 1) return -1;
  rc = cfsetispeed(&tty, speed);
  if (rc == - 1) return -1;

  tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;  // 8-bit chars
  // disable IGNBRK for mismatched speed tests; otherwise receive break
  // as \000 chars
  tty.c_cc[VMIN] = 0;   // read doesn't block
  tty.c_cc[VTIME] = 5;  // 0.5 seconds read timeout

  tty.c_cflag |= (CLOCAL | CREAD);  // ignore modem controls,
  // enable reading
  tty.c_cflag &= ~(PARENB | PARODD);  // shut off parity
  tty.c_cflag |= parity;
  tty.c_cflag &= ~CSTOPB;
  //    tty.c_iflag |= ICRNL | BRKINT; //ICRNL
  tty.c_iflag |= IGNCR;
  tty.c_cflag &= ~CRTSCTS;
  //    tty.c_oflag |= OPOST | ONLCR;
  // tty.c_iflag |= ISIG | ICANON | IEXTEN;
  tty.c_lflag |= ISIG | IEXTEN | ICANON;
  tty.c_lflag &= ~ECHO;
  tty.c_cc[VEOF] = 0x0;
  tty.c_cc[VEOL] = 0x0;

  if (tcsetattr(fd, TCSANOW, &tty) != 0) 
  {
    log_info("error from tcsetattr %s\r\n", strerror(errno));
    return -1;
  }
  return 0;
}

这是你如何使用它:

rc = set_interface_attribs(fd, B9600, 0);

从现在开始,数据应该可以逐行获取。 read manual 中解释了所有错误和可能的返回值。假设没有错误,读取任意大小的缓冲区应返回 EAGAIN(资源暂时不可用)并返回代码 -1 或字节到换行符“\n”。

【讨论】:

  • 规范模式不使用 VMIN 和 VTIME。 memset() 在 tcgetattr() 之前是多余的。
  • 嗨@sawdust:- 感谢您的回答。你能给出一个示例代码,以便我可以尝试一下。
  • 嗨@pbn:-谢谢回答,我已经尝试过这段代码。但它不起作用。此外,最好使用tcflush(fd, TCOFLUSH); 刷新 prev。价值观。再次感谢您的重播。
  • @AxCool 好的,我注意到的另一件事是您没有在帧读取代码中设置struct timeval。查看select manual 中的示例部分。请注意,您还缺少选择超时的分支。手册中也有说明。
  • 解决问题后,我会根据 cmets 改进我的答案。
【解决方案4】:

您的原始代码存在许多问题,导致它“仅获取前 16 个字节”

  • 代码(如已发布)仅执行单个 read() 系统调用(而不是连续循环以从设备读取数据)。

  • 输入显然是 ASCII 文本,分隔为以回车和换行结尾的行,但您的程序使用非规范模式而不是规范模式进行读取。 minicom 输出证实了@pbn 的假设。

  • 您的程序在非阻塞模式下使用串行终端,而不是阻塞模式,并使用 select()usleep() 调用等待数据到来。

  • termios 初始化(除了不兼容 POSIX)有几个错误,包括应用于 cflag 成员的不正确的 iflag 符号, 字符大小位不使用 ~CSIZE 清除,并且 CREAD 未启用。

  • 您的读取例程在调用 select() 之前不必要地刷新(即丢弃)所有已接收但未读取的数据。

用于打开和配置串行终端的修改例程(用于阻止规范模式):

#define BAUDRATE    B9600

int init_comm_channel(char *comport)
{   
    struct termios tty_attributes;
    int fd;

    fd = open(comport, O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("open comport error.\n");
        return (-2);
    }
    if (tcgetattr(fd, &tty_attributes) == -1) {
        perror("tcgetattr termios function error.\n");
        return (-3);
    }
    tty_attributes.c_cflag |= CLOCAL | CREAD;
    tty_attributes.c_cflag &= ~CSIZE;
    tty_attributes.c_cflag |= CS8;         /* 8-bit characters */
    tty_attributes.c_cflag &= ~PARENB;     /* no parity bit */
    tty_attributes.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty_attributes.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    tty_attributes.c_lflag |= ICANON | ISIG;  /* canonical input */
    tty_attributes.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);

    tty_attributes.c_iflag &= ~INPCK;
    tty_attributes.c_iflag |= IGNCR;
    tty_attributes.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
    tty_attributes.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */

    tty_attributes.c_oflag &= ~OPOST;

    cfsetospeed(&tty_attributes, BAUDRATE);       //setting communication speed and other attributes
    cfsetispeed(&tty_attributes, BAUDRATE);
    tcflush(fd, TCIOFLUSH);

    if (tcsetattr(fd, TCSANOW, &tty_attributes) < 0) {
        perror("tcsetattr function error.\n");
        return (-4);
    }
    return fd;
}

每个系统调用读取一行的修改例程:

#define SIZE    64
unsigned char frame[SIZE];

char *frame_read(int fd)
{
    int read_ret_val;

    if (fd < 0) {
        printf("Before read over comm channel, channel must be initialize\n");
        exit (EXIT_FAILURE);
    }
    read_ret_val = read(fd, frame, SIZE - 1);
    if (read_ret_val < 0) {
        perror("read");
        exit (EXIT_FAILURE);
    }
    frame[read_ret_val] = 0; /* terminate string */
    return (frame);
}

修改后的 ma​​in() 例程永远循环:

int main(int argc, char *argv[])
{
    int fd;
    char *readb;
    char com_portname[13] = {0};

    if (argc > 1)
        strcpy(com_portname, argv[1]);  // give the first port number for GPS receiving
    if ((fd = init_comm_channel(com_portname)) < 0) {
        printf("port is not opened\n");
        exit (EXIT_FAILURE);
    }
    printf("port is open for communication:\n");
    do {
        readb = frame_read(fd);
        while (*readb > 0)
            printf("the data is 0x%x\n", *readb++);
        printf("The line is: %s", frame);
    } while (1);  /* loop for another line */
    close(fd);
}

【讨论】:

  • 嗨@sawdust:感谢您分享答案。我正在尝试这个答案并将分享结果。 +1 这里是答案:-)。
  • 嗨@sawdust :- 我知道你很忙,我已经尝试过这段代码。很抱歉,我得到的结果与终端接收到的数据不同。终端接收到的数据是:- b5 62 01 07 5c 00 48 0b 8d 15 e0 07 0c 1d 04 19 30 37 61 00 00 00 dc 98 04 00 03 01 0b 05 a1 23 37 2e 5e 9d c7 07 308 31 0c 0 0d 00 广告 cd 00 00 3b aa 00 00 7b 01 00 00 e5 ff ff ff 23 00 00 00 7c 01 00 00 24 55 cd 00 01 0a 00 00 3a 81 84 00 06 01 00 e0 0 0 86 4c 0220 0 0 00 00 00 00 80 28 1e b5 62.
  • 但是代码输出是:- b5 62 50 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 69 c 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 与实际不同。你能提出修改建议吗?
  • 在您的原始问题中,您发布了一个指向您尝试读取的数据的链接,即 ASCII 文本。我发布的规范模式代码只会读取 ASCII 文本行,这将解决您的原始问题。显然你已经转移到另一个问题上,并试图用不适当的配置读取不同的数据(即二进制数据)。二进制数据必须以非规范模式读取。见stackoverflow.com/questions/6947413/… 不同的问题需要不同的答案。
  • :- 很抱歉,我无法清楚地解释场景。我也非常感谢您在百忙之中抽出时间来回答。谢谢:-)
猜你喜欢
  • 2011-06-07
  • 2012-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多