NodeMCU y MicroPython: múltiples entradas analógicas

Una de las particularidades del NodeMCU con el chip ESP8266 es tener un solo puerto analógico, a diferencia del Arduino UNO o Nano que tienen varios, esta diferencia genera que al momento de querer trabajar con más de un sensor analógico tengamos una limitante y debamos buscar alternativas.

Podemos resolver este problema utilizando diferentes métodos, por ejemplo utilizar un Arduino conectado al NodeMCU para que sense los valores de los sensores analógicos y los comunique por serie; pero se incorpora cierta complejidad y no podríamos resolver todo con MicroPython en un solo programa.

Multiplexor Analógico de 16 canales

La solución que encontré es con un pequeño chip llamado 74HC4067 que nos permite tener hasta 16 entradas analógicas por medio de una multiplexación de 4 canales digitales.

La forma de conectarlo es la siguiente:

NodeMCU PinMultiplexor Analógico
D4S0
D3S1
D2S2
D1S3
VCC3v3
GNDGND
ENGND
SIGA0

En el diagrama anterior tenemos conectado la salida de cada potenciometro a un puerto analógico de 74HC4067, en nuestro ejemplo solo utilizaremos 3 entradas para mostrar su uso. Cada sensor analógico va conectado en alguno de los puerto C0-C15.

Para poder leer cada uno de los valores de los puertos C0-C15 tenemos que poner en HIGH o LOW los puertos S0-S3 siguiendo la tabla indicada por el fabricante, se puede encontrar en el siguiente link: http://www.ti.com/lit/ds/symlink/cd74hct4067.pdf.

De esta forma si queremos leer el puerto C0 debemos poner en en LOW D4,D3,D2 y D1. Para leer el puerto C1 seria HIGH D4 y LOW D3,D2 y D1. Extendiendo esto se entiende la idea.

Si solo deseamos leer los puerto C0, C1 y C3 para poder probar el ejemplo del diagrama se puede utilizar el siguiente código de ejemplo:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from machine import Pin, ADC
import time

s0 = Pin(2, Pin.OUT)
s1 = Pin(0, Pin.OUT)
s2 = Pin(4, Pin.OUT)
s3 = Pin(5, Pin.OUT)

adc = ADC(0)

while True:

    # Se lee C0
    # Todos los pins van a LOW
    s0.off()
    s1.off()
    s2.off()
    s3.off()

    c0 = adc.read()

    # Se lee C1
    # Ponemos en HIGH S0 y el resto en LOW
    s0.on()
    s1.off()
    s2.off()
    s3.off()

    c1 = adc.read()    

    # Se lee C2
    # Ponemos en HIGH S1 y el resto en LOW
    s0.off()
    s1.on()
    s2.off()
    s3.off()

    c2 = adc.read()  

    print(c0,c1,c2)

    time.sleep_ms(250)

Si en cambio deseamos tener una forma dinámica de acceder a cada uno de los pins, podemos crear una lista con los elementos de la tabla de verdad anterior y recorrerla según el nro. de pin analógico que deseamos leer, a continuación el programa completo:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from machine import Pin, ADC
import time

s0 = Pin(2, Pin.OUT)
s1 = Pin(0, Pin.OUT)
s2 = Pin(4, Pin.OUT)
s3 = Pin(5, Pin.OUT)

adc = ADC(0)

multiplexor = [  
    [0,0,0,0], #0
    [1,0,0,0], #1
    [0,1,0,0], #2
    [1,1,0,0], #3
    [0,0,1,0], #4
    [1,0,1,0], #5
    [0,1,1,0], #6
    [1,1,1,0], #7
    [0,0,0,1], #8
    [1,0,0,1], #9
    [0,1,0,1], #10
    [1,1,0,1], #11
    [0,0,1,1], #12
    [1,0,1,1], #13
    [0,1,1,1], #14
    [1,1,1,1], #15
]

pinCx = list(range(0,15))

