27 октября 2017
Кравченко Виктор

Raspberry Pi 3: GPIO (#2) — библиотека RPi.GPIO: управление пинами, ШИМ, прерывания

Радиотехника и электроника Цифровые устройства Умный дом Raspberry
01
Содержание:
02

Данная статья является полным руководством по работе с одной из самых простых и популярных библиотек по взаимодействию с GPIO-выходами Raspberry Pi 3 — RPi.GPIO. В данной статье подробно разбираются способы управления пинами, генерация ШИМ-сигналов, обработка прерываний, отладка в среде разработке Python, а также подводятся итоги и делаются выводы о преимуществах и недостатках библиотеки RPi.GPIO.

03

У данной статьи есть видеоверсия:

Подписывайтесь на канал , чтобы быть в курсе обновлений!

04 На заметку:
Документацию по использованию библиотеки RPi.GPIO для Python можно найти на официальном ресурсе https://sourceforge.net/projects/raspberry-gpio-python/.
Ссылка на раздел официального ресурса языка Python — https://pypi.python.org/pypi/RPi.GPIO.
05 На заметку:
По умолчанию, в актуальной версии Raspbian OS библиотека RPi.GPIO для Python предустановлена.
06

В предыдущей статье было обосновано использование следующего шаблона на написания любых программ по работе c GPIO на Питоне:

07 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем try: # === Инициализация пинов === #pin=5 #GPIO.setmode(GPIO.BCM) #GPIO.setup(pin, GPIO.OUT, initial=1) # Здесь размещаем основной рабочий код # ... except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения finally: GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
08

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

09 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем try: # === Инициализация пинов === #pin=5 #GPIO.setmode(GPIO.BCM) #GPIO.setup(pin, GPIO.OUT, initial=1) # Здесь размещаем основной рабочий код # ... except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C
except Exception as e:
# ...
print("Other Exception") # Прочие исключения
print("- Exception message: "+str(e)) # Подробности исключения
finally: GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
10

Это необходимо сделать для простоты отладки. Дело в том, что среда разработки может указать лишь на синтаксическую ошибку, а в остальном программа будет вылетать с общим исключением. Именно внесенные изменения, помогут понять, что послужило причиной. Например, если умышленно добавить строку, которая вызовет исключение print(2/0):

11 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем try: print(2/0) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except Exception as e: # ... print("Other Exception") # Прочие исключения print("- Exception message: "+str(e)) # Подробности исключения finally: # Комментируем cleanup() так как не трогаем пины #print("CleanUp") # Информируем о сбросе пинов #GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
12

То при исполнении появится сообщение с описанием исключения:

13
14

Но как видно, сообщение далеко не полное и не указывает на место в скетче, которое вызвало исключение. Для получения полной информации необходимо воспользоваться объектом traceback:

15 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем
import sys, traceback # Импортируем библиотеки для обработки исключений
try: print(2/0) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения
print("--- Start Exception Data:")
traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback
print("--- End Exception Data:")
finally: # Комментируем cleanup() так как не трогаем пины #print("CleanUp") # Информируем о сбросе пинов #GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
16
17

Теперь получена вся необходимая информация — тип исключения, сообщение и место в скетче, спровоцировавшее исключение.

18

И в завершение добавим бесконечный цикл while(), для того, чтобы программа не завершилась сразу после исполнения:

19 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # === Инициализация пинов === #GPIO.setmode(GPIO.BCM) # ... # Здесь размещаем основной рабочий код # ... while 1: # Этот цикл нужен, чтобы программа не завершилась сразу после запуска i=0 # Бессмысленная переменная, чтобы у цикла while было тело. # Если есть код для цикла while, предыдущую строку можно удалить except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем о сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
20

Испытательный стенд

В данной статье, все примеры будут приводиться на следующем стенде (принципиальная и наглядная схемы):

21
Слева — принципиальная схема, справа — наглядная
22 Внимание:
При использовании в макетировании T-образного 40-пинового борда, после сборки схемы, не подключая питание, рекомендуется путем прозвонки мультиметром убедиться в корректности соединения — в соответствии пинов Raspberry Pi 3 и подключенного Т-образного борда.
23

Информация о плате и библиотеке RPi.GPIO

В библиотеке RPi.GPIO есть ряд функций, позволяющих получить информацию как о самой библиотеке, так и об устройстве. Получить информацию можно в самой оболочке без написания скетча, при этом предварительно не забыв импортировать саму библиотеку RPi.GPIO:

24 Python
1
2
3
import RPi.GPIO as GPIO GPIO.RPI_INFO # Информация об устройстве GPIO.VERSION # Информация о версии библиотеки
25
26

Получить конкретное свойство можно, обратившись к нему напрямую:

27 Python
1
2
GPIO.RPI_INFO['PROCESSOR'] # 'BCM2837' GPIO.RPI_INFO['P1_REVISION'] # 3. Варианты: 0 = Compute Module, 1 = Rev 1, 2 = Rev 2, 3 = Model B+/A+
28

Программно узнать версию языка Python можно так:

29 Python
1
2
3
4
import sys sys.version # или import sys; sys.version
30

Хотя вся эта информация отображается при запуске оболочки Python Shell:

31
32

В случае невозврата пинов в исходное состояние функцией cleanup(), при запуске нового скетча библиотека RPi.GPIO сгенерирует предупреждение об этом. Такого рода уведомления можно отключить функцией setwarnings():

33 Python
1
2
GPIO.setwarnings(False) # Отключить предупреждения - не рекомендуется GPIO.setwarnings(True) # Включить предупреждения
34

Инициализация пинов

Базовым навыком в работе с любым МК является манипуляция состояниями пинов и чтение этих состояний.

35

Перед всеми манипуляциями, необходимо настроить режим нумерации пиновBCM или BOARD. Делается это при помощи функции GPIO.setmode():

36 Python
1
2
3
GPIO.setmode(GPIO.BOARD) # или GPIO.setmode(GPIO.BCM) # Предпочтительно
37

Узнать, какой режим установлен, можно при помощи функции GPIO.getmode():

38 Python
1
mode = GPIO.getmode() # Вернет GPIO.BOARD, GPIO.BCM или None
39 На заметку:
Таким образом, в используемой схеме, при нумерации GPIO.BCM светодиоды подключены к пинам 14, 15, 18, а кнопки к пинам 25, 8, 7.
40

После установки режима нумерации, можно переходить к установке режима работы пина. Как обычно, возможны 2 варианта — INPUT (режим чтения состояния) и OUTPUT (режим установки состояния):

41 Python
1
2
3
4
pin=14 GPIO.setup(pin, GPIO.IN) # Режим INPUT - чтение состояния # или GPIO.setup(pin, GPIO.OUT) # Режим OUTPUT - установка состояния
42

Поскольку каждый из GPIO-пинов Raspberry Pi 3 снабжен встроенным стягивающим/подтягивающим резистором, при инициализации пина в режиме INPUT, можно в этой же команде осуществить подтяжку/стяжку этого пина, а также отключить подтягивание/стягивание:

43 Python
1
2
3
4
5
6
pin=25 GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # При установке режима пина, стягиваем его к нулю # или GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # При установке режима пина, подтягиваем его к единице # или GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF) # При установке режима пина, отключаем подтягивание/стягивание
44

В случае установки пину режима OUTPUT, функция setup() во время инициализации, позволяет задать начальное состояние пина:

45 На заметку:
Актуальная версия библиотеки RPi.GPIO (0.6.3) позволяет в качестве значения аргумента initial, вместо констант 1/0 также использовать GPIO.HIGH/GPIO.LOW или True/False.
46 Python
1
2
3
4
pin=14 GPIO.setup(pin, GPIO.OUT, initial=GPIO.HIGH) # Инициализация пина в режим OUTPUT со значением логическая единица HIGH # или GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW) # Инициализация пина в режим OUTPUT со значением логический ноль LOW
47

Чтение и установка состояний, списки и кортежи

Для того, чтобы узнать состояние пина, установленного в режим INPUT можно воспользоваться функцией input():

48 Python
1
2
pin=25 value=GPIO.input(pin) # В переменную value будет записано состояние пина 25
49

Задать состояние пина, инициализированного в режим OUTPUT, можно функцией output()

50 Python
1
2
pin=14 GPIO.output(pin, GPIO.HIGH) # Установка на 14 пине логической единицы
51

В качестве второго аргумента функции GPIO.output() можно использовать 0/GPIO.LOW/False или 1/GPIO.HIGH/True.

52

Ну и напоследок, немного синтаксического сахара. Одним вызовом функции output() можно установить значения произвольного количества пинов. Для этого, в качестве первого аргумента, в эту функцию достаточно передать массив пинов.

53 На заметку:
В Python массивы (array) в классическом понимании используются крайне редко. Вместо них используются списки (list) и кортежи (tuple), которые, в отличие от массивов, могут содержать объекты разных типов. Отличие между списками и кортежами заключается в том, что кортеж, это по сути неизменяемый список, и поэтому кортеж занимает меньше памяти.
Инициализация списка осуществляется при помощи квадратных скобок (a=[1,2]), а кортежа — при помощи круглых (a=(1,2)).

Подробно — Python Documentation — Data Structures.
54 Python
1
2
3
4
5
pins=[14, 15, 18] # Инициализация списка list # или pins=(14, 15, 18) # Инициализация кортежа tuple - так тоже корректно GPIO.setup(pins, GPIO.OUT) # setup() также работает для списка пинов GPIO.output(pins, GPIO.HIGH) # Установка логической единицы на пинах 14, 15, 18
55

Также, одним вызовом функции output() можно присвоить разные значения разным пинам, передав в качестве второго аргумента список значений:

56 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
GPIO.setmode(GPIO.BCM) pins=[14, 15, 18] # Инициализация списка пинов GPIO.setup(pins, GPIO.OUT) # Установка режима работы пинов values=[GPIO.LOW, GPIO.HIGH, GPIO.HIGH] # Список значений # или values=[0, 1, 1] # или values=[False, True, True] # или values=[GPIO.LOW, 1, True] GPIO.output(pins, values) # Устанавливаем пинам из списка, значения из списка
57

И, в конце концов, созданный ранее список (не кортеж) пинов или значений можно изменять:

58 Python
1
2
3
4
5
6
7
8
9
10
GPIO.setmode(GPIO.BCM) pins=[10, 15, 18] # Ошибочно заданный пин для демонстрации примера pins[0]=14 # Исправляем ошибку print(pins) # Выводим список и видим, что все изменения отразились в списке values=[1, 0, 1] # Задаем значения для пинов GPIO.setup(pins, GPIO.OUT) # Все пины в режим OUTPUT GPIO.output(pins, values) # 14 и 18 пины в HIGH, 15 пин - LOW)
59

Также в функцию output() можно сразу передавать параметры — списки/кортежи:

60 Python
1
2
3
GPIO.setmode(GPIO.BCM) GPIO.setup([14, 15, 18], GPIO.OUT) # Все пины в режим OUTPUT GPIO.output([14, 15, 18], [1, 0, 1]) # 14 и 18 пины в HIGH, 15 пин - LOW
61

Для установки состояния пина, можно использовать функцию чтения состояния пина:

62 Python
1
2
3
4
5
GPIO.setmode(GPIO.BCM) GPIO.setup(14, GPIO.OUT) # Все пины в режим OUTPUT while 1: GPIO.output(14, not GPIO.input(14)) # Инвертируем состояние пина time.sleep(0.2) # С паузой в 0.2 секунды
63

Функция cleanup(), предназначенная для возврата пинов в исходное состояние, может применяться не только ко всем пинам, но и к одному или нескольким конкретным пинам. Для этого, достаточно передать аргументом, номер одного пина или список пинов:

64 Python
1
2
3
4
5
GPIO.cleanup() # Вернуть все пины в исходное состояние # или GPIO.cleanup(14) # Вернуть в исходное состояние один пин # или GPIO.cleanup([14, 15, 18]) # Вернуть в исходное состояние несколько пинов
65

Теперь можно применить полученную информацию к собранной схеме (обратите внимание на 17 строку скетча — здесь применена новая структура — словарь (dictionary)):

66 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # === Инициализация пинов === GPIO.setmode(GPIO.BCM) pinsLED=[14, 15, 18] # Три светодиода pinsBtnsPullUp=[25, 8] # Две кнопки замыкаются на ноль и подтянуты к лог. единице pinsBtnsPullDown=[7] # Одна кнопка замыкается на единицу и стягивается к нулю GPIO.setup(pinsLED, GPIO.OUT, initial=0) # Все пины со светодиодами в режим OUTPUT GPIO.setup(pinsBtnsPullUp, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопки в режим INPUT, к нулю с подтяжкой к единице GPIO.setup(pinsBtnsPullDown, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # Кнопка в режим INPUT, к единице со стяжкой к нулю
dictLED={14:25,
15:8,
18:7} # Создаем словарь для установки соответствия кнопок светодиодам
while 1: for i in pinsLED: # Перебираем светодиоды # Далее находим в словаре dictLED кнопку соответствующую светодиоду # и проверяем её состояние - если кнопки, подтянутые к земле не нажаты, то светодиоды горят, иначе - гаснут # если кнопка, подтянутая к единице, не нажата, то светодиод не горит, иначе - загоряется if GPIO.input(i)!=GPIO.input(dictLED[i]): GPIO.output(i, GPIO.input(dictLED[i])) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
67

Программный ШИМ (PWM) и RPi.GPIO

Начиная с версии 0.5.2а библиотеки RPi.GPIO (актуальная на момент написания версия — 0.6.3), на всех GPIO-пинах доступна программная реализация ШИМ.

68

Качество программной реализации ШИМ при помощи библиотеки RPi.GPIO оставляет желать лучшего. Причин тому несколько:

  • максимальная доступная частота ШИМ при генерации на 1 пине составляет в среднем 8,5 КГц. При включении генерации ШИМ на дополнительных пинах, частота снижается,
  • высокий уровень джиттера (причем как фазового, так и частотного) — достигает 1%,
  • не выдерживаются ни заданная частота, ни коэффициент заполнения, даже на малых частотах (100 Гц),
  • минимальный и максимальный возможный коэффициент заполнения (скважность) составляет 5% и 95%, таким образом суммарно закрывая всего 90% фазового диапазона.

Джиттер (англ. jitter — дрожание) или фазовое дрожание цифрового сигнала данных — нежелательные фазовые и/или частотные случайные отклонения передаваемого сигнала.
69

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

70 На заметку:
Поскольку Linux, во-первых, является многозадачной, а во-вторых, не является операционной средой реального времени, не рекомендуется использовать ШИМ на пинах GPIO в ситуациях, когда требуется высокоточное ШИМ-управление оборудованием (например, сервоприводами).
71

Для того, чтобы сгенерировать ШИМ на пине нужно воспользоваться объектом PWM и двумя его методами ChangeDutyCycle (изменить скважность) и ChangeFrequency (изменить частоту):

72 Python
1
2
3
4
5
6
7
8
9
10
11
GPIO.setmode(GPIO.BCM) # Выбираем режим нумерации пинов pinPWM=14 GPIO.setup(pinPWM, GPIO.OUT) # Устанавливаем pinPWM в режим OUTPUT pwm = GPIO.PWM(pinPWM, 50) # Создаем ШИМ-объект для пина pinPWM с частотой 50 Гц pwm.start(50) # Запускаем ШИМ на пине со скважностью 50% (0-100%) # Можно использовать данные типа float - 49.5, 2.45 pwm.ChangeDutyCycle(80) # Изменяем скважность до 80% pwm.ChangeFrequency(1000) # Изменяем частоту до 1000 Гц (также можно float) pwm.stop() # Останавливаем ШИМ
73

Пример плавного изменения яркости горения (мигания) светодиода представлен в скетче (по описанным выше причинам нормально не работает):

74 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # === Инициализация пинов === GPIO.setmode(GPIO.BCM) # Выбираем режим нумерации пинов pinPWM=14 GPIO.setup(pinPWM, GPIO.OUT) # Устанавливаем pinPWM в режим OUTPUT pwm = GPIO.PWM(pinPWM, 100) # создаем ШИМ-объект для пина pinPWM с частотой 100 Гц pwm.start(10) # Запускаем ШИМ на пине со скважностью 10% (0-100%) # Можно использовать данные типа float - 49.5, 2.45 pwm.ChangeFrequency(1000) # Изменяем частоту до 10 КГц (также можно float) while 1: for i in range(0,101): pwm.ChangeDutyCycle(i) # Изменяем коэффициент запонения (скважность) от 0 до 100% time.sleep(0.2) # Пауза на 0,1 сек time.sleep(5) # Пауза на 5 сек, для осциллографа for i in range(100,-1,-1): pwm.ChangeDutyCycle(i) # Изменяем коэффициент запонения (скважность) от 100 до 0% time.sleep(0.2) # Пауза на 0,1 сек time.sleep(5) # Пауза на 5 сек, для осциллографа except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: pwm.stop() # Останавливаем ШИМ - необязательно print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
75

Тестирование ШИМ можно посмотреть в видео:

Подписывайтесь на канал , чтобы быть в курсе обновлений!

76

Прерывания (interrupts)

Начиная с версии 0.5.1 библиотеки RPi.GPIO, работа с прерываниями стала очень простой и доступной. Работа с прерываниями в библиотеке RPi.GPIO представлена 3 основными вариантами:

  • ожидание наступления события (wait_for_edge())
  • детектирование наступления события (event_detected())
  • многопоточная обработка событий в функциях обратного вызова (Threaded callbacks)

77

Теперь о каждом из них подробнее.

78

wait_for_edge()
Работу функции wait_for_edge() кратко можно описать так: остановить выполнение программы до тех пор, пока не наступит заданное событие. Синтаксис функции выглядит так:

79
Синтаксис Описание
GPIO.wait_for_edge(pin, event [, timeout=time_in_milliseconds]) pin — номер пина,
event — событие, может принимать 3 значения:
GPIO.RISING — изменение сигнала по фронту (LOWHIGH)
GPIO.FALLING — изменение сигнала по спаду (HIGHLOW)
GPIO.BOTH — изменение сигнала и по фронту, и по спаду (LOWHIGH и HIGHLOW)
time_in_milliseconds — время в миллисекундах, после истечения которого исполнение программы продолжится в любом случае.
80

Примеры использования:

81 Python
1
2
3
4
5
pin=25 GPIO.wait_for_edge(pin, GPIO.RISING) # ... # прочий код, который не исполнится, пока на 25 пине # логический ноль не сменится логической единицей
82 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
pin=25 GPIO.wait_for_edge(pin, GPIO.RISING, timeout=10000) # ... # прочий код, который не исполнится, пока на 25 пине # логический ноль не сменится логической единицей # или не пройдет 10 секунд # контроль того, что вывело из функции wait_for_edge() осуществляется так: pin_wait = GPIO.wait_for_edge(pin, GPIO.RISING, timeout=5000) if pin_wait is None: print('Timeout!') else: print('Edge detected on pin ', pin_wait)
83

Пример для общей схемы статьи. При запуске загорается первый светодиод (пин 14) и горит до тех пор, пока не будет отжата первая кнопка (пин 25). Далее светодиод гаснет на 1 секунду и загорается снова. Теперь он погаснет при наступлении одного из двух событий — если первая кнопка будет нажата или пройдет отведенное время (5 сек заданные в таймауте):

84 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # === Инициализация пинов === GPIO.setmode(GPIO.BCM) pinLED=14 # Пин светодиода pinBtn=25 # Пин кнопки GPIO.setup(pinLED, GPIO.OUT, initial=1) # Пин со светодиодом в режим OUTPUT и включаем GPIO.setup(pinBtn, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопку в режим INPUT, с подтяжкой к единице (замыкается на ноль) GPIO.wait_for_edge(pinBtn, GPIO.RISING) # Программа не будет исполняться дальше, пока с кнопки не придет фронт (отжатие, 0->1) GPIO.output(pinLED, 0) # Выключаем светодиод time.sleep(1) # Пауза на секунду, чтобы увидеть, что сработало))) # У это части задан таймаут - 5 секунд: светодиод погаснет или по нажатию кнопки, или по прошествии 5 секунд GPIO.output(pinLED, 1) # Включаем светодиод снова # Программа не будет исполняться дальше, пока с кнопки не придет спад (нажатие, 1->0) if GPIO.wait_for_edge(pinBtn, GPIO.FALLING, timeout=5000) is None: # wait_for_edge() cразу добавили в условие print('wait_for_edge() - Timeout!') # Событие не наступило - выход по таймауту else: # Событие наступило - кнопка нажата print('wait_for_edge() - Edge detected on pin ', pinBtn) GPIO.output(pinLED, 0) # Выключаем светодиод except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
85

event_detected()
Работу функции event_detected() кратко можно описать так: программа создает флаг, к которому можно обратиться в любой момент исполнения программы, для того, чтобы определить, произошло ли событие. Синтаксис функции выглядит так:

86
Синтаксис Описание
GPIO.add_event_detect(pin, event Функция начинает отслеживать событие на заданном пине, аргументы:
pin — номер пина,
event — событие, может принимать 3 значения:
GPIO.RISING — изменение сигнала по фронту (LOWHIGH)
GPIO.FALLING — изменение сигнала по спаду (HIGHLOW)
GPIO.BOTH — изменение сигнала и по фронту, и по спаду (LOWHIGH и HIGHLOW)
GPIO.event_detected(pin) Результат работы функции — флаг True/False, сигнализирующий о том, наступило ли заданное событие:
True — с начала наблюдения, заданное событие наступало
False — с начала наблюдения, заданное событие не наступало
pin — номер пина.
87

Примеры использования:

88 Python
1
2
3
4
5
pin=14 GPIO.add_event_detect(pin, GPIO.RISING) # Добавляем детектирование восходящего импульса - фронта на 5 пине # любой другой код, можно добавить сюда цикл на 5 сек для демонстрации if GPIO.event_detected(pin): # Если событие было зафиксировано, то: print('Button pressed')
89

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

90 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # === Инициализация пинов === GPIO.setmode(GPIO.BCM) pinLED1=14 # Пин светодиода pinLED2=15 # Пин светодиода pinBtn=25 # Пин кнопки замыкается на ноль и подтянут к лог. единице GPIO.setup([pinLED1, pinLED2], GPIO.OUT, initial=0) # Пины со светодиодом в режим OUTPUT, выключены GPIO.setup(pinBtn, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопку в режим INPUT, к нулю с подтяжкой к единице GPIO.add_event_detect(pinBtn, GPIO.FALLING) # Добавляем детектирование события - нажатие кнопки # Делаем произвольные манипуляции - мигаем первым светодиодом for i in range(0,10): # Если во время этого цикла (мигания светодиодом) нажать кнопку, то после этого загорится второй светодиод GPIO.output(pinLED1, 1) time.sleep(0.2) GPIO.output(pinLED1, 0) time.sleep(0.2) if GPIO.event_detected(pinBtn): # Если за время цикла мигания было нажатие, то загорится 2 светодиод GPIO.output(pinLED2, 1) GPIO.wait_for_edge(pinBtn, GPIO.FALLING, timeout=5000) # Выход по нажатию кнопки или через 5 секунд except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
91

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

92 Важно:
В скетчах на языке Python, при работе с прерываниями библиотеки RPi.GPIO, функция паузы sleep() не блокирует обработку прерываний так, как это происходит у Arduino с функцией delay().
93

Для работы с этим типом прерываний используются следующие функции:

94
Синтаксис Описание
GPIO.add_event_detect(pin, event, callback=callback_func) pin — номер пина,
event — событие, может принимать 3 значения:
GPIO.RISING — изменение сигнала по фронту (LOWHIGH)
GPIO.FALLING — изменение сигнала по спаду (HIGHLOW)
GPIO.BOTH — изменение сигнала и по фронту, и по спаду (LOWHIGH и HIGHLOW)
callback_func — функция, которая должна быть вызвана при наступлении заданного события.
GPIO.add_event_callback(pin, callback=callback_func) Функция, добавляющая дополнительный обработчик-функцию к уже существующему детектированию, где
pin — номер пина,
callback_func — функция, которая должна быть вызвана при наступлении заданного события.
GPIO.remove_event_detect(pin) Функция, удаляющая все обработчики прерываний конкретного пина, где
pin — номер пина
95

Для того, чтобы вызвать функцию-обработчик, её нужно написать. Делается это так (имена могут быть произвольными, но в соответствии с правилами именования функций в Python):

96 Python
1
2
3
4
5
def callback_func1(pin): print("Вызывается первая функция-обработчик на пине: "+str(pin)) def callback_func2(pin): print("Вызывается вторая функция-обработчик на пине: "+str(pin))
97

Схематичный пример работы с прерываниями с их обработкой в функциях обратного вызова:

98 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def callback_func1(pin): # Объявляем функцию-обработчик callback_func1 print("Вызывается первая функция-обработчик на пине: "+str(pin)) def callback_func2(pin): # Объявляем функцию-обработчик callback_func2 print("Вызывается вторая функция-обработчик на пине: "+str(pin)) # Инициализация пинов # ... # Можно сразу с детектированием, назначить функцию-обработчик GPIO.add_event_detect(pin, GPIO.RISING, callback=callback_func1) # Добавляем детектирование события с функцией-обработчиком callback_func1 # А потом функцией add_event_callback() добавлять дополнительные функции-обработчики на этот пин GPIO.add_event_callback(pin, callback=callback_func2) # Добавляем дополнительный обработчик события - функцию callback_func2 # А можно сначала добавить детектирование, а потом прикрутить обработчики GPIO.add_event_detect(pin, GPIO.FALLING) # Добавляем детектирование события - например, нажатие кнопки GPIO.add_event_callback(pin, callback=callback_func1) # Добавляем обработчик события - функцию callback_func1 GPIO.add_event_callback(pin, callback=callback_func2) # Добавляем обработчик события - функцию callback_func1 # Удалить обработчики событий конкретного пина можно функцией remove_event_detect() GPIO.remove_event_detect(pin) # Удаляем все обработчики пина
99

В качестве аргумента callback можно передавать lambda-функцию (лямбда). В данном случае необходимо иметь ввиду, что во-первых, в lambda-функцию можно записать только одно выражение и во-вторых, исполняться она будет быстрее, чем внешняя функция:

100 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # Инициализация пинов GPIO.setmode(GPIO.BCM) pinLED=14 pinBtn=25 # Кнопка замыкается на ноль и подтянута к лог. единице GPIO.setup(pinLED, GPIO.OUT, initial=0) # Пин со светодиодом в режим OUTPUT GPIO.setup(pinBtn, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопку в режим INPUT, к нулю с подтяжкой к единице
GPIO.add_event_detect(25, GPIO.BOTH, lambda pin: GPIO.output(pinLED, not GPIO.input(pin))) # При нажатии кнопки светодиод будет загораться
while 1: time.sleep(0.1) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
101

Устранение дребезга в обработке прерываний
Дополнительным бонусом функций add_event_detect() и add_event_callback() является возможность программного устранение дребезга. Для того, чтобы её использовать, необходимо при использовании каждой из них добавить в вызов аргумент bouncetime:

102
Синтаксис Описание
GPIO.add_event_detect(pin, event, callback=callback_func, bouncetime=time_in_milliseconds) ...
time_in_milliseconds — время фильтрации дребезга в миллисекундах.
GPIO.add_event_callback(pin, callback=callback_func, bouncetime=time_in_milliseconds)
103

Примеры использования:

104 Python
1
2
3
4
5
6
GPIO.add_event_detect(pin, GPIO.RISING, callback=callback_func1, bouncetime=50) # Программное устранение дребезга 50 мс GPIO.add_event_callback(pin, callback=callback_func2, bouncetime=50) # Программное устранение дребезга 50 мс # или GPIO.add_event_detect(pin, GPIO.FALLING) # Добавляем детектирование события - например, нажатие кнопки GPIO.add_event_callback(pin, callback=callback_func1, bouncetime=500) # Программное устранение дребезга 500 мс GPIO.add_event_callback(pin, callback=callback_func2, bouncetime=100) # Программное устранение дребезга 100 мс
105

Но при применении программного устранения дребезга при нажатиях кнопок, есть вероятность получить не то поведение, на которое рассчитывалось. Особенно это актуально при применении отслеживания событий по фронту и по спаду — GPIO.RISING и GPIO.FALLING. Дело в том, что борьба с дребезгом начинается после наступления события, а это значит, что с большой долей вероятности следующее прерывание будет отработано не так как нужно:

106
107

О этом нужно помнить и логику программы продумывать с учетом этого. Здесь можно посоветовать заменить событие на GPIO.BOTH и в функции реализовать уже проверку состояния пина — в этом случае программное устранение дребезга будет полезным (даже при таком подходе происходят сбои в работе):

108 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: def callback_func1(pin): # Обявляем функцию-обработчик callback_func2 if not GPIO.input(pin): # Если кнопка нажата (на пине 0), то... GPIO.output(pinLED, not GPIO.input(pinLED)) # ...меняем состояние светодиода else: i=0 # Ничего не делаем # Инициализация пинов GPIO.setmode(GPIO.BCM) pinLED=14 pinBtn=25 # Кнопка замыкается на ноль и подтянута к лог. единице GPIO.setup(pinLED, GPIO.OUT, initial=0) # Пин со светодиодом в режим OUTPUT GPIO.setup(pinBtn, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопку в режим INPUT, к нулю с подтяжкой к единице GPIO.add_event_detect(25, GPIO.BOTH, callback=callback_func1, bouncetime=200) # Устранение дребезга 200 мс while 1: time.sleep(0.1) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
109

Пример для общей схемы статьи. Первый светодиод мигает с частотой 2 Гц. Первая кнопка включает/выключает мигание первого светодиода. Вторая кнопка, в случае мигания первого светодиода, при нажатии изменяет частоту его мигания до 20 Гц. При отпускании второй кнопки, частота мигания возвращается к 2 Гц. Третий светодиод мигает с частотой 0,25 Гц — он демонстрирует, что функция паузы sleep() никак не влияет на работу прерываний. Третья кнопка, в зависимости от закомментированной строки, при нажатии либо удаляет обработчики событий, либо нажатием управляет вторым светодиодом — нажата/горит или не нажата/не горит:

110 Python
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: flagPWM=True # Флаг для контроля состояния ШИМ светодиода def change_blink1(pin): # Объявляем функцию-обработчик change_blink1 global flagPWM # Даем понять функции, что переменная не локальная, а глобальная print("Вызывается первая функция-обработчик на пине: "+str(pin)) if not flagPWM: # Если ШИМ выключен, включаем его print("Включаем мигание на пине: "+str(pin)) pwm.start(50) # Коэффициент заполнения (скважность) 50% pwm.ChangeFrequency(2) # Частота 2 Гц (2 раза в сек) else: print("Выключаем мигание на пине: "+str(pin)) pwm.stop() # Выключаем ШИМ flagPWM=not flagPWM # Инвертируем флаг def change_blink2(pin): # Объявляем функцию-обработчик change_blink2 print("Вызывается вторая функция-обработчик на пине: "+str(pin)) if not GPIO.input(pin): print("Увеличиваем частоту на пине: "+str(pin)+" до 20 Гц") pwm.ChangeFrequency(20) else: print("Возвращаем частоту на пине: "+str(pin)+" до 2 Гц") pwm.ChangeFrequency(2) def remove_all_callbacks(pin): # Объявляем функцию-обработчик remove_all_callbacks print("Удаляем все обработчики") GPIO.remove_event_detect(25) # Удалаяем все обработчики пина GPIO.remove_event_detect(8) # Удалаяем все обработчики пина GPIO.remove_event_detect(7) # Удалаяем все обработчики пина # Инициализация пинов GPIO.setmode(GPIO.BCM) pinsLED=[14, 15, 18] # Три светодиода pinsBtnsPullUp=[25, 8] # Две кнопки замыкаются на ноль и подтянуты к лог. единице pinsBtnsPullDown=[7] # Одна кнопка замыкается на единицу и стягивается к нулю GPIO.setup(pinsLED, GPIO.OUT, initial=0) # Все пины со светодиодами в режим OUTPUT GPIO.setup(pinsBtnsPullUp, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Кнопки в режим INPUT, к нулю с подтяжкой к единице GPIO.setup(pinsBtnsPullDown, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # Кнопка в режим INPUT, к единице со стяжкой к нулю pwm = GPIO.PWM(14, 2) # Создаем ШИМ-объект для пина 14 с частотой 2 Гц pwm.start(50) # Запускаем ШИМ на пине со скважностью 10% (0-100%) GPIO.add_event_detect(25, GPIO.BOTH, callback=change_blink1, bouncetime=500) # Добавляем детектирование события с функцией-обработчиком change_blink1 GPIO.add_event_detect(8, GPIO.BOTH, callback=change_blink2) # Добавляем детектирование события с функцией-обработчиком change_blink2 GPIO.add_event_detect(7, GPIO.BOTH, lambda pin: GPIO.output(15, not GPIO.input(pin))) # Добавляем детектирование события с lambda-функцией # или #GPIO.add_event_detect(7, GPIO.RISING, callback=remove_all_callbacks) # Добавляем детектирование события с функцией-обработчиком remove_all_callbacks while 1: GPIO.output(18, not GPIO.input(18)) # Мигаем светодиодом раз в 2 секунды time.sleep(2) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
111

gpio_function() или как узнать настройки порта

В библиотеке RPi.GPIO есть функция gpio_function(), которая позволяет узнать в каком режиме работает конкретный пин:

112 Python
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
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import time # Импортируем класс для работы со временем import sys, traceback # Импортируем библиотеки для обработки исключений try: # Инициализация пинов GPIO.setmode(GPIO.BCM) pins = [2, 3, 4, 17, 27, 22, 10, 9, 11, 5, 6, 13, 19, 26, 14, 15, 18, 23, 24, 25, 8, 7, 12, 16, 20, 21] # Для простоты восприятия создадим словарь с возможными значениями pin_states = {0:"GPIO.OUT", 1:"GPIO.IN", 40:"GPIO.SERIAL", 41:"GPIO.SPI", 42:"GPIO.I2C", 43:"GPIO.HARD_PWM", -1:"GPIO.UNKNOWN"} for pin in pins: state = GPIO.gpio_function(pin) print ("%s %d status: %s" % ("pin", pin, pin_states[state])) print ("Change pins mode") GPIO.setup([2,3,4], GPIO.OUT) for pin in [2,3,4]: state = GPIO.gpio_function(pin) print ("%s %d status: %s" % ("pin", pin, pin_states[state])) except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
113
114

Отладка (Debugging)

Несмотря на кажущуюся простоту, среда разработки Python предоставляет инструменты по отладке написанного кода. Для того, чтобы воспользоваться средствами отладки, необходимо расставить точки останова (breakpoints) — строки кода, в которых хотелось бы посмотреть на состояние переменных. Для этого в среде Python необходимо кликнуть правой кнопкой мыши на необходимую строку кода и в выпавшем контекстом меню выбрать пункт Set Breakpoint. После этого строка будет подсвечена желтым цветом:

115
116

После того, как все необходимые места обозначены, перед запуском, необходимо запустить Debugger (средство отладки). В оболочке Shell меню Debug → Debugger:

117
118

После того как Debugger запущен, можно запускать код — F5. Теперь, при достижении точки останова, исполнение программы остановится и в окне Debugger можно будет увидеть состояния всех переменных:

119
120

При нажатии на кнопку Go выполнение программы продолжится до перехода к следующей точке останова.

122

Похожие запросы:

  • Raspberry Pi 3 GPIO — прерывания (interrupts)
  • Raspberry Pi 3 GPIO — ШИМ (PWM)
  • Аппаратный ШИМ на Raspberry Pi 3
  • RPi.GPIO основы работы с GPIO Малинки
comments powered by HyperComments

Яндекс.Метрика