Функции кнопок Python, как ни странно, не выполняют то же самое

#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()
  

На первый взгляд это выглядит как довольно простой скрипт. При обнаружении нажатия кнопки:

  1. удалите события
  2. распечатать сообщение
  3. подождите 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 есть оболочка.