Как свести аналоговый ввод всего к трем случаям?

Я изучаю возможность использования трехпозиционного переключателя в одном из моих проектов для переключения между различными настройками с помощью всего лишь одного аналогового входного контакта. Для трех позиций я использую землю/0 В, ~2,5 В (через делитель напряжения 2x10 кОм) и полное напряжение 5 В. Я делаю это еще и потому, что планирую в будущем аналогичным образом использовать 5-позиционный поворотный переключатель.

Из-за допусков и шума показания аналогового вывода составляют не точно 0, 511, 1023, а скорее 0-1, 509-512 и 1021-1023. Чтобы принять это во внимание, я использую функцию map следующим образом:

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

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

}

void loop() {

  alrmSwState = analogRead(alarmSwitchPin);       // Читаем ввод

  byte alrmSet = map(alrmSwState, 0, 1023, 0, 2); // Сокращение ввода до трех случаев

  if (alrmSet != alrmPrevSet) {                   // Только если состояние переключателя изменилось
    switch (alrmSet) {
      case 0:
        Serial.println("Low");
        break;
      case 1:
        Serial.println("Medium");
        break;
      case 2:
        Serial.println("High");           
        break;
    }
    alrmPrevSet = alrmSet;
    delay(200);                                   // В целях тестирования
  }
}

Я хочу, чтобы настройка менялась (или, в данном случае, консоль печаталась) только тогда, когда состояние действительно изменилось. Однако это не работает, поскольку функция map выполняет математические вычисления в целочисленном формате, что не позволяет отобразить верхнюю позицию (в данном случае).

Каковы мои варианты, если я хочу придерживаться метода switch? Мне любопытно, можно ли это сделать без операторов и условий if или, как более общий вопрос, какой лучший вариант — правильно сопоставить 10-битный аналоговый вход всего с 3 регистрами/числами. .

PS: Я думал, что analogReadResolution() может быть вариантом, но я работаю с Arduino UNO, где это недоступно.

, 👍1


4 ответа


Лучший ответ:

4

Самое простое решение вашей проблемы — изменить вызов map() на

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

В приведенном выше вызове сопоставленные интервалы имеют полуоткрытый тип, например [a, b), где под начальными значениями (а именно 0) понимаются включительно, а конечные значения (1024 и 3) являются эксклюзивными.

Хотя это и не ясно из документации, это правильный способ использования функция map(). В противном случае усеченное деление дает вам очень неравномерные интервалы. Сравните:

     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

Результат, который вы получите, очень близок к ответу Джеффа Вахауса.

Что меня раздражает в этом подходе, так это то, что 32-битное целое число деление, которое map() использует внутри себя, является очень дорогостоящей операцией. на небольших 8-битных Arduinos. Если вместо 342 и 683 использовать 256 и 768 в качестве пороговых значений, то вы сможете принять решение, просто взглянув на старший байт аналогового чтения:

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);
}

Обратите внимание, что деление на 256 оптимизировано компилятором более дешевый битовый сдвиг, но это имеет место только в том случае, если alrmSwState имеет беззнаковый целочисленный тип. Вот почему выше он объявлен как uint16_t.

,

3

Замените строку:

байт alrmSet = map(alrmSwState, 0, 1023, 0, 2);

с

байт alrmSet = alrmSwState / ((1023/3) + 1);

И это должно делать то, что вы хотите. Обратите внимание, что в C дробные результаты усекаются (не округляются)

,

0

Вы также можете прочитать значение несколько раз и просуммировать их, а затем принять решение на основе суммы.

Это эквивалентно усреднению значений, но без деления на «n», поскольку вас не интересует фактическое число, а только диапазон, в который оно попадает. Это приведет к улучшению отношения сигнал/шум, поскольку мы предполагаем, что ошибки с высоким значением считывания столь же вероятны, как и ошибки с низким значением, поэтому их сумма стремится к нулю, поскольку сумма реального значения V стремится к (n *В).

Используйте переменную типа int или большую для суммы, в зависимости от того, сколько показаний вы решите снимать каждый раз.

,

2

Для преобразования необходимо всего три строки кода.

almSet=0;
if (alrmSwState>300) almSet++;   // увеличиваем almSet
if (alrmSwState>600) almSet++;
,