El integrado MCP2221/A
- El integrado MCP2221
- Módulo Python EasyMCP2221
- Salidas digitales
- Entradas digitales
- Entradas analógicas (ADC)
- Salidas analógicas (DAC)
- Puerto I2C
- I2C: Primeros pasos
- I2C: Slave Helper
- I2C: SMBus
- Interfaz universal por USB
- EasyMCP Workbench
- Proyecto avanzado: RTC
- Práctica con LEDs
- Conclusión
Cualquier aficionado a la electrónica encuentra muy motivador ver cómo su programa interactúa con el mundo real: ya sea encendiendo una lámpara, midiendo temperaturas o moviendo un motor. La función principal del integrado MCP2221 es hacer de interfaz USB a UART e I2C. Sin embargo, lo realmente destacable en este chip es su sencillez de uso y sus 4 pines GPIO, capaces de manejar no sólo señales digitales, sino también analógicas.
Los pines GPIO son muy habituales en los microcontroladores (léase Arduino, ESP32, PIC, etc.). La desventaja, sin embargo, de trabajar con microcontroladores es que debemos programarlos. Algunos requieren un hardware específico, como los PIC. Otros, un IDE concreto como Arduino. Y, en caso de encontrarnos algún bug, en todos ellos el proceso de depuración es complicado.
El MCP2221 o MCP2221A de Microchip nos da la posibilidad de utilizar pines GPIO directamente conectados al PC, como si fueran un periférico más.
En este artículo veremos qué puede hacer y cómo utilizarlo. Presentaremos las capacidades del integrado y sus limitaciones. Ilustraremos los usos más habituales con ayuda de ejemplos en Python. Proyectaremos una interfaz universal USB y terminaremos con algún proyecto más avanzado.
El integrado MCP2221
El MCP2221 tiene sólo 14 patillas y lo podéis encontrar en formato DIP through hole. Lo cual facilita montarlo directamente en una placa de prototipos. Lo mejor es que no requiere ningún componente adicional para ello.
Tampoco requiere drivers ya que utilizaremos una librería Python con la que manejaremos el dispositivo desde el espacio de usuario.
Contamos con 4 pines de propósito general (GPIO) que podemos emplear como:
- Entradas o salidas digitales con las que podemos leer el estado de interruptores o pulsadores, encender LEDs, accionar relés, mover un motor paso-a-paso, etc.
- Entradas analógicas (ADC) para leer potenciómetros, voltajes, intensidades, temperaturas, o cualquier otra magnitud que varíe de forma continua.
- Salida analógica (DAC) capaz de generar una tensión o forma de onda arbitraria.
- Detector de interrupciones para activar una señal cuando la entrada cambia de estado, aunque sea por un instante.
- Salida de reloj con varias frecuencias para sincronizar otros circuitos, generar ondas cuadradas, etc.
Aunque el chip se conecta directamente al puerto USB, os recomendamos usar un aislador USB. Este dispositivo crea una barrera hardware entre el controlador USB y el exterior. Tanto para la alimentación como para los datos. De esta forma no dañaréis el controlador si, por error, cortocircuitáis alguno de los cables.
El datasheet sugiere colocar al menos un condensador de desacople en la alimentación y otro de bypass en el regulador interno de 3.3V. Si bien en las pruebas puede funcionar perfectamente sin ellos, estos componentes añadirán fiabilidad a nuestros diseños.
Una vez conectado, el PC lo identifica como un dispositivo con dos interfaces: una es de tipo CDC (Communication Device Class), para la UART, equivalente a un puerto serie. Y la otra es de tipo HID (Human Interface Device). Trabajaremos sólo con esta segunda.
La limitación más importante de este chip es su velocidad de repuesta, la cual viene marcada por la especificación USB para la interfaz HID. Esta dicta un polling rate máximo de 1000Hz. En la práctica esto se traduce en que sólo podemos enviar comandos al chip y recibir respuestas 500 veces por segundo.
Es decir, la frecuencia de muestreo del ADC, del DAC o de las entradas/salida lógicas estará limitada a un máximo de 500Hz. Tampoco podremos generar o detectar eventos cuya separación temporal sea inferior a 2ms.
Por otro lado, podemos despreocuparnos de detalles como el tiempo de adquisición de ADC o el tiempo de estabilización de la tensión de salida. Puesto que todos los comandos llevan implícito este retardo.
Módulo Python EasyMCP2221
Para operar con el MCP2221/A basta con enviar los mensajes apropiados a su interfaz HID. Contamos con librerías en varios lenguajes, algunas oficiales proporcionadas por Microchip, y otras Open Source.
Easy MCP2221 es un módulo Python pensado para hacer pequeños experimentos y prácticas. De modo que cualquiera pueda usarlo fácilmente sin conocer detalles internos del integrado; con los valores por defecto más habituales y con las funciones expuestas de forma clara, bien documentada y con abundantes ejemplos.
Tenéis toda la documentación en readthedocs - Easy MCP2221. Encontraréis instrucciones de instalación, solución de problemas, descripción de las funciones, etc.
Se instala con pip
como un paquete Python.
El repositorio principal con el código y todos los ejemplos está en GitHub - EasyMCP2221.
Nada más ejecutar la librería debería reconocer el chip y mostrarnos toda su configuración.
>>> import EasyMCP2221
>>> EasyMCP2221.Device()
{
"Chip settings": {
"ADC reference value": "VDD",
"Clock output duty cycle": 25,
"Clock output frequency": "375kHz",
"DAC output value": 21,
"DAC reference value": "VDD",
"Interrupt detection edge": "both",
"Power management options": "disabled",
"USB PID": "0x00DD",
"USB VID": "0x04D8",
"USB requested number of mA": 100
},
"Factory Serial Number": "01234567",
"General Purpose IO settings": {
"GP0": {
"Default output value": 1,
"Pin designated operation": "GPIO_IN"
},
"GP1": {
"Default output value": 0,
"Pin designated operation": "GPIO_IN"
},
"GP2": {
"Default output value": 1,
"Pin designated operation": "GPIO_IN"
},
"GP3": {
"Default output value": 0,
"Pin designated operation": "GPIO_IN"
}
},
"USB Manufacturer": "Microchip Technology Inc.",
"USB Product": "MCP2221 USB-I2C/UART Combo",
"USB Serial Number": "0002596888"
}
Veamos algunos ejemplos sencillos para familiarizarnos con las funciones más comunes.
Salidas digitales
Las salidas lógicas pueden conmutar entre nivel alto y nivel bajo. Sirven para fijar valores digitales. Cuando están en estado bajo su salida es cero. Y cuando están en estado alto es igual a la tensión de alimentación. En nuestro caso, 5V.
El MCP2221/A puede gestionar hasta 25mA por cada salida. Siempre que no excedamos los 90mA entre todas. Es suficiente para encender un LED. Pero si quisiéramos excitar un relé o alimentar un motor deberíamos amplificarla por medio de un transistor.
El ejemplo clásico para mostrar el uso de las salidas digitales consiste en hacer parpadear un LED.
Tan sólo conectaremos un led con una resistencia limitadora a cualquiera de las patillas de propósito general (GP). Ya que las cuatro pueden actuar como entrada o salida lógica. Por ejemplo, GP2.
En el código configuramos el pin que queramos como salida lógica. Luego alternamos su estado de salida en bucle.
import EasyMCP2221
mcp = EasyMCP2221.Device()
mcp.set_pin_function(gp2 = "GPIO_OUT")
while True:
mcp.GPIO_write(gp2 = True)
sleep(0.5)
mcp.GPIO_write(gp2 = False)
sleep(0.5)
Hacemos parpadear un LED para demostrar que controlamos el estado de esa patilla a voluntad.
Este es el resultado:
Recordad que sólo podemos enviar un comando al chip cada 2ms. Por lo que, si no usáramos sleep, obtendríamos una frecuencia máxima de 250Hz.
Entradas digitales
Las entradas lógicas permiten leer el estado de interruptores o pulsadores. Detectan un nivel alto cuando la tensión aplicada es superior a 1.2V aproximadamente. Y detectan como nivel bajo cualquier tensión inferior a este valor.
Un ejemplo muy sencillo sería hacer una puerta lógica combinando entradas y salidas. Hemos conectado dos interruptores a las patillas GP1 y GP2, y un LED a la patilla GP3. En el bucle principal leemos las entradas, efectuamos la operación lógica, XOR en este caso, y reflejamos el estado en la salida.
El programa consistirá en:
- determinar la función de cada patilla
- leer el estado de las entradas
- fijar la salida GP3 en función de la combinación de GP1 y GP2.
mcp.set_pin_function(
gp1 = "GPIO_IN",
gp2 = "GPIO_IN",
gp3 = "GPIO_OUT")
while True:
(gp0, gp1, gp2, gp3) = mcp.GPIO_read()
mcp.GPIO_write(gp3 = gp1 ^ gp2) # XOR gate
El integrado no cuenta con resistencias internas de pull-up o pull-down. Por esta razón, cuando usemos pulsadores en lugar de conmutadores, debemos colocar una resistencia a positivo o a masa. Según el estado que queramos obtener cuanto la entrada quede flotante.
Entradas analógicas (ADC)
Si las entradas digitales permitían leer valores 0 y 1, las entradas analógicas nos permiten leer tensiones variables. Sirven de voltímetro. Para potenciómetros, sensores de luz o temperatura, etc.
El MCP2221/A cuenta con un ADC de 10 bits. Es decir, registra valores entre 0 y 1023. Siendo 0 el potencial de masa y 1024 la tensión de referencia (por defecto igual a la alimentación). Tenemos disponibles tres canales: GP1, GP2 y GP3. La entrada GP0 no dispone de ADC.
Al igual que ocurre con el resto de comandos, la tasa máxima de muestreo es 500Hz. Lo cual nos limita a usarlo sólo con señales que varían lentamente.
Como ejemplo, registraremos la tensión en los bornes de un condensador mientras este se carga o se descarga.
GP2 actúa aquí como salida lógica; a la cual hemos conectado un condensador con una resistencia en serie. Cuando GP2 esté a nivel bajo, descargará el condensador. Y cuando la llevamos a nivel alto, lo cargará.
GP3 será una entrada analógica con la que monitorizamos la tensión en los terminales del condensador a medida que este se va cargando.
El ejemplo completo lo tenéis en GitHub V_T_plot_C.py. Aquí os dejo un extracto.
mcp.set_pin_function(
gp2 = "GPIO_OUT",
gp3 = "ADC")
mcp.ADC_config()
...
print("Charging...")
mcp.GPIO_write(gp2 = True)
while ... :
Vc = mcp.ADC_read()[2]
Cuando hayamos terminado de recoger los datos, procederemos a hacer una gráfica con PyPlot:
Como ya sabréis, el tiempo que tarda un condensador en alcanzar determinado nivel viene dado por su capacidad y la resistencia en serie. Llamamos constante de tiempo RC al resultado de multiplicar ambas magnitudes.
Con un condensador de 1uF y una resistencia de 100k la constante de tiempo debería ser 100ms. Tras la primera constante de tiempo la carga debería estar en el 63% (línea verde del gráfico). Aquí dicha línea se alcanza sobre los 90ms, eso significa que la resistencia o el condensador (o ambos) tiene un valor inferior al nominal.
Salidas analógicas (DAC)
A diferencia de una salida digital, cuyos estados son 0 y 1, la salida de un DAC (Conversor de Digital a Analógico) puede tomar una gradación de valores. Hasta 32 valores distintos en este caso. Siendo 0 el valor de masa y 32 el valor de referencia (por defecto igual a la tensión de alimentación).
Tan sólo GP2 o GP3 pueden actuar como salida analógica. Aún así, el MCP2221/A cuenta con un único un DAC. Por lo que en la práctica sólo podemos utilizar GP2 o GP3. Ya que, si usamos las dos, ambas estarán al mismo valor. Con ellas, podemos generar una tensión precisa y controlada digitalmente. Servirá por ejemplo para generar una tensión de calibración, una forma de onda arbitraria, una señal de audio, etc.
Vamos a generar todos los posibles valores en un bucle:
# Use GP2 as DAC output.
mcp.set_pin_function(gp2 = "DAC")
# Configure VDD as DAC reference
mcp.DAC_config(ref="VDD")
while True:
for v in range(0,32):
mcp.DAC_write(v)
for v in range(30,0,-1):
mcp.DAC_write(v)
El resultado es una sucesión de pendientes ascendiente y descendiente con todas las posibles tensiones de salida del DAC; empezando desde 0 y llegando al 97% del valor de referencia.
También vemos cómo cada valor se mantiene durante 2ms. Al igual que el resto de comandos del MCP2221/A, la tasa de actualización el DAC es 500Hz. De ahí la pendiente escalonada.
Combinando el ADC y el DAC podríamos hacer un sencillo trazador de curvas digital.
Veamos la caída de tensión de un LED.
Usaremos GP2 como salida analógica (DAC) y GP1 y GP3 como entrada analógica.
Haremos subir la tensión en GP2 progresivamente (la cual mediremos en GP1 Vr
); mientras mediremos la tensión en los terminales del diodo Vd
.
Este es un extracto de GitHub - V_V_plot.py.
for step in range(0,32):
mcp.DAC_write(step)
(V1, _, V3) = mcp.ADC_read()
# 10 bit, 5V ref
Vr[step] = V1 / 1024 * 5
Vd[step] = V3 / 1024 * 5
Sobre la línea diagonal están los puntos para los que la tensión en el LED es igual a la tensión aplicada. O, dicho de otra forma, la caída de tensión en la resistencia es cero porque no hay paso de corriente.
Esto sucede hasta, aproximadamente, 1.4V. Una vez llegado a este umbral, la caída de tensión se estabiliza sobre los 1.6V. Aumentando poco a poco a medida que aumenta la intensidad. Como en cualquier diodo.
Os podríais preguntar por qué es necesario monitorizar la tensión de salida real del DAC con GP1 si podemos calcular su valor de antemano.
La respuesta es que debemos medirla porque el DAC tiene una impedancia de salida muy elevada. Apenas es capaz de suministrar unos pocos miliamperios. Y, no sólo eso, la impedancia de salida no es fija, sino que depende del punto de la escala en que nos encontremos.
Veámoslo en un gráfico.
Con este montaje esperamos comparar la tensión de salida real (medida en GP1) con la tensión de salida teórica de GP2.
El programa completo lo tenéis en GitHub DAC_impedance_plot.py.
Este es el resultado sin carga. Quitaremos la resistencia. Sólo para ver que ambas tensiones, la real y la esperada coinciden (dentro de la tolerancia).
Al colocar una carga, aunque sea una pequeña resistencia de 20kohm, la recta se distorsiona. Estando los valores reales siempre por debajo del valor esperado.
El DAC no debe utilizarse nunca directamente para manejar cargas como un LED, un altavoz o un motor. Porque no funcionará. Siempre debemos colocar un buffer a la salida, como veremos más adelante.
Puerto I2C
I2C es un protocolo de comunicación entre circuitos integrados muy sencillo y difundido. Para saber cómo funciona I2C os recomendamos leer El bus I2C a bajo nivel.
Cientos de chips de todo tipo implementan este protocolo: ADC, DAC, memorias, sensores (de luz, de presión, de voltaje, de temperatura, de corriente, de distancia, de aceleración, de gas, de calidad del aire, …), RTC, displays, procesadores criptográficos, controladores (de PWM, de 1-wire, de I/O…), etc.
El chip MCP2221/A es muy práctico para hablar con dispositivos I2C. No obstante, se ve limitado por el retardo de los 2ms por comando. Ya que cada transmisión I2C requiere varios comandos consecutivos.
Por ejemplo, para leer un registro por I2C es preciso enviar 5 comandos:
- obtener el estado del puerto I2C para asegurarnos de que está libre antes de empezar
- escribir en el bus la dirección del esclavo y del registro a leer
- obtener el estado del módulo I2C para saber si el esclavo respondió correctamente al comando
- leer un byte (o varios) del bus I2C y almacenarlo en el buffer interno del MCP2221
- leer el buffer interno para recuperar el/los bytes leídos
Eso son 10 ms. Independientemente de la velocidad del bus y del número de bytes.
Por esta razón, debemos leer o escribir varios bytes seguidos cuando sea posible y no hacerlo de uno en uno.
Veamos ahora algunos ejemplos.
I2C: Primeros pasos
Empecemos por el integrado PFC8591 de NXP. El mismo que ya utilizamos en Conexión GPIO de Raspberry Pi 3.
Se trata de un ADC muy sencillo. Tiene una resolución de sólo 8 bits y cuatro canales. Venden módulos con el chip, una resistencia sensible a la luz (LDR), otra sensible a la temperatura (NTC), un potenciómetro y una cuarta entrada flotante.
Conectaremos este módulo a nuestro MCP2221/A conforme al siguiente esquema:
Lo primero es averiguar si el dispositivo está bien conectado y responde.
Cada integrado I2C tiene una dirección en un rango de 0 a 127. Un escáner I2C consiste en probar a leer un byte de cada posible dirección. Si hay algún chip conectado responderá y anotaremos que en esa dirección hay algo escuchando.
No es posible, usando el MCP2221/A, llamar a una dirección I2C sin leer o escribir ningún byte.
for addr in range(0, 0x80):
try:
mcp.I2C_read(addr)
print("I2C slave found at address 0x%02X" % (addr))
except EasyMCP2221.exceptions.NotAckError:
pass
El código completo lo tenéis en GitHub - I2C_scan.py.
El PCF8591 atiende en la dirección 0x48.
$ python I2C_scan.py
Searching...
I2C slave found at address 0x48
Ahora podemos comunicarnos con él. Pero cada chip tiene unos comandos y una interfaz particulares.
Por suerte el PFC8591 es muy sencillo de utilizar. Tan sólo debemos escribir el byte de control para que incremente automáticamente el canal y después leer 4 bytes seguidos (uno por cada canal).
# Control register: .0.. .... - No DAC output
# ..00 .... - 4 individual inputs
# .... .1.. - Auto-increment channel
# .... ..01 - ADC start channel 1
mcp.I2C_write(addr, [0b0000_0101])
while True:
(ntc, ldr, flo, pot) = mcp.I2C_read(addr, 4)
print("NTC: %2d%%, LDR: %2d%%, Float: %2d%%, Pot: %2d%%" %
(ntc / 256 * 100, # Temperature
ldr / 256 * 100, # Light
flo / 256 * 100, # Floating input
pot / 256 * 100)) # Potentiometer
El ejemplo completo lo tenéis en GitHub PCF8591.py.
El resultado es:
NTC: 45%, LDR: 54%, Float: 42%, Pot: 10%
NTC: 45%, LDR: 56%, Float: 44%, Pot: 10%
NTC: 45%, LDR: 53%, Float: 42%, Pot: 10%
...
I2C: Slave Helper
Otros dispositivos I2C operan con registros. Cada chip tiene unos registros que admiten unos valores (escritura) o contienen ciertos datos (lectura). Dentro de la librería EasyMCP2221, la clase I2C Slave Helper nos facilita esta labor.
El AMG8833 8x8 IR Grid Eye es un sensor infrarrojo que contiene en su interior 64 sensores en una matriz de 8x8. Con él podríamos hacer una termocámara; aunque con una resolución de sólo 64 pixeles. Está ajustado para detectar la temperatura corporal. Por lo que es más apropiado para identificar personas con fiebre durante el COVID, o como detector de movimiento o presencia.
El módulo que estamos usando integra un regulador de tensión modelo XC6206. Lo que nos permite alimentarlo a 5V sin ningún problema.
Cada uno de los 64 sensores devuelve un valor de 2 bytes. Así pues, para obtener la imagen completa debemos leer 128 bytes desde el registro 80h
en adelante. AMG88** reference - Panasonic.
mcp.I2C_speed(400_000)
sensor = mcp.I2C_Slave(0x69)
data = sensor.read_register(0x80, 128)
El ejemplo completo está en GitHub - AMG8833.py. Incluyendo la lectura de los datos, la conversión de 11 bits + signo a float y la representación como mapa de calor.
La clase I2C Slave Helper también sirve para interactuar con una memoria EEPROM del tipo 24LCxxx.
Si bien no se organizan en registros, podemos tratarlas como tal. Siempre que, al inicializar la clase I2C_Slave, indiquemos que las direcciones de los registros serán de dos bytes, en vez de uno sólo como es habitual.
Así escribimos una cadena de bytes en una dirección cualquiera. Y acto seguido la recuperamos.
>>> mcp = EasyMCP2221.Device()
>>> eeprom = mcp.I2C_Slave(0x50, reg_bytes=2)
>>> eeprom.write_register(0x3FC0, b'Hola. Probando.')
>>> eeprom.read_register(0x3FC0, 15)
b'Hola. Probando.'
En realidad la 24LC128 no tiene 16000 registros, sino 256 páginas de 64 bytes cada una.
En binario necesitamos 8 bits para indicar la página (256) y 6 bits más para decir la posición dentro de la página (64).
Podemos escribir la dirección 3FC0
en binario 00_11111111_000000
. Así separado vemos claramente como estamos haciendo referencia al comienzo de la última página.
>>> eeprom.write_register(0b00_11111111_000000, 64 * b'A')
>>> eeprom.read_register(0b00_11111111_000000, 64)
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Los ficheros examples/eeprom2file.py y examples/file2eeprom.py sirven, respectivamente, para volcar el contenido completo de una EEPROM en un fichero y para grabar un fichero dado en la memoria.
I2C: SMBus
En un artículo anterior llamado La presión atmosférica, BMP280 describimos el BMP280. Un sensor de temperatura y presión barométrica especialmente complicado de usar.
Para leer la temperatura o la presión debíamos leer múltiples registros y combinarlos mediante una serie de operaciones aritméticas.
Por suerte, existen librerías Open Source listas para interactuar con aquellos chips más comunes. Lo normal es que dichas librerías utilicen el protocolo SMBus, semejante al I2C.
Un PC corriente no cuenta con SMBus externo, por lo que sólo podríamos ejecutarlas en una Raspberry o similar. Pero Easy MCP2221 / SMBus proporciona una clase compatible con SMBus. La cual nos permitirá utilizar estas librerías localmente en nuestra máquina.
Por ejemplo, tomemos el BME280. Un triple sensor combinado de temperatura, humedad y presión barométrica, evolución del ya mencionado BMP280.
Para manejar este chip existe una librería llamada pimoroni-bme280
.
El módulo EasyMCP2221 traducirá las órdenes SMBus a comandos I2C del MCP2221/A. Al ser todo Python, este mecanismo funciona igualmente tanto en Windows como en Linux.
Basta tomar el ejemplo all-values.py y adaptarlo para usar el SMBus proporcionado por EasyMCP2221:
from EasyMCP2221 import SMBus
from bme280 import BME280
bus = SMBus(1)
bme280 = BME280(i2c_dev=bus)
temperature = bme280.get_temperature()
pressure = bme280.get_pressure()
humidity = bme280.get_humidity()
print('{:05.2f}*C {:05.2f}hPa {:05.2f}%'.format(temperature, pressure, humidity))
Unas pocas líneas son suficientes para hacer una sencilla estación meteorológica.
17.93*C 983.76hPa 51.57%
Interfaz universal por USB
A la vista de su versatilidad, hemos decidido proyectar una Interfaz universal USB con estas características principales:
- Capaz de alimentar circuitos externos a 3.3V y 5V hasta 300mA.
- Protección contra cortocircuitos en I/O y alimentación
- Puerto I2C
- Puerto serie UART
- Sonda lógica de cuatro canales
- Salida DAC filtrada y amplificada
Dividiremos el esquema en partes para explicarlo mejor.
Inmediatamente tras la entrada de alimentación USB nos encontramos con el fusible F1. Se trata de un fusible autorrearmable de la serie PolySwitch 1206L de Littelfuse.
Este tipo de fusibles funcionan como una resistencia PTC. Es decir se calientan al paso de la corriente eléctrica. Si la corriente supera un cierto umbral, al calentarse, su resistencia interna aumenta. Lo cual provoca que se calienten más aún, en un proceso realimentado; restringiendo la intensidad lo suficiente como para no causar daños al circuito.
Este modelo soporta una corriente mantenida (Ihold) de 250mA. Significa que, con una temperatura ambiente de 25ºC, nunca se desconectará si estamos por debajo de 250mA.
Su intensidad de disparo (Itrip) es de 500mA. Lo que significa que, llegados a esta intensidad, comenzará el proceso que acabamos de describir. Empezará a calentarse hasta saltar. Más rápido cuanto más elevada es la corriente.
En paralelo con F1 encontramos el LED amarillo D1 y su resistencia en serie. Este LED sólo lucirá cuando F1 esté abierto, indicando así la condición de cortocircuito.
C1 atenuará las caídas de tensión puntuales, ocasionadas por la resistencia conjunta del cable de alimentación y de F1. Según el estándar USB, este condensador no debe superar los 10uF. De lo contrario, el consumo súbito al conectar el circuito incumpliría la especificación. En consecuencia, algún controlador podría desactivar el bus, pues consideraría el dispositivo como defectuoso.
U1 es un regulador de 3.3V tipo MCP1702 de Microchip. Capaz de entregar 250mA sostenidos y hasta 550mA en picos. Esta configuración ya la vimos en la entrada Proyectos a batería y cerveza fría. C2 es necesario para evitar auto-oscilaciones. Y C3 sirve para estabilizar la tensión de salida durante los picos de corriente mientras U1 se adapta y reacciona al cambio.
El conmutador v_select nos permite seleccionar si trabajaremos con los 5V directamente desde el USB o con los 3.3V del regulador.
La línea que hemos nombrado como VDD alimentará el integrado U4, los buffer de salida y los conectores. Pero, atención, porque las sondas lógicas se alimentarán siempre a 5V.
Las señales de I2C y UART provienen directamente del MCP2221/A, nombrado U4 en el esquema.
En el caso del bus I2C hemos colocado las resistencias R6 y R7 como pull-up a la línea de VDD, que puede ser 5 o 3.3V. El valor de estas resistencias no es crítico. Pudiendo ser cualquiera entre 1k y 10k. Aunque una resistencia mayor podría dificultar las transmisiones I2C a alta velocidad (400kHz), sobre todo si el bus supera cierta longitud.
A cada salida conectamos una sonda lógica muy simple. Se compone de un amplificador operacional tipo LM358 actuando como comparador y un par de LEDs. Las resistencias serie R2 y R8 debemos adaptarlas a nuestros LED para que el brillo de ambos colores sea similar.
U2A compara la señal de la entrada inversora con la señal de referencia que viene de RV1. Si la tensión en la entrada inversora se mantiene por debajo de la tensión de referencia, la salida de U2A estará en nivel alto. En estas condiciones sólo estará encendido el LED verde. Señalizando nivel bajo.
Cuando la tensión en la patilla inversora aumente, bien porque se ha activado la patilla correspondiente de U4 (si actúa como salida) o bien porque hemos suministrado una tensión externa (en caso de actuar como entrada), la salida de U2A se irá a nivel bajo. Activando el LED rojo, D2 y apagando el verde.
Con RV1 ajustaremos la tensión de referencia para igualarla con la que el MCP2221/A detecta como nivel alto (alrededor de 1.2V).
Esta es la única parte del circuito que se alimenta siempre a 5V. De esta forma no alteraremos la tensión de referencia ni el brillo de los LED al cambiar el voltaje de salida.
Cada conector de entrada/salida cuenta con un pin bidireccional, una salida amplificada y terminales de alimentación y masa.
El pin 2 de J5 está pensado para ser usado como entrada o salida. La resistencia R15 limita la corriente a unos 20mA en caso de consumo excesivo o cortocircuito.
Lo cual puede ocurrir muy fácilmente si por error pensamos que tenemos configurada una patilla de U4 como entrada lógica, pero en realidad la habíamos configurado como salida.
En el pin 3 hemos colocado un buffer y, por tanto, sólo puede usarse como salida.
Para el buffer de las salidas GP2 y GP3 necesitamos un operacional cuyas entradas y salida puedan abarcar el rango completo, de 0 al positivo de alimentación. Ya que debemos amplificar la tensión del DAC sin distorsionarla. Estos amplificadores se llaman rail-to-rail.
Para las salidas GP0 y GP1 no es necesario puesto que son salidas digitales. Sin embargo, tampoco podríamos usar el LM358 porque su salida máxima siempre es 1.5V menor que su alimentación.
La lógica CMOS requiere, para identificar una tensión como valor alto de forma inequívoca, que esta sea igual o superior al 70% de la alimentación. Cuando trabajamos con 5V, la salida máxima del LM358 es 3.5V, lo cual sigue siendo un valor lógico alto (aunque en el límite).
Pero cuando cambiemos a 3.3V, el máximo entregado por el LM358 será tan sólo de 1.8V. Cuando necesitamos 2.3V o superior. Por esta razón hemos elegido usar un operacional rail-to-rail para las cuatro salidas.
Proponemos el chip MCP6004 o MCP6024 también de Microchip. Es un cuádruple operacional de bajo consumo, con entrada y salida rail-to-rail y se fabrica en empaquetado DIP. Además, sus salidas están protegidas contra sobre-corrientes.
R12 y C5 forman un filtro pasa bajos con el que filtraremos el ruido del DAC.
Tanto el terminal 4 de J5 como U5 están conectados a la línea VDD, pudiendo alternar su tensión de salida entre 3.3 y 5V.
Este es el circuito terminado, montado sobre una placa perforada:
EasyMCP Workbench
Para que trabajar con este chip sea aún más sencillo, contamos con una aplicación gráfica basada en la librería EasyMCP2221.
Es muy práctica para hacer un escáner I2C rápido, medir tensiones con el ADC o fijar una salida en el DAC para alguna prueba. También para comprobar la configuración del chip y cambiarla si es conveniente.
Tenemos la opción de configurar la función de cada patilla, leer los ADC en tiempo real, variar el DAC, detectar dispositivos I2C… Además de otras funciones de las que no hemos hablado en este artículo, como visualizar la actividad I2C o UART, configurar la salida de reloj, la Interrupción al Cambio (IOC) o la gestión de energía del USB con remote wake-up.
La podéis descargar de GitHub - EasyMCP2221 Workbench GUI.
Proyecto avanzado: RTC
Hasta ahora hemos tratado proyectos sencillos. Vamos a ver dos proyectos un poco más avanzados.
El primero es aprender el cómo funciona un RTC (Real Time Clock).
Un RTC es un chip que sirve como reloj y calendario externo. Actúa de apoyo al microcontrolador principal; ya que utiliza un cristal de cuarzo para mantener su propia frecuencia y puede funcionar en ausencia de la alimentación, con una batería de respaldo.
El RTC que utilizaremos es un clásico DS1307 con interfaz I2C.
Hemos conectado al bus I2C dos elementos: el RTC y una pantalla LCD donde escribiremos la fecha y la hora.
El uso que haremos de las patillas GPIO del MCP2221/A es el siguiente:
GP0 está en modo salida digital. Desde ella alimentamos el DS1307. Cuando esté a nivel alto el integrado funcionará normalmente. Y llevándola a nivel bajo simulamos un corte de corriente.
GP1 está en modo IOC (detección de cambios). El DS1307 puede configurarse para sacar por la patilla 7 una señal de 1Hz. Esta señal será la que disparare el proceso que lee la fecha y hora, y la escribe en la pantalla LCD.
GP2 actúa en modo DAC y servirá para simular la batería de respaldo. Jugando con el valor de salida simulamos una batería cargada o descargada.
GP3 está en modo indicador de actividad I2C. El LED se encenderá sólo cuando el bus I2C esté activo. Bien sea leyendo la fecha o escribiendo en la LCD.
El código lo tenéis en GitHub - I2C Clock
Práctica con LEDs
Utilizaremos una placa basada el en chip PCA9685. Un controlador PWM de 16 canales con interfaz I2C. Lo podéis encontrar buscando un controlador para servos (aunque realmente el chip está pensado para LEDs).
Usaremos la interfaz I2C para comandar el PCA9685 y hacer efectos en una fila de LEDs.
Configuramos la patilla GP0 como entrada digital. Puede servirnos para cambiar de efecto o para encender y apagar.
Las patillas GP1, GP2 y GP3 las configuraremos como entrada analógica, conectadas a tres potenciómetros. Servirán para controlar distintos parámetros como el brillo, la velocidad o la persistencia.
Tenéis el programa en GitHub - I2C ledbar.
Este sería el resultado:
Conclusión
El MCP2221/A es un chip muy versátil. Bien sea para aprender a utilizar algún sensor. Bien para introducir a un estudiante en el mundo de la robótica y programación. O bien como pequeño laboratorio de prácticas.
Carece de la autonomía de un microcontrolador, pero a cambio nos da la simplicidad de no tener que programarlo. Y la facilidad de depurar el código en cualquier IDE.
No es el único integrado que puede hacer de puente entre USB e I2C, pero si el único que tiene entradas y salidas analógicas, no requiere componentes externos, y lo encontramos en empaquetado DIP.