Introducir demoras o retardos de tiempo en un programa es una práctica sumamente frecuente. Micropython nos ofrece distintas formas de llevarlo a cabo, algunas de las cuales veremos aquí. Analizaremos los métodos del módulo time y la diferencia entre retardos bloqueantes (blocking) y no bloqueantes (non blocking) y cómo implementarlos.
El módulo time
Uno de los primeros programas que todos hacemos cuando nos iniciamos en Micropython es poner a parpadear un led manteniéndolo encendido y apagado un instante de tiempo usando el método sleep del módulo time.
#Típico blink
from time import sleep
from machine import Pin
led = Pin (2, Pin.OUT) #Led incluido
while (True):
led.on ()
sleep (1)
led.off ()
sleep (1)
Este módulo incluye varios métodos sumamente útiles cuando tenemos que hacer retardos o mediciones de tiempo en nuestros programas. Para saber todos los métodos que incluye time (y esto es aplicable a cualquier módulo), podemos importarlo desde el Shell y luego pedir ayuda (help):
Aprovecho la ocasión para abordar un tema que a veces puede ser confuso, en relación a la forma en que se importan los módulos. Si sólo fuéramos a utilizar el método sleep, podríamos escribir:
from time import sleep
Como hemos hecho en otras oportunidades. Sin embargo, si usamos otro método del mismo módulo que no sea sleep, por ejemplo sleep_ms obtendremos un error, porque de esta manera sólo hemos importado el código que corresponde al método sleep.
Esto se soluciona fácilmente agregando el método sleep_ms al importar:
Para no tener que importar todos los métodos uno por uno, podemos escribir de la siguiente forma:
from time import *
Cuando importamos de esta manera, nos podemos referir a los métodos directamente por su nombre, tal como:
sleep (2)
sleep_ms (100)
Esto tiene un pequeño inconveniente: si importamos varios módulos, puede ocurrir que dos métodos tengan el mismo nombre, y surja un conflicto. Para evitar este problema, conviene entonces realizar la importación de esta forma:
import time
Así, se importan todos los métodos, los cuales deben ser referenciados indicando el nombre del módulo:
time.sleep (2)
time.sleep_ms (100)
Volviendo a los retardos de tiempo, time pone a nuestra disposición tres métodos básicos para realizarlos:
sleep (retardo): Hace un retardo de la cantidad indicada de segundos.
sleep_ms (retardo): Hace un retardo de la cantidad indicada de milisegundos.
sleep_us (retardo): Hace un retardo de la cantidad indicada de microsegundos.
Estos tres métodos tienen un inconveniente: Mientras se hace el retardo, no podemos realizar ninguna otra operación. Si estamos haciendo parpadear un led, por ejemplo, no podemos al mismo tiempo leer un pulsador o una tecla, porque la mayor parte del tiempo el programa está encerrado en bucles de retardo dentro de los cuales no es posible leer las entradas. Por esta causa a estas funciones se las llama bloqueantes (blocking), porque mientras funcionan “bloquean” al micro y no le permiten realizar otra tarea.
Retardos no bloqueantes
Para poder realizar otras tareas mientras hacemos un retardo, debemos emplear otra estrategia, los llamados retardos no bloqueantes (non-blocking). El módulo time incluye algunos métodos que nos pueden servir para ello:
ticks_ms (): Devuelve un contador de milisegundos que se incrementa permanentemente.
ticks_us (): Igual que el anterior, pero en microsegundos
ticks_cpu (): Igual que los anteriores, pero con una resolución mayor, ya que cuenta ciclos de reloj.
ticks_add (ticks, delta): Suma el valor delta (que puede ser positivo o negativo) a un contador de ticks. Usado en combinación con ticks_diff permite hacer retardos de tiempo.
ticks_diff (valor_viejo, valor_nuevo): Calcula la diferencia de ticks entre el valor_viejo y el valor_nuevo.
Ticks
Aunque puede variar según la implementación, en general en microcontroladores como los ESP8266 o ESP32 los contadores de ticks se inician cuando el micro se enciende (Power On Reset)
Combinando estas funciones podemos lograr retardos de tiempo no bloqueantes, como en el siguiente ejemplo:
import time
from machine import Pin
led1 = Pin (2, Pin.OUT) #Led incluido en el Nodemcu
led2 = Pin (5, Pin.OUT) #Segundo led
tecla = Pin (0, Pin.IN, pull=Pin.PULL_UP) #tecla como entrada
start = time.ticks_ms()
while (True):
if time.ticks_diff(time.ticks_ms(), start) > 1000:
print (".")
led1.value (not led1.value()) #Cambio el led
start = time.ticks_ms() #Recargo el tiempo
led2.value(tecla.value()) #El estado del led = estado de tecla
En el ejemplo, lo primero es importar los módulos a utilizar, en este caso time para los retardos y machine para el control de los pines-
A continuación se definen las entradas y salidas y se inicia un bucle sin fin. Antes de entrar al bucle se guarda en la variable start el valor actual de contador de tick_ms y dentro del mismo se hace la diferencia, usando ticks_diff con el valor de ese momento. Cuando la diferencia sea mayor a 1000 es que han pasado 1000 milisegundos (o un segundo) así que se cambia la salida del led1 incluido en la placa (si está prendido se apaga y viceversa) y se actualiza la variable start, preparándola para un nuevo ciclo de 1000 milisegundos.
Lo importante es que dentro de este bucle hay otras instrucciones, en este caso, una línea que copia el estado de la tecla en el led2, de manera que este cambie cuando se apriete la misma. Esto demuestra que el método de retardo no es bloqueante, porque se puede hacer parpadear el led1 y al mismo tiempo, actualizar el estado del led2. Esto se puede apreciar en el video:
Funciones de fecha y hora
El módulo time también cuenta con métodos que nos permiten llevar cuenta de la hora y la fecha. Sin embargo, hace falta algún mecanismo adicional para establecer la hora y fecha actual (“poner en hora” el reloj), ya sea fijándola de forma manual (a través de un teclado por ejemplo) o accediendo a un servicio por Internet. También debe tenerse en cuenta que estos valores son derivados de contadores internos del microcontrolador, que se detienen cuando éste se apaga, así que puede ser necesaria una bateria adicional de backup para mantenerlo en marcha (o utilizar un chip adicional especialmente diseñado para funcionar como RTC -reloj de tiempo real- con su propia batería).
Estos métodos son los siguientes:
time (): Devuelve el número de segundos registrados por el contador interno (que como dijimos suele iniciar al encender).
localtime (segundos): Convierte el parámetro segundos a una tupla de 8 elementos con el siguiente formato: (año, mes, dia_mes, horas, minutos, segundos, dia_semana, dia_año) . Si no se suministra segundos, lo toma del contador interno (el mismo valor que nos devuelve time () ). Los valores que puede tomar cada elemento de la tupla son los siguientes:
- año tiene cuatro dígitos (ej: 2020)
- mes de 1 a 12
- dia_mes de 1 a 31
- horas de 0 a 23
- minutos de 0 a 59
- segundos de 0 a 59
- dia_semana de 0 (Lunes) a 6 (Domingo)
- dia_año de 1 a 366
localtime toma como base el 1 de Enero de 2000 a las 0 hs. Los valores que resulten de convertir el parámetro segundos se adicionan a esta fecha. Por ejemplo, si el micro lleva un día prendido y dejamos que localtime tome el contador interno, obtendremos el día 2 de Enero de 2000.
mktime (): Realiza la operación inversa a localtime. A partir de una tupla de 8 elementos con el formato anterior devuelve la cantidad de segundos transcurridos desde el 1 de Enero de 2000. (Si le pasamos la fecha de hoy, devuelve la cantidad de segundos entre 1 de Enero de 2000 y hoy).
En este artículo les presente todos los métodos incluidos en el módulo time que nos sirven para realizar distintas operaciones relacionadas con la medición del tiempo. En otros artículos veremos el módulo RTC, que añade funcionalidades interesantes y como usar el protocolo NTP para acceder a un servicio que nos brinde la hora actual a través de Internet.
Espero que el artículo les haya sido de utilidad, cualquier consulta o sugerencia pueden hacerla en la sección de comentarios mas abajo..