Arduino readString(); code runs slow

2019-05-28 09:48发布

问题:

I have the following code which I need to execute quickly, yet its taking a lot of time to change the value, anyway over way of making this task quicker?

I am using indexOf() and substring() to accomplish this task. This is for changing the strip LED colors.

// declare LED Series A Pins R-G-B (PWM Pins)
  int const AledRedPin = 6;
  int const AledGreenPin = 5;
  int const AledBluePin = 3;

// declare LED Series B Pins R-G-B (PWM Pins)
  int const BledRedPin = 10;
  int const BledGreenPin = 11;
  int const BledBluePin = 9;

// serial input variable & string
// initialise LED Series A Pins R-G-B (PWN Value: 0 to 255)
// initial value = 255
  int AledRed = 255;
  int AledGreen = 255;
  int AledBlue = 255;

// initialise LED Series A Pins R-G-B (PWN Value: 0 to 255)
// initial value = 255
  int BledRed = 255;
  int BledGreen = 255;
  int BledBlue = 255;

//serial input
  String Command = "";

//string manipulation
  int cmdindexval = 0;
  String CommandType = "";
  int CommandValue = 0;
  String Series = "";

void setup() {
  // put your setup code here, to run once:
  // start serial
    Serial.begin(9600);
      while (!Serial) {
        ; // wait for serial port to connect. Needed for native USB
      }

  // set LED Series A Pins as Output R-G-B
    pinMode(AledRedPin, OUTPUT);
    pinMode(AledGreenPin, OUTPUT);
    pinMode(AledBluePin, OUTPUT);

  // set LED Series B Pins as Output R-G-B
    pinMode(BledRedPin, OUTPUT);
    pinMode(BledGreenPin, OUTPUT);
    pinMode(BledBluePin, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  // read from serial if it's available
    if (Serial.available() > 0) {
      Command = Serial.readString(); //read string from serial monitor
      cmdindexval = Command.indexOf('='); //read characters until '=' then assign the value
      CommandType = Command.substring(0, cmdindexval); //assign the value from 0 to cmdindexval
      //Series = Command.substring(0, 1); //read first character
      CommandValue = Command.substring(cmdindexval + 1).toInt(); //assign the value after '=' and convert string to Int
      Serial.println(CommandType + " ,is equal to " + CommandValue + " ,Series: " + Series);    
      //if (Series == "A") {
        if (CommandType == "ACledRed"){
          AledRed = CommandValue;
        }
       else if (CommandType == "ACledGreen"){
          AledGreen = CommandValue;
        }
        else if (CommandType == "ACledRedBlue") {
          AledBlue = CommandValue;
        }
      //}
      //else if (Series == "B") {
        if (CommandType == "BCledRed") {
          BledRed = CommandValue;
        }
        else if (CommandType == "BCledGreen") {
          BledGreen = CommandValue;
        }
        else if (CommandType == "BCledBlue") {
          BledBlue = CommandValue;
        }
     //}
    } //end serial

    analogWrite(AledRedPin, AledRed);
    analogWrite(AledGreenPin, AledGreen);
    analogWrite(AledBluePin, AledBlue);

    analogWrite(BledRedPin, BledRed);
    analogWrite(BledGreenPin, BledGreen);
    analogWrite(BledBluePin, BledBlue);

}

回答1:

From the Arduino docs on readString:

Serial.readString() reads characters from the serial buffer into a string. The function terminates if it times out (see setTimeout()).

and the docs on setTimeout:

Serial.setTimeout() sets the maximum milliseconds to wait for serial data when using Serial.readBytesUntil(), Serial.readBytes(), Serial.parseInt() or Serial.parseFloat(). It defaults to 1000 milliseconds.

This means that the readString is always waiting 1 sec to make sure that the sending of the string is finished and has the complete string.
Unfortunately that means it's slow to respond. You could lower the timeout with the setTimeout, but you would still have some delay, or if you set it too low you could potentially get incomplete stings.

The best solution would be to use readStringUntil, so you know you have a complete string when you get a terminator character (like a newline).

Replace

Command = Serial.readString();

with

Command = Serial.readStringUntil('\n');

and make sure you set the Serial monitor so send the newline character.



回答2:

Edit: see important update at the end.

This can be made significantly faster, but first let's have a look at the work that has to be done in every loop iteration with the current code:

  • As @gre_gor already explained, you could be losing some time in readString().
  • for each value, between 15 and 20 bytes have to be sent, read, parsed and converted to int.
  • for each received value (R, G or B), analogWrite() is called 6 times (and analogWrite() isn't really fast). This means that in order to change the two series, analogWrite() is called 36 times (and this is probably where most time is lost). And if no serial data is available, analogWrite() is still called 6 times.
  • and also, Serial.println() is called each time in the example - so it would be best to turn this off.

To speed this up, the RGB values could be sent in a small buffer (assuming you have control over the sending side as well), and read with Serial.readBytesUntil().

If the values for both A and B are sent together, the 6 RGB values can be sent as 6 bytes:

byte rcvBuffer[7];

void loop() {

    if (Serial.available() > 0) {

        // message: RGBRGBx - but see update below
        int numRead = Serial.readBytesUntil(0x78, rcvBuffer, 7); // 0x78 is 'x'

        if (numRead == 7) { // or 6, see below

            analogWrite(AledRedPin,   rcvBuffer[0]);
            analogWrite(AledGreenPin, rcvBuffer[1]);
            analogWrite(AledBluePin,  rcvBuffer[2]);

            analogWrite(BledRedPin,   rcvBuffer[3]);
            analogWrite(BledGreenPin, rcvBuffer[4]);
            analogWrite(BledBluePin,  rcvBuffer[5]);
        }
        // else ignore this read - could be a first unaligned read
    }
}

If only the values for A or B are sent together:

byte rcvBuffer[5];

void loop() {

    // You could probably even remove the Serial.available() check
    if (Serial.available() > 0) {

        // message: TRGBx where T is Type ('A' or 'B')
        int numRead = Serial.readBytesUntil(0x78, rcvBuffer, 5); // 0x78 is 'x'

        if (numRead == 5) {  // or 4, see below

            switch (rcvBuffer[0]) {
                case 'A':
                    analogWrite(AledRedPin,   rcvBuffer[1]);
                    analogWrite(AledGreenPin, rcvBuffer[2]);
                    analogWrite(AledBluePin,  rcvBuffer[3]);
                    break;
                case 'B':
                    analogWrite(BledRedPin,   rcvBuffer[1]);
                    analogWrite(BledGreenPin, rcvBuffer[2]);
                    analogWrite(BledBluePin,  rcvBuffer[3]);
                    break;
                default :
                    // do nothing, or send error message
            }
        }
    }
}

I used 'x' as the stop byte to make it visible, but you could as well use a zero byte.

Now, I'm not really sure if readBytesUntil() also reads the terminating byte into the buffer or skips it, and can't test this right now. But I would think only the RGB values are read into the buffer. In this case you'll have to change those values to the ones I put in the comments.

To save even more time, you could check each value and only call analogWrite() if that value did change since the last call (for each R, G and B).


Update: Obviously we can't just use 'x' or a zero byte as the stop byte, because each of the RGB values could also be an 'x' or zero byte (it's getting late here :). And while ReadBytes() could be used instead, it's better to have a stop byte to keep the buffers aligned. So I would suggest to use 0xff (255) as the stop byte and make sure none of the RGB values can be 0xff.

And just in case there could be other message types in the future, each message could also be prepended with a message code (1 or 2 bytes).