Saltar al contenido

Interrupciones

Hola controleros y controleras, en esta entrada aprenderemos a programar las interrupciones con MicroPython y para eso usaremos la poderosa Raspberry Pi Pico o también el NodeMCU8266.

Antes de comenzar, te hago la invitación que aprendas a programar microcontroladores con nuestro Curso Gratuito de MicroPython.

MicroPython

Y que te suscríbas al canal si te interesa la programación de microcontroladores o la teoría del control.

Interrupciones con Micropython

En esta entrada, veremos una característica más flexible presente en la mayoría de microcontroladores como el RP2040 o los ESP.

Veremos como emplear las interrupciones o IRQs (interrupt requests) en MicroPython.

En entradas pasadas aprendimos a programar interrupciones con el Arduino y de la misma forma interrupciones con el microcontrolador PIC.

Una interrupción es un requerimiento de prioridad que se le hace al microcontrolador. En ese punto, el microcontrolador deja de hacer su tarea, va y atiende la interrupción y finalmente retorna donde estaba para continuar con su tarea.

Interrupcciones en Micropython

Tener en cuenta que siempre que se programen interrupciones con micropython estas deben ser lo más simples posible, para que el microcontrolador vuelva rápidamente a la ejecución del programa principal. 

Una buena práctica es informarle al código principal que ha ocurrido la interrupción mediante el uso de una variable global

A continuación vamos a aprender como configurar las interrupciones en la raspberry pi pico y las interrupciones en el ESP8266 pero puedes extenderlo a otro microcontrolador usando MicroPython.

La función de manejo de interrupciones debe aceptar un parámetro de tipo Pin. Este parámetro le indica al sistema cual fue el GPIO que generó la interrupción.

def handle_interrupt(pin):

En nuestro programa principal se procede a configurar el GPIO como entrada el cual se va a configurar con el llamado de interrupción.

boton = Pin(14, Pin.IN)

Con el método de micropython irq() configuramos el Pin de Entrada para que actue ante el llamado de la interrupción. Simplificadamente podemos definir dos argumentos de entrada:

boton.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)

Pero todos los parámetros del método irq de micropython son:

boton.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt, priority=1, wake=None, hard=False)

El método irq micropython acepta los siguientes argumentos, si el modo pin es, Pin.IN entonces la fuente de interrupción es el valor externo en el pin. Si el modo pin es, Pin.OUT entonces la fuente de disparo es el búfer de salida del pin. De lo contrario, si el modo pin es Pin.OPEN_DRAIN, la fuente de disparo es el búfer de salida para el estado ‘0’ y el valor del pin externo para el estado ‘1’.:

  • Trigger: define el modo de disparo. Hay 3 condiciones diferentes:
    • Pin.IRQ_FALLING (flanco de bajada): para activar la interrupción siempre que el pin pase de ALTO a BAJO;
    • Pin.IRQ_RISING (flanco de subida): para activar la interrupción siempre que el pin pase de BAJO a ALTO.
  • handler: esta es una función que será llamada cuando se detecte una interrupción, en este caso la función handle_interrupt().
  • priority establece el nivel de prioridad de la interrupción. Los valores que puede tomar son específicos del puerto, pero los valores más altos siempre representan prioridades más altas.
  • wake selecciona el modo de energía en el que esta interrupción puede despertar el sistema. Puede ser machine.IDLEmachine.SLEEPmachine.DEEPSLEEP. Estos valores también pueden combinarse con OR para hacer que un pin genere interrupciones en más de un modo de alimentación.
  • hard si es verdadero, se utiliza una interrupción de hardware. Esto reduce la demora entre el cambio de pin y la llamada al controlador. Es posible que los manejadores de interrupciones estrictas no asignen memoria. No todos los puertos admiten este argumento.

Si necesita escribir un programa que ejecute una interrupción cada vez que cambia un pin, sin importar si está subiendo o bajando, puede combinar los dos argumentos del flanco de subida y bajada usando una or con la barra vertical (|):

