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

Я изучаю возможность использования трехпозиционного переключателя в одном из моих проектов для переключения между различными настройками с помощью всего лишь одного аналогового входного контакта. Для трех позиций я использую землю/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

Обсуждение

Решите ее с помощью аналогового значения, а не с помощью функции карты. Точки наклона находятся на 256 и 768. Ниже 256 — 0, между 256 и 768 — 1, выше 768 — 2., @Jot

Вы выбрали хитрый метод. Вероятно, вам придется использовать как гистерезис, так и усреднение. Усреднение замедлит отклик — каким бы быстрым ни был процессор. Если вы категорически против использования поворотного устройства ввода, большинство из них выберет поворотный энкодер, который является цифровым по своей природе., @st2000

карта выполнит alrmSwState/512, вернет 2 только для 1023, @Juraj


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.

,

Большое спасибо за указание на то, как правильно использовать функцию карты (никогда раньше не слышали о полуоткрытом типе в этом контексте). Также спасибо, что указали мне на более эффективный метод., @fertchen

Я только что попробовал использовать uint: в среднем положении корпус иногда переключается между 1 и 2 из-за шума, например, при прикосновении к изолированному проводу (alrmSwState ~ 509-514). Поэтому условие «if» не будет работать должным образом. В качестве обходного пути я делю на 250, получая в результате пригодные для использования варианты 0, 2 и 4. Это не так эффективно, как вы предполагали. Учитывая, что я использую нечетное количество положений переключателя (то есть одно положение очень близко к центру/точке разрыва ), прав ли я в своем предположении, что у меня нет способа выполнить деление так элегантно, как вы предложили, без добавления значительный объем кода?, @fertchen

@fertchen: переключение между 1 и 2 в среднем положении является нормальным и ожидаемым. Вот почему «случай 1» и «случай 2» приводят к одним и тем же операторам в приведенном выше примере кода. Если у вас более трех случаев, самым простым вариантом может быть использование map(). В противном случае цепочка else if, хотя и менее элегантная, вероятно, более эффективна, чем Map()., @Edgar Bonet


3

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

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

с

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

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

,

это не верно. это даст 3, 2, 1, 0, @Juraj

Верно. Максимальное возвращаемое значение составляет 1023 при делении на 342 = 2,991... что дает результат целочисленного деления 2., @Jeff Wahaus

извините, я забыл, что Calc округляет до ближайшего значения. я проголосовал за, @Juraj

и карта выполнит команду alrmSwState/512, что неверно., @Juraj

Почему 342, а не 343? Мне нравится видеть комментарии в коде, объясняющие все цифры., @Jot

343 тоже подойдет, но 342 дает наиболее равномерный разброс. Число получается по формуле: (1023/3) + 1 или (Макс_возвращаемое_значение / число_состояний) + 1., @Jeff Wahaus

Здесь я согласен с Джотом. Было бы хорошо, если бы ваш код читал byte alrmSet = alrmSwState/((1023/3) + 1);. Тогда не существует магических чисел, значение которых является загадкой. Компилятор оптимизирует деление, так что это не будет неэффективно., @Nick Gammon


0

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

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

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

,

2

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

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