Efficient way for reading input commands

Sorry, this might be asked many times, but I need to make this clear. I have seen different examples available and would like to know what is the proper way of waiting for commands in terms of performance?

I have currently this, but is it the most efficient approach?:

char choice;
void setup() 
{
  Serial.begin(9600);  
}
void loop(void)
{ 
  readInputCommands();
}
void readInputCommands()
{
  if(Serial.available())
  {
    choice = Serial.read();
  }
  
  if(choice == 'a')
  {
    digitalWrite(device1, HIGH);
  }
  if(choice == 'b')
  {
    digitalWrite(device1 LOW);
  }
  
  if(choice == 'c')
  {
    digitalWrite(device2, HIGH);
  }
  if(choice == 'd')
  {
    digitalWrite(device2, LOW);
  }
  
  if(choice == 'e')
  {
    digitalWrite(device3, HIGH);
  }
  if(choice == 'f')
  {
    digitalWrite(device3, LOW);
  }
}

I have seen many use else if, is it better in terms of performance or maybe even something else?

if the selection is by a simple type such as an int or char try using a switch statement
in terms of code generation there is probably not much difference but switch may be more readable

1 Like

don't care to much if several ifs (or else Ifs) are more "efficent" than a switch case.
The compiler will do a pretty good job in optimizing your sketch anway.
Use what you can read best - even in some months.

What you can try is to put your if or switch within your if Serial available statement, so all the ifs / or switch case are only processed if you have readed something from serial:

1 Like
struct Commands_devices_T
{
  unsigned char command;
  uint8_t       pin;
  bool          value;
};

Commands_devices_T commands_devices[] =
{
  {'a', device1, true},
  {'b', device1, false},
  {'c', device2, true},
  //Etc etc.
};

void    readInputCommands()
{
  unsigned char choice;
  if(Serial.available())
  {
    choice = Serial.read();
  }
  
  for (uint8_t c = 0; c < sizeof(commands_devices)/sizeof(commands_devices[0]), c ++)
  {
    if (commands_devices[c].command == choice)
    {
      digitalWrite(commands_devices[c].pin, commands_devices[c].value);
    }
  }
}

Something like this. Not faster than your solution or technically better, but somewhat easier to maintain IMO.
Disclaimer: I didn't compile this so there may be very silly bugs, and this is obviously not a complete sketch.

1 Like

Well that code has a simple flaw - it repeatedly handles any command read forever, rather than just once when the command is actually read:

You need to only obey a command when its read. There is no reason for choice to be a global variable (this is part of the problem in fact).

And its normal to use "else if" when selecting alternative paths of execution.

Here's how I'd improve that:

void readInputCommands()
{
  if(Serial.available() > 0)
  {
    char choice = Serial.read();

    if(choice == 'a')  // only interpret command if its newly arrived....
    {
      digitalWrite(device1, HIGH);
    }
    else if(choice == 'b')
    {
      digitalWrite(device1 LOW);
    }
    .... etc etc ...
    else
    {
       // unknown command?  Perhaps flag as an error somehow.
    }
  }
}

Note that in this example switch/case would be much neater than if/else if/else

It would also be cleaner to package the command execution in yet another function as its a different thing from reading the command. Lots of small well-named functions makes code much clearer and easier to evolve.

2 Likes

or with a switch

void readInputCommands() {
  int r = Serial.read();
  if (r != -1)
    switch (r) {
      case 'a': digitalWrite(device1, HIGH); break;
      case 'b': digitalWrite(device1 LOW);   break;
      default: /* unknonw command */;        break;
    }
}

r is not even needed, you could handle -1 as part of the switch case

1 Like

what about commands with values ?

consider

// pcRead - debugging using serial monitor

const char version [] = "PcRead 201114a";

int debug = 0;

// ---------------------------------------------------------
// toggle output bit
int
readString (
    char *s,
    int   maxChar )
{
    int  n = 0;

    Serial.print ("> ");
    do {
        if (Serial.available()) {
            int c    = Serial.read ();

            if ('\n' == c)
                break;

            s [n++] = c;
            if (maxChar == n)
                break;
        }
    } while (true);

    return n;
}

// -----------------------------------------------------------------------------
// process single character commands from the PC
#define MAX_CHAR  10
char s [MAX_CHAR] = {};

int  analogPin = 0;