boton.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=handle_interrupt)
Función ZIP Python

La Función zip() en Python

Timer con Microcontrolador PIC

Timer PIC

Conteo de tiempo con utime micropython

Muchas veces cuando usamos interrupciones con nuestro microcontrolador (Raspberry Pi Pico, NodeMCU8266v3, etc) con micropython, se vuelve interesante poder medir tiempos de ejecución de la llamada del servicio de interrupción.

Además de las funciones de retardo, el método utime de Micropython nos ofrece otras funcionalidades para medir el tiempo.

Podemos comenzar, creando una variable que almacene la cantidad de milisegundos que han transcurrido desde que la biblioteca utime comenzó a contar. Para eso usamos la función de micropython utime.ticks_ms().

timer_start = utime.ticks_ms()

Esta variable nos puede servir como valor de referencia para próximas mediciones de tiempo.

Podemos usar utime.ticks_diff () para obtener la diferencia entre el momento en que se activa esta línea de código y el punto de referencia que se encuentra en la variable timer_start.

timer_elapsed = utime.ticks_diff(utime.ticks_ms(), timer_start)

Medir la Velocidad de un Motor DC con MicroPython

Para este ejemplo iremos a utilizar las interrupciones de nuestra placa Raspberry Pi Pico, o de la NodeMCU8266v3 lolin usando Micropython para medir la velocidad de un motor DC.

Para esto, vamos a colocarle un aspa a nuestro motor DC y colocaremos un encoder el cual lo vamos a realizar con leds infrarojos receptor y emisor. En este punto, puedes emplear si lo deseas ya el módulo del encoder que venden comercialmente.

Modulo encoder velocidad motor DC

En mi caso, dado que no dispongo de este sensor, voy a usar los componentes electrónicos leds infrarojos receptor y emisor (fotodiodos y fototransistores) y con un schmitt trigger 4093 para garantizar el estado lógico de la señal. Y de esa forma usar el Encoder con MicroPython para la lectura de la velocidad del motor.

Vamos a emplear el mismo circuito para controlar la velocidad de un motor DC usando la Señal de PWM con la Raspberry Pi Pico y también vamos a programar la NodeMCU ESP8266 a través de MicroPython como lo vimos en la entrada anterior.

interrupciones con micropython usando la Raspberry Pi Pico – Velocidad de Motor DC
interrupciones con micropython usando la NodeMCU8266v3 Lolin – Velocidad de Motor DC

"""
Programa de Ejemplo de PWM
Control de Giro y Velocidad de un motor DC con Puente H

by: Sergio Andrés Castaño Giraldo
controlautomaticoeducacion.com
Canal de YouTube: https://www.youtube.com/c/SergioACastañoGiraldo
"""

import machine
import utime


def encoder_handler(pin):
    global paso
    paso += 1
    
    

