23.07.2010

Уроки Wiring (5)

Урок 5. Нажми на кнопку!

(продолжение, начало см. Урок 4, Немного о синтаксисе Wiring)

... получишь результат!" - пела когда-то группа "Технология". На Ардуино, к сожалению, всего одна кнопка: бесспорно важная, но малоинтересная.  Нажимая на нее, мы просто перезапускаем скетч с самого начала.

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



Внутренняя логика МК Arduino имеет имеет тип КМОП, то есть состоит из большого количества полевых транзисторов. В отличие от своего предшественника - биполярного транзистора - полевой управляется напряжением.

Эй, не засыпайте! Это же был грандиозный прорыв, позволивший создать экономичные чипы! В отличие от древней ТТЛ- и ТТЛШ-логики на биполярных транзисторах, КМОП не требовал протекания больших токов, достаточно было создать разность потенциалов. И, увы, это же было и слабой его стороной - быстродействие напрямую зависело от напряжения. Но мы сейчас не будем в это углубляться: самое главное для нас понять одну простую истину:

нельзя оставлять активный вход КМОП микросхемы неподключенным

Почему? Если вход никуда не подключен, это делает потенциал на нем неопределенным.

Следовательно, вот такая схема неправильна:


(забегая вперед замечу, что она становится правильной, если подключается внутренний нагрузочный резистор)

Когда вы замыкаете кнопку, то соединяете вход ATmega с землей, и на нем устанавливается "твердый" логический ноль. Но вот  пока она не нажата, этот вход микроконтроллера висит "в воздухе" (или, иначе говоря "плавает"), в итоге оператор digitalRead может в зависимости от настроения условий окружающей среды вернуть либо HIGH, либо LOW. Что же делать? Может быть, для определенности стоит подключить ее к линии питания?


Увы, но так тоже нельзя! В этом случае при разомкнутом контакте мы своего добьемся - на входе будет логическая единица. Но стоит нам нажать на кнопку, как мы получим соединение VCC с GND и струйку синего едкого дымка. Это - короткое замыкание или КЗ. Классическим выходом из ситуации оказывается "подтягивающий" или pullup резистор, который оказывается между Vcc и GND во время замыкания кнопки:


(кстати, линия сброса Arduino выполнена именно по такой схеме).

Из предыдущих уроков вы уже знаете, что последовательность команд:

pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);

...приведет к появлению потенциала +5В на ножке с номером 'pin'. Если же пин работает в режиме ввода, то эффект от такой последовательности будет совсем другой. Операторы:

pinMode(pin, INPUT);
digitalWrite(pin, HIGH);

...подключат к пину с номером 'pin' встроенный в микроконтроллер ATmega (читаем -  невидимый для нас) нагрузочный резистор номиналом около 20 кОм:


С точки зрения всех здравых смыслов, запись в пин возможна только в режиме выхода. Вышеописанное исключение сделано для наглядности - конструкция должна как бы за себя говорить: "подтянуть вход к HIGH".

Чем удобны встроенные нагрузочные резисторы? В первую очередь - не надо припаивать лишний элемент на плату. И места под него тоже не надо резервировать. Да если потребуется схему изменить - отпаивать тоже не придется ;) 

Теперь перейдем к программной части. 

С точки зрения программирования, нет ничего проще, чем прочитать состояние входа:

int val = digitalRead(pin);
if (val == LOW) {
 // кнопка замкнута
} else {
 // кнопка разомкнута
}

Вставьте вместо комментариев необходимые действия - и, готово! Точнее - почти готово...

У всех механических переключателей существует проблема дребезга. Наверное, и вам случалось, нажимая один раз на кнопку какого-либо интересного устройства, получать в ответ 3-4 срабатывания, которые затем, нехорошо ругаясь, приходилось удалять (и так каждый раз). Это происходит благодаря тому, что контакты отрываются или смыкаются не сразу, а в течение какого-то промежутка времени, хоть и незаметного для человека, однако ощутимого для микроконтроллера, который выполняет 16 миллионов операций в секунду.

Существует ряд способов борьбы с дребезгом - например, поставить триггер Шмидта. Но к чему усложнять себе жизнь, если можно ограничиться программным решением? Итак, еще одно простое правило:

не надо читать состояние кнопки сразу после ее срабатывания

Разберем приемы борьбы с дребезгом на скетче, который зажигает светодиод L при нажатии кнопки, подключенной к цифровому входу номер 6:

int L = 13;
int B = 6;
int val;
void setup() {
  pinMode(L,OUTPUT);
  pinMode(B,INPUT);
  digitalWrite(B,HIGH);
}
void loop {
  val = digitalRead(B);
  digitalWrite(L,val);
  delay(100);
}


(после старта скетча все пины работают в режиме входа, оператор  pinMode(B,INPUT) нужен просто для наглядности).

В основном цикле loop мы делаем паузу в 1/10 секунды после каждого чтения кнопки (обычно ее хватает). Правда, скетч при этом останавливается целиком, что не очень хорошо в том случае, когда надо оперативно реагировать на какие-то события. Вот еще один вариант:

int L = 13;
int B = 6;
int val;
void setup() {
  pinMode(L,OUTPUT);
  pinMode(B,INPUT);
  digitalWrite(B,HIGH);
}
void loop {
  if ( (millis() % 100) == 0) {
    val = digitalRead(B);
    digitalWrite(L,val);
  } }

Функция millis() возвращает число миллисекунд, прошедших с момента запуска Arduino. Если кто-то программировал для архитектуры IBM PC, наверняка знаком с понятием "тики" - вот это его аналог. Arduino-тики считаются совершенно незаметно для программиста - по прерыванию, с использованием запрограммированного специальным способом таймера МК ATmega. Как всегда, кропотливая работа создателей скрыта от глаз, и нам остается только воспользоваться плодами их трудов - главное, не запрещать прерывания ;) 

Операция A % B возвращает остаток от деления числа A на B, в данном случае на 100. А поскольку условие сравнивает его с нулем, то блок операторов в фигурных скобках исполнится только если число миллисекунд будет кратно 100: 100, 200, 300 ...

Конечно, в сложных скетчах может потребоваться более изощренный метод устранения дребезга - попробуйте написать самостоятельно процедуру, которая будет предотвращать считывание состояния кнопки после изменения ее состояния. Для простоты разрешается использовать все тот же millis().

Продолжение следует - Урок 6. Что за PWM?

2 комментария:

  1. Привет! А какая именно команда подключает внутренний резистор?
    И еще предлагаю упомянуть про "универсальные кнопки"- которые при помощи обычной кнопки и библиотеки обрабатывают события: нажатие, отпускание, клик, удержание и не имеют дребезга!
    http://download.bleq.nl/DebounceButton_0.3.zip
    внутри все описано!

    ОтветитьУдалить
  2. Спасибо большое за статьи. Это пока лучший цикл статей, про подтягивающие резисторы - 1000 раз натыкался, но зачем и для чего только сейчас понял.
    Пишите еще, подписался по rss, с удовольствием почитаю.

    ОтветитьУдалить