Which code is better for 3 leds ON OFF via Serial

I’m sending to the Serial those 3 options:

A 0

A 1

B 0

B 1

C 0

C 1

I have the following code:

const int ledPin = 13;
const int ledPin2 = 12;
const int ledPin3 = 11;

byte incomingData = 0;
int state1 = 0;
int state2 = 0;
int state3 = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  Serial.setTimeout(50);
  digitalWrite(ledPin, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    incomingData = Serial.read();
    if (incomingData == 'A') {
      state1 = Serial.parseInt();
      digitalWrite(ledPin, state1);
    }
    if (incomingData == 'B') {
      state2 = Serial.parseInt();
      digitalWrite(ledPin2, state2);
    }
    if (incomingData == 'C') {
      state3 = Serial.parseInt();
      digitalWrite(ledPin3, state3);
    }
  }
}


Another code:

const int ledPin = 13;
const int ledPin2 = 12;
const int ledPin3 = 11;

byte incomingData = 0;
int state = 0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  Serial.setTimeout(50);
  digitalWrite(ledPin, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    incomingData = Serial.read();
    if (incomingData == 'A') {
      state = Serial.parseInt();
      digitalWrite(ledPin, state);
    }
    else if (incomingData == 'B') {
      state = Serial.parseInt();
      digitalWrite(ledPin2, state);
    }
    else if (incomingData == 'C') {
      state = Serial.parseInt();
      digitalWrite(ledPin3, state);
    }
  }
}


Which code is better to use? will both works?

You need to define what you mean when you say better. Faster, less memory, more resilient, easier to maintain, easier to extend, something else ?

How about this ?
Is it better than either of the them ?

const int ledPin = 13;
const int ledPin2 = 12;
const int ledPin3 = 11;

char incomingData = 0;

void setup()
{
    // put your setup code here, to run once:
    Serial.begin(115200);
    pinMode(ledPin, OUTPUT);
    Serial.setTimeout(50);
    digitalWrite(ledPin, LOW);
}

void loop()
{
    if (Serial.available() > 0)
    {
        incomingData = Serial.read();
        if (incomingData == 'A')
        {
            digitalWrite(ledPin, Serial.parseInt());
        }
        else if (incomingData == 'B')
        {
            digitalWrite(ledPin2, Serial.parseInt());
        }
        else if (incomingData == 'C')
        {
            digitalWrite(ledPin3, Serial.parseInt());
        }
    }
}

What happens if you send only a singe character ?
What have you got Line ending set to in the Serial monitor and should the code take that into account ?

Incidentally, none of teh sketches, including mine, set the pinMode() of the ledPins properly

This looks great. less variable.

My two codes will work as well right?

So I guess it is matter of better reading?

How about this (untested !) if you want fewer variables and less code

char incomingData = 0;
const byte ledPins[] = { 13, 12, 11 };
const byte ledCount = sizeof(ledPins);

void setup()
{
    Serial.begin(115200);
    Serial.setTimeout(50);

    for (int p = 0; p < ledCount; p++)
    {
        pinMode(ledPins[p], OUTPUT);
        digitalWrite(ledPins[p], LOW);
    }
}

void loop()
{
    if (Serial.available() > 0)
    {
        incomingData = Serial.read();
        if (incomingData >= 'A' && incomingData <= 'C')
        {
            digitalWrite(ledPins[incomingData - 'A'], Serial.parseInt());
        }
    }
}

It is still full of holes and really, really needs more code to parse the input before acting on it

I would say that using Serial.parseInt() is risky businesss because it can timeout and return 0 and you don't know if you actually read 0 or not.

I would do something fully asynchronous as a state machine, like this

enum ReadState { WAIT_FOR_LINE, WAIT_FOR_STATE };

const char minLine = 'A';
const char maxLine = 'C';

bool readCommand(byte &line, byte &state) {
  static ReadState currentState = WAIT_FOR_LINE;
  static byte tmpLine = 0;

  int c = Serial.read();
  if (c == -1) return false;

  switch (currentState) {
    case WAIT_FOR_LINE:
      if (c >= minLine && c <= maxLine) {
        tmpLine = c - minLine; // extract the line number
        currentState = WAIT_FOR_STATE;
      }
      break;

    case WAIT_FOR_STATE:
      if (c == '0' || c == '1') { // valid state
        state = c - '0';
        line = tmpLine;
        currentState = WAIT_FOR_LINE;
        return true;
      }
      else if (c >= minLine && c <= maxLine) { // handle cases like A  B 1 which should still find B 1
        tmpLine = c - minLine;
      }
      else if (! isspace(c))  { // skip spaces
        currentState = WAIT_FOR_LINE;
      }
      break;
  }

  return false;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Ready");
}

