Интернет. Программы. Игры. Операционные системы. Антивирусы

Тактовая кнопка на avr микроконтроллере. Запуск микроконтроллера (сброс в начальное состояние)

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

Способ первый - традиционный

рис1а рис1б

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

Когда кнопка отпущена – вывод мк через резистор соединен с “плюсом” питания (рис. 1а). Когда кнопка нажата – вывод мк соединен с землей. Подтягивающий резистор R1 ограничивает силу тока в цепи переключателя. Если бы его не было, то при нажатии кнопки мы бы просто закоротили наш источник питания.

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

Что произойдет, если вывод микроконтроллера окажется в режиме выхода? Это будет зависеть от состояния этого вывода. Если на выводе “логический ноль” – ничего страшного не случиться, потому что - в первом случае (рис1а) величина втекающего тока ограничена резистором R1, а во втором случае (рис1б) никакой ток вообще не потечет. При нажатии кнопки тоже ничего не случиться, поскольку разность потенциалов между выводом и “землей” в этом случае будет равна нулю.

Если же на выводе будет ”логическая единица” и кнопка окажется нажатой, то через вывод микроконтроллера на землю потечет ток величиной в несколько десятков миллиампер и вывод порта может “погореть”. Предельно допустимый ток для вывода микроконтролера AVR согласно документации равен 40 мА. Поэтому иногда нелишним бывает поставить между выводом мк и кнопкой резистор номиналом в несколько сотен ом, например 330 (рис 1с). Так, например, подключены кнопки на отладочной плате STK500. Это сделано для подстраховки, чтобы пользователь нечаянно не спалил микроконтроллер в ходе своих эксперементов.

Для своих макетов впрочем можно обойтись и без этого резистора.

Второй способ - с использованием диодов

Используется когда кнопок больше двух, а выводы мк хочется сэкономить. Каждой кнопке в данном случае соответствует свой цифровой код, а количество кнопок, которые можно таким способом повесить на N выводов мк = 2 N - 1. То есть на три вывода можно повесить 7 кнопок, на четыре – 15 и так далее... но я бы больше 7-ми вешать не стал. Увеличивается количество дополнительных внешних компонентов, усложняется схема и программа мк. Кроме того, для большого количества кнопок есть и другие схемы включения. Подтягивающие резисторы на схеме не показаны, подразумевается, что используются внутренние.

Кстати, через диоды еще можно завести сигналы от кнопок на вывод внешнего прерывания контроллера (рис. 3). При нажатии любой кнопки вывод внешнего прерывания через диод будет замыкаться на землю и вызывать прерывание (естественно при условии, что оно настроено и разрешено). Таким образом контроллеру не нужно будет постоянно опрашивать кнопки, эта процедура будет запускаться только по событию внешнего прерывания.

Данная схема актуальна не для всех микроконтроллеров AVR, потому что в некоторых моделях микроконтроллеров внешнее прерывание может возникать по любому изменению на любом выводе. (например в ATmega164P)

Третий способ – для матричной клавиатуры

Такой вариант подключения обычно используется для блоков из нескольких кнопок, которые объединены конструктивно и соединены электрически по матричной схеме. Но никто не запрещает использовать эту схему и для включения обычных кнопок, однако реальную экономию она дает при количестве кнопок? 9.

Выводы PС0, PС1, PС2, PC3 – это строки матрицы, выводы PB0, PB1, PB2 – это столбцы матрицы. Кнопки можно опрашивать либо по строкам, либо по столбцам. Допустим, мы опрашиваем их по столбцам. Процедура опроса будет выглядеть следующим образом. Начальное состояние всех выводов – вход с включенным подтягивающим резистором. Устанавливаем вывод PB0 в режим выхода и выставляем ноль. Теперь нажатие кнопок S1, S2, S3, S4 будет замыкать выводы PС0, PС1, PС2, PC3 на 0 питания. Опрашиваем эти выводы и определям нажата ли какая-нибудь кнопка в данный момент. Устанавливаем вывод PB0 в режим выхода и включаем подтягивающий резистор. Устанавливаем вывод PB1 в режим выхода и выставляем ноль. Снова опрашиваем выводы PС0, PС1, PС2, PC3. Теперь нажатие кнопок S5, S6, S7, S8 будет замыкать выводы PС0, PС1, PС2, PC3. Последний столбец кнопок опрашиваем аналогично.

Строки матрицы можно завести через диоды на вывод внещнего прерывания. Тогда логику программы можно было бы построить так. Если клавиатура не используется в течении нескольких минут, микроконтроллер переходит в режим пониженного энергопотребления. При этом выводы PB0, PB1, PB2 – конфигурируются как выходы с нулевым логическим уровнем. Когда одна из кнопок нажимается, вывод прерывания через диод замыкается на ноль. Это вызывает внешнее прерывание, микроконтроллер просыпается и запускает таймер по сигналам которого происходит сканирование клавиатуры. Параллельно запускается счетчик времени, который сбрасывается при нажатии любой из кнопок. Как только он переполняется, микроконтроллер опять переходит в режим пониженного энергопотребления.

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

