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.
6 Answers 6
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()
.
#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.
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).
-
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– mr-matt2017年10月29日 17:52:03 +00:00Commented Oct 29, 2017 at 17:52
-
In that case 11 bytes would be enough, one value for each band.Michel Keijzers– Michel Keijzers2017年10月29日 21:40:48 +00:00Commented 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– mr-matt2017年10月30日 02:27:40 +00:00Commented 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).Michel Keijzers– Michel Keijzers2017年10月30日 09:38:31 +00:00Commented 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– mr-matt2017年10月30日 18:54:36 +00:00Commented Oct 30, 2017 at 18:54
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.
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.
-
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– mr-matt2017年10月29日 17:51:23 +00:00Commented 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– user314812017年10月29日 18:08:14 +00:00Commented Oct 29, 2017 at 18:08
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.
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).strip.show();
out of the loop for a bigger speedup than type conversion overhead