【问题标题】:Reading serial port faster更快地读取串口
【发布时间】:2015-12-30 01:55:39
【问题描述】:

我有一个使用 USB 将 RGB 颜色代码发送到 Arduino 的计算机软件。当它们被缓慢发送时它工作正常,但是当它们每秒发送数十个时它会吓坏。我认为发生的事情是 Arduino 串行缓冲区填充得如此之快,以至于处理器无法按照我阅读的方式处理它。

#define INPUT_SIZE 11

void loop() {
  if(Serial.available()) {
    char input[INPUT_SIZE + 1];
    byte size = Serial.readBytes(input, INPUT_SIZE);
    input[size] = 0;

    int channelNumber = 0;

    char* channel = strtok(input, " ");
    while(channel != 0) {
      color[channelNumber] = atoi(channel);

      channel = strtok(0, " ");
      channelNumber++;
    }

    setColor(color);
  }
}

例如,计算机可能会发送255 0 123,其中数字用空格分隔。当发送间隔足够慢或缓冲区总是只填充一种颜色代码时,这可以正常工作,例如255 255 255,它是 11 个字节 (INPUT_SIZE)。但是,如果颜色代码不是 11 字节长并且立即发送第二个代码,则代码仍会从串行缓冲区读取 11 个字节并开始组合颜色并将它们弄乱。如何避免这种情况,但要尽可能保持高效?

【问题讨论】:

  • 您的意思是字节,而不是位。但是,如果您打算读取的数据并不总是 11 个字节长,那么您就不能只读取 11 个字节并期望一切正常。如果第一条消息是“1 2 3”,第二条消息是“12 3 4”,则输入中的数据将为“1 2 312 3 4”。在我看来,不可能知道它是指“1 2 3”后跟“12 3 4”,还是表示“1 2 31”后跟“2 3 4”?你能改变格式,例如将三个字节作为二进制字节发送,而不是转换为文本?
  • 感谢@ThomasPadron-McCarthy!我更改了计算机上的程序以发送 HEX 颜色代码。现在我总是收到六个字节,然后我需要将其转换回 RGB。 pastebin.com/JL31zwWQ
  • 不明白怎么是6字节?您应该为每个通道发送 1 个字节,即三个。此外,使用结构将原始输入映射到您的格式化版本。请参阅下面的帖子。

标签: c arduino serial-port embedded usb


【解决方案1】:

不是读串口快的问题,而是当输入数据变长时不读取固定的11个字符块的问题。

您告诉它读取直到收到 11 个字符或发生超时,但如果第一组少于 11 个字符,并且第二组紧随其后,则不会超时,您将部分读取第二组.您似乎明白这一点,所以我不确定您如何得出“更快阅读”会有所帮助的结论。

使用您现有的 ASCII 十进制空格分隔三元组数据编码,一种解决方案是一次读取输入一个字符,直到读取整个三元组,但是您可以更简单地使用 Arduino ReadBytesUntil() 函数:

#define INPUT_SIZE 3

void loop()
{
    if (Serial.available())
    {
        char rgb_str[3][INPUT_SIZE+1] = {{0},{0},{0}};

        Serial.readBytesUntil( " ", rgb_str[0], INPUT_SIZE );
        Serial.readBytesUntil( " ", rgb_str[1], INPUT_SIZE );
        Serial.readBytesUntil( " ", rgb_str[2], INPUT_SIZE );

        for( int channelNumber = 0; channelNumber < 3; channelNumber++)
        {
            color[channelNumber] = atoi(channel);
        }

        setColor(color);
    }
}

请注意,此解决方案不需要重量级的 strtok() 处理,因为 Stream 类已为您完成了定界工作。

但是,有一个更简单、更有效的解决方案。在您的解决方案中,您发送 ASCII 十进制字符串,然后要求 Arduino 花费 CPU 周期不必要地提取字段并转换为整数值,而您可以直接发送字节值 - 如有必要,让功能更强大的 PC 进行任何必要的处理从而打包数据。那么代码可能很简单:

void loop()
{
    if( Serial.available() )
    {
        for( int channelNumber = 0; channelNumber < 3; channelNumber++)
        {
            color[channelNumber] = Serial.Read() ;
        }

        setColor(color);
    }
}

请注意,我没有测试任何上述代码,例如,在某些情况下,Arduino 文档缺少关于返回值的描述。您可能需要稍微调整一下代码。