Будем рассматривать наиболее простой и распространенный вариант кнопки, зачастую называемый тактовой кнопкой, имеющую два нормально разомкнутых контакта, замыкаемых при нажатии на кнопку.

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

Если мы подключим один из контактов, например, к общему проводу («земле»), а второй к выбранному выводу микроконтроллера, переключенного в режим входа, то выяснится, что такой метод не работает. При нажатии кнопки вывод микроконтроллера соединяется с землей, и программа будет считывать (с помощью функции digitalRead) логический 0 с этого вывода, но при отпущенной кнопке вывод микроконтроллера не будет соединен ни с какой линией, что часто называют «висит в воздухе». В таком режиме программа будет считать с вывода и 0 и 1 совершенно случайным образом.

Правильное подключение предполагает, что в разомкнутом состоянии вывод микроконтроллера должен быть соединен через резистор, например с шиной питания, а в замкнутом - с землей, либо наоборот. Сопротивление резистора не должно быть слишком маленьким, чтобы ток, текущий через него при замкнутых контактах кнопки не был слишком большим. Обычно используют значения порядка 10-100 кОм. Оба варианта подключения можно изобразить следующим образом:

Первый вариант предпочтительнее, поскольку подтягивающие к +5В резисторы уже есть внутри микроконтроллера – их нужно только программно включить. Кнопка будет либо соединять вывод микроконтроллера с землей, либо разъединять, и тогда он "притянется" резистором к +5В.

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

Пример программы, зажигающей светодиод на 13 выводе при нажатии кнопки на 2 выводе будет выглядеть примерно так:

Обращаем внимание на то, что значение, прочитанное с 2 вывода, инвертируется с помощью оператора «!», поскольку при нажатии на кнопку будет считываться «0», а при ее отпускании «1».

Дребезг контактов

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

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

Существует специальная библиотека Bounce, упрощающая борьбу с дребезгом контактов, и имеющая дополнительные возможности:

Как и в большинстве случаев, установка библиотеки сводится к распаковке архива в подпапку \hardware\libraries\ папки с ПО Arduino.

Полная документация на библиотеку может быть найдена на сайте разработчика, а здесь рассмотрим только наиболее важное.

Bounce – конструктор объекта

Bounce имя_объекта = Bounce(вывод, интервал);

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

Метод Bounce::update

имя_объекта.update()

Возвращает значение типа int – истину, если состояние вывода изменилось, ложь, если нет.

Метод Bounce::read

имя_объекта.read()

Возвращает значение типа int – состояние вывода.

Метод Bounce::rebounce

имя_объекта.rebounce(время_повтора)

Повторяет последнее произошедшее событие через заданное время в миллисекундах.

Рассмотрим исходный код программы, посылающей в последовательный порт сообщение «pressed» при нажатии кнопки, сообщение «released» при ее отпускании, и повторяющей сообщение «pressed» каждые пол секунды при удержании кнопки.

#include

Bounce bouncer = Bounce(2 , 40 ) ; //создаем экземпляр класса Bounce для 2 вывода

void setup()
{
pinMode(2 , INPUT) ; //переключаем 2 вывод в режим входа
digitalWrite(2 , 1 ) ; //включаем на нем подтягивающий резистор
Serial.begin (9600 ) ; //установка порта на скорость 9600 бит/сек
}

void loop()
{
if (bouncer.update () ) { //если произошло событие
if (bouncer.read () == 0 ) { //если кнопка нажата
Serial.println ("pressed" ) ; //вывод сообщения о нажатии
bouncer.rebounce (500 ) ; //повторить событие через 500мс
} else {
Serial.println ("released" ) ; //вывод сообщения об отпускании
}
}
}


В предыдущих уроках я рассказывал, как с мк вывести информацию: и . А в этом уроке мы будем вводить информацию при помощи кнопок. Кнопки бывают нескольких видов: фиксирующие и тактовые.Из названия кнопки понятен принцип ее работы: тактовая - нажал, контакты замкнулись, разжал - разомкнулись; фиксирующие фиксируют своё состояние: нажал - замкнулись контакты, еще раз нажал - разомкнулись.

Стандартная схема подключения кнопок очень простая, выглядит так


Идея работы такова: на ножку через резистор 10к подается напряжение 5 вольт, на ножке логическая единица. Но когда мы нажимаем кнопку, мы ножку замыкаем на землю, а ток-то через резистор потечет маленький, и он будет не в состоянии удержать 5 вольт, и на ножке напряжение просядет до 0 вольт, а это логический 0.Эти моменты мы и будем отлавливать в программе. Напишем программу, которая будет при нажатии кнопки включать светодиод, при отжатой - выключать

