Как свести аналоговый ввод всего к трем случаям?
Я изучаю возможность использования трехпозиционного переключателя в одном из моих проектов для переключения между различными настройками с помощью всего лишь одного аналогового входного контакта. Для трех позиций я использую землю/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, где это недоступно.
@fertchen, 👍1
Обсуждение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-битных ардуино. Если вместо 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
Замените строку:
байт 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
Вы также можете прочитать значение несколько раз и просуммировать их, а затем принять решение на основе суммы.
Это эквивалентно усреднению значений, но без деления на «n», поскольку вас не интересует фактическое число, а только диапазон, в который оно попадает. Это приведет к улучшению отношения сигнал/шум, поскольку мы предполагаем, что ошибки с высоким значением считывания столь же вероятны, как и ошибки с низким значением, поэтому их сумма стремится к нулю, поскольку сумма реального значения V стремится к (n *В).
Используйте переменную типа int или большую для суммы, в зависимости от того, сколько показаний вы решите снимать каждый раз.
Для преобразования необходимо всего три строки кода.
almSet=0;
if (alrmSwState>300) almSet++; // увеличиваем almSet
if (alrmSwState>600) almSet++;
- Отображение значений с аналогового входа дает неожиданные значения?
- Как сопоставить целые значения и округлить их до десятков/тысяч?
- Как использовать этот 3-контактный ползунковый переключатель?
- Отправка значения с одного Arduino на другой
- Использование аналогового входа для чтения кнопки
- Как работать с аналоговыми контактами в цикле?
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- Распиновка аналога Arduino Pro Micro
Решите ее с помощью аналогового значения, а не с помощью функции карты. Точки наклона находятся на 256 и 768. Ниже 256 — 0, между 256 и 768 — 1, выше 768 — 2., @Jot
Вы выбрали хитрый метод. Вероятно, вам придется использовать как гистерезис, так и усреднение. Усреднение замедлит отклик — каким бы быстрым ни был процессор. Если вы категорически против использования поворотного устройства ввода, большинство из них выберет поворотный энкодер, который является цифровым по своей природе., @st2000
карта выполнит alrmSwState/512, вернет 2 только для 1023, @Juraj