以上都不能解决同步问题——即当颜色值在流时,你怎么知道哪个是 RGB 三元组的开始?您必须依靠获取第一个字段值并在此后保持计数和同步——这很好,直到 Arduino 在数据流启动后启动,或者被重置,或者 PC 进程被终止并异步重新启动。但是,这对于您的原始实现也是一个问题,因此可能需要在其他地方处理。

【讨论】:

  • 如何发送三个字节的颜色?
  • @MikkoP :R、G、B 组件的一个字节范围。一个字节可以表示 0 到 255 的值。您的串行通信需要配置为 8 个数据但帧(通常在任何情况下都是默认值)
【解决方案2】:

首先,我同意@Thomas Padron-McCarthy 的观点。发送字符串而不是字节数组(11 个字节而不是 3 个字节,以及解析过程)会简单地浪费资源。另一方面,您应该遵循的方法取决于您的发件人:

  • 是否周期性
  • 大小是否固定

如果是周期性的,您可以查看消息的时间段。如果没有,您需要在缓冲区已满之前检查消息。 如果您认为可打印编码不适合您;无论如何我都会在消息中添加校验和。假设您有固定大小的消息结构:

typedef struct MyMessage
{
    // unsigned char id; // id of a message maybe?
    unsigned char colors[3]; // or unsigned char r,g,b; //maybe
    unsigned char checksum; // more than one byte could be a more powerful checksum
};

unsigned char calcCheckSum(struct MyMessage msg)
{
    //...
}

unsigned int validateCheckSum(struct MyMessage msg)
{
    //...
    if(valid)
       return 1;
    else
       return 0;
}

现在,您应该以滑动窗口的方式检查每 4 个字节(MyMessage 的大小)是否有效:

 void findMessages( )
 {
     struct MyMessage* msg;
     byte size = Serial.readBytes(input, INPUT_SIZE);
     byte msgSize = sizeof(struct MyMessage);
     for(int i = 0; i+msgSize <= size; i++)
     {
         msg = (struct MyMessage*) input[i];
         if(validateCheckSum(msg))
         {// found a message
             processMessage(msg);
         }
         else
         {
             //discard this byte, it's a part of a corrupted msg (you are too late to process this one maybe)
         }
     }
 }

如果不是固定大小,它就会变得复杂。但我猜你不需要听到这种情况。

编辑(2) 我已经在 cmets 上删除了这个编辑。 最后一件事,我会使用循环缓冲区。首先将接收到的字节添加到缓冲区中,然后检查该缓冲区中的字节。

编辑(3) 我考虑过cmets。我看到了可打印的编码消息的意义。我想我的问题是在一家军事公司工作。我们这里没有可打印的编码“火灾”参数 :) 一直有很多消息来来去去,解码/编码可打印的编码消息会浪费时间。此外,我们使用的硬件通常具有非常小的位域消息。我接受检查/理解可打印消息可能更容易。

希望对你有帮助, 悟空。

【讨论】:

  • Arduino Serial 类已经被缓冲并被中断驱动,并且由于没有多线程,辅助缓冲区将毫无用处。 +1 用于解决同步问题。
  • 我没用过,也没见过ardunio :) 谢谢指正。
  • 字符串更容易调试,如果性能不是关键因素,它们可能是更好的选择。
  • @FUZxxl,我不同意你的观点,如果你不是以消息形式发送文本。我从未见过发送字符数组而不是结构化消息是更好的选择。
  • @FUZxxl :数据“清晰易读”可能会使调试更容易,但在这种情况下是错误的实际原因(尝试将可变长度数据处理为固定长度) .它突出了 Arduino 的最大问题——没有支持片上调试的调试器。在调试器中观察字节变量将是微不足道的。在这种情况下,在 Arduino 端使用调试打印输出比不必要地消耗通道带宽更可取。
【解决方案3】:

如果你真的想要更快......这有点牵强。

我能想到的满足您的需求并提供同步的最快方法是为每种颜色发送一个字节并以定义的方式更改奇偶校验位,假设您可以读取奇偶校验错误字符的奇偶校验和字节值。

您将不得不处理不断变化的奇偶校验,并且大多数字符将无法被人类阅读,但它必须是发送三个字节数据的最快方法之一。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-26
    • 2017-02-15
    • 2011-06-01
    • 2011-06-18
    • 2015-08-07
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    相关资源
    最近更新 更多