#include #include void main(void) { // инициализация порта D PORTD=0b00000000; DDRD=0b10000000; while (1) { if (PIND & 0b00000100) /*проверяем, какой логический уровень у нас на ножке знак & - означает побитовое "И" например в PIND в нас находится 0b00000100, тогда 0b00000100 & 0b00000100 = 0b00000100, то есть true, а если в PIND у нас 0b00000000, то 0b00000000 & 0b00000100 = 0b00000000 а это false */ PORTD=0b00000000; // записываем ноль в седьмой бит порта D else PORTD=0b10000000; // записываем единицу в седьмой бит порта D }; delay_ms(100); // делаем задержку в 100 милисекунд для защиты от дребезга контактов }

В большинстве современных микроконтроллеров есть встроенный подтягивающий резистор R1, поэтому внешний можно и не ставить
Чтобы включить внутренний подтягивающий резистор нужно при инициализации порта в регистре PORTD выставить соответствующий бит, на котором висит кнопка, в единицу: PORTD=0b00000100;
А что же произойдет, если вывод будет сконфигурирован как выход:

  • Если на выводе логический ноль, ничего страшного не случится
  • если на выводе вдруг окажется логическая единица, то при нажатии кнопки мы попросту закоротим вывод на землю, и через него потечет ток, который ножка не выдержит (ток через ножку не должен превышать 40 милиампер), и вероятнее всего, она перегорит
Поэтому для защити желательно поставить между выводом микроконтроллера и кнопкой резистор ом на 300
Есть еще много способов как подключить кнопки к микроконтроллеру, например с помощью диодов или с помощью ацп, но я их описывать не буду, так как курс рассчитан для начинающих. Если надо, то найдете сами.

На первом уроке мы научились подавать напряжение ножкой микроконтроллера. Теперь нужно научиться управлять микроконтроллером без перепрошивки.

Зачем это нужно? Например, вы сделали часы на микроконтроллере, нужно выставить время, но очень не удобно каждый раз перепрошивать, когда собьется время. Намного удобнее пользоваться кнопками, например, одной менять часы, другой минуты.

Помните в первом уроке мы настраивали ножку как выход, т.е. мы могли ей подавать напряжение. Так вот, ножку можно настроить как вход. В таком режиме можно проверить есть ли на ней напряжение или нет.

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

После создания проекта приведем код к такому виду:

#include #include void main(void ) { PORTB= 0x02 ; DDRB= 0x01 ; while (1 ) { if (PINB.1== 0 ) { PORTB.0= 1 ; delay_ms(100 ) ; PORTB.0= 0 ; delay_ms(100 ) ; } } ; }

#include #include void main(void) { PORTB=0x02; DDRB=0x01; while (1) { if(PINB.1==0) { PORTB.0=1; delay_ms(100); PORTB.0=0; delay_ms(100); } }; }

Как мы видим, по сравнению с первым уроком изменилась настройка порта

if(PINB.1==0) {}

данную строчку нужно читать так — если на ножке 1 порта В подключили землю (0 потенциал), то выполнить код в фигурных скобках. В нашем примере это код из первого урока. Если кнопка не замкнута, то ничего не делать. Промоделировать можно в Proteuse.

Вместо кнопки можно поставить датчик, реле и т.п., вместо светодиода — пищалку, получится сигнализация.

Архив с прошивкой и файлом протеуса доступен

Update1: Зачем нужна подтяжка порта?
У входа мк большое сопротивление, если будут течь даже микротоки вызванные помехами, то по закону Ома U=R*I это может привести к тому, что на входе появится лог 1. Чтобы не было таких проблем в AVR микроконтроллерах можно подключить ножку к плюсу питания, через подтягивающий резистор. В этом случае даже, логика работы меняется наоборот — но если появится помеха, нам это не важно, ведь у нас на входе уже логическая единица.

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

Update2: Добавлен тест, в котором вы можете проверить на сколько хорошо вы усвоили материал урока

This movie requires Flash Player 9

Сегодня мы расширим свой кругозор по изучению работы портов микроконтроллера и изучим второе назначение порта — работу на вход. И для изучения работы на вход мы применим обычную тактовую кнопку.

Как всегда, создадим проект в Atmel Studio, выберем Atmega8A, назовем проект Test04 и код также в main.c, как обычно, скопируем с проекта предыдущего урока.

В качестве подопытного порта давайте возьмём порт B. Можно с успехом использовать любой порт. И в качестве ножки возьмем нулевую ножку. Итак у нас ножка B0.

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

Добавим кнопку в протеусе, для этого в поиске компонентов найдём Button

Затем подключим нашу кнопку вот таким вот образом к ножке B0 контроллера

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

Для этого мы, во-первых настроим порт B. Мы можем объявить все ножки порта B на вход, так как нам не важны настройки остальных ножке, ибо мы их не используем

DDRD = 0xFF;

DDRB = 0x00;

В случае, когда мы работали с портом D на выход, биты регистра PORTD отвечали за уровень на соответствующих ножках. А в случае, когда порт инициализирован на вход, как наш порт B, то биты регистра PORTB будут уже отвечать за подтягивание к соответствующим ножкам порта резисторов на шину питания. Если будет логическая единица, то регистр будет подтягиваться, а если логический ноль — то не будет. Поэтому мы в 0 бите регистра установим 1

PORTD = 0b00000001;

PORTB = 0b00000001;

Соберём код и запустим его в протеусе. Мы видим, что на ножке B0 у нас установилась логическая 1 , а если мы нажмём кнопку, то увидим, что на ней будет логический 0 , о чём свидетельствует синий цвет квадратиков на ножке и на кнопке.

В бесконечном цикле закомментируем весь код. В видеоверсии урока показано, как данную операцию можно выполнить одним движением (выделяем весь текст, который мы хотим закомментировать, затем нажимаем одновременно Shift и обычный слэш (не обратный))

// for(i=0;i<=7;i++)

// {

// PORTD = (1<

// _delay_ms(500);

// }

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

Чтобы нам следить за каким-либо действием или состоянием, нам необходимо будет обработать условие.

Условие в языке C добавляется с помощью команды if .

И в качестве условия мы возьмём состояние ножки 0 порта B или состояние бита 0 регистра PINB .

Как же можно получить состояние одного бита, ведь в языке C в отличие от ассемблера нет битовых операций?

Можно пойти на хитрость и применить вот такую конструкцию PINB &0b00000001 .

Данная конструкция нам и проверит нулевой бит. То есть если в регистре PINB также будет 1 в нулевом его бите, то независимо от состояния остальных битов в данном регистре мы получим ненулевой результат, что также является истиной. То есть если ни с чем не сравнивать в условии результат, то условие эквивалентно сравниванием с нулём, только наоборот. Для истинности результат должен быть ненулевым — (результат!=0 ).

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

while (1)

if (!( PINB &0b00000001))

{

}

else

{

}

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

DDRB =0x00;

PORTD =0b00000000 ;

PORTB =0b00000001;

while (1)

If (!(PINB &0b00000001))

PORTD =0b00000001;

Else

PORTD =0b00000000;

Теперь давайте пересоберём проект и пойдём в протеус смотреть, удалось ли нам что-то.

Чтобы у нас при сборке не было даже предупреждений, уберём объявление переменной i, так как она в коде не используется

int main ( void )

// unsigned char i;

Unsigned char butcount =0;

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

Казалось бы, что мы своей цели уже добились. Но чтобы сделать наш код более ответственным и совершенным, мы просто обязаны провести борьбу с дребезгом контактов, так как такое явление может иметь место, это только в протеусе всё идеально, на практике такое бывает не всегда.

И чтобы это как-то отследить и определить, что это было именно нажатие, а не дребезг, то мы будим отслеживать нажатие некоторое время, ну или некоторое количество тактов или циклов. Для этого в начале функции main() до бесконечного цикла мы добавим другую переменную (i нам ещё пригодится и мы её портить не будем). Назовём мы переменную butcount , так как имя переменной должно как-то само за себя говорить и тем самым достигается ещё большая читабельность кода

// unsigned char i;

unsigned char butcount =0;

И чтобы воспользоваться данной переменной, мы применим ещё одно условие. И у нас будет условие в условии. Это всё допустимо и очень широко используется. И в зависимости от этого условия мы данную переменную будем наращивать (инкрементировать). Условием будет у нас достижение данной переменной определённой величины. То есть попробуем сделать так, чтобы значение переменной не достигало 5

if (!( PINB &0b00000001))

if ( butcount < 5)

{

butcount ++;

}

А когда значение данной переменной достигнет значения 5, то мы уже в данный цикл не попадём, а попадём мы в тело оператора else , который мы сейчас и добавим и в его теле напишем следующий код

Butcount ++;

else

PORTD =0b00000001;

То есть мы как раз после достижения пятёрки и будем обрабатывать нажатие кнопки и включать на нулевой ножке порта D высокое состояние.

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

else

if (butcount >0)

{

butcount —;

}

else

{

PORTD =0b00000000;

}

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

Давайте теперь соберём проект и проверим его работу сначала в протеусе, а затем и на практике. Выглядит это приблизительно так. Интереснее конечно это смотреть в видеоуроке

Смотреть ВИДЕОУРОК

Post Views: 13 083