GPIO для чайников (часть 5)
-
Снова возвращаемся к кнопкам и создаём первое полезное приложение.
Во второй части статей «GPIO для чайников» мы подключали кнопку между двумя портами. Один порт устанавливали на «вывод» и в состояние «1», а другим портом читали эту «1» через кнопку. При отпущенной кнопке на втором порту читался «0», а при нажатой- «1».
Всё вроде бы работало. Но недавно у такого способа подключения обнаружился один неприятный недостаток. Такое подключение кнопки оказалось неустойчивым к помехам. Когда я свою RPI подключил к сети при помощи Wi-Fi USB донгла, то все программы, в которых использовалось такое включение кнопки, просто сошли с ума. RPI детектировал нажатие кнопки даже тогда, когда к ней никто не притрагивался. Виной всему оказался длинный провод, которым была подключена кнопка к RPI. Работающий Wi-Fi адаптер наводил на нём потенциал, с уровнем достаточным для детектирования «1» на порте GPIO. Такое поведение кнопки никуда не годится.Нужно принимать меры по помехозащищённости. В данном случае поможет использование экранированного провода до кнопки, либо даже простое уменьшение длины проводов, соединяющих кнопку с RPI. Но можно поступить проще. Раз наш RPI реагирует на помехи при отсутствии сигнала на входе, то разумным решением будет перевести порт, которым мы читаем состояние кнопки в состояние логической «1». Раз на нём всегда будет «1», то уже никакая помеха не сможет этот порт перевести в «0». А значит, наш порт станет невосприимчивым к помехам.
Давайте подключим нашу кнопку по такой схеме:
Если вы использовали кнопку от системного блока с коннектором BLS, то достаточно его просто развернуть на 90 градусов. Теперь наша кнопка подключена к порту Р1-05 и к пину «GND» (земля, или 0 по русски).
Ну и напишем такую простенькую программу button1.c
// Проверка работоспособности кнопки, подключённой // к порту Р1_05 и GND. // Компиляция: gcc -o button1 button1.c -lbcm2835 -lrt // Исполнение: sudo ./button1 #include <stdio.h> #include <bcm2835.h> #define PIN_IN RPI_GPIO_P1_05 //#define PIN_IN RPI_V2_GPIO_P1_05 // Для RPI ревизии v2 раскомментировать эту строку и удалить предыдущую int main() { if (!bcm2835_init()) // Инициализация GPIO return 1; //Завершение программы, если инициализация не удалась bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT); // Порт на ввод printf("Ждём нажатия на кнопку.\n"); while(bcm2835_gpio_lev(PIN_IN)) // Повторяем все действия, заключённые в скобки {} пока не будет нажата кнопка {} printf("Кнопочка нажата!\n"); return (bcm2835_close ()); // Выход из программы }
Компилируем и запускаем. Убеждаемся, что наша кнопка, включённая таким образом, действительно работает. Можно после запуска программы повертеть провод перед Wi-Fi донглом и убедиться, что RPI никак на него не реагирует, а реагирует только на нажатую кнопку.
А теперь разберёмся, как это работает.
Если мы зайдём на страницу http://elinux.org/RPi_Low-level_peripherals То в таблице
«HeaderPinout,bottomrow:»
мы увидим такое описание портов:
Pin Number Pin Name Rev1 Pin Name Rev2 Hardware Notes P1-01 3.3 V 3.3 V 50 mA max (01 &17) P1-03 GPIO 0 GPIO 2 1K8 pull up resistor P1-05 GPIO 1 GPIO 3 1K8 pull up resistor P1-07 GPIO4 GPIO4
Мы видим, что в описании порта Р1-05 написано «1K8 pull up resistor». Это значит, что в RPI вывод этого порта подключён к шине +3,3в через резистор сопротивлением 1,8кОм. Это называется подтяжкой. А раз этот порт подключён к +3,3в, то это значит, что в любом состоянии на входе этого порта будет присутствовать логическая «1». Если мы теперь с учётом этой информации перерисуем схему подключения нашей кнопки, то она у нас будет выглядеть вот так:
Теперь мы видим, что если кнопка не нажата, то с порта мы будем считывать «1». А когда мы нажмём на кнопку, то мы замкнём вход порта с «землёй», а значит, получим на входе в порт логический «0». Т.е. нам теперь, чтобы отследить нажатие на кнопочку, нужно просто ждать, когда на порте появится «0». Собственно чем наша выше написанная программа и занимается.
Цикл while(bcm2835_gpio_lev(PIN_IN)){} повторяется до тех пор, пока функция bcm2835_gpio_lev(PIN_IN) не вернёт в программу «0». Мы эту функцию уже использовали раньше в наших программах, только там был ещё оператор инвертирования результата, в виде восклицательного знака перед функцией, что означало, что мы ждём возвращения «1». Тут же мы ждём возвращения «0», по этому от инверсии мы избавились.
При замкнутой кнопке у нас через резистор течёт небольшой ток I=U/R=3,3/1800=1,8мА. Ток этот очень маленький, что практически никак не скажется на потребляемой RPI мощности. Как видим, это очень удачный способ подключения кнопки к RPI. Мало того, что он очень помехоустойчивый (ведь помеха может навести в проводе ток, но вот избавить провод от тока она не в состоянии), так ещё мы освободили целый порт для любых других нужд. Одна кнопка- один порт.
Но, разумеется, это совершенно не значит, что наша первая схема подключения кнопки оказалась никуда негодной. Например, подключить матричную клавиатуру можно лишь первым способом. Тогда например, для подключения клавиатуры 4х4 кнопки нам понадобится всего 8 портов. Если же мы попытаемся подключить такую клавиатуру по нашему сегодняшнему способу, то мы займём 16 портов GPIO. И тем не менее, для всех остальных случаев, нашу сегодняшнюю схему подключения можно считать оптимальной.
Теперь мы знаем, что благодаря наличию подтяжки на порту Р1-05, мы можем легко использовать этот порт для подключения кнопки. Так же из таблицы следует, что подтяжкой обладает ещё и порт Р1-03. И мы можем точно так же подключить к нему ещё одну кнопочку. Возникает резонный вопрос: «А если я хочу подключить, скажем, 3 кнопочки, а портов с подтяжкой всего 2, как быть?». Ответ прост- можно выполнить подтяжку любого порта самостоятельно, впаяв дополнительный резистор на своей плате и соединив им любой другой порт GPIO с +3,3в. К стати, сопротивление этого резистора не обязано быть именно 1,8кОм. Схема будет прекрасно работать и с резистором в 10кОм. Значит нам можно поставить в цепь подтяжки порта любой резистор сопротивлением от 1,8 кОм и до 10-12кОм. Но не торопитесь бежать за паяльником. У RPI маленькие секреты ещё не закончились. Попробуем обойтись без самодельной подтяжки.
Для начала предлагаю провести маленький эксперимент. Давайте подключим нашу кнопку к портам Р1-13 и Р1-14. Р1-13, это обычный порт GPIO, а Р1-14, это GND. Собственно у нас ничего не изменилось. Всё так же наша кнопка подключена между портом GPIO и GND, за исключением того, что этот порт не имеет резистора подтяжки. Изменим нашу программу button1.c заменив определение порта Р1-05 на Р1-13. Откомпилируем программу и запустим её. Мы видим, что она не работает должным образом. Программа считает, что кнопка нажата, хотя мы к ней и не притрагивались. А всё потому, что отсутствует подтяжка порта и на нём всё время находится «0». А по логике работы нашей программы, наличие «0» на порту говорит о нажатии кнопки.
Пришло время вспомнить о том, что я говорил в первой части статей «GPIO для чайников». А говорил я о том, что внутри процессора BCM2835
порты GPIO имеют возможность подключения внутренними резисторами подтяжки как к +3,3в, так и к 0 (или GND). Так почему бы нам не воспользоваться этим чудесным свойством в своих корыстных целях? Для воплощения нашей задумки нам достаточно лишь включить подтяжку порта Р1-13 к +3,3в. Тогда наша схема подключения кнопки автоматически превратится в такую:
По сути, наша схема ничуть не изменилась. Только подтягивающий резистор переместился внутрь нашего процессора BCM2835.
Для включения подтягивающего резистора существует функция
void bcm2835_gpio_set_pud (uint8_tpin, uint8_t pud)
где:
pin - номер порта, к которому мы применяем данную функцию;
pud - управляющая команда, включающая нужный нам режим подтяжки (0- подтяжка отключена, 1- подтяжка к GND, 2- подтяжка к +3,3в).Ну что ж, добавим эту функцию в нашу программу. Теперь она должна выглядеть так:
// Проверка работоспособности кнопки, подключённой // к порту Р1_13 и GND. // Компиляция: gcc -o button2 button2.c -lbcm2835 -lrt // Исполнение: sudo ./button2 #include 'stdio.h' #include 'bcm2835.h' #define PIN_IN RPI_GPIO_P1_13 //#define PIN_IN RPI_V2_GPIO_P1_13 // Для RPI ревизии v2 раскомментировать эту строку и удалить предыдущую int main() { if (!bcm2835_init()) // Инициализация GPIO return 1; //Завершение программы, если инициализация не удалась bcm2835_gpio_fsel(PIN_IN, BCM2835_GPIO_FSEL_INPT); // Порт на ввод bcm2835_gpio_set_pud(PIN_IN, 2); // Включаем подтяжку порта к +3,3в printf("Ждём нажатия на кнопку.\n"); while(bcm2835_gpio_lev(PIN_IN)) // Повторяем все действия, заключённые в скобки {} пока не будет нажата кнопка {} printf("Кнопочка нажата!\n"); bcm2835_gpio_set_pud(PIN_IN, 0); // Отключаем подтяжку порта return (bcm2835_close ()); // Выход из программы }
Сохраним нашу программу под именем button2.c , скомпилируем и запустим. Всё, теперь наша программа адекватно реагирует на нажатие кнопки.
Ну вот, теперь вы знаете ещё полтора способа подключения кнопки к RPI.
Настало время испытать наши знания в деле. На этот раз мы не будем играть с моргающими светодиодами. Наших знаний уже вполне достаточно, чтобы превратить RPI в самостоятельное, работающее, законченное устройство, которое сможет выполнять уже полезную работу. Предлагаю превратить наш RPI в интрнет-радио! Причём это «радио» должно быть автономным, т.е. обходиться без клавиатуры, мыши и монитора. А управлять мы им будем при помощи кнопочек, которые мы только что научились подключать к RPI.
Некоторые пояснения.
В связи с тем, что библиотека bcm2835 периодически обновляется, то в неё вносятся изменения, которые могут повлиять на работоспособность ранее написанных примеров.
Так, на сегодняшний день доступна библиотека версии 1.16.
Если вы установили себе библиотеку более новой версии, чем 1.8 (установка которой описана в первой статье "GPIO для чайников"), то у вас может возникнуть проблема с компиляцией написанных программ.
Например при компиляции может вылезти сообщение:
/usr/local/lib/libbcm2835.a(bcm2835.o): In function `bcm2835_delayMicroseconds': /home/pi/bcm2835-1.15/src/bcm2835.c:350: undefined reference to `clock_gettime' /home/pi/bcm2835-1.15/src/bcm2835.c:360: undefined reference to `clock_gettime' collect2: ld returned 1 exit status
Чтобы обойти эту проблемму, достаточно при компиляции добавить опцию -l rt
Т.е., если раньше мы компилировали нашу программу button командой:
gcc -o button button.c -l bcm2835
то теперь нужно писать так:
gcc -o button button.c -lbcm2835 -lrt
Просто автор библиотеки в более новых версиях модифицировал функцию bcm2835_delayMicroseconds, которая теперь учитывает скорость выполнения команд процессором и ей требуется обращение к real-time функциям.
Если вы владелец Raspberry PI Version2:
Для ревизии платы Raspberry PI v2 необходимо изменять определения портов GPIO.
Например, если мы для ревизии v1 писали определение для порта Р1_03 так:
#define PIN RPI_GPIO_P1_03
то для ревизии v2 эта строка должна выглядеть вот так:
#define PIN RPI_V2_GPIO_P1_03
Т.е. для определения любых портов ревизии v2 нужно добавлять в запись "_V2" между "RPI" и "_GPIO"
В прочем, это необходимо только тогда, когда вы используете в своих проектах порты с номерами: Р1-03, Р1-05 и Р1-13.
Для других портов это не имеет никакого значения, какое определение вы используете, т.к. остальные порты GPIO совпадают в обеих ревизиях.
Зато в ревизии v2 вывели дополнительно 4 новых порта на разъём Р5. Этих портов нет в ревизии v1 вобще. Обладатели же ревизии v2 могут использовать эти порты абсолютно так же, как и прочие. Номера этих портов: Р5-03, Р5-04, Р5-05 и Р5-06.
Соответственно псевдонимами этих портов будут:
RPI_V2_GPIO_P5_03 RPI_V2_GPIO_P5_04 RPI_V2_GPIO_P5_05 RPI_V2_GPIO_P5_06
-
Нравятся мне ваши статьи. Разжёвано так всё всегда хорошо.
Вам в школы/университеты надо идти преподавать -
Спасибо. С нетерпением ждем продолжение.
-
Так живо пишете, хотелось бы про прерывания статью, видел пример с прерываниями, вот функция
if (bcm2835_gpio_eds(PIN)) { // Now clear the eds flag by setting it to 1 bcm2835_gpio_set_eds(PIN); printf("low event detect for pin 15\n"); } // wait a bit delay(500);
Только ну незнай, тут тоже нужно опрашивать через некоторое время bcm2835_gpio_set_eds, а хотелось бы, чтобы например в режиме ожидания
while (1) { delay(500); }
Инициализировалось это самое прерывание(на кнопочку например нажали) и программа заканчивает обрабатывать предыдущий delay и САМА(без опроса) запускает функцию ту которую мы обозначили.
-
Не совсем понял почему, когда нажимаем кнопку, то с порта P1-05 мы будем считывать ноль? Ведь на него же подается напряжение +3.3 В
-
3.3В присутствует всегда, это логическая 1, а когда конпка нажимается, то на порту появляется логический 0, т.к. 3,3В ушли на землю.
-
По схеме GPIO --1K8 pull up resistor-- имеют только P1-03 и P1-05... P1-13 не имеет внутренней подтяжки.. Соответственно включать нечего..
-
А точно подтяжка у P1-05 через 1K8 pull up resistor на 3,3 В.. Или через 1K8 pull up на GND. тэ е. Куда именно подтяжка на землю или на питание 3.3 В?
-
Вопрос закрыт. Up значит вверх...к питанию...
-
Подскажите чайнику, перерыл все, не могу найти пример программы, чтобы зажигать светодиод от нажатия кнопки, но чтобы он не тух когда кнопку отпускаешь, а тух уже при следующем нажатии. И так далее бесконечно раз…
За ранее спасибо! -
Схема подключения кнопки в самом начале — это я даже не знаю как назвать. Такое даже любителю в голову не придёт. А уж в качестве «познавательного» материала это вообще нельзя показывать, это не может быть учебным материалом. Вы бы лучше это убрали и не позорились!
И где защита от дребезга контактов кнопки? В примере при нажатии кнопки просто выход из программы — прокатит, но если вы будете управлять программой, то такие фортели получите, мама не горюй! Стыдно должно быть такие «учебные» материалы выкладывать. -
Есть ли разница как работать с пином: 3.3В + «Пин» или «Пин» + «Земля»?
-
То, что логика инвертируется — понятно.
-
Всем привет.
есть raspberry pi3 нужно собрать монтажную плату и подключить пару кнопок.
за вознаграждение.
пишите если у кого есть желание. -
Блин.. да какие же суки кругом статьи пишут.. открываешь, читаешь, надо ко ко ко два резистора, поддяжку туда, поддтяжку сюда, иначе на землю замкнёт всё сгорит. В мне 4 кнопки надо, так там мешок резисторов погони, весь вечер их искал. А первая мысль, какой даун так схемы делает, в ардуино конечно тоже резисторы в каждую щель хотят, но никогда с ними не возился, всё так подключанию, ничего нигде не горело.. А тут на, особенная схема, помрёшь пока подключишь..
А не деле вот оно, как всё просто.