How can one read an integer from serial as fast as possible?

So I am trying to take control of a remote car I have via arduino. So far so good. ESC is arming and servo works just fine. Now I wanna be able to control the car over serial.
To do so I send a command “S50” to set servo to 50 or “M96” to engage the motor in lowest speed. The only problem with my method is that it takes at least 400ms to parse an integer which is no good. Is there a way to do this better?

Only possible solution I can think of is send a character after command who’s decimal value is same as desired integer.

For example if I wanna set motor speed to 96 I would send "M". Character has a decimal value of 96 so then instead of

int sspeed = Serial.parseInt();

I can do

int sspeed = Serial.read();

This kinda answers my question but perhaps there is even a better way?

#include <Servo.h>

Servo motor;
Servo servo;

void setup()
{
  Serial.begin(9600);
  Serial.println("Waiting..."); 
  
  delay(5000);
  
  Serial.println("Arming..."); 
  motor.attach(2);
  servo.attach(10);
    motor.write(0);
    delay(20);
  Serial.println("Armed"); 
}

void loop()
{
  if (Serial.available() > 0) {
    char command = Serial.read();
    Serial.print("Received command \"");
    Serial.print(command);
    Serial.println("\"");

    if (command == 'M')
    {
      int sspeed = Serial.parseInt();
      Serial.print("Setting motor speed to:");
      Serial.println(sspeed);
      motor.write(sspeed);
    }
    else if (command == 'S')
    {
      int sspeed = Serial.parseInt();
      Serial.print("Setting servo to ");
      Serial.print(sspeed);
      Serial.println(" degrees");
      servo.write(sspeed);
    }
  }
}

There is no way that parsing an integer should take 400 milliseconds.

If that is the delay you are getting, you are probably looking for the problem in the wrong place. I have serial transmissions that send thousands of characters a second, very quickly.

It's not clear from your explanation, what your setup is. Is the arduino in the remote controlled car or is it the device which is sending the command ? Is this delay at the sending end or the receiving end ?

if the Arduino is receiving all these commands like "M50" or "S47", then is it in the car ?

You are then trying to send these verbose debugging messages back down the same serial connection ? No point trying to save a byte here and there on the commands in one direction while sending verbose stuff in the other direction.

I use a mega with 4 serial ports. And the debugging verbose stuff runs on one serial port from the Mega to the laptop. And the actual process communication runs from the Mega to the device being controlled, and it is as concise as possible.

Your configuration is obviously quite difficult. But if speed is an issue for you, the first thing to do is to try drastically cutting down on the other stuff using the same communication channel.

The way in which you are reading the serial data is also fundamentally flawed.

Suppose you send M 9 6 to the serial port. And then it reads 9. And runs the motor at speed 9 and not speed 96. As a simple example, that is the problem you are facing.

It is probably a better idea to collect all the characters from the serial input stream, and then try and convert to a number when you have got all of them. You may need a delimited character at the end, something like "M96#" so you know when you are finished. Another byte is not going to kill you, thats not where your problem lies.

And if you must have your verbose feedback, then try this: process the command and apply the result to the servo or the motor, first, and then write the verbose debug feedback afterwards. In other words, the opposite order to what you are currently doing.

Based on first replies I thought that I might be clogging up the serial connection for feedback. So I removed all the feedback and have following

#include <Servo.h>

Servo motor;
Servo servo;

void setup()
{
  Serial.begin(9600);
  Serial.println("Waiting..."); 
  
  delay(5000);
  
  Serial.println("Arming..."); 
  motor.attach(2);
  servo.attach(10);
    motor.write(0);
    delay(20);
  Serial.println("Armed"); 
}

void loop()
{
  if (Serial.available() > 0)
  {
    char command = Serial.read();
    
    switch (command)
    {
      case 'M':
        motor.write(Serial.parseInt());
      break;
      case 'S':
        servo.write(Serial.parseInt());
      break;
      case 'X':
        servo.write(0);
        motor.write(0);
      break;
    }
  }
}

Delay is still there. I actually uploaded a video. You notice there is feedback but even after removing all the feedback the delay is same.
Also please notice how after killing it with X, the motor stops almost instantly but when M99 is entered it takes some time to think.

The problem is still likely to be with the way Serial.parseint() works.

how does this function “know” how many bytes it supposed to be waiting for ?
If it receives a 1 and then a 4, does it go with 14, or does it wait to see if
you are actually trying to send 147 ?

I don’t know , and neither does it. I don’t use this function. I don’t have any idea
how it is implemented. I’d collect all the characters and then try and convert them to an integer.

It may help, to have either a delimiter character, or a definite, fixed number of characters
in the message. Then you will know exactly how many characters you have to wait for.

The other thing to realise, is that you need to take into account the relative speed at which the serial communication and the arduino code are working.

