viernes, 7 de julio de 2017

Controlar un botón con Python, detectar un evento del botón (Parte 2)

En un post anterior ya vimos como detectar un evento de un botón, pero muchas veces podemos ralizar una tarea de distintas maneras. En este post os voy a mostrar otra manera de ejecutar una función usando los puertos GPIO y un pequeño programa en Python para controlar botón o pulsador, aquí os dejo una imagen del que he usado yo.



Para ello voy a usar una placa protoboard, cables de conexión, 1 botón y mi RaspberryPi. Si sois nuevos en esto os aconsejo leer los anteriores posts (del uso de los GPIO en el apartado Programación/GPIO).


1. Organización del circuito

En primer lugar vamos a ver como nos debe quedar nuestro circuito para este ejercicio. Aquí os dejo una imagen para que veáis la distribución que he usado, es la misma que en el ejercicio anterior.



si no habéis leído los anteriores post os recomiendo hacerlo (sobretodo el primero, en el que detallo el uso de las conexiones y hay un apartado al final sobre el uso de las resistencias, aquí está el link). 
Bien, como podéis ver uso el pin número 6 (cable negro para tierra, etiquetado como GROUND) y el pin número 12 (cable rojo para el positivo, etiquetado como GPIO18). 


2. Código Python

2.1 Capturar la pulsación de un botón

Para empezar vamos a crear un pequeño programa en python que al ejecutarlo nos permita detectar si se ha pulsado un botón. Si no sabéis como crear un archivo con un programa en python os recomiendo leer los post sobre programación python del apartado Programación/GPIO del blog.

En primer lugar abrimos el IDLE de Python en nuestra RaspberryPi (yo uso Python3), una vez abierto creamos un nuevo fichero (en el menú File-New File) y introducimos el siguiente código

# importamos librerias
import RPi.GPIO as GPIO
import time
# indicamos el uso de  la identificacion BCM para los GPIO
GPIO.setmode(GPIO.BCM)
# configuramos el pin 18 como entrada y activamos 
# la resistencia de activacion del pin 18 con PUD_UP
# esto hará que al presionar el botón se interrumpe
# la tensión de 3,3V del pin
GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)
# bucle infinito que recoge el estado de la resistencia
# del pin18, si el estado es Falso (interrupción 3,3V) 
# mostramos el texto boton presionado
while True:
    input_state=GPIO.input(18)
    if input_state==False:
        print('Boton presionado')
        time.sleep(0.2)

click aquí para descargar el código

guardamos el archivo en una ubicación conocida (en el menú File-Save As), si es necesario apuntad la ruta por si la necesitáis usar más tarde. 

Podemos ejecutar directamente este pequeño programa presionando la tecla F5 (también se puede ejecutar des de el menú Run-Run Module)

Si todo es correcto se abre una nueva ventana de IDLE de python y empieza la ejecución, 
si presionamos el botón en la pantalla aparecerá el texto Boton presionado. Fijaos que no estamos capturando el evento de presionado del botón como en el post anterior, sino que la función dentro del bucle infinito comprueba si el estado del pin de activación ha cambiado (se ha desactivado la corriente de 3,3V) y si es así se ejecuta la función. Podéis mantener presionado el botón y por el efecto de rebote del botón veréis que la función se ejecuta más de una vez. La línea final time.sleep(0.2) sirve para evitar el rebote ya que deja en suspensión la ejecución del código durante 0.2 segundos, si queréis podéis aumentar el tiempo de latencia para mejorar el funcionamiento.
Enhorabuena, ya conocéis otra técnica para detectar la presión del botón a través del cambio de estado de un PIN. Vamos a ver algo más.

2.2 Crear un switch (on/off) con un botón

Vamos a modificar el código anterior para crear un swtich (on/off) que enciende y apaga un led. Aqui os dejo una imagen de como debe quedar el circuito




y aquí tenéis el código, en el que vamos a usar el GND, GPIO18 y GPIO23

# importamos librerias
import RPi.GPIO as GPIO
import time
# indicamos el uso de  la identificacion BCM para los GPIO
GPIO.setmode(GPIO.BCM)
# configuramos el pin 18 como entrada y activamos 
# la resistencia de activacion del pin 18 con PUD_UP
# esto hará que al presionar el botón se interrumpe
# la tensión de 3,3V del pin
GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)
#  configuramos el pin 23 como salida para el led
GPIO.setup(23,GPIO.OUT)
# definimos dos variables para guardar el estado del led
# por defecto el estado del led es False (apagado)
switch_state=False
# por defecto el estado anterior es True (encendido)
old_input_state=True # activada
# bucle infinito que recoge el estado del pin18
# cada vez que presionamos el botón,el estado del switch
# alterna entre True/False
while True:
    # guardo en una variable el estado del pin
    new_input_state=GPIO.input(18)
    # si el estado es False (presionado),y el estado
    # anterior es True cambiamos el valor del switch
    if new_input_state==False and old_input_state==True:
        switch_state=not switch_state
        # tiempo de demora para evitar rebote
        time.sleep(0.3)
    # guardamos el estado actual del GPIO18
    old_input_state=new_input_state
    # modificamos el estado del led
    GPIO.output(23,switch_state)

click aquí para descargar el código

al ejecutarlo podréis activar y desactivar el led cada vez que pulsamos el botón.

Espero que os haya gustado, nos leemos pronto.

7 comentarios:

  1. Buenas noches:
    estoy entrando en el mundo de la raspberry y he intentado por mucho hacer un pequeño programa que me imprima en la interfaz de tkinter los eventos de un boton pulsador como hacer la suma de cada ves que se pulse tengo este codigo que hace exactamente lo que quiero, pero lo hace desde un Button de tkinter, la pregunta es como puedo hacer esto desde un pulsador conectado a un GPIO(23) espero explicarme bien,

    from tkinter import*
    import RPi.GPIO as GP
    GP.setwarnings(False)

    GP.setmode(GP.BCM)
    GP.setup(2, GP.IN)#Boton
    GP.setup(23, GP.OUT)
    GP.output(23, False)

    raiz=Tk()
    cont=0
    def ledon():
    global cont
    cont+=1
    lcon.set(cont)
    print("LED button pressed")
    print(cont)
    if GP.input(23)==True:
    GP.output(23,False)
    ledButton["text"] = "PRENDER LED"
    else:
    GP.output(23,True)
    ledButton["text"] = "APAGAR LED"

    def cerrarPrograma():
    print("exit Button pressed")
    GP.cleanup()
    raiz.quit()

    raiz.title("Manejo de led desde tk")
    raiz.resizable(0,0)
    raiz.geometry("250x200")

    exitButton = Button(raiz, text="exit", command = cerrarPrograma, bg='red', height=4, width=6 )
    exitButton.pack(side=BOTTOM)

    ledButton=Button(raiz, text="PRENDER LED", command=ledon, bg='green', height=4, width=8)
    ledButton.pack()

    lcon=StringVar(value=cont)

    vercontador=Label(raiz, textvariable=lcon, bg='yellow', height=4, width=4)
    vercontador.pack()

    raiz.mainloop()

    ResponderEliminar
    Respuestas
    1. Hola, en el ejemplo, Controlar un botón con Python, detectar un evento del botón (Parte 2), el código muestra como interceptar el evento de botón presionado, en el momento en que se cumple esta situación deberías añadir el código que deseas ejecutar. El input_state es el que "captura" el estado.
      En el ejemplo, Controlar un botón con Python, detectar un evento del botón (Parte 1), también tienes un ejemplo de código, al presionar el botón se imprime en la consola "boton presionado", deberías sustituir esta línea por el código que deseas que se ejecute.

      Eliminar
  2. buen dia Alx Garcia gracias por responder
    esa parte la comprendo o mejor dicho te pregunto como capturo ese evento en la interfaz Tkinter? por ejemplo en una etiqueta Label mostrar las veses que voy presionando el boton, realmente eso es lo que quiero pero no he podido dar con el chiste mira este codigo y si te das a la tarea de correrlo veras el problema en el que me encuentro y a lo mejor me puedas ayudar a encontrar la solucion, veras que solo falta hacer el llamado de la funcion y corre bien lo que no corre es la interfaz, no muestra ventana por ningun lado y si lo quitas corre la interfaz pero por obvias razones no correra el script

    from tkinter import*
    import RPi.GPIO as GP
    import threading
    GP.setwarnings(False)

    GP.setmode(GP.BCM)
    GP.setup(2, GP.IN)#Boton
    GP.setup(23, GP.OUT)
    GP.output(23, False)
    cont=0
    def control(sel):
    while True:
    if GP.input(2):
    GP.output(23, GP.LOW)
    else:
    GP.output(23, GP.HIGH)
    GP.cleanup()

    raiz=Tk()
    raiz.title("Manejo de led desde tk")
    raiz.resizable(0,0)
    raiz.geometry("250x200")
    #raiz.attributes("-fullscreen",True)



    lcon=StringVar(value=cont)

    vercontador=Label(raiz, textvariable=lcon, bg='yellow', height=4, width=4)
    vercontador.pack()


    raiz.mainloop()

    ResponderEliminar
    Respuestas
    1. Hola, disculpa que haya tardado un poco pero voy bastante liado, partiendo del esquema con el botón y el led de este mismo ejemplo, puedes aplicar el siguiente código modificado a partir del mismo ejemplo (a mi me ha funcionado perfectamente, ya me dirás que tal)

      from tkinter import*
      # importamos librerias
      import RPi.GPIO as GPIO
      import time
      # indicamos el uso de la identificacion BCM para los GPIO
      GPIO.setmode(GPIO.BCM)
      # configuramos el pin 18 como entrada y activamos
      # la resistencia de activacion del pin 18 con PUD_UP
      # esto hará que al presionar el botón se interrumpe
      # la tensión de 3,3V del pin
      GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)
      # configuramos el pin 23 como salida para el led
      GPIO.setup(23,GPIO.OUT)
      # definimos dos variables para guardar el estado del led
      # por defecto el estado del led es False (apagado)
      switch_state=False
      # variable guarda presion del boton
      cont=0

      # funcion que se ejecuta repetidamente gracais a raiz.after
      def presionado():
      #indicamos que cont es una variable global
      global cont
      # guardo en una variable el estado del pin
      new_input_state=GPIO.input(18)
      # si el estado es False (presionado)
      if new_input_state==False:
      # tiempo de demora para evitar rebote
      time.sleep(0.3)
      # aumentamos la variable cont
      cont=cont+1
      # transformo cont en una stringVar
      lcon=StringVar(value=cont)
      # asigno el nuevo valor al label
      vercontador.configure(textvariable=lcon)

      # llamamos de nuevo a la funcion
      raiz.after(10,presionado)

      # inicializo pantalla de tkinter
      raiz=Tk()
      # nombre de la pantalla
      raiz.title("Manejo de led desde tk")
      # caracteristicas de la pantalla
      raiz.resizable(0,0)
      raiz.geometry("250x250")
      # transformo cont en una stringVar
      lcon=StringVar(value=cont)
      # inicializo un label que mostrara un numero
      vercontador=Label(raiz, textvariable=lcon, bg='yellow', height=4, width=4)
      # pongo el label en un pack() para posicionarlo
      vercontador.pack()
      # llamamos a la funcion afer por primera vez
      raiz.after(10,presionado)
      # iniciamos el programa
      raiz.mainloop()

      Eliminar
  3. muchísimas gracias corre super el código. yo ya había logrado mostrar el estado con el .after pero lo raro era que solo funcionaba bien con el GPIO 2, si lo cambiaba el script corría derecho, sin presionar el botón el led y el contador aumentaba sin parar y con este corre perfecto en cualquier gpio ademas me ahorraste la parte de averiguar el uso pull_up ya que en ves de el botón voy a conectar un sensor y me recomendaron hacer uso de esta sentencia para saber su estado.
    muchísimas gracias.

    ResponderEliminar
  4. Buenas noches, hace mucho de esta entrada. Espero que me puedas ayudar.
    Tu codigo me funciona muy bien, pero si quisiera que fueran para 16 pulsadores y 16 reles. Que solucion me darias?
    yo he echo una prueba con dos. Pero es escribir mucho codiga tontamente(soy novato)y seguro que hay una forma mas corta.

    Gracias
    Saludos

    ResponderEliminar
    Respuestas
    1. Hola Xavi, para ahorrar código deberías separar tu código en funciones. Las funciones pueden recibir parámetros y se pueden llamar tantas veces como quieras...

      Eliminar