Using switch instead of if

I am playing around with a flex sensor and three LEDs. I have used a three if statements to turn each of the three LEDs depending on the value read from the sensor. Is it possible to do the same thing using switch instead of if so my code isn't as long?

Example of what I have going now.

//if(sensor > 800)
//{
//digitalWrite(ledY, HIGH);
//}
//else
//{
//digitalWrite(ledY, LOW);
//}

You can't use a switch statement for this purpose--it replaces only a specific type of construct:

if (a == b) {}
if (a == c) {}
if (a == d) {}

to

switch(a) {
case a:
  break;
case b:
  break;
case c:
  break;
}

What you can do, however, is condense statements:

bool b = sensor > 800; // booleans store true/false values
digitalWrite(ledY, b);

Although switch won't do it, as mentioned, you can reduce that if to this:

digitalWrite(ledY, sensor > 800);

Thank you all for the input. The reduced version is great. I didn't know it could be done that way. I will stick it in my reference for the future.

wildbill:
Although switch won't do it, as mentioned, you can reduce that if to this:

digitalWrite(ledY, sensor > 800);

This depends on HIGH being 1 and LOW being 0.
While this currently is the case in the Arduino header files,
it presumes a specific under-the-hood implementation that is not necessarily guaranteed
to be portable in the future since the API definition for digitalWrite() uses HIGH and LOW
rather than 0 and 1 or 0 and non-zero.

So while this does work today,
from a future proofing and portability standpoint it is not good practice to use
an API with parameters other than how it is defined.

To be fully portable and compliant to the API definition you would want to use:

digitalWrite(ledY, (sensor > 800) ? HIGH : LOW);

--- bill

Why do you keep saying that? Its really quite unlikely that the Arduino headers would ever redefine HIGH as 255. Despite the reason that you mentioned in your last post, it wouldn't happen for a host of reasons. First, it would break existing code, and second, even if there was an integrated D/A, so digitalwrite dealt with PWM, it would be incredibly inefficient to always use a 100% PWM for a logic high, because you would waste a timer. Not to mention that there aren't enough timers for pwm on every pin. Furthermore, 1 is the universally excepted logic high (think of boolean, true/false) not 255. So you can safely assume that the constants HIGH and LOW wont be redefined until bits are replaced with qubits.

bperrybap:

digitalWrite(ledY, (sensor > 800) ? HIGH : LOW);

Thanks for the interesting line of code. There are some things in it that I'm not familiar with so it gives me something to do some research and learn how they work. Thanks again everyone for the input.

summerman,
That line with the ? is called a ternary.
It is quite simple. It is like an if/else.

expr ? A : B

It looks at the value of expr and if the result is non zero it picks A else it picks B.

The one thing I would like to stress is that when writing your code, always really focus
and try to use an API as it is defined. Not only is it good programming practice,
but It future proofs your code against future changes
to the API and possibly allows it run in other environments.
The earlier example (the one without the ternary) did not use the digitalWrite() API as it is
defined since it did not use the HIGH and LOW constants which is how digitalWrite() is defined.
The earlier example assumed that HIGH is 1 and LOW is 0.
This is because in C if you use an expression the result of that express is then
used.

So in

[code]digitalWrite(ledY, sensor > 800);

The expression is
sensor > 800

If sensor is > 800 the expression evaluates to 1 and then 1 is passed to the digitalWrite() function.
If sensor is less than or equal to 800 the expression is 0 and 0 is passed to the digitalWrite() function.

While you can get away with that since it does work today,
it is not good programming practice to make assumptions about the specific values of defines/parameters within
an API or even try to take advantage of them even if you happen to know what they are.
In some cases there are legitimate reasons to stray from using an API as defined
like perhaps you get significantly better or smaller code. But those tend
to be quite rare and are usually made through a conscious decision.
In this case I would recommend using the ternary operator from my previous example
since it ensures that the digitalWrite() function uses the constants HIGH and LOW
which how the API is defined.

When the ternary is used,

digitalWrite(ledY, (sensor > 800) ? HIGH : LOW);

the expression will now pass HIGH or LOW instead of 0 or 1
since the ternary looks at the expression and if it evaluates to 1 it picks HIGH
and if 0 it picks LOW.

Now at this particular time, HIGH is defined to be 1 and LOW is defined to be 0 so
for the time being you can get away without doing the ternary as was
in the first example.

However, in general, it always best to use an API as it is defined and use the define
values rather than other values even if you happen to know what they are.

At this very moment Atmel is looking to try to help provide new processors for Arduino
that are much faster with many additional capabilities.

It is an ARM vs an AVR so if these new processors are to be supported by Arduino, then
there will have to be some changes to things like core code and even APIs.
So the best way to try and make your code portable across the different platforms is to try
to stick to using the defined APIs as closely as possible in case things need to be
modified under the hood for the new processors.

--- bill
[/code]

bperrybap:

wildbill:
Although switch won't do it, as mentioned, you can reduce that if to this:

digitalWrite(ledY, sensor > 800);

This depends on HIGH being 1 and LOW being 0.
While this currently is the case in the Arduino header files,
it presumes a specific under-the-hood implementation that is not necessarily guaranteed
to be portable in the future since the API definition for digitalWrite() uses HIGH and LOW
rather than 0 and 1 or 0 and non-zero.

Whilst I am inclined to agree, in principle, that you should send the documented values to a function, for something like digitalWrite, it basically has to set the output pin off if it gets zero, and on if it gets something else. What other value can the output pin be set to?

Currently digitalWrite does this:

if (val == LOW) {
		uint8_t oldSREG = SREG;
                cli();
		*out &= ~bit;
		SREG = oldSREG;
	} else {
		uint8_t oldSREG = SREG;
                cli();
		*out |= bit;
		SREG = oldSREG;
	}

It seems to me inconceivable that someone would choose to rewrite it like this:

if (val == LOW) {
		uint8_t oldSREG = SREG;
                cli();
		*out &= ~bit;
		SREG = oldSREG;
	} else if (val == HIGH) {
		uint8_t oldSREG = SREG;
                cli();
		*out |= bit;
		SREG = oldSREG;
	}

So we have the third case (not HIGH and not LOW) doing nothing. It's an extra test for one thing, which would slow things down, and it would break existing code. So the motivation for doing it would seem to be small.

It isn't what the output can be set to - there is also PWM - more on that later,
but what the input to the API function must be to get the desired result.

As far as being required to set the output pin to off if it gets zero, and on for non zero,
there is no such requirement in the API definition.
The API definition for digitalWrite() only promises to set the output level to GND or disable the pullup
when it gets a value of LOW and to VCC or enable the pullup when it gets a value of HIGH.
It does not say you can use 0 and 1 or 0 and non zero to set the pin. It says LOW and HIGH.
The only way you know for sure that you can use 0 for LOW and 1 for HIGH is to look at the definitions
for LOW and HIGH - which at this point do to happen to be 0 and 1.

An API does not make any guarantee about behaviors beyond its specification.
While it is a likely assumption that HIGH could be replaced with 1 or non-zero and LOW could be replaced with 0,
it is just that an assumption. In this case that assumption happens to be correct.

But you further verified my point. The only way you know that using 0 and 1 vs LOW and HIGH is safe,
or the behavior of other values is to actually look at the values of LOW and HIGH and then also
at the actual code inside the digitalWrite() function to see how they are used.
(which was my comment in the other thread that bilbo referred to)

The safest way to use an API is to treat it as a black box that you
cannot see inside of.

Without looking at the code you cannot be sure what happens when you use unspecified values.
In other words if you send down values other than LOW and HIGH (which today are 0 and 1)
like 2 - 255 what happens? Depending on how the code was implemented, that
might turn the pin on it might turn the pin off and it might do nothing.
The current code only looks for LOW (which happens to be 0) and then if it is LOW the pin output goes to GND and any other value
goes to VCC. This allows any non zero value to turn the pin on. Now the code could have just as easily been
written to check for HIGH (which is currently 1) and if not HIGH set the pin to GND.
In the case where HIGH were checked instead of LOW, any value other than 1 would turn the pin off.

By looking at the defines for LOW and HIGH and the current code,
you can tell that it is only 0 that matters. So any non zero value
could be used. 2, 3, 42 to set the output pin to VCC or turn on the pullup.
However the API does not make this guarantee.
The API only offers a behavior for the values LOW and HIGH
to use other values, is taking advantage of an internal implementation.


I believe that when we offer advice and suggestions to less experienced programmers that we
try to steer them in the direction of better programming practices. Making assumptions about the
inner workings of an API or writing code that knowingly takes advantage of the particular inner workings of
an implementation instead of its defined interface, IMHO, is not something to be encouraged
and is a potentially recipe for problems down the road.

In other words I don't want encourage people to make assumptions about the values of defines.
i.e. that LOW is always 0 or HIGH is always 1 that FALSE is always 0 or TRUE is always 1, etc....
which causes them to write code that bypasses the use of the actual defines.


As far as changing the existing AVR 8 bit arduino core code for digitalWrite() as in the second example to explicitly
check for HIGH and LOW, that would be just plain silly.

bilbo refered to a comment I made (in another thread) which proposed a theoretical
example as to why you want to stick to the strict definition of an API.

I'll drag it back in here for completeness.
Consider that Arduino may be migrating to other newer controllers/processors.
(see the press release above for actual evidence that it is really moving in that direction).
Newer controllers may have actual DA converters on them so now you have the issue
of how to create a true analog output vs a digital PWM.
APIs will have to change and be extended to incorporate things like this.
And this is where people that took advantage of the internal implementation have
the potential to get burned.