void
pcRead (void)
{

    static int  val = 0;

    if (Serial.available()) {
        int c = Serial.read ();

        switch (c)  {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            val = c - '0' + (10 * val);
            break;

        case 'A':
            analogPin = val;
            Serial.print   ("analogPin = ");
            Serial.println (val);
            val = 0;
            break;

        case 'D':
            debug ^= 1;
            break;

        case 'I':
            pinMode (val, INPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT");
            val = 0;
            break;

        case 'O':
            pinMode (val, OUTPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" OUTPUT");
            val = 0;
            break;

        case 'P':
            pinMode (val, INPUT_PULLUP);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT_PULLUP");
            val = 0;
            break;


        case 'a':
            Serial.print   ("analogRead: ");
            Serial.println (analogRead (val));
            val = 0;
            break;

        case 'c':
            digitalWrite (val, LOW);
            Serial.print   ("digitalWrite: LOW  ");
            Serial.println (val);
            val = 0;
            break;

        case 'p':
#if !defined(ARDUINO_ARCH_ESP32)
            analogWrite (analogPin, val);
            Serial.print   ("analogWrite: pin ");
            Serial.print   (analogPin);
            Serial.print   (", ");
            Serial.println (val);
            val = 0;
#endif
            break;

        case 'r':
            Serial.print   ("digitalRead: pin ");
            Serial.print   (val);
            Serial.print   (", ");
            Serial.println (digitalRead (val));
            val = 0;
            break;

        case 's':
            digitalWrite (val, HIGH);
            Serial.print   ("digitalWrite: HIGH ");
            Serial.println (val);
            val = 0;
            break;

        case 't':
            Serial.print   ("pinToggle ");
            Serial.println (val);
            digitalWrite (val, ! digitalRead (val));
            val = 0;
            break;

        case 'v':
            Serial.print ("\nversion: ");
            Serial.println (version);
            break;

        case '\n':          // ignore
            break;

        case '"':
            while ('\n' != Serial.read ())     // discard linefeed
                ;

            readString (s, MAX_CHAR-1);
            Serial.println (s);
            break;

        case '?':
            Serial.println ("\npcRead:\n");
            Serial.println ("    [0-9] append to #");
            Serial.println ("    A # - set analog pin #");
            Serial.println ("    D # - set debug to #");
            Serial.println ("    I # - set pin # to INPUT");
            Serial.println ("    O # - set pin # to OUTPUT");
            Serial.println ("    P # - set pin # to INPUT_PULLUP");
            Serial.println ("    a # - analogRead (pin #)");
            Serial.println ("    c # - digitalWrite (pin #, LOW)");
            Serial.println ("    p # -- analogWrite (analogPin, #)");
            Serial.println ("    r # - digitalRead (pin #)");
            Serial.println ("    s   - digitalWrite (pin #, HIGH)");
            Serial.println ("    t   -- toggle pin # output");
            Serial.println ("    v   - print version");
            Serial.println ("    \"   - read string");
            Serial.println ("    ?   - list of commands");
            break;

        default:
            Serial.print ("unknown char ");
            Serial.println (c,HEX);
            break;
        }
    }
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    pcRead ();
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin(115200);

    Serial.println (version);
#if defined(ARDUINO_ARCH_ESP32)
    Serial.println ("esp32");
#endif
}

They don't even really have to check if the buffer is empty and the 'default' case is optional.

void readInputCommands()
{
  switch (Serial.read())
  {
    case 'a': digitalWrite(device1, HIGH); break;
    case 'b': digitalWrite(device1 LOW);   break;
  }
}
1 Like

Yes. If there is no need to catch errors

I was trying to match the code where there was an

else
    {
       // unknown command?  Perhaps flag as an error somehow.
    }

So you need to differentiate between -1 when there was no input (not an error) and if you received something else that was not expected

just accept -1 as "no character received" and than use default for any "error handling" you want.

void readInputCommands()
{
  int c = Serial.read();
  switch (c)
  {
    case -1: break; // nothing received --> OK    
    case 'a': digitalWrite(device1, HIGH); break;
    case 'b': digitalWrite(device1 LOW);   break;
    default: 
      Serial.print(F("unknown command ")); 
      Serial.println(c, HEX); 
  }
}

Hello mrwd
Take a view here to get some ideas.
https://www.norwegiancreations.com/2020/01/simplifying-a-cli-functionality-on-an-arduino-using-the-serialmenu-library/
Have a nice day and enjoy coding in C++.

Yes that was in my comment below the code

The reason I differentiated was two fold: making it a bit clearer to read (in my view) and also this is to make the switch effficient. Most of the time you’ll get -1 and a switch generates a costly branching mechanism. So if you need to save a few microseconds that helps.(but may be the optimizer handles this adjust as a whole, I have not checked the assembly code)

How stupid this code is from 0 to 10?

Logic: if I get command g = case 'g' , I need to turn Pin3 and Pin4 on. Then keep Pin3 for certain amount of time and switch it off. I also need be able to turn it on off, by certain commands e and f.

void readInputCommands1() {
  int r = Serial.read();
  if (r != -1)
  {
    switch (r) 
    {
      case 'a': digitalWrite(Pin1, HIGH); break;
      case 'b': digitalWrite(Pin1, LOW);   break;
      case 'c': digitalWrite(Pin2, HIGH); break;
      case 'd': digitalWrite(Pin2, LOW);   break;
      case 'e': digitalWrite(Pin3, HIGH); break;
      case 'f': digitalWrite(Pin3, LOW);   break;
      case 'g': switchState = HIGH;
                digitalWrite(Pin4, HIGH);
                digitalWrite(Pin3, HIGH);
                currentMillis = millis();
                break;
      case 'h': digitalWrite(Pin4, LOW);   break;
      default: /* unknonw command */;        break;
    }

  if (switchState == HIGH) 
  {
    if (millis() - currentMillis >= period)
    {
      switchState = LOW;
    }

    digitalWrite(Pin3, switchState);
  }

    r = '\0';
  }
}

You might want to look into 'state machines'.

1 Like

as i suggested, you may want to have a generic commands to set, clr or toggle a specific pin, preceding the command with digits indicating the value of the pin

Robin2 handles that as shown here by using a flag called newData, initialised false. No actions are performed on data while that flag is false. It gets set true when data is* read, which enables the action, done under control of an if looking at that flag. Then as soon as the action is complete (in the example the action is simply to show the data, but it could be anything) the flag is set false again, so the action cannot happen again until the flag is set true on the arrival of more data.

*yes I know, it's plural :wink:

this is a bad fix for a bad design.

why not call a function to process the data when a complete message is received?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.