В сегодняшней статье мы рассмотрим основные нюансы и механизмы управления портами ввода-вывода GPIO платы Raspberri Pi. И, конечно же, как обычно, все проверим на практическом примере.
Все версии Raspberry Pi оснащены штыревым разъемом, на который выведены порты GPIO. Начиная с 2014-го года разъем стал 40-пиновым (ранее было 26). На версиях Pi Zero и Pi Zero W присутствует посадочное место для разъема, но физически он не распаян:
В любом случае, как и с любой другой платой, расположение сигналов на этом разъеме для конкретной модификации лучше всего проверить по официальному даташиту. Хотя на разных версиях плат 40-пиновые разъемы для совместимости идентичны. Распиновка и сам разъем выглядят следующим образом:
Порты можно разделить на несколько категорий:
Разберем по очереди... Пины 3.3V и 5V могут использоваться для питания внешних устройств, подключенных к плате. При этом ограничение по току выглядит так:
- Максимальный ток для вывода 5V определяется по формуле:
I_{5V} = I_{вх} \medspace - \medspace I_{п}
где I_{вх} - это входной ток источника, который мы используем для питания платы. А I_{п} - это ток, потребляемый самой платой, а также внешними устройствами, подключенными по USB. То есть мы подаем входной ток, часть его потребляют узлы платы, а то, что остается мы можем использовать для дополнительных устройств, подключенных к пину 5V.
- Для выводов 3.3V все проще - максимальный ток составляет 50 мА и на этом точка.
В итоге за вычетом выводов 3.3V, 5V и Ground остаются 28 пинов GPIO, помеченных на схеме зеленым. GPIO0 и GPIO1 можно считать зарезервированными (их назначение обязательно обсудим в отдельной статье). Таким образом, остается 26 выводов, которые можно использовать по-своему усмотрению. Каждый из них может быть сконфигурирован на работу в том или ином режиме. Вот, к примеру, возможные функции портов для платы Raspberry Pi 4:
То есть любой из портов ввода-вывода может выполнять до 6-ти различных функций, в зависимости от конфигурации. В частности, GPIO2 может быть использован как сигнал SDA для I2C1 (SDA1), либо, к примеру, как сигнал MOSI для интерфейса SPI3 (SPI3_MOSI).
Об I2C и SPI поговорим в ближайших статьях, а сегодня нас интересует использование GPIO в качестве "обычных" входов и выходов. В режиме выхода на пин может быть выведен высокий (3.3 В) или низкий (0 В) уровень напряжения. А, соответственно, в режиме входа мы можем проанализировать, какой уровень подан на пин - высокий или низкий - 3.3 В или 0 В. Тут важно обратить внимание, что порты не являются толерантными к 5 В, то есть подавать на вход 5 В категорически нельзя.
В режиме входа для любого из GPIO можно активировать внутреннюю подтяжку к питанию или земле (pull-up/pull-down). Если вывод настроен на использование подтяжки вверх, то это означает, что при отсутствии сигнала со входа будет считываться высокий уровень. С pull-down ситуация обратная - на входе в данном случае будет низкий уровень.
Исключением являются пины GPIO2 и GPIO3. Они имеют фиксированную подтяжку вверх, без вариантов.
В режиме выхода мы снова возвращаемся к ключевому параметру, а именно к ограничению по току. Максимальный ток для одного вывода составляет 16 мА. При этом, если используются несколько выходов, то суммарный ток не должен превышать 50 мА. Превышение допустимых значений с большой вероятностью приведет к выгоранию порта.
Собственно, давайте рассмотрим практический пример. Задействуем два вывода GPIO - один в качестве входа, второй в качестве выхода. На вход подключим кнопку, а на выход - светодиод. И реализуем программу, которая будет опрашивать сигнал на входе и по нажатию кнопки зажигать светодиод.
Выберем GPIO3 для светодиода и GPIO4 для кнопки. Схема подключения будет такой:
Давайте в двух словах разберем, что, как и зачем подключено. Начнем с кнопки. Программно активируем для GPIO4 подтяжку вверх, поэтому если кнопка не нажата мы получим на входе высокий уровень (логическую единицу). Соответственно, для того, чтобы определить нажатие кнопки, подключим ее к земле. В итоге при нажатой кнопке на входе будет низкий уровень (логический ноль). Поскольку используем внутреннюю подтяжку, в этом примере ставить дополнительный внешний резистор подтяжки не будем.
Резистор R_{1} - токоограничительный. Как вытекает из названия он нужен для ограничения тока ) Рассмотрим, что произойдет, если вывод GPIO4 будет ошибочно настроен не как вход, а как выход и на нем будет 3.3 В. При нажатии кнопки произойдет короткое замыкание 3.3 В на землю, что приведет к безвозвратному повреждению порта платы. Это в случае отсутствия резистора.
А при наличии резистора ток будет ограничен величиной:
I = \frac{3.3 \medspace В}{1000 \medspace Ом} = 3.3 \medspace мА
В данном случае получаем 3.3 мА, что вполне допустимо, и позволит сохранить порту жизнь в случае возникновения замыкания.
Итак, переходим к диоду, который подключен через резистор R_{2}. Каждому диоду соответствует своя собственная вольт-амперная характеристика (ВАХ), определяющая возможные значения прямого напряжения и тока. Рассмотрим первый попавшийся под руку светодиод, например такой - ссылка:
Открываем даташит на него и находим зависимость тока от напряжения при прямом включении:
Максимальный ток для GPIO в режиме выхода, как мы уже выяснили, составляет 16 мА. Чтобы не превысить это значение подадим на светодиод, например, 10 мА. Этому току, исходя из графика, соответствует напряжение 2 В. Теперь по закону Ома нам остается только определить величину резистора. На выходе GPIO у нас 3.3 В, на диоде должно падать 2 В, значит на резисторе остается:
U_{рез} = 3.3 \medspace В \medspace - \medspace 2 \medspace В = 1.3 \medspace В
При этом ток в цепи должен быть равен 10 мА, тогда сопротивление резистора:
R_{2} = \frac{1.3 \medspace В}{10 \medspace мА} = 130 \medspace Ом
Если точной величины под рукой нет, можно взять резистор номиналом чуть больше. В общем, суть тут одна - обеспечить режим работы, при котором ток через выход не превышает допустимые 16 мА. Для большинства светодиодов в документации просто приводятся конкретные типовые значения напряжения и тока, например 2.5 В и 15 мА. Расчет будет выглядеть точно так же, как и рассмотренный, просто значения другие.
В общем, с подключением разобрались, время переходить к программной реализации. Существует огромное многообразие возможных способов для управления GPIO, мы же сегодня остановимся на использовании python.
И для работы с портами ввода-вывода используем библиотеку/модуль RPi.GPIO. В Raspberry Pi OS он включен по умолчанию, но в случае отсутствия команда для установки такая:
sudo apt-get install python-rpi.gpio
Создаем файл gpio_test.py и добавляем в него следующий код:
import RPi.GPIO as GPIO import time GPIO_LED = 3 GPIO_BUTTON = 4 DELAY_TIME = 0.5 GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_LED, GPIO.OUT) GPIO.output(GPIO_LED, GPIO.LOW) GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP) try: while True: time.sleep(DELAY_TIME) if GPIO.input(GPIO_BUTTON) == 0: GPIO.output(GPIO_LED, GPIO.HIGH) print('led on') else: GPIO.output(GPIO_LED, GPIO.LOW) print('led off') except KeyboardInterrupt: GPIO.cleanup() print('exiting')
Пройдемся подробнее, прямо по всем строкам последовательно. Делаем import модулей для работы с GPIO и для использования временной задержки:
import RPi.GPIO as GPIO import time
Добавим переменные для хранения номеров портов и величины задержки. У нас светодиод на GPIO3, а кнопка на GPIO4. Так и определим:
GPIO_LED = 3 GPIO_BUTTON = 4 DELAY_TIME = 0.5
Далее настроим режим нумерации портов, чтобы номера соответствовали названию сигнала (например, GPIO3), а не порядковому номеру на разъеме:
GPIO.setmode(GPIO.BCM)
Настраиваем GPIO3 на работу в режиме выхода и гасим светодиод, подав низкий уровень:
GPIO.setup(GPIO_LED, GPIO.OUT) GPIO.output(GPIO_LED, GPIO.LOW)
GPIO4 - в режиме входа с подтяжкой вверх:
GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
Далее организуем вечный цикл while True, в котором и будем проверять состояние кнопки и мигать светодиодом. Только обернем его в блок try, чтобы отловить исключение, возникающее, когда пользователь завершает процесс (нажав Ctrl + C). В случае завершения выполнения программы вызываем GPIO.cleanup():
try: while True: time.sleep(DELAY_TIME) if GPIO.input(GPIO_BUTTON) == 0: GPIO.output(GPIO_LED, GPIO.HIGH) print('led on') else: GPIO.output(GPIO_LED, GPIO.LOW) print('led off') except KeyboardInterrupt: GPIO.cleanup() print('exiting')
Ну и, конечно, здесь же у нас вся полезная работа. Если кнопка нажата (а в нашей схеме этому соответствует низкий уровень/логический ноль на GPIO4), то зажигаем светодиод и выводим сообщение "led on". При отпускании кнопки - обратный процесс. Проверять кнопку будем каждые 0.5 с (DELAY_TIME).
Запускаем программу командой:
python gpio_test.py
И теперь, нажимая кнопку, можем наблюдать включение и выключение светодиода, как мы и планировали 👍