Подключение кнопки к микроконтроллеру

Кнопка и микроконтроллер
Кнопку к микроконтроллеру можно подключить двумя основными способами, с подтяжкой линии порта к высокому логическому уровню или низкому через резистор, как показано на картинке ниже.
Я обычно использую первый вариант подключения, можно конечно использовать внутренние подтягивающие резисторы на входах PORTB, но мне еще не доводилось применять такой способ.
Подключение кнопки к МК
В простейшем варианте микроконтроллер все время опрашивает одну или несколько кнопок, при нажатии которых выполняет соответствующую подпрограмму и снова возвращается на опрос кнопок. Подпрограмма обычно выполняется очень быстро, естественно за это время можно не успеть отжать кнопку, вследствие чего подпрограмма выполнится многократно. Чтобы этого не происходило после обнаружения нажатой кнопки необходимо добавить проверку отжатого состояния, но и в этом случае из-за явления дребезга контактов выполнение подпрограммы может начаться до отжатия кнопки. Поэтому после обнаружения нажатой кнопки необходимо добавить временную задержку в 5-20 мс. Проверка отжатого состояния будет многократно выполняться через заданный интервал времени. После обнаружения отжатия выполниться соответствующая подпрограмма, после чего опрос кнопок будет продолжен, но и здесь дребезг после отжатия может еще продолжаться, поэтому можно добавить паузу также перед опросом нажатого состояния кнопки. Я обычно этого не делаю, достаточно паузы во время опроса отжатого состояния кнопки. В итоге у нас получается примерно такой код:

Бывает нужно, чтобы кнопка имела две функции, на короткое и длительное нажатие. В этом случае во время опроса отжатого состояния необходимо добавить таймер времени. После обнаружения нажатого состояния записываем в дополнительный регистр число и уходим на паузу, после возврата декрементируем значение регистра и снова опрашиваем кнопку на предмет отжатого состояния. Этот цикл продолжается до тех пор, пока не отожмется кнопка или значение регистра не станет равным нулю. При обнаружении отжатого состояния до обнуления регистра, выполняется подпрограмма program1, при обнулении выполнится подпрограмма program2. Например, необходимо задать продолжительность длительного нажатия в 1 секунду, а пауза равна 10 мс, тогда в регистр счетчика надо записать число 100.

А если удерживать кнопку в течении 3 секунд, тогда подпрограмма program2 выполнится несколько раз, чтобы избежать этого в конце этой подпрограммы перед возвратом или после возврата нужно добавить опрос отжатого состояния кнопки. Ниже представлен код программы, где кнопка knp1 имеет 2 функции:

Возможно такое что, кнопка knp1используется внутри самой подпрограммы program2, в этом случае перед ее опросом внутри подпрограммы вначале также следует дождаться отжатия кнопки.

Иногда необходимо чтобы при однократном нажатии кнопки, подпрограмма выполнилась один раз, а при удерживании выполнялась многократно, например, ускоренное приращение числа на цифровом табло. Такую функцию можно реализовать очень просто, необходимо лишь исключить опрос отжатого состояния кнопки. Но здесь имеются свои недостатки, пропадает “дискретность” выполнения подпрограммы, ведь за одно кратковременное нажатие подпрограмма может выполниться несколько раз в зависимости от длительности нажатия, а нам требуется однократное выполнение. Данную функцию можно реализовать на основе предыдущего примера, немного изменив код. После длительного нажатия и выполнения подпрограммы, уходим на паузу, далее однократно выполняем проверку отжатого состояния (а не дожидаемся, пока отпустят кнопку), после чего при условии нажатой кнопки снова выполняем подпрограмму. Этот цикл будет продолжаться пока не отпустят кнопку. Здесь пауза будет определять скорость вызова подпрограммы (скорость приращения числа на цифровом табло). В итоге сохраняется “дискретность”, совместно с ускоренным многократным выполнением подпрограммы при удержании кнопки. Код программы выглядит следующим образом:

А теперь рассмотрим случай, когда микроконтроллер непрерывно выполняет какие-либо задачи, в этом случае ожидать отжатие кнопки недопустимо. Здесь можно воспользоваться прерываниями по изменению уровня на входах RB4-RB7 или по входу внешнего прерывания INT.

Для прерываний по входам RB4-RB7 алгоритм следующий: при изменении сигнала с 1 на 0 происходит переход в подпрограмму обработчика прерываний, где необходимо сначала определить, что вызвало прерывание, когда имеется несколько источников прерываний. Когда источник определен, необходимо выяснить на каком именно входе изменился уровень сигнала, определив его, выполняем соответствующие операции внутри самого обработчика прерываний, либо устанавливаем флаг в регистре, который специально выделяем для этих целей. Далее необходимо считать регистр PORTB, для устранения несоответствия ранее сохраненного значения с сигналом на входах порта, иначе флаг прерывания сбросить не удастся. После сброса флага выходим из обработчика прерывания.

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

Также в данном алгоритме не учтено повторное возникновение прерывания при отжатии кнопки, смена сигнала с 0 на 1, ведь прерывания по входам RB4-RB7 возникают по переднему и заднему фронту сигнала. Но здесь все просто, одно прерывание можно пропустить, выполняя соответствующие операции только при наличии 0 на входе.

Решить проблему дребезга можно с помощью таймера. После перехода в подпрограмму обработчика прерываний, при смене сигнала с 1 на 0, запрещаем прерывания по входам RB4-RB7, определяем вход, где изменился сигнал, устанавливаем флаг в специально выделенном регистре, запускаем таймер на 5-20 мс и выходим из подпрограммы прерывания. Через установленный промежуток времени, по переполнению таймера возвращаемся в обработчик прерываний, останавливаем таймер, сбрасываем флаг по переполнению, считываем PORTB для устранения несоответствия, сбрасываем флаг и разрешаем прерывания по входам RB4-RB7. При повторном возникновении прерывания в результате отжатия кнопки, весь процесс повторится, за исключением того что, пропускаем выполнение соответствующих операций, так как сигнал на входе равен 1. Код программы представлен ниже:

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

Гораздо проще и надежнее поместить код опроса кнопок в цикле основной программы, в то место где они будут опрашиваться хотя бы через каждые 100 мс, по мере выполнения других задач. Для предотвращения многократного выполнения подпрограммы при длительном нажатии на кнопку нужно использовать дополнительный регистр с флагами контроля состояния кнопок. Если во время очередного опроса в цикле основной программы кнопка окажется не нажатой, то устанавливаем в 1 соответствующий флаг регистра. При обнаружении нажатия сначала проверяем флаг, если он равен 1, сбрасываем его и выполняем подпрограмму, после чего идем на опрос второй кнопки или далее по циклу основной программы.
При следующем опросе флаг будет равен 0, ничего не делаем, идем также на опрос второй кнопки, или далее по циклу не задерживаясь. Такой пропуск будет происходить до тех пор, пока не отожмем кнопку и флаг снова не установиться в 1. Код выглядит следующим образом:

Как видно все очень просто, не нужно никаких прерываний и таймеров.

У этой записи 11 комментариев

  1. Внутренние подтягивающие резисторы удобно при отладке использовать. Меньше монтажа делать придется. Ну а в готовом устройстве внешние резисторы будут понадежнее )
    Хотя при такой хорошей программной фильтрации дребезга, при использовании трансформаторного источника питания(или батареек) и кондеров по питанию, можно избежать лишних наводок и внутренний подтяг использовать. Что поможет например в компактных устройствах.

  2. Кнопки бывают самые разные, с дребезгом от 10 мкс до долей секунды.
    Кроме того, они стареют и параметры дребезга меняются в худшую сторону.
    Поэтому, гораздо целесообразнее не делать программную задержку, а поставить параллельно кнопке конденсатор, меняя номинал которого можно быстро подстроиться под любые кнопки.

    1. Не согласен. Конденсатор можете добавить после программных способов борьбы с дребезгом. И то для успокоения души.

  3. Здравствуйте.
    Помогите пожалуйста! Уже неделю мучаюсь. Кучу программ по кнопочкам перешерстил и Ваши попробовал. Итог один – по отдельности кнопки работают всё супер, а две вместе не получается! А я пытаюсь поставить несколько кнопок. Каждая на свой порт и работа одновременного нажатия двух, трёх кнопок к примеру. Как изменить к примеру Ваш код, хотя-бы самый верхний, он попроще. Заранее благодарен.
    P.S. только учусь программировать!

    1. Приветствую, в самом первом коде у меня стоит проверка отжатия кнопки, поэтому когда нажата одна кнопка, вторая просто не опрашивается. Если убрать проверку отжатия, тогда можно реализовать опрос нескольких одновременно нажатых кнопок, но при этом подпрограммы назначенные на кнопки будут выполняться многократно по циклу, пока кнопки нажаты. Я не знаю какая у вас стоит задача на нажатие кнопок, можно ли ее выполнять многократно?

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

  4. Нда, первый рисунок из серии как делать НЕЛЬЗЯ. Сразу видно что писал программист. Теоретически – можно замыкать кнопкой на “землю”. Вот только если случайно порт будет стоять на выход… КЗ на землю и смерть контроллера. Поэтому подтяжка с ОДНИМ резистором ТОЛЬКО к “земле”. А для подавления дребезга контактов конденсатор на 0,1мк-1 мк параллельно резистору, хватает его.

    1. Почему нельзя? это не запрещено, и почему-то большинство схем именно с таким подключением. Просто надо быть повнимательнее, иначе не стоит заниматься схемотехникой. Если даже забудешь настроить порт, то он по умолчанию во время сброса при включении настраивается на ВХОД.

  5. В последнем примере ошибка. В опросе второй кнопки снова опрашивается первая кнопка:
    oproskn2 btfss knp1
    goto kn2
    Либо код верен (тогда я его просто не понял), но не верны комментарии. Потому что они расходятся со смыслом кода.

    1. Да там ошибка, надо исправить knp1 на knp2

  6. А как (мне чайнику) резистор посчитать?

  7. Объясните, пожалуйста, какое несоответствие устраняется считыванием значения PORTB в регистр W?

Имя (обязательно)Email (обязательно)Веб-сайт

Добавить комментарий