void loop() {
  byte line, state;
  if (readCommand(line, state)) {
    Serial.print("led on line "); Serial.print(line);
    Serial.print(" set to "); Serial.println(state ? "High" : "Low");
  }
}

The function isspace() returns true for any whitespace character, which includes space ' ', tab '\t', carriage return '\r', and line feed '\n'. So both CR and LF are treated as whitespace by isspace() which means the code should work if you type A B or C and return in the Serial monitor (whatever its config) and then type 0 or 1 and return. if should work also if you type in one go A 0 B 1 C 1 or A0B1C1 etc.. basically spaces are ignored.

PS: @hk_jh - you might benefit from studying how to handle the serial line - see Serial Input Basics and read about state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

1 Like

is this code will work as well?

const int ledPin = 2;
const int ledPin2 = 3;
const int ledPin3 = 4;

byte incomingdata = 0;
int state = 0;


void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
}

void loop() {

  if (Serial.available() > 0) {
    incomingdata = Serial.read();
    if (incomingdata == 'A') {
      state = Serial.parseInt();
      digitalWrite(ledPin, state);
    }
    if (incomingdata == 'B') {
      state = Serial.parseInt();
      digitalWrite(ledPin2, state);
    }
    if (incomingdata == 'C') {
      state = Serial.parseInt();
      digitalWrite(ledPin3, state);
    }
  }
}

using inly if instead of else if

you still have the timeout issue if you don't enter the data "quickly" and you don't check that you are reading a meaning full state.

also digitalWrite expects HIGH or LOW, so you could honor the spec and write

  digitalWrite(ledPinxx, state == 0 ? LOW : HIGH);

but if I will define it like this:

const int ledPin = 2;
const int ledPin2 = 3;
const int ledPin3 = 4;

byte incomingdata = 0;
int state = 0;


void setup() {
  Serial.begin(115200);
  Serial.setTimeout(50);
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
}

void loop() {

  if (Serial.available() > 0) {
    incomingdata = Serial.read();
    if (incomingdata == 'A') {
      state = Serial.parseInt();
      digitalWrite(ledPin, state);
    }
    if (incomingdata == 'B') {
      state = Serial.parseInt();
      digitalWrite(ledPin2, state);
    }
    if (incomingdata == 'C') {
      state = Serial.parseInt();
      digitalWrite(ledPin3, state);
    }
  }
}

Personally, I prefer @J-M-L ‘s approach, it’s a lot more scalable, and uses better strategies for expansion and input exceptions.

Ideally, you might develop this to offer an input buffer that you can parse and ‘escape’ from, as well as extra parameters - flash, dual, alternating etc.

As long as you use state = Serial.parseInt(); you introduce a timeout and some weakness. if the state is not received within 1 second after the pin identifier, parseInt() times out and returns 0. At that point you can't tell if that 0 was because the end user actually sent the command LOW or just did not send anything...

So that's a weakness. You might be fine with it, just know it's there.

another, more generic approach

value precedes cmd character and multiple value/cmds can be concatenated e.g. "1B0C"

const byte PinLed [] = { 13, 12, 11, 10 };
const int  Nled      = sizeof (PinLed);

enum { LedOff = HIGH, LedOn = LOW };

// -----------------------------------------------------------------------------
int val;

void loop () {
    if (Serial.available()) {
        char c = Serial.read ();

        if (isdigit (c))
            val = 10*val + c-'0';

        else if (isprint (c)) {
            int idx = c - 'A';
            if (Nled > idx)
                digitalWrite (PinLed [idx], val);
            val = 0;
        }
    }
}

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

    for (int n = 0; n < Nled; n++)  {
        pinMode      (PinLed [n], OUTPUT);
        digitalWrite (PinLed [n], LedOff);
    }
}

Whether it is "better" or not depends on what @hk_jh means by that and they haven't said