while True:

    # Si queremos leer los valores de todos los puertos Cx
    for pin in pinCx:
        s0.value(multiplexor[pin][0])
        s1.value(multiplexor[pin][1])
        s2.value(multiplexor[pin][2])
        s3.value(multiplexor[pin][3])

        cx = adc.read()  

        print(pin," = ",cx)

    # Se lee C2
    # Ponemos en HIGH S1 y el resto en LOW
    s0.value(multiplexor[2][0])
    s1.value(multiplexor[2][1])
    s2.value(multiplexor[2][2])
    s3.value(multiplexor[2][3])

    c2 = adc.read()  

    print("Valor de C2 = ",c2)

    time.sleep_ms(250)

Si utilizamos una función, podemos simplificar aún mas:


1
2
3
4
5
6
7
8
9
def valor_analogico(cx):
    s0.value(multiplexor[pin][0])
    s1.value(multiplexor[pin][1])
    s3.value(multiplexor[pin][3])
    s2.value(multiplexor[pin][2])

    lectura = adc.read()    

    return lectura

Para utilizar simplemente invocamos de la siguiente manera:


1
lectura = valor_analogico(2) #leemos C2

Se puede encontrar el ejemplo en github:
https://github.com/gsampallo/micropython_examples/blob/master/74HC4067/multiplexor.py

Sistema de riego sencilla: corrigiendo la fecha

Un inconveniente que surgió con la captura de las imágenes desde la cámara IP es la fecha y hora de la cámara, hasta el momento no encontré manera de setear la fecha correcta, por ende en todas las imágenes la marca de agua con la fecha y hora es incorrecta.

Alma en el fondo de la imágen

La imagen anterior se corresponde con la marca de tiempo 2019-09-04 13:20:09, sin embargo en la marca de agua figura otra fecha del 2018.

La forma que encontré hasta el momento es correr un pequeño script en Python para corregir la fecha superponiendo un recuadro negro con la fecha correcta, la cual toma del nombre del archivo; para esto dos funciones de OpenCV.

El script es sencillo consta de tres partes:
1. Lista todos los archivos png dentro de la carpeta donde se estan guardando las imágenes.


1
2
3
4
5
ruta = "C:/Users/Guillermo/Desktop/img_riego"
output = "C:/Users/Guillermo/Desktop/output/"
for file in os.listdir(ruta):
    if file.endswith(".png"):
        print(file)

Simplemente recorremos los archivos dentro de la ruta (vble que contiene el path hasta las imágenes) y evaluamos si el archivo termina con .png o no para validar que sean imágenes.


2. Abre cada archivo, agrega el rectángulo negro y el texto. El texto además identifica que nro. de días es, no muestra la fecha. Es para poder identificar rápidamente el paso del tiempo.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        img = cv2.imread(ruta+"/"+file)

        cv2.rectangle(img,(850,54),(1261,84),(0,0,0),-1)

        t = file.split("_")

        if(nombre != str(t[0])):
            nombre = str(t[0])
            nroDia = nroDia + 1

        hora = t[1].split(".")[0]
        texto = "Dia "+str(nroDia)+" "+str(hora)

        font = cv2.FONT_HERSHEY_SIMPLEX
        cv2.putText(img,texto,(850,82), font, 1,(255,255,255),2,cv2.LINE_AA)

Abrimos la imagen y almacenamos en img; agregamos el rectangulo cuya coordenamas previamente las sacamos por medio de algun soft de edicion de imagenes, en este caso siempre seran las mismas.

Evaluamos si en el nombre de la imagen cambio o no la fecha, en caso que haya cambiado, incrementamos el nro de dias en 1.

Por ultimo ponemos el texto en la imagen.


3. Guarda la imagen en una carpeta de salida, de manera de conservar el original:


1
cv2.imwrite(output+file,img)

Se puede encontrar el script completo aqui.

Basta con hacer algunos pequeños ajustes para que se pueda utilizar para cualquier tipo de foto.

El resultado es el siguiente: