Кнопку к микроконтроллеру можно подключить двумя основными способами, с подтяжкой линии порта к высокому логическому уровню или низкому через резистор, как показано на картинке ниже.
Я обычно использую первый вариант подключения, можно конечно использовать внутренние подтягивающие резисторы на входах PORTB, но мне еще не доводилось применять такой способ.
В простейшем варианте микроконтроллер все время опрашивает одну или несколько кнопок, при нажатии которых выполняет соответствующую подпрограмму и снова возвращается на опрос кнопок. Подпрограмма обычно выполняется очень быстро, естественно за это время можно не успеть отжать кнопку, вследствие чего подпрограмма выполнится многократно. Чтобы этого не происходило после обнаружения нажатой кнопки необходимо добавить проверку отжатого состояния, но и в этом случае из-за явления дребезга контактов выполнение подпрограммы может начаться до отжатия кнопки. Поэтому после обнаружения нажатой кнопки необходимо добавить временную задержку в 5-20 мс. Проверка отжатого состояния будет многократно выполняться через заданный интервал времени. После обнаружения отжатия выполниться соответствующая подпрограмма, после чего опрос кнопок будет продолжен, но и здесь дребезг после отжатия может еще продолжаться, поэтому можно добавить паузу также перед опросом нажатого состояния кнопки. Я обычно этого не делаю, достаточно паузы во время опроса отжатого состояния кнопки. В итоге у нас получается примерно такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ; oproskn1 btfsc knp1 ;опрос 1-ой кнопки goto oproskn2 ;1-ая кнопка не нажата: переход на опрос 2-ой кнопки kn1 call pausknp ;1-ая кнопка нажата: вызов паузы (5-20мс), btfss knp1 ;опрос 1-ой кнопки goto kn1 ;1-ая кнопка не отжата: переход на метку kn1 call program1 ;1-ая кнопка отжата: вызов подпрограммы program1 oproskn2 btfsc knp2 ;опрос 2-ой кнопки goto oproskn1 ;2-ая кнопка не нажата: переход на опрос 1-ой кнопки kn2 call pausknp ;2-ая кнопка нажата: вызов паузы (5-20мс) btfss knp2 ;опрос 2-ой кнопки goto kn2 ;2-ая кнопка не отжата: переход на метку kn2 call program2 ;2-ая кнопка отжата: вызов подпрограммы program2 goto oproskn1 ;переход на метку oproskn1 ; |
Бывает нужно, чтобы кнопка имела две функции, на короткое и длительное нажатие. В этом случае во время опроса отжатого состояния необходимо добавить таймер времени. После обнаружения нажатого состояния записываем в дополнительный регистр число и уходим на паузу, после возврата декрементируем значение регистра и снова опрашиваем кнопку на предмет отжатого состояния. Этот цикл продолжается до тех пор, пока не отожмется кнопка или значение регистра не станет равным нулю. При обнаружении отжатого состояния до обнуления регистра, выполняется подпрограмма program1, при обнулении выполнится подпрограмма program2. Например, необходимо задать продолжительность длительного нажатия в 1 секунду, а пауза равна 10 мс, тогда в регистр счетчика надо записать число 100.
А если удерживать кнопку в течении 3 секунд, тогда подпрограмма program2 выполнится несколько раз, чтобы избежать этого в конце этой подпрограммы перед возвратом или после возврата нужно добавить опрос отжатого состояния кнопки. Ниже представлен код программы, где кнопка knp1 имеет 2 функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ;кнопка knp1 имеет 2 функции: на короткое и длительное нажатие ;кнопка knp2 имеет одну функцию на короткое нажатие oproskn1 btfsc knp1 ;опрос 1-ой кнопки goto oproskn2 ;1-ая кнопка не нажата: переход на опрос 2-ой кнопки movlw .100 ;1-ая кнопка нажата: запись числа 100 в регистр shet movwf shet kn1 call pausknp ;вызов паузы (5-20мс) btfsc knp1 ;опрос 1-ой кнопки goto prog1 ;1-ая кнопка отжата: переход на метку prog1 decfsz shet,F ;1-ая кнопка не отжата: декремент регистра shet goto kn1 ;значение регистра не равно 0: переход на метку kn1 call program2 ;значение регистра равно 0: вызов подпрограммы program2 kn11 call pausknp ;вызов паузы (5-20мс) btfss knp1 ;опрос 1-ой кнопки goto kn11 ;1-ая кнопка не отжата: переход на метку kn11, ;ждем отжатия кнопки goto oproskn2 ;1-ая кнопка отжата: переход на метку oproskn2 prog1 call program1 ;вызов подпрограммы program1 oproskn2 btfsc knp2 ;опрос 2-ой кнопки goto oproskn1 ;2-ая кнопка не нажата: переход на опрос 1-ой кнопки kn2 call pausknp ;2-ая кнопка нажата: вызов паузы (5-20мс) btfss knp2 ;опрос 2-ой кнопки goto kn2 ;2-ая кнопка не отжата: переход на метку kn2 call program3 ;2-ая кнопка отжата: вызов подпрограммы program3 goto oproskn1 ;переход на метку oproskn1 ; |
Возможно такое что, кнопка knp1используется внутри самой подпрограммы program2, в этом случае перед ее опросом внутри подпрограммы вначале также следует дождаться отжатия кнопки.
Иногда необходимо чтобы при однократном нажатии кнопки, подпрограмма выполнилась один раз, а при удерживании выполнялась многократно, например, ускоренное приращение числа на цифровом табло. Такую функцию можно реализовать очень просто, необходимо лишь исключить опрос отжатого состояния кнопки. Но здесь имеются свои недостатки, пропадает “дискретность” выполнения подпрограммы, ведь за одно кратковременное нажатие подпрограмма может выполниться несколько раз в зависимости от длительности нажатия, а нам требуется однократное выполнение. Данную функцию можно реализовать на основе предыдущего примера, немного изменив код. После длительного нажатия и выполнения подпрограммы, уходим на паузу, далее однократно выполняем проверку отжатого состояния (а не дожидаемся, пока отпустят кнопку), после чего при условии нажатой кнопки снова выполняем подпрограмму. Этот цикл будет продолжаться пока не отпустят кнопку. Здесь пауза будет определять скорость вызова подпрограммы (скорость приращения числа на цифровом табло). В итоге сохраняется “дискретность”, совместно с ускоренным многократным выполнением подпрограммы при удержании кнопки. Код программы выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ;кнопка knp1 имеет 2 функции: на короткое и длительное нажатие с многократным ускоренным ;выполнением подпрограммы program1 ;кнопка knp2 имеет одну функцию на короткое нажатие oproskn1 btfsc knp1 ;опрос 1-ой кнопки goto oproskn2 ;1-ая кнопка не нажата: переход на опрос 2-ой кнопки movlw .100 ;1-ая кнопка нажата: запись числа 100 в регистр shet movwf shet kn1 call pausknp ;вызов паузы (5-20мс) btfsc knp1 ;опрос 1-ой кнопки goto prog1 ;1-ая кнопка отжата: переход на метку prog1 decfsz shet,F ;1-ая кнопка не отжата: декремент регистра shet goto kn1 ;значение регистра не равно 0: переход на метку kn1 kn11 call program1 ;значение регистра равно 0: вызов подпрограммы program1 call pause ;вызов паузы (скорость вызова подпрограммы program1) btfsc knp1 ;опрос 1-ой кнопки goto oproskn2 ;1-ая кнопка отжата: переход на метку oproskn2 goto kn11 ;1-ая кнопка не отжата: переход на метку kn11 для ;повторного выполнения подпрограммы program1 prog1 call program1 ;вызов подпрограммы program1 oproskn2 btfsc knp2 ;опрос 2-ой кнопки goto oproskn1 ;2-ая кнопка не нажата: переход на опрос 1-ой кнопки kn2 call pausknp ;2-ая кнопка нажата: вызов паузы (5-20мс) btfss knp2 ;опрос 2-ой кнопки goto kn2 ;2-ая кнопка не отжата: переход на метку kn2 call program2 ;2-ая кнопка отжата: вызов подпрограммы program3 goto oproskn1 ;переход на метку oproskn1 ; |
А теперь рассмотрим случай, когда микроконтроллер непрерывно выполняет какие-либо задачи, в этом случае ожидать отжатие кнопки недопустимо. Здесь можно воспользоваться прерываниями по изменению уровня на входах RB4-RB7 или по входу внешнего прерывания INT.
Для прерываний по входам RB4-RB7 алгоритм следующий: при изменении сигнала с 1 на 0 происходит переход в подпрограмму обработчика прерываний, где необходимо сначала определить, что вызвало прерывание, когда имеется несколько источников прерываний. Когда источник определен, необходимо выяснить на каком именно входе изменился уровень сигнала, определив его, выполняем соответствующие операции внутри самого обработчика прерываний, либо устанавливаем флаг в регистре, который специально выделяем для этих целей. Далее необходимо считать регистр PORTB, для устранения несоответствия ранее сохраненного значения с сигналом на входах порта, иначе флаг прерывания сбросить не удастся. После сброса флага выходим из обработчика прерывания.
Специально выделенный регистр с флагами проверяем в цикле основной программы, где по установленному флагу выполняем какую-либо подпрограмму, после чего сбрасываем флаг.
Опять же из-за дребезга контактов при таком алгоритме прерывания могут генерироваться многократно, добавлять паузу на время дребезга внутри обработчика прерываний бессмысленно, так как теряется процессорное время необходимое для выполнения других задач.
Также в данном алгоритме не учтено повторное возникновение прерывания при отжатии кнопки, смена сигнала с 0 на 1, ведь прерывания по входам RB4-RB7 возникают по переднему и заднему фронту сигнала. Но здесь все просто, одно прерывание можно пропустить, выполняя соответствующие операции только при наличии 0 на входе.
Решить проблему дребезга можно с помощью таймера. После перехода в подпрограмму обработчика прерываний, при смене сигнала с 1 на 0, запрещаем прерывания по входам RB4-RB7, определяем вход, где изменился сигнал, устанавливаем флаг в специально выделенном регистре, запускаем таймер на 5-20 мс и выходим из подпрограммы прерывания. Через установленный промежуток времени, по переполнению таймера возвращаемся в обработчик прерываний, останавливаем таймер, сбрасываем флаг по переполнению, считываем PORTB для устранения несоответствия, сбрасываем флаг и разрешаем прерывания по входам RB4-RB7. При повторном возникновении прерывания в результате отжатия кнопки, весь процесс повторится, за исключением того что, пропускаем выполнение соответствующих операций, так как сигнал на входе равен 1. Код программы представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | ;предварительно при начальной настройке регистров специального назначения необходимо сбросить ;флаг по переполнению TMR1, флаг прерывания по входам RB4-RB7 ;разрешить прерывание по переполнению TMR1, а также прерывания по входам RB4-RB7 ;регистр flag с флагами кнопок опрашиваем в теле основной программе, не забывая их сбросить org 0004h ;начать выполнение подпрограммы с адреса 0004h btfss PIR1,TMR1IF ;опрос флага прерываний по переполнению TMR1 goto metka1 ;флаг не установлен: переход на метку metka1 - опрос ;других источников прерываний bcf T1CON,TMR1ON ;флаг установлен: остановка таймера TMR1 bcf PIR1,TMR1IF ;сброс флага прерывания по переполнению TMR1 movf PORTB,W ;считывание значения PORTB для устранения несоответствия bcf INTCON,RBIF ;сброс флага прерывания по входам RB4-RB7 bsf INTCON,RBIE ;разрешение прерывания по входам RB4-RB7 retfie ;выход из обработчика прерываний metka1 btfss INCON,RBIF ;опрос флага прерываний по входам RB4-RB7 goto metka2 ;флаг не установлен: переход на метку metka2 - опрос ;других источников прерываний bcf INTCON,RBIE ;флаг установлен: запрет на прерывания по входам RB4-RB7 btfss PORTB,4 ;опрос линии RB4 bsf flag,0 ;0 на линии RB4: установка флага для линии RB4 btfss PORTB,5 ;1 на линии RB4: опрос линии RB5 bsf flag,1 ;0 на линии RB5: установка флага для линии RB5 btfss PORTB,6 ;1 на линии RB5: опрос линии RB6 bsf flag,2 ;0 на линии RB6: установка флага для линии RB6 btfss PORTB,7 ;1 на линии RB6: опрос линии RB7 bsf flag,3 ;0 на линии RB7: установка флага для линии RB7 movlw .224 ;запись числа 45536 в таймер TMR1, пауза 20мс, movwf TMR1L ;частота тактового генератора 4 МГц, movlw .177 ;коэффициент предделителя TMR1 заранее установлен 1:1 movwf TMR1H bsf T1CON,TMR1ON ;включение таймера TMR1 retfie ;выход из обработчика прерываний metka2 ................. ;опрос других источников прерываний ................. ................. retfie ;выход из обработчика прерываний ; |
Честно говоря, такую конструкцию я не применял, не нравится мне все это дело. Вешать кнопки на прерывания лучше всего для пробуждения микроконтроллера из спящего режима.
Гораздо проще и надежнее поместить код опроса кнопок в цикле основной программы, в то место где они будут опрашиваться хотя бы через каждые 100 мс, по мере выполнения других задач. Для предотвращения многократного выполнения подпрограммы при длительном нажатии на кнопку нужно использовать дополнительный регистр с флагами контроля состояния кнопок. Если во время очередного опроса в цикле основной программы кнопка окажется не нажатой, то устанавливаем в 1 соответствующий флаг регистра. При обнаружении нажатия сначала проверяем флаг, если он равен 1, сбрасываем его и выполняем подпрограмму, после чего идем на опрос второй кнопки или далее по циклу основной программы.
При следующем опросе флаг будет равен 0, ничего не делаем, идем также на опрос второй кнопки, или далее по циклу не задерживаясь. Такой пропуск будет происходить до тех пор, пока не отожмем кнопку и флаг снова не установиться в 1. Код выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ................ ................ ;код основной программы ................ oproskn1 btfss knp1 ;опрос 1-ой кнопки goto kn1 ;1-ая кнопка нажата: переход на метку kn1 bsf flag,0 ;1-ая кнопка не нажата: устанавливаем флаг 1-ой кнопки goto oproskn2 ;переход на метку oproskn2 kn1 btfss flag,0 ;опрос флага состояния 1-ой кнопки goto oproskn2 ;флаг 1-ой кнопки не установлен: переход на метку oproskn2 bcf flag,0 ;сброс флага состояния 1-ой кнопки call program1 ;вызов подпрограммы program1 oproskn2 btfss knp1 ;опрос 2-ой кнопки goto kn2 ;1-ая кнопка нажата: переход на метку kn1 bsf flag,1 ;1-ая кнопка не нажата: устанавливаем флаг 2-ой кнопки goto metka1 ;переход на метку metka1 kn2 btfss flag,1 ;опрос флага состояния 2-ой кнопк goto metka1 ;флаг 2-ой кнопки не установлен: переход на метку metka1 bcf flag,1 ;сброс флага состояния 2-ой кнопки call program2 ;вызов подпрограммы program2 metka1 ................. ;дальнейшее исполнение основной программы ................. ................. |
Как видно все очень просто, не нужно никаких прерываний и таймеров.
LrStein
22 Дек 2013Внутренние подтягивающие резисторы удобно при отладке использовать. Меньше монтажа делать придется. Ну а в готовом устройстве внешние резисторы будут понадежнее )
Хотя при такой хорошей программной фильтрации дребезга, при использовании трансформаторного источника питания(или батареек) и кондеров по питанию, можно избежать лишних наводок и внутренний подтяг использовать. Что поможет например в компактных устройствах.
В.Г.Семенов
24 Янв 2017Кнопки бывают самые разные, с дребезгом от 10 мкс до долей секунды.
Кроме того, они стареют и параметры дребезга меняются в худшую сторону.
Поэтому, гораздо целесообразнее не делать программную задержку, а поставить параллельно кнопке конденсатор, меняя номинал которого можно быстро подстроиться под любые кнопки.
Александр
10 Авг 2018Не согласен. Конденсатор можете добавить после программных способов борьбы с дребезгом. И то для успокоения души.
Юрий
3 Мар 2018Здравствуйте.
Помогите пожалуйста! Уже неделю мучаюсь. Кучу программ по кнопочкам перешерстил и Ваши попробовал. Итог один – по отдельности кнопки работают всё супер, а две вместе не получается! А я пытаюсь поставить несколько кнопок. Каждая на свой порт и работа одновременного нажатия двух, трёх кнопок к примеру. Как изменить к примеру Ваш код, хотя-бы самый верхний, он попроще. Заранее благодарен.
P.S. только учусь программировать!
admin
4 Мар 2018Приветствую, в самом первом коде у меня стоит проверка отжатия кнопки, поэтому когда нажата одна кнопка, вторая просто не опрашивается. Если убрать проверку отжатия, тогда можно реализовать опрос нескольких одновременно нажатых кнопок, но при этом подпрограммы назначенные на кнопки будут выполняться многократно по циклу, пока кнопки нажаты. Я не знаю какая у вас стоит задача на нажатие кнопок, можно ли ее выполнять многократно?
Лучше всего подойдет последний код, с сохранением состояний кнопок в регистр флагов, при этом все кнопки будут опрашиваться, и при нажатии кнопки подпрограмма будет выполняться однократно.
Андрей
9 Апр 2018Нда, первый рисунок из серии как делать НЕЛЬЗЯ. Сразу видно что писал программист. Теоретически – можно замыкать кнопкой на “землю”. Вот только если случайно порт будет стоять на выход… КЗ на землю и смерть контроллера. Поэтому подтяжка с ОДНИМ резистором ТОЛЬКО к “земле”. А для подавления дребезга контактов конденсатор на 0,1мк-1 мк параллельно резистору, хватает его.
admin
9 Апр 2018Почему нельзя? это не запрещено, и почему-то большинство схем именно с таким подключением. Просто надо быть повнимательнее, иначе не стоит заниматься схемотехникой. Если даже забудешь настроить порт, то он по умолчанию во время сброса при включении настраивается на ВХОД.
Андрей
26 Июн 2018В последнем примере ошибка. В опросе второй кнопки снова опрашивается первая кнопка:
oproskn2 btfss knp1
goto kn2
Либо код верен (тогда я его просто не понял), но не верны комментарии. Потому что они расходятся со смыслом кода.
admin
26 Июн 2018Да там ошибка, надо исправить knp1 на knp2
Евгений
5 Июн 2019А как (мне чайнику) резистор посчитать?
Сергей
15 Янв 2021Объясните, пожалуйста, какое несоответствие устраняется считыванием значения PORTB в регистр W?