It is often the case when dealing with user text input that the code to make sure that it is formatted correctly and will not cause problems is larger and more complicated than the code to deal with properly formed input. When prompted to enter a number the code needs to deal with anything entered, including non numeric input and any range of numbers including negative and floating point numbers

We have, of course, wandered away from the original question which was which of two imperfect sketches was better, but that is no bad thing

the difference between your 2 versions is multi ifs vs if/elseif.

in the multiple ifs version, each case will be tested while in the if/elseif version only the cases up to the one that is true will be tested.

another version could us a switch statement

void loop() {
    if (Serial.available() > 0) {
        incomingData = Serial.read();
        switch (incomingData)  {
        case  'A':
            state1 = Serial.parseInt();
            digitalWrite(ledPin, state1);
            break;

        case  'B':
            state2 = Serial.parseInt();
            digitalWrite(ledPin2, state2);
            break;

        case  'C':
            state3 = Serial.parseInt();
            digitalWrite(ledPin3, state3);
            break;
    }
}

determining an index from the cmd value, e,g, idx = c - 'A' avoid multiple tests as well as extra logic for each possible cmd

Or, for SAG, define your state within your command. Use ‘A’ to set the first output to a 1, use ‘a’ to set the same output to 0. No parsing, simple, straightforward.

loop: read a character, set/clear an output

2 Likes

(Sorry, had a requirements discussion some time ago)

With respect to the word “better”, how many of the proposed examples are tested to handle input that is “out of spec” as defined in the post #1 ?
Some out of spec inputs { A2 a0 a1 A10 A15 AA AB AB1 BD1 A0D1 }, and note there is a space between A and 0 etc. It is not clear from the specs if this space is optional or not.

Yes - my point was basically that they are equally « bad » against my standard because they can fail in some unexpected way.

1 Like

so if I type
AB 1AC 2return
what should be recognized?

equivalent to "0A0B1A0C"

  • "A" A off
  • "B" B off
  • ' ' not ignored (isupper() fix that issue)
  • "1A" A on
  • "C" C off (corrected)

the trailing 2 will be part of the value for the next letter command
'return' is neither a digit nor printaable, therefore ignored
i believe any value other than zero is treated as HIGH by digitalWrite()

an 'a' or 'E' would be translated to a idx (= c - 'A') > Nled and ignored

with

shouldn't the code ignore A, recognize B 1 ignore A and recognize C 2 as per OP's initial requirements (and wonder what to do with 2 - my take is that is should be ignored and thus only B 1 should occur).

1 Like

not in the code i posted. (default value of the argument is zero)

did the OP sepcify requirements simply describe what is being done.

preceding the cmd with the value simplifies the code by avoiding the need to recognize the command and then wait for the argument which may be multiple characters. multiple commands can be concatenated on the same line or straddle lines with the code i posted.

for handling a more conventional approach: cmd argument -- i believe it's better to capture an entire line using Serial.readBytesUntil() and then extract a cmd string and argument using scanf()

const byte PinLed [] = { 13, 12, 11, 10 };
const int  Nled      = sizeof (PinLed);

enum { LedOff = HIGH, LedOn = LOW };
char s [90];

// -----------------------------------------------------------------------------
int val;

void loop () {
    if (Serial.available()) {
        char buf [90];
        int n = Serial.readBytesUntil ('\n', buf, sizeof(buf)-1);
        buf [n] = '\0';

        char cmd [20];
        int  arg [5];
        sscanf (buf, "%s %d %d %d\n", cmd, &arg [0], &arg [1], &arg[2]);
        sprintf (s, " %6s cmd\n %6d arg0\n %6d arg1\n %6d arg2",
                        cmd, arg [0], arg [1], arg [2]);
        Serial.println (s);

        if (isupper (*cmd))  {
            int idx = *cmd - 'A';
            if (idx < Nled)
              digitalWrite (PinLed [idx], arg [0]);
            sprintf(s, "  cmd %s %c, idx %d, arg %d", cmd, *cmd, idx, arg [0]);
            Serial.println (s);
        }
    }
}

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

    for (int n = 0; n < Nled; n++)  {
        pinMode      (PinLed [n], OUTPUT);
        digitalWrite (PinLed [n], LedOff);
    }
}