How to boil down analog input to just three cases? I am investigating the use of a 3-position-switch for one of my projects to switch between different settings using just one analog input pin. For the three positions I am using ground/0V, ~2.5V (via 2x10k voltage divider) and full 5V. I am doing this also because I am planning to use a 5-position rotary switch in a similar way in the future.

Due to tolerances and noise, the readings on the analog pin are not precisely 0, 511, 1023, but rather 0-1, 509-512 and 1021-1023. To take this into account, I am using the map function like this:

const byte alarmSwitchPin = A0;
int alrmSwState;
byte alrmSet;
byte alrmPrevSet;

void setup() {
  Serial.begin(9600);
  pinMode(alarmSwitchPin, INPUT);

}

void loop() {

  alrmSwState = analogRead(alarmSwitchPin);       // Read input

  byte alrmSet = map(alrmSwState, 0, 1023, 0, 2); // Boil input down to three cases

  if (alrmSet != alrmPrevSet) {                   // Only if state of switch has changed
    switch (alrmSet) {
      case 0:
        Serial.println("Low");
        break;
      case 1:
        Serial.println("Medium");
        break;
      case 2:
        Serial.println("High");           
        break;
    }
    alrmPrevSet = alrmSet;
    delay(200);                                   // For testing purposes
  }
}


I only want the setting to change (or, in this case, the console to print) when the state has actually changed. This does not work, though, because the map function is doing its math in integer, which fails in mapping the High position (in this case).

What are my options if I want to stick with the switch method? I am curious if it is possible to get this done without if statements and conditions or, as a more general question, what the best option is to properly map the 10 bit analog input to just 3 cases/numbers.

PS: I thought analogReadResolution() might be an option but I am working with Arduino UNO where this is unavailable.
The simplest fix to your problem is to change the map() call to



byte alrmSet = map(alrmSwState, 0, 1024, 0, 3);


In the call above, the mapped intervals are of the semi-open type, e.g.
[a,&nbsp;b), where the start values (namely 0) are understood as being
inclusive and the end values (1024 and 3) are exclusive.

Although not clear from the documentation, this is the proper way to use
the map() function. Otherwise, the truncated division gives you very
uneven intervals. Compare:

     x       map(x, 0, 1023, 0, 2)
----------------------------------
   0 –  511     0
 512 – 1022     1
1023            2

     x       map(x, 0, 1024, 0, 3)
----------------------------------
   0 –  341     0
 342 –  682     1
 683 – 1023     2


The result you get is very close to Jeff Wahaus’ answer.

What I find annoying about this approach is that a 32-bit integer
division, which map() uses internally, is a very expensive operation
on the small 8-bit Arduinos. If instead of 342 and 683, you use 256 and
768 as thresholds, then you can make the decision by just looking at the
high byte of the analog reading:

uint16_t alrmSwState = analogRead(alarmSwitchPin);

alrmSet = alrmSwState / 256;

if (alrmSet != alrmPrevSet) {
    switch (alrmSet) {
        case 0:
            Serial.println("Low");
            break;
        case 1:
        case 2:
            Serial.println("Medium");
            break;
        case 3:
            Serial.println("High");           
            break;
    }
    alrmPrevSet = alrmSet;
    delay(200);
}


Note that the division by 256 is optimized by the compiler into a much
cheaper bit shift, but this is the case only if alrmSwState is of an
unsigned integer type. That's why it is declared above as uint16_t.