简体   繁体   中英

Reading serial port faster

I have a computer software that sends RGB color codes to Arduino using USB. It works fine when they are sent slowly but when tens of them are sent every second it freaks out. What I think happens is that the Arduino serial buffer fills out so quickly that the processor can't handle it the way I'm reading it.

#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);
  }
}

For example the computer might send 255 0 123 where the numbers are separated by space. This works fine when the sending interval is slow enough or the buffer is always filled with only one color code, for example 255 255 255 which is 11 bytes ( INPUT_SIZE ). However if a color code is not 11 bytes long and a second code is sent immediately, the code still reads 11 bytes from the serial buffer and starts combining the colors and messes them up. How do I avoid this but keep it as efficient as possible?

It is not a matter of reading the serial port faster, it is a matter of not reading a fixed block of 11 characters when the input data has variable length.

You are telling it to read until 11 characters are received or the timeout occurs, but if the first group is fewer than 11 characters, and a second group follows immediately there will be no timeout, and you will partially read the second group. You seem to understand that, so I am not sure how you conclude that "reading faster" will help.

Using your existing data encoding of ASCII decimal space delimited triplets, one solution would be to read the input one character at a time until the entire triplet were read, however you could more simply use the Arduino ReadBytesUntil() function:

#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);
    }
}

Note that this solution does not require the somewhat heavyweight strtok() processing since the Stream class has done the delimiting work for you.

However there is a simpler and even more efficient solution. In your solution you are sending ASCII decimal strings then requiring the Arduino to spend CPU cycles needlessly extracting the fields and converting to integer values, when you could simply send the byte values directly - leaving if necessary the vastly more powerful PC to do any necessary processing to pack the data thus. Then the code might be simply:

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

        setColor(color);
    }
}

Note that I have not tested any of above code, and the Arduino documentation is lacking in some cases with respect to descriptions of return values for example. You may need to tweak the code somewhat.

Neither of the above solve the synchronisation problem - ie when the colour values are streaming, how do you know which is the start of an RGB triplet? You have to rely on getting the first field value and maintaining count and sync thereafter - which is fine until perhaps the Arduino is started after data stream starts, or is reset, or the PC process is terminated and restarted asynchronously. However that was a problem too with your original implementation, so perhaps a problem to be dealt with elsewhere.

First of all, I agree with @Thomas Padron-McCarthy. Sending character string instead of a byte array(11 bytes instead of 3 bytes, and the parsing process) is would simply be waste of resources. On the other hand, the approach you should follow depends on your sender:

  • Is it periodic or not
  • Is is fixed size or not

If it's periodic you can check in the time period of the messages. If not, you need to check the messages before the buffer is full. If you think printable encoding is not suitable for you somehow; In any case i would add an checksum to the message. Let's say you have fixed size message structure:

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;
}

Now, you should check every 4 byte (the size of MyMessage) in a sliding window fashion if it is valid or not:

 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)
         }
     }
 }

If It's not a fixed size, it gets complicated. But i'm guessing you don't need to hear that for this case.

EDIT (2) I've striked out this edit upon comments. One last thing, i would use a circular buffer. First add the received bytes into the buffer, then check the bytes in that buffer.

EDIT (3) I gave thought on comments. I see the point of printable encoded messages. I guess my problem is working in a military company. We don't have printable encoded "fire" arguments here :) There are a lot of messages come and go all the time and decoding/encoding printable encoded messages would be waste of time. Also we use hardwares which usually has very small messages with bitfields. I accept that it could be more easy to examine/understand a printable message.

Hope it helps, Gokhan.

If faster is really what you want....this is little far fetched.

The fastest way I can think of to meet your needs and provide synchronization is by sending a byte for each color and changing the parity bit in a defined way assuming you can read the parity and bytes value of the character with wrong parity.

You will have to deal with the changing parity and most of the characters will not be human readable, but it's gotta be one of the fastest ways to send three bytes of data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM