#python #raspberry-pi #gpio
#python #raspberry-pi #gpio
Вопрос:
В настоящее время у меня есть 2 кнопки, подключенные к моему Raspberry Pi (это те, в которых есть кольцевые светодиоды), и я пытаюсь выполнить этот код
#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17, GPIO.OUT) #green LED
GPIO.setup(18, GPIO.OUT) #red LED
GPIO.setup(4, GPIO.IN, GPIO.PUD_UP) #green button
GPIO.setup(27, GPIO.IN, GPIO.PUD_UP) #red button
def remove_events():
GPIO.remove_event_detect(4)
GPIO.remove_event_detect(27)
def add_events():
GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)
def red(pin):
remove_events()
GPIO.output(17, GPIO.LOW)
print "red pushed"
time.sleep(2)
GPIO.output(17, GPIO.HIGH)
add_events()
def green(pin):
remove_events()
GPIO.output(18, GPIO.LOW)
print "green pushed"
time.sleep(2)
GPIO.output(18, GPIO.HIGH)
add_events()
def main():
while True:
print "waiting"
time.sleep(0.5)
GPIO.output(17, GPIO.HIGH)
GPIO.output(18, GPIO.HIGH)
GPIO.add_event_detect(4, GPIO.FALLING, callback=green, bouncetime=800)
GPIO.add_event_detect(27, GPIO.FALLING, callback=red, bouncetime=800)
if __name__ == "__main__":
main()
На первый взгляд это выглядит как довольно простой скрипт. При обнаружении нажатия кнопки:
- удалите события
- распечатать сообщение
- подождите 2 секунды, прежде чем добавлять события и снова включать светодиод
Обычно это отлично работает, когда я нажимаю зеленую кнопку. Я пробовал это несколько раз подряд, и это работает без сбоев. Однако с красным цветом он хорошо работает в первый раз и во второй раз, но после завершения второго цикла red (pin) скрипт просто останавливается.
Учитывая, что оба события довольно похожи, я не могу объяснить, почему он выходит из строя в конце 2-й красной кнопки.
РЕДАКТИРОВАТЬ: я изменил контакты с красного и зеленого соответственно (либо полностью на разные контакты, либо поменял их местами). В любом случае, код красной кнопки (на самом деле теперь зеленая кнопка) всегда вызывает ошибку. Так что, похоже, это не проблема с физической красной кнопкой и не проблема с выводом, это просто оставляет код виноватым…
Комментарии:
1. Возможно, один из
GPIO.output
вызовов вызвал исключение, а затемadd_events()
больше никогда не вызывался?2. Спасибо за вашу мысль по этому вопросу. Я добавил предложения except, но они не были запущены. Похоже, это было не так.
3. Это также не объясняет, почему он работает хорошо один раз, но всегда выходит из строя в конце второго цикла…
4. Этот код не показывает никакой другой возможности, которую я вижу. Это весь код, который у вас есть? Вы пробовали другие выходные контакты (кроме 17 и 18)? Вы пробовали другие входные контакты (кроме 4 и 27)? Что произойдет, если вы чередуете 1 зеленый щелчок — 1 красный щелчок и т. Д. Он по-прежнему перестает отвечать после второго красного щелчка или после второго зеленого щелчка? Вы можете видеть отпечатки на каком-нибудь последовательном мониторе? Если да, добавьте больше (например, после каждой строки), чтобы вы знали точный путь до возникновения проблемы.
5. Это серьезно весь код, который у меня есть. Я также пробовал разные контакты, да. Результат кажется потрясающе одинаковым. Я также чередовал, кажется, что он всегда останавливается на втором цикле красного, независимо от того, что я пытаюсь (т. Е. 1 красный, 10 зеленых, а затем еще 1 красный) Я также добавил строки печати, чтобы увидеть, где что-то пошло не так, и он делает это вплоть до последней строки кода, так что я действительно сбит с толку…
Ответ №1:
Я смог воспроизвести вашу проблему на своем Raspberry Pi 1, модель B, запустив ваш скрипт и подключив соединительный кабель между землей и GPIO27 для имитации нажатий красной кнопки. (Это контакты 25 и 13 в моей конкретной модели Pi.)
Интерпретатор python выходит из строя из-за ошибки сегментации в потоке, предназначенном для опроса событий GPIO после red
возврата после обработки нажатия кнопки. После рассмотрения реализации модуля Python GPIO
мне становится ясно, что небезопасно вызывать обратный вызов remove_event_detect
из обработчика событий, и это вызывает сбой. В частности, удаление обработчика событий во время текущего выполнения этого обработчика событий может привести к повреждению памяти, что приведет к сбоям (как вы видели) или другим странным поведениям.
Я подозреваю, что вы удаляете и повторно добавляете обработчики событий, потому что вы обеспокоены получением обратного вызова во время нажатия кнопки. В этом нет необходимости. Модуль GPIO запускает один поток опроса для отслеживания событий GPIO и будет ждать возврата одного обратного вызова перед вызовом другого, независимо от количества событий GPIO, которые вы просматриваете.
Я предлагаю вам просто совершать вызовы по add_event_detect
мере запуска вашего скрипта и никогда не удалять обратные вызовы. Простое удаление add_events
and remove_events
(и их вызовов) из вашего скрипта исправит проблему.
Если вас интересуют подробности проблемы в GPIO
модуле, вы можете взглянуть на исходный код C для этого модуля. Взгляните на run_callbacks
и remove_callbacks
в файле RPi.GPIO-0.6.2/source/event_gpio.c
. Обратите внимание, что обе эти функции используют глобальную цепочку struct callback
узлов. run_callbacks
обходит цепочку обратного вызова, захватывая один узел, вызывая обратный вызов, а затем следуя ссылке этого узла на следующий обратный вызов в цепочке. remove_callbacks
будет проходить ту же цепочку обратного вызова и освобождать память, связанную с обратными вызовами на определенном выводе GPIO. Если remove_callbacks
вызывается в середине run_callbacks
, узел, удерживаемый в данный момент run_callbacks
, может быть освобожден (и его память может быть повторно использована и перезаписана), прежде чем последует указатель на следующий узел.
Причина, по которой вы видите эту проблему только для красной кнопки, вероятно, связана с порядком вызовов add_event_detect
и remove_event_detect
приводит к тому, что память, ранее использовавшаяся узлом обратного вызова для красной кнопки, восстанавливается для какой-либо другой цели и перезаписывается раньше, чем аналогичным образом восстанавливается память, используемая узлом обратного вызова с зеленой кнопкой. Однако будьте уверены, что проблема существует для обеих кнопок — это просто удача, что память, связанная с обратным вызовом зеленой кнопки, не изменяется до того, как последует указатель на следующий узел обратного вызова.
В целом, существует проблема отсутствия синхронизации потоков вокруг использования цепочки обратного вызова в модуле GPIO в целом, и я подозреваю, что аналогичные проблемы могут возникнуть, если remove_event_detect
или add_event_detect
вызываются во время выполнения обработчика событий, даже если события удаляются из другого потока! Я бы предположил, что автор RPi.GPIO
модуля должен использовать некоторую синхронизацию, чтобы гарантировать, что цепочка обратного вызова не может быть изменена во время выполнения обратных вызовов. (Возможно, в дополнение к проверке, изменяется ли цепочка в самом потоке опроса, pthread_mutex_lock
и pthread_mutex_unlock
может использоваться для предотвращения изменения цепочки обратного вызова другими потоками, пока она используется потоком опроса.)
К сожалению, в настоящее время это не так, и по этой причине я предлагаю вам remove_event_detect
полностью избегать вызова, если вы можете этого избежать.
Комментарии:
1. Хорошая отладка. Вы могли бы упомянуть, как вам удалось перехватить интерпретатор в segfault, но в остальном это очень полная информация. Отчасти потому, что GPIO обслуживает только одно событие за раз, не рекомендуется спать в обработчике событий. Решение состоит в том, чтобы установить время окончания события, а затем продолжить сканирование. Способ избежать нескольких событий — использовать команды test-and-swap, которые в ARMv6 (Raspberry Pi 1) и более поздних версиях являются STREX и LDREX: infocenter.arm.com/help/topic/com.arm.doc.dht0008a/… Возможно, для Pi и Python есть оболочка.