If your loop() function starts at the beginning, and there is one character in the UART buffer, and it reads it, then there may, or may not, be any more characters there. Your code is ASSUMING there will be characters there, and there may not be, and who knows what happens after that ? You could get badly out of step with your letters and your digits.

Most code you will see, tends to look like this

while ( Serial.available() > 0 ) { }

using a while rather than an if.

There are a whole bunch of approaches which might work. For example, you could always send 4 characters, and not do anything at the receiving end until 4 characters are available.

while ( Serial.available() > 3 ) { // process your 4 character message } // otherwise do nothing, loop and keep waiting. Or do something else

Okay. Let's assume that I will be sending two bytes to the arduino. First byte for motor and second for servo. How do I got about actually reading two bytes and only then changing values for motor and servo?

Are you saying now that your numbers will be single-byte numbers ?

Part of the issue before, is that you were sending some numbers 2 bytes and some as 3 bytes.

With serial communications, you need to know when a message starts, and when it ends. That is usually why there is some sort of distinctive character or sequence at the beginning and the end.

Suppose you just send 2 bytes of data. If you ever get out of step, you will be reading the second character as the first character, and then waiting an arbitrary time for the first character of the next message, and interpreting that as the second character of the previous message. See the problem ?

This is especially possible with wireless communications. So you need some kind of logic for incomplete messages and timeout as well.

But you don't want to re-invent the wheel right now. To get to the bottom of your current issue, I would stick with your current approach of M and S messages. I would use characters for the data. I would always send 3 numerical digits so M147 and M047 instead of M47.

I would wait in the loop until 4 digits are available in the serial port buffer, and then read them at once into a 4-char array and then read an integer from the array. This wont take 400 ms.

What are you actually sending these messages from ? another arduino ?

Okay I think I got it now. WIll be back with code and results tomorrow.

Thank you :smiley:

You can use the parseInt() method, if you send data intelligently. Sending M96 causes parseInt() to wait a while to see if you are really going to send M961 or M968 or...

If, on the other hand, you send "M96;" or M96 ", there is no reason for parseInt() to wait. It sees, as soon as a non-digit character arrives, that the numeric value is complete, so it can return without waiting for more data.

You do need to read the last character, though.

Okay so honestly, I have no idea why I tried to pass an number with 0-255 range as an integer. Thus having all the trouble.
But with some feedback I was able to learn something before actually having a problem with it.

As promised here is code that works right now. I have not polished it and made an error checks (ie. if {} are sent but no data), but I will for final product.

#include <Servo.h>

#define DEBUG

Servo motor;
Servo servo;

char data[2];
boolean serial_start = false;
boolean serial_end = false;
int index = 0;

char startchar = '{';
char endchar = '}';

void setup()
{
  Serial.begin(9600);
  Serial.println("Waiting..."); 
  
  delay(2000);
  
  Serial.println("Arming..."); 
  motor.attach(2);
  servo.attach(10);
  motor.write(0);
  delay(2000);
  Serial.println("Armed"); 
}

// Receives {CC} where C is a char
// Only CC is stored in data

void loop()
{
  while(Serial.available() > 0)
  {
    char readChar = Serial.read();
    
    #ifdef DEBUG
    Serial.print("Received: ");
    Serial.println(readChar);
    #endif
    
    if (readChar == startchar)
    {
      serial_start = true;
      index = 0;
      #ifdef DEBUG
        Serial.println("Starting char received");
      #endif
    }
    else if (readChar == endchar)
    {
      serial_end = true;
      break;
      #ifdef DEBUG
        Serial.println("Ending char received");
      #endif
    }
    else
    {
      #ifdef DEBUG
        Serial.println("Data char received");
      #endif
      if (index <= 1)
      {
       data[index] = readChar;
       #ifdef DEBUG
        Serial.print("Data char assigned to index ");
        Serial.println(index);
       #endif
       index++;
      }
      
    }
  }
  
  if (serial_start && serial_end)
  {
    #ifdef DEBUG
      Serial.print("Packet received with data [");
      Serial.print(data[0]);
      Serial.print(",");
      Serial.print(data[1]);
      Serial.println("]");
    #endif
    motor.write(data[0]);
    servo.write(data[1]);
    Serial.print("Setting motor and servo to: ");
    Serial.print((byte)(data[0]));
    Serial.print(" ");
    Serial.println((byte)(data[1]));
    serial_start = false;
    serial_end = false;
  }
}

P.S. Even tho I send a feedback over same serial, there is almost no delay unlike when using parseInt.

So thank you once again and maybe I’ll be posting more here

The traditional reason for favouring sending serial data as text rather than binary numbers, is the problem that some byte values have some kind of special meaning, which could cause undesired behavior from the serial device.

    else if (readChar == endchar)
    {
      serial_end = true;
      break;
      #ifdef DEBUG
        Serial.println("Ending char received");
      #endif
    }

The break is like a return statement. Code after it will never, under any circumstances, be executed.

    #ifdef DEBUG
      Serial.print("Packet received with data [");
      Serial.print(data[0]);
      Serial.print(",");
      Serial.print(data[1]);
      Serial.println("]");
    #endif
    motor.write(data[0]);
    servo.write(data[1]);

What is sending the data? If it is coming from the serial monitor, the data is coming as strings. data[0] is one character. data[1] is one character. How are those characters useful for moving a servo?

As I mentioned earlier code is no polished.

I actually cast data[0] data[1] as bytes

    motor.write((byte)(data[0]));
    servo.write((byte)(data[1]));

[not read all the posts]

...serial as fast as possible?

The first thing I would do to is use a higher baud-rate if possible 115200 is 12x faster than 9600

For those that are kinda interested here is an update. Everything is going according to the plan. I’ll leave the code that works for me as of today. Still not polished and I plan to send one byte for motor speed and steering. Also have to upgrade esc and motor along with setting up dual servo for steering. Plus some CNC milling for custom mounts. But really happy as of now

#include <Servo.h>

//#define DEBUG

Servo motor;
Servo servo;

char data[2];
boolean serial_start = false;
boolean serial_end = false;
int index = 0;

byte startchar = 254;
byte endchar = 255;

void setup()
{
  Serial.begin(9600);
  Serial.println("Waiting..."); 
  
  delay(2000);
  
  Serial.println("Arming..."); 
  motor.attach(2);
  servo.attach(10);
  motor.write(0);
  delay(2000);
  Serial.println("Armed"); 
}

// Receives {CC} where C is a char
// Only CC is stored in data

void loop()
{
  while(Serial.available() > 0)
  {
    byte readChar = Serial.read();
    
    #ifdef DEBUG
    Serial.print("Received: ");
    Serial.println(readChar);
    #endif
    
    if (readChar == startchar)
    {
      serial_start = true;
      index = 0;
      #ifdef DEBUG
        Serial.println("Starting char received");
      #endif
    }
    else if (readChar == endchar)
    {
      serial_end = true;
      #ifdef DEBUG
        Serial.println("Ending char received");
      #endif
      break;
    }
    else
    {
      #ifdef DEBUG
        Serial.println("Data char received");
      #endif
      if (index <= 1)
      {
       data[index] = readChar;
       #ifdef DEBUG
        Serial.print("Data char assigned to index ");
        Serial.println(index);
       #endif
       index++;
      }
    }
  }
  
  if (serial_start && serial_end)
  {
    #ifdef DEBUG
      Serial.print("Packet received with data [");
      Serial.print(data[0]);
      Serial.print(",");
      Serial.print(data[1]);
      Serial.println("]");
    #endif
    motor.write((byte)(data[0]));
    servo.write((byte)(data[1]));
    Serial.print("Setting motor and servo to: ");
    Serial.print((byte)(data[0]));
    Serial.print(" ");
    Serial.println((byte)(data[1]));
    serial_start = false;
    serial_end = false;
  }
}

And here is C# code that sends xbox input to arduino. It’s a mess and I should make it better, but it will happen in time.

        void timer1_Tick(object sender, EventArgs e)
        {
            // Update controller data
            gs.Update();

            float leftStickX = gs.LeftStick.Position.X;
            float leftStickY = gs.LeftStick.Position.Y;
            int leftStickXINT = (int)(leftStickX * 1000);
            int leftStickYINT = (int)(leftStickY * 1000);

            
            if (leftStickYINT < 0)
                leftStickYINT = 0;

            seriesX.Points.Clear();
            seriesY.Points.Clear();
            seriesX.Points.Add(leftStickXINT);
            seriesY.Points.Add(leftStickYINT);
            lblControllerX.Text = leftStickXINT.ToString();
            lblControllerY.Text = leftStickYINT.ToString();

            seriesX2.Points.Clear();
            seriesY2.Points.Clear();
            seriesX2.Points.Add(leftStickX);
            seriesY2.Points.Add(leftStickY);
            lblControllerX2.Text = leftStickX.ToString();
            lblControllerY2.Text = leftStickY.ToString();

            byte motorSpeed = Map(leftStickYINT, 0, 1000, 94, 100 + 1);
            byte steerValue = Map(leftStickXINT, -1000, 1000, 180, 0 );

            lblRCThrottle.Text = motorSpeed.ToString();
            progressBar1.Value = Map(leftStickYINT, 0, 1000, 0, 100 + 1);

            lblRCSteer.Text = steerValue.ToString();

            if (sp.IsOpen && (motorSpeed != oldSpeed || steerValue != oldSteer))
            {
                sp.Write(new byte[] { 254, motorSpeed, steerValue, 255 }, 0, 4);
                oldSpeed = motorSpeed;
                oldSteer = steerValue;
            }
            
        }

LOWER YOUR VOLUME AT THE END!!! I WARNED YOU!!

Enjoy my engrish