To demonstrate what can happen when people write code that use and API
outside of its specification, I outlined a theoretical API change and underlying
code change for digitalWrite() of how it could be updated to allow
taking more values than HIGH and LOW to extend its functionality to include the
PWM functionality as well as simple on/off.
From an API perspective, in order to do that all that would be necessary would be to redefine HIGH to be 255.
That way LOW would set the pin to GND, HIGH set the pin be VCC and 0-255 would be PWM values.
In fact this is exactly how analogWrite() works today. A value of LOW sets the pin to
a solid GND and a value of HIGH sets the pin to VCC so LOW and HIGH do not use a PWM channel
but rather set the pin to a solid state. The values in between use a PWM channel if it is available.
If not available, those values less than half the full value get changed to LOW, and those above
the half way point get changed to HIGH.

That was example showing how users of an API that use the API in way
that is not defined can get burned if the API is later enhanced for future functionality.

In the theoretical example above, those that used digitalWrite() with HIGH and LOW would
continue to function as expected with the enhanced digitalWrite() but those that used 0 and 1 or
0 and non zero would get burned because they took advantage of an implementation behavior
that was outside the API definition and not guaranteed to work.
i.e. 1 would be very low duty cycle PWM instead of full on.

--- bill

interesting discussion, so I add my 2 cents,

I prefer the full if ...else ... statement, as that is code that is readable by me and people that maintain the code after me (from whom I don't know the skill level).

if(sensor > 800)
{
  digitalWrite(ledY, HIGH);
}
else
{
  digitalWrite(ledY, LOW);
}

Only when the flash needs to be optimized (for speed or foortprint) I would consider the compact format e.g. in a library.

Anyone tested the difference is speed and size of the three variants mentioned here? Is there?

Back to the original question:

Is it possible to do the same thing using switch instead of if so my code isn't as long?

Yes you can do this with a switch but code would not be smaller.

switch(val > 800)
{
  case true: digitalWrite(ledY, HIGH); break;
  case false: digitalWrite(ledY, LOW); break;
}

or also seen quite usefull sometimes.

val = analogRead(A0);
switch(val/100)
{
  case 0: 
  case 1: 
  case 2:
  case 3: 
  case 4: 
  case 5: digitalWrite(ledY, HIGH); break;
  case 6: 
  case 7: 
  case 8:
  case 9: 
  case 10: digitalWrite(ledY, LOW); break;
}

bperrybap:
Making assumptions about the inner workings of an API or writing code that knowingly takes advantage of the particular inner workings of an implementation instead of its defined interface, IMHO, is not something to be encouraged and is a potentially recipe for problems down the road.

You make some good points, so I agree that this is unwise.

In fact, I cringe when I see people doing direct port manipulation (eg. PORTD = 0b0010000) when it isn't totally required by the application. This sort of stuff is non-portable. It's hard to read. It's hard to debug. And there have been numerous posts about code with that sort of thing in it, which suddenly "doesn't work" when applied to a different chip. Whereas using the proper API calls, which are protected by suitable #ifdef's and #define's, you generally have less problems.

There was another thread recently where someone advised using ++x rather than x++ "because it is faster". Apart from the fact that it, demonstrably, wasn't - I prefer to not use obscure code to save the odd machine cycle.

So back to the original question, your suggested use of:

digitalWrite(ledY, (sensor > 800) ? HIGH : LOW);

... would be the safest. More readable would be to move the "magic" 800 out to a define or constant, eg.

const int MID_POINT = 800;

digitalWrite(ledY, (sensor > MID_POINT) ? HIGH : LOW);

Then if you happen to use 800 in more places than one (at present) they can be condensed to the one place. Also it is clearer what the meaning of 800 is.

bperrybap:
The safest way to use an API is to treat it as a black box that you cannot see inside of.

To be pedantic about using the API as a black box, it should define what types it uses. However on this page for example:

http://arduino.cc/en/Reference/DigitalRead

It just says it returns HIGH or LOW. And this is without defining their type.

Ditto for:

And if you look into the code you see this:

#define HIGH 0x1
#define LOW  0x0

So really HIGH and LOW are untyped.

Now if you argue that the type is a boolean, then you could reasonably say it can only ever hold 0 or 1.

bperrybap:
From an API perspective, in order to do that all that would be necessary would be to redefine HIGH to be 255.

Well, what if they redefine HIGH to be 65535?

The issue now arises, if a type isn't specified, how can we safely do this? ...

byte x = digitalRead (1);  // find if pin 1 is on or off
digitalWrite (2, x);    // set pin 2 to what pin 1 was

That code could fail if we decided that HIGH was (say) anything above 512. Thus "x" can't ever hold a HIGH value.

So really, good programming practice requires that the API be more rigorously documented. Otherwise people have to dive into the code, and in the absence of formal specifications, use "what the code does" as a specification.

bperrybap,

Thanks for the explanation of the ? ternary. I like this condensed code so I added some comments to my code for future reference explaining how the line of code works. I was trying earlier to find out what the "? HIGH : LOW" portion of the code meant but couldn't find anything until you explained it. Where would I find information/references like this in the future?