更快地读取串口

时间:2015-12-26 08:30:12

标签: c arduino serial-port embedded usb

我有一台计算机软件,可以使用USB将RGB颜色代码发送到Arduino。当它们被缓慢发送时它工作正常,但是当它们每秒发送数十个时它就会吓坏了。我认为发生的是Arduino串行缓冲区填充速度太快,以至于处理器无法像我读取它那样处理它。

private void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    //var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    var itemId = ((SampleDataGroup)e.ClickedItem).UniqueId;
    if (!Frame.Navigate(typeof(ItemPage), itemId))
    {
        throw new Exception(this.resourceLoader.GetString("NavigationFailedExceptionMessage"));
    }
}

例如,计算机可能会发送#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,即11个字节(255 255 255)。但是,如果颜色代码不是11个字节长并且第二个代码立即发送,则代码仍然从串行缓冲区读取11个字节并开始组合颜色并将它们混淆。我该如何避免这种情况,但要尽可能保持高效?

3 个答案:

答案 0 :(得分:2)

这不是更快地读取串行端口的问题,当输入数据具有可变长度时,不能读取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进程被异步终止和重新启动。然而,对于您的原始实现,这也是一个问题,因此可能需要在其他地方处理问题。

答案 1 :(得分:0)

首先,我同意@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) 我在评论时已经删除了这个编辑。 最后一件事,我会使用循环缓冲区。首先将接收到的字节添加到缓冲区中,然后检查该缓冲区中的字节。

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

希望它有所帮助, 格克。

答案 2 :(得分:0)

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

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

你将不得不处理变化的奇偶校验,大多数字符都不是人类可读的,但它必须是发送三个字节数据的最快方法之一。