def main():
    global paso
    paso = 0
    
    #Placa -> Raspberry Pi Pico = True, ESP8266 = False  
    placa= False
    
    
    frequency = 10000  #10Khz
    sentido = True #Sentido derecha
    
    if placa:
        potenciometro = machine.ADC(26) #Raspberry Pi Pico ADC0
        r_pwm = machine.PWM(machine.Pin(16), frequency) #PWM derecha
        l_pwm = machine.PWM(machine.Pin(17), frequency) #PWM izquierda
        boton = machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_UP)
        encoder = machine.Pin(14, machine.Pin.IN)
        encoder.irq(trigger=machine.Pin.IRQ_FALLING, handler=encoder_handler)
    else:
        potenciometro = machine.ADC(0)  #NodeMCU8266v3 ADC0
        r_pwm = machine.PWM(machine.Pin(4), frequency) #PWM derecha
        l_pwm = machine.PWM(machine.Pin(5), frequency) #PWM izquierda
        boton = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
        encoder = machine.Pin(14, machine.Pin.IN)
        encoder.irq(trigger=machine.Pin.IRQ_FALLING, handler=encoder_handler)
        
    timer_start = utime.ticks_ms()

    while True:
        #Pregunta por el boton
        if not boton():
            utime.sleep_ms(200) #Anti-Rebote
            while not boton():
                pass
            utime.sleep_ms(200) #Anti-Rebote
            sentido = not sentido
        
        
        #Aplica el PWM al motor
        if placa:
            velocidad = potenciometro.read_u16();
            if sentido:
                r_pwm.duty_u16(velocidad)
                l_pwm.duty_u16(0)
            else:
                r_pwm.duty_u16(0)
                l_pwm.duty_u16(velocidad)
        else:
            velocidad = potenciometro.read();
            if sentido:
                r_pwm.duty(velocidad)
                l_pwm.duty(0)
            else:
                r_pwm.duty(0)
                l_pwm.duty(velocidad)
        
        """  
        # Usando únicamnete Retardo       
        utime.sleep_ms(1000)
        state = machine.disable_irq()
        rpm = paso * 60 / 2
        paso = 0
        print(rpm, 'RPM')
        machine.enable_irq(state)
        """      
           
        timer_elapsed = utime.ticks_diff(utime.ticks_ms(), timer_start)
        if timer_elapsed >= 1000:
            #Calculo de las RPM (2 aspas)
            state = machine.disable_irq()
            rpm = paso * 60 / 2
            paso = 0
            machine.enable_irq(state)
            timer_start = utime.ticks_ms()
            print(rpm, 'RPM')
                

if __name__ == '__main__':
    main()

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Comentarios (4)

Hola buenos dias

Un saludo y gracias por esta valiosa información

Tengo una duda:
Sabes cuales I/O del RPi pico pueden se pueden utilizar como interrupciones y cuantas máximo debería utilizar en mi programa

Por ejemplo si requiero medir 8 generadores de pulsos con mi rpi pico es una opción valida ?

Responder

Todos los pines GPIO se pueden configurar como un pin de interrupción externa en los siguientes cuatro cambios en el estado de los pines GPIO:
nivel alto
Nivel bajo
Flanco positivo (transición de activo bajo a activo alto)
Flanco negativo (transición de activo alto a activo bajo)

Ahora cuantas como máximo puedes activar no lo tengo claro, habría que leer en la documentación para poder verificar ese dato.
Éxitos!

Responder

hola sergio,saludos ,yo hice un codigo similar al tuyo pero siempre aparece leyendo 0 RPM,me podias colaborar,el codigo es:

import machine
import utime

# definicion de la funcion para el encoder…

def Encoder_interrupt(pin):
global contador
contador += 1

def main():
global contador
contador = 0

#Definicion de pines…

encoder = machine.Pin(14, machine.Pin.IN)
encoder.irq(trigger=machine.Pin.IRQ_FALLING, handler=Encoder_interrupt)
to = utime.ticks_ms()

while True:
Ti = utime.ticks_ms()
Tt = utime.ticks_diff(Ti, to)

if Tt >=1000 :

estado = machine.disable_irq()
contador = contador * 60
contador = 0
print (str(contador)+» rpm»)
machine.enable_irq(estado)
to = Ti

# utime.sleep_ms(200)

if __name__ == ‘__main__’:
main()

gracias por tus sugerencias de antemano…

Responder

Juan, es porque estas imprimiendo el contador justo despues de cerarlo:
contador = contador * 60
contador = 0
print (str(contador)+” rpm”)
Por eso siempre ves CERO.
Debes usar el contador para calcular las RPM con relación a la resolución de tu encoder, en mi caso, mi encoder tiene dos aspas, el calculo de las rpm es rpm = paso * 60 / 2
Y ese rpm es el que imprimo
print(rpm, ‘RPM’)

Responder