Improving switch case.

I have been working on a switch case for serial char messages, But i ran in to a little problem.
I can not separate "Q" from "Qe" .. without starting 2 cases. Of course i can throw an if statement for the "Q" but i would like to know if it can be done with a switch case.

The serial commands comes from a windows driver and are quite time sensitive. I don't know if this switch case will be faster than else if statements, perhaps you can tell me ?

Here is my code:

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

 

}

void loop() {
   if (Serial.available() > 0) {        // Listen for anything on the serialport
       String cmd = Serial.readStringUntil('#');  // Reads string until the "#"  
     
       // Available commands:  ":U#", ":GD#", ":GR#", ":Sd#", ":Sr#", ":Mn#", ":Ms#", ":Me#", ":Mw#", ":RS#", ":RM#", ":RC#", ":RG#", ":Q#", ":Qe#", ":Qw#", ":Qn#", ":Qs#"
      
   switch (cmd.charAt(1)){  // Reads the 2. char. 
      case 'U':                         // if 2. char==U execute case 'U'
       Serial.println("U#");
       break; 
      
      case 'G':                          //if 2. char==G move on
      case 'S':                          //if 2. char==S move on
      case 'M':                          //if 2. char==M move on
      case 'R':                          //if 2. char==R move on
          
          switch (cmd.charAt(2)){ // And if 3. char==D execute case 'D'
            case 'D':
            Serial.println("GD#");  
          break;
            case'd':                  
            Serial.println("Sd#");   
          break; 
            case 'R':
            Serial.println("GR#");
          break;
            case'r':
            Serial.println("Sr#");
          break; 
            case'n':
            Serial.println("Mn#");
          break;
            case's':
            Serial.println("Ms#");
          break;
            case'e':
            Serial.println("Me#");
          break;
            case'w':
            Serial.println("Mw#");      
          break;
            case'S':
            Serial.println("RS#");
          break;
            case'M':
            Serial.println("RM#");
          break;
            case'C':
            Serial.println("RC#");
          break;
            case'G':
            Serial.println("RG#");     
          break;
          }
   }  

   switch (cmd.charAt(1)){  // Reads the 2. char.
    case'Q':
    Serial.println("Q#");
    break;
   }
  
   switch (cmd.charAt(1)){  // Reads the 2. char.
    case'Q':
      switch (cmd.charAt(2)){  // Reads the 3. char.
        case'e':
        Serial.println("Qe#");
      break;
        case'w':
        Serial.println("Qw#");
      break;
       case'n':
        Serial.println("Qn#");
      break;
       case's':
        Serial.println("Qs#");
      break;
              
        }
   }
          
        }//end serial
   
   }// end loop

V-Drive:
I don't know if this switch case will be faster than else if statements, perhaps you can tell me ?

The generated code is identical (for AVR processors).

:confused: So it doesn't matter if I use Switch case or else if ,it compiles to the same code ??

The last time I checked that was the case.

Can you check again, because i spent a lot of time getting this far, thinking that switch cases was superior compared to else if´s if you had many cases :smiley: :smiley: (Just kidding)

When i make the above code as else if i have to put the most time sensitive lines on top to make sure that the answer arrive before the next command is send. Will that also apply for the switch case.
I guess the answer must be yes then...

My understanding is that when you use else if, its like allowing fall-through execution with switch case by not using break.

Using a single if statement is like a case end with a break.

You may find sometimes "falling-through" certain cases is desirable just like sometimes using "else if" is desirable.

V-Drive:
Can you check again...

1.8.0 ... compare then branch ... exactly what an if-statement would produce...

   switch (cmd.charAt(1)){  // Reads the 2. char.
 6e0:	84 35       	cpi	r24, 0x54	; 84
 6e2:	44 f4       	brge	.+16     	; 0x6f4 <main+0x1c0>
 6e4:	82 35       	cpi	r24, 0x52	; 82
 6e6:	64 f4       	brge	.+24     	; 0x700 <main+0x1cc>
 6e8:	87 34       	cpi	r24, 0x47	; 71
 6ea:	51 f0       	breq	.+20     	; 0x700 <main+0x1cc>
 6ec:	8d 34       	cpi	r24, 0x4D	; 77
 6ee:	09 f0       	breq	.+2      	; 0x6f2 <main+0x1be>
 6f0:	4d c0       	rjmp	.+154    	; 0x78c <main+0x258>
 6f2:	06 c0       	rjmp	.+12     	; 0x700 <main+0x1cc>
 6f4:	85 35       	cpi	r24, 0x55	; 85
 6f6:	09 f0       	breq	.+2      	; 0x6fa <main+0x1c6>
 6f8:	49 c0       	rjmp	.+146    	; 0x78c <main+0x258>
...

V-Drive, as in motorboat drive?

-jim lee

If timing is important, get rid of if/else or switch/case and start using function pointers.

In the below, I wrote functions for your commands.

void cmdG(char *ptr)
{
  Serial.println("G");
  Serial.println(ptr);
}

void cmdM(char *ptr)
{
  Serial.println("M");
  Serial.println(ptr);
}

void cmdQ(char *ptr)
{
  Serial.println("Q");
  Serial.println(ptr);
}

void cmdR(char *ptr)
{
  Serial.println("R");
  Serial.println(ptr);
}

void cmdS(char *ptr)
{
  Serial.println("S");
  Serial.println(ptr);
}

void cmdU(char *ptr)
{
  Serial.println("U");
}

Each function takes an argument; we will use this later to pass the second part of the received data to the function. For the received command "Qs#", the function will print "Q" (hardcoded) followed by the second letter ("s").

Next you can place pointers to those functions in an array. I called the array 'commands' and it contains one entry for each letter 'A' to 'Z'.

void(*commands[])(char *ptr) =
{
  NULL, // A
  NULL, // B
  NULL, // C
  NULL, // D
  NULL, // E
  NULL, // F
  cmdG, // G
  NULL, // H
  NULL, // I
  NULL, // J
  NULL, // K
  NULL, // L
  cmdM, // M
  NULL, // N
  NULL, // O
  NULL, // P
  cmdQ, // Q
  cmdR, // R
  cmdS, // S
  NULL, // T
  cmdU, // U
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
};

For valid commands, it contains the name of the function, all others are NULL indicating that there is no function for it.

You can execute the function by calling commands[letter - 'A'](some pointer).

Next I don't like the use of String (capital S). I modified your code a little to make use of a character array to read the received data. From a performance perspective, it does not make much of a difference as far as I could see. Note that I personally would use an approach as shown in Serial Input Basics - updated and (in this case) check the performance.

void loop()
{
  unsigned long startTime;
  unsigned long endTime;
  if (Serial.available() > 0)          // Listen for anything on the serialport
  {
    // command buffer; needs to be big enough to hold the full message plus termination nul character
    char command[5];
    // just to make sure, clear the buffer before reading
    memset(command, 0, sizeof(command));
    // get the data
    Serial.readBytesUntil('#', command, 4);
 
    ...
    ...
  }
}

And next you can fill in the dots above to execute the associated function.

    startTime = micros();
    // get index into function pointer array
    int cmd = command[1] - 'A';
    // check if we have a valid index (0.. 25)
    if (cmd < 0 || cmd >= 'Z')
    {
      Serial.println("OOPS");
      return;
    }
    // check if we have a function for this command
    if (commands[cmd] != NULL)
    {
      // execute the command and pass the remainder of the received data as the argument
      commands[cmd](&command[2]);
    }
    else
    {
      Serial.println("Unknown command");
    }
    Serial.print(micros() - startTime); Serial.println(" processing time");
  }

This code executes about 30% faster than your code :wink: Added benefit is that the loop stays reasonably nice and tidy (no nested switch/case and no long switch statements)

Note:
I did not fix possible bugs; e.g. what happens if, for some reason, only two bytes are available (specially a problem with the "Q" command that has optional arguments) or if the received data did not start with a ':' (a lesser issue in this case). You will need to validate the received messages.

Optimising the speed of processing a single character of serial input is absurd. The processor can do thousands of operations in the time it takes a single character to be transmitted. It can never become a bottleneck in the system unless you somehow launch a thousand floating-point calculations for each character.

Most attempts to second-guess the compiler will be slower. That compiler took many, many years to get to its current level of sophistication. For the inner loop of your program that needs to control a motor or some other device on a very tight schedule, then it may sometimes be worthwhile re-writing a small piece of your code in assembler. But there's no way you can beat the compiler for general-purpose calculations and branches that will be faster in every case.

By small, I mean about 10 lines of C code. If you are writing more assembler than that in any Arduino project then you're a hardcore assembler programmer and your code will be slower or bulkier than the compiler.

So, if you don't to anything totally stupid (like looping a million times for one character input) then this is simply not a question that needs to be answered.

You are right. My problems has nothing to do with speed, so i went back to else if.
My problem was "noise" on the serial and I mistook it for slow execution.
There for the switch case, as i presumed it would execute faster.

The real problem was the use of println and a terminating marker '#' at the same time.

So changing all Serial.println to Serial.print solved everything.