1

I'm making an LED spectrum analyzer with an Arduino Due and my PC. The audio processing is done on the PC, and then sent to the Arduino. The exact data sent is a 'coordinate', or more specifically, a number representing a particular band, and another number representing the maximum amplitude of that band. There are 11 bands. Each coordinate has a x and a y value, separated by a comma, and then sent on a new line.

This is my code as of now:

void loop() {
  if (Serial.available()) {
    xCoord = Serial.parseInt();
    yCoord = Serial.parseInt();
    if (xCoord != 0) {
      int r, g, b;
      for (int i = 0; i < 8; i++) {
        int fromCoordToIndex = ((xCoord - 1) * 8) + i;
        //
        //do some calculations for the color
        //
        strip.setPixelColor(fromCoordToIndex, strip.Color(r, g, b));
        strip.show();
      }
    }
  }
}

The problem is parseInt seems to be very very slow (it's not the timeout issue, it's set to 1). From what I can see, it seems to be skipping some data, so every now and then a band gets missed out. The only way I can fix this is to insert a delay between each coordinate being sent. I found that 40 ms works ok, but remember there are 11 bands, so that means about half a second to refresh the whole board, which feels like a slide show...

I've tried inputting individual characters and then using toInt, I've tried parseInt obviously, I've tried native USB (it's a Due), and I've tried fiddling with baud rates. None seems to have had any effect.

16
  • 1
    You can made a faster parseInt if your data has a rigid format (for example, always 3 digits long, padded with zeros or spaces. Tell us if that is the case (and what format are you using).
    – user31481
    Commented Oct 29, 2017 at 8:34
  • 1
    Post the details of your format to nail the question.
    – user31481
    Commented Oct 29, 2017 at 9:01
  • 3
    A much faster alternative is to not require parsing at all. Send binary data instead. Commented Oct 29, 2017 at 9:07
  • 1
    For example, all values are positive 3 digits (padded with zero), and they come in pairs, comma separated, like 003,456 ... 450,001 ...
    – user31481
    Commented Oct 29, 2017 at 9:08
  • 1
    move strip.show(); out of the loop for a bigger speedup than type conversion overhead
    – dandavis
    Commented Oct 29, 2017 at 23:39

6 Answers 6

2

The annoying thing about parseInt() is not its speed, it's the fact that it relies on a timeout. If the timeout is too long, it slows you down. If it's too short, it may induce errors.

A safer way to parse the sort of messages you are using is to buffer all the incoming characters until you find a carriage return, then parse the whole line. For example:

// Just echo the messages with a different separator.
void parse(char *buffer)
{
    char *s = strtok(buffer, ",");
    int x = atoi(s);
    s = strtok(NULL, ",");
    int y = atoi(s);
    Serial.print(x);
    Serial.print(':');
    Serial.println(y);
}

void loop()
{
    static char buffer[BUFFER_SZ];
    static size_t lg = 0;
    while (Serial.available()) {
        char c = Serial.read();
        if (c == '\r') {        // carriage return
            buffer[lg] = '\0';  // terminate the string
            parse(buffer);
            lg = 0;             // get ready for next message
        }
        else if (lg < BUFFER_SZ - 1) {
            buffer[lg++] = c;
        }
    }
}

Note that you should add some error checking when you call strtok().

1
#include <SoftwareSerial>
SoftwareSerial s1(5,6);

void setup {
s1.begin(9600);
s1.setTimeout(50); //sets the timeout of parseint() to 50ms

}

Please note that lowering the timeout too much can cause errors, i would not go below 10.

0

Instead of using parseint, send the data for the 11 bands in a very specific form to the Arduino so the Arduino can 'parse' it very easily.

Maybe instead of sending text, send 33 bytes for RGB (3) times 11 bands are the levels (0-255) of the consecutive channels. Than there is no conversion needed (except for reading the bytes).

6
  • Well I don't need to send the color, I just need to know how high each band should be. So sending extra values would just be pointless.
    – mr-matt
    Commented Oct 29, 2017 at 17:52
  • In that case 11 bytes would be enough, one value for each band. Commented Oct 29, 2017 at 21:40
  • Excuse my ignorance, but what exactly do you mean by byte? If each band has a number between (and including) 0 and 13 how do I represent that as a byte?
    – mr-matt
    Commented Oct 30, 2017 at 2:27
  • A byte is 8 bits (8 zero's or ones). WIth a byte (8 bits) you can store a number between 0 and 255 (or 256 different values). You could even send 2 values from 0-13 in one byte by putting by calculating value1 * 13 + value2 (which will always be less than 256). Or more according bits: value1 << 4 + value2... The << operator shifts the bits four to the left, which is equally as multiplying by 16. Value1 will be in the left (MSB) four bits, and Value2 in the right (LSB) four bits. So if your system changes to 16 channels you don't have to change anything (compared to the 11 channels using this). Commented Oct 30, 2017 at 9:38
  • Ok, so if I send "025" to the arduino with serial, could I use serial.readBytes()? And if I do that, would converting it to an integer be slow like from a string?
    – mr-matt
    Commented Oct 30, 2017 at 18:54
0

Premature optimization is the root of all evil (or at least most of it) in programming. Donald Knuth.

I don't know how fast/slow parseInt() is. Neither do you. You have to time it first.

OK. Maybe it's really slow, but is it the slowest part of your code? Slow compared to what?

Is parseInt what you need? parseInt doesn't convert negative values and it can time-out (but still return zero ... Duh!).

If you read the whole line as a String and then parse the values, you avoid the time-out problem and you can also verify that the data is correct.

This sketch show how to process your data:

void setup() {
  Serial.begin(9600);
  while(!Serial);

  String lines[] = {
    "1,1",
    "10,20",
    "11,1000",
  };
  int channel;
  int value;

  for (int i=0; i < 3; i++) {      
    sscanf(lines[i].c_str(), "%d,%d", &channel, &value);
    Serial.print("channel="); Serial.print(channel);
    Serial.print(" value="); Serial.println(value);
  }
}

void loop() { 
}

Well, I'm using sscanf, that's slower than parseInt for sure, but it get the job done. I doubt you (or me) can do it better, and there more useful thing to do with our time.

Edit:

I profiled

sscanf("0001,0001", "%d,%d", &channel, &value);

with an Arduino UNO and it took 147us to convert a single par of values.

0

Your data has 11 channels and a single value for each channel. The values will be presented in graphic format, so it fits in the range 0-255 without degrading the presentation. You always can scale your readings to your display's resolution.

You then send these 11 values in binary form. One byte per channel plus one byte to separate one data set from another. You don't need to send the channel number, just the values.

At the Arduino end you read 12 bytes and then loop thru it converting the bytes to ints.

#define CHANNELS 11

void setup()
{
  Serial.begin(9600);

  while(!Serial);
}

void loop()
{
  char packet[CHANNELS + 1];
  int values[CHANNELS];

  // Read 12 bytes (11 bytes of data + 1 byte delimiter)
  int nBytes = Serial.readBytes(packet, sizeof(CHANNELS + 1));

  if(nBytes == CHANNELS + 1) {
    // Convert bytes to ints;
    for(int i=0; i<CHANNELS; i++) {
      values[i] = packet[i];
    }
  } else {
     // Error. 
     }

  // Here you display your graph.
}

This code doesn't have any error-detection. For that you can add an extra byte with the checksum of the whole dataset.

2
  • Interesting issue with this one, nBytes never will be 11+1, when I tested it, it would always be 4, then 4, and then 4, rather than 12 in one go. Any ideas?
    – mr-matt
    Commented Oct 29, 2017 at 17:51
  • You are doing almost right. When dealing with comms (Serial, tcp/ip) never assume that you will get all your bytes in one reading. You have to be prepared to read data in pieces. You have the basic idea; now is up to you to fill the remaining details. Another problem in front of you is syncing both computers: you have to reserve a special value (255, for example) to signal the start (or end) of dataset. Right now, the first bytes sent are probably lost. After you add all your error-checking, recovery and buffering your code will be 5x bigger. That's is how it works.
    – user31481
    Commented Oct 29, 2017 at 18:08
0

I seem to have fixed it by lowering the baud rate from 115200 to 9600. This answer inspired the change. Apparently, the higher the baud the faster the information is sent, but the higher the likelihood of an error, and the lower the baud rate, the slower it is sent, but the more reliable it is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.