26 июня 2012 г.

Вешаем много кнопок на один вход Arduino

Сегодня мы рассмотрим читерский способ навешивания дофига кнопок на всего лишь один вход Arduino. Согласитесь, не классно занимать восемью кнопками восемь цифровых пинов, как мы это делали в прошлый раз. Вместо этого мы повесим их на один аналоговый вход. Я покажу фокус на примере четырех кнопок, но стоит понимать, что, теоретически, можно спокойно навесить и сотню.



Веселые делители

АЦП Arduino измеряет напряжение на входах A0-A5 (обычно от 0 до 5 вольт) и преобразует его в чиселки от 0 до 1023. 

Прежде всего подтянем аналоговый вход A0 на +5В через резистор с сопротивлением x. АЦП будет все время считывать с него 1023, что является максимальным значением. 

Подключим к этому же входу кнопку, которая будет тупо соединять вход с землей. При нажатии кнопки АЦП будет считывать примерно 0. На самом деле, здесь определяющую роль играет сопротивление кнопки и проводов, но мы это не учитываем.

С остальными кнопками поступим хитро. Один конец подключим к земле, а второй - через резистор на вход. Если сопротивления резисторов будут различны, то при нажатии кнопки АЦП будет считывать разные значения.



Легко заметить, что при нажатии кнопки с резистором образуется делитель напряжения, и АЦП измеряет падение напряжение на нижнем резисторе. 


В случае, если сопротивление в ветви с кнопкой равно x Ом, так же, как и в питающей ветви, что АЦП измерит U*x/(x+x) = U/2. Если нижнее сопротивление будет равно 2x, то измерится U*2x/(x+2x) = 2/3*U. И так далее. 

Мы сможем легко определить, какая именно кнопка нажата, по характерному измеренному значению напряжения.

Хардверная реализация 

У меня под рукой, как всегда, только резисторы по 220 Ом, поэтому я буду соединять из последовательно, чтобы получить бОльшее сопротивление. Это немного некрасиво, зато работает. Итак, схема:


Здесь желтыми проводками все подключено ко входу АЦП, красный и черный, традиционно, - питание и земля. 

Софтверная реализация

Итак, при нажатии первой кнопки АЦП считывает значение между 60 и 75, второй - 513-514, третьей - 683-684, четвертой - 768 -769.

Разобьем всю шкалу от 0 до 1023 на 5 промежутков так, чтобы каждое считываемое значение попало только в один промежуток:

static int key_values[4]={100,600,700,800};
Будем сравнивать считываемое значение с границами промежутков, начиная с левой. Для этого заведем функцию, которая будет сразу возвращать номер нажатой кнопки:


int get_key(int value){
  for (int i=0; i<4; i++){
    if (value < key_values[i]) return i+1;
  }
}
Обратите внимание, что кнопки мы пронумерновали по-человечески, начиная с первой. Именно по этому мы возвращаем i+1.



Весь код:

//определяем переменные для хранения измеряего значения
int value, old_value;
//храним референсные значения
static int key_values[4]={100,600,700,800};

void setup(){
  //инициализируем последовательное соединение
  Serial.begin(9600);
}

void loop(){
  //считываем значение со входа
  value = analogRead(0);
  Serial.println(value);
  //если значение отличается от предыдущего больше, 
  //чем на 50, и оно меньше 1000
  if (abs(value-old_value) > 50 && value < 1000){ 
    //выводим текст
    Serial.print("key ");
    //выводим номер нажатой кнопки
    Serial.print(get_key(value));
    //выводим еще текст
    Serial.println(" pressed");
  }
  //запоминаем значение
  old_value = value;
  //пауза
  delay(100);  
}

//функция для определения номера нажатой кнопки
int get_key(int value){
  //пробегаем по массиву референсных значений
  for (int i=0; i<4; i++){
    //если измеренное значение попало в промежуток между 
    //референсными значениями, выводим номер промежутка
    if (value < key_values[i]) return i+1;
  }
}
Здесь как раз видна разница между функциями Serial.print() и Serial.println(): первая после вывода не делает перевод строки, а вторая делает.

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

Видео