MCP23S17 Expansor de 16 bits de puertos GPIO de entrada y salida y conexión SPI

publicado en: Portada | 2

Los expansores de puertos GPIO se usan de forma muy habitual en los circuitos microcontrolados, tanto con la finalidad de aumentar las entradas-salidas, que suele ser un recurso escaso en un µC, como para poder separar (1) a nivel físico los dispositivos controlados del circuito que los controla y (2) a nivel lógico su gestión en el programa.

El MCP23S17 permite controlar un puerto de 16 bits o 2 puertos de 8 bits de entrada-salida genéricos (GPIO) gestionándolos desde un microcontrolador por medio del bus SPI. Existe un integrado similar, el MCP23017, que usa comunicaciones I2C, cuyo funcionamiento es equiparable excepto por el tipo de bus que utiliza y la velocidad de transmisión de datos que implica: mientras que el MCP23017 (I2C) puede funcionar a 100 KHz, 400 KHz o 1,7 MHz, el MCP23S17 (SPI) del que trata este texto funciona hasta a 10 MHz.

MCP23S17 Expansor de entrada y salida de GPIO de 16-Bit con bus SPI

Para avisar al microcontrolador de un cambio de estado en los puertos, el MCP23S17 dispone de dos interrupciones INTA para el puerto de 8 bits A e INTB para el puerto de 8 bits B. Estas interrupciones son configurables en señal (activa en nivel alto o bajo), en activación asociada (como una operación OR sobre ambas interrupciones) o independiente y en modo: activación por cualquier cambio o cuando sea diferente de un valor preconfigurado (el del registro DEFVAL).

El MCP23S17 puede funcionar según dos tipos de acceso. El modo byte, que no incrementa la dirección al terminar la operación, por lo que sirve para realizar accesos sucesivos a la misma dirección, y el modo secuencial, que incrementa la dirección automáticamente, por lo que sirve para alternar las direcciones automáticamente después de cada acceso.

MCP23S17 Expansor de 16 bits de puertos GPIO de entrada y salida y conexión SPI

La implementación hardware del MCP23S17 es muy sencilla, los únicos componentes pasivos que necesita para su funcionamiento son las resistencias para establecer los niveles alto o bajo por defecto. Como además existe versión DIP, es muy cómodo para aplicarlo en montajes de prueba como componente auxiliar.

En el siguiente esquema se muestran los bloques de conexión del MCP23S17. Las patillas A0, A1 y A2 establecen los tres penúltimos bits de la dirección en el bus SPI. Como la señal de reset se establece a nivel bajo, es necesario conectarla a una resistencia pull-up. La alimentación del integrado puede estar comprendida entre 1V8V y 5V5.

MCP23S17 circuito de aplicación del expansor de 16 bits de puertos GPIO de entrada y salida y conexión SPI

Tanto la configuración como las operaciones de lectura y escritura sobre el MCP23S17 se realizan accediendo a los diferentes registros organizados en pares para facilitar la configuración del puerto A y B. Excepto en el caso de IOCON, que es el registro de configuración de entrada salida principal, las direcciones de los registros dependen del bit BANK del registro IOCON. Cuando se establece el bit IOCON.BANK a nivel bajo las direcciones de los registros para el puerto A y B se alternan: primero un registro que configura el puerto A y luego el de configuración equivalente para el puerto B. Si el bit IOCON.BANK vale 1, todos los registros del puerto A se sitúan al principio y luego se encuentran todos los del puerto B. Para evitar errores que generen direcciones incorrectas, no debe cambiarse IOCON.BANK cuando el modo de direccionamiento es secuencial porque genera automáticamente una nueva dirección, que en este caso podría no ser válida, después de cada operación.

dirección registro funcionamiento
BANK=0 BANK=1
0x00 0x00 IODIRA Configura el modo como entrada o como salida. Los bits a nivel alto indican que el GPIO del puerto correspondiente (A o B) es de entrada y los bits que estén a nivel bajo en el registro indican que el GPIO es de salida.
0x01 0x10 IODIRB
0x02 0x01 IPOLA Establece la polaridad del bit del puerto. Cuando el bit está a nivel alto, el pin correspondiente GPIO invierte su valor; cuando el bit está a nivel bajo la polaridad es la normal y un valor alto en un pin corresponde con una señal a nivel alto y uno bajo a un nivel bajo.
0x03 0x11 IPOLB
0x04 0x02 GPINTENA Sirve para establecer los bits que producen la activación de la interrupción. Los bits de GPINTENA-GPINTENB a nivel alto influyen en el disparo de la interrupción y los que estén a nivel bajo no afectan a la generación de las interrupciones.
0x05 0x12 GPINTENB
0x06 0x03 DEFVALA Almacena el valor del puerto para no lanzar la interrupción si INTCON está activo. Si al comparar el estado actual del puerto el valor es diferente del configurado en DEFVALA-DEFVALB y los bits de INTCONA-INTCONB indican que su valor debe tenerse en cuenta, se dispara la interrupción.
0x07 0x13 DEFVALB
0x08 0x04 INTCONA Establece los bits del puerto que se comparan con DEFVAL para activar la interrupción cuando sean diferentes (los que estén a nivel alto). Los bits de INTCONA-INTCONB cuyo valor sea cero no se consideran al comparar el estado actual del puerto con el valor almacenado en DEFVALA-DEFVALB para disparar la interrupción.
0x09 0x14 INTCONB
0x0a 0x05 IOCON Ambos registros contienen la misma información y si se cambia el valor de uno también cambia el valor de otro. Para evitar confusiones no se les nombra como IOCONA-IOCONB sino que se hace referencia a ambos como IOCON.
Bits que determinan la configuración de entrada salida:

  • bit 7 BANK Establece la forma en la que se organizan las direcciones de los registros. Cuando el bit está a nivel alto (activo) los registros asociados a cada puerto (PORTA y PORTB) se separan en dos bancos de memoria. Cuando el bit está a nivel bajo (vale cero) las direcciones de los registros se alternan entre PORTA y PORTB (IODIRA, IODIRB, IPOLA, IPOLB…) en un único banco secuencial. Hay que asegurarse de que el modo de acceso es byte (NO secuencial) antes de cambiar el bit BANK ya que el modo de acceso secuencial podría apuntar a una dirección incorrecta al incrementarse automáticamente.
  • bit 6 MIRROR Sirve para asociar las dos señales de interrupción. Cuando el indicador está desactivado cada interrupción (INTA e INTB) funcionan por separado. Si el bit está a nivel alto (vale uno) ambas interrupciones se conectan internamente y se activan a la vez.
  • bit 5 SEQOP Al activarse, activa el modo de acceso secuencial, es decir, el puntero de dirección se incrementa automáticamente. Si el indicador está desactivado (vale cero) la dirección no se incrementa y se accede a la misma hasta que se cambie expresamente.
  • bit 4 DISSLW El el MCP23S17 no tiene efecto. En el MCP23017 Controla la tasa de cambio en el voltaje (slew rate) en SDA en comunicaciones I2C.
  • bit 3 HAEN Al activar este bit se utiliza la parte de la dirección del bus que establecen las patillas A0, A1 y A2. Si el bit está activado se atiende a los valores de las patillas A0, A1 y A2. Si el bit está desactivado se considera que la dirección se fija en 0b0100000X, es decir, los valores de A0, A1 y A2 se toman como cero.
  • bit 2 ODR Se utiliza para indicar que las patillas de interrupción están en modo colector abierto, es decir, cuando se activa, la polaridad se establece por las resistencias pull-up pull-down y no por el bit INTPOL.
  • bit 1 INTPOL Establece la polaridad de las salidas de las interrupciones (INTA e INTB). Cuando está a nivel alto, la interrupción manda una señal en alto al dispararse y cuando está a nivel bajo, la interrupción envía un nivel bajo cuando se dispara.
  • bit 0 UIMP Sin implementar, si se lee devuelve siempre el valor cero
0x0b 0x15 IOCON
0x0c 0x06 GPPUA Sirve para controlar las resistencias pull-up internas de los puertos. Cuando se establece a nivel alto un bit de entrada de un puerto (un pin) se activa la resistencia interna de 100 kΩ.
0x0d 0x16 GPPUB
0x0e 0x07 INTFA Es un indicador de los bits que han producido una interrupción. El registro establece a nivel alto los bits por los cuales se genera la interrupción. Es de solo-lectura, no de escritura.
0x0f 0x17 INTFB
0x10 0x08 INTCAPA Contiene el estado de cada puerto en el momento de producirse una interrupción (la «captura» del estado del puerto) y sirve para poder analizarlo permitiendo que la entrada cambie a otro estado mientras tanto.
0x11 0x18 INTCAPB
0x12 0x09 GPIOA Cuando se accede para leer devuelve el valor del puerto de entrada pero si se accede para escribir tiene el mismo efecto que OLAT: escribir en el latch de salida, es decir, no accede directamente al puerto de salida sino a través del latch de salida.
0x13 0x19 GPIOB
0x14 0x0a OLATA Da acceso al latch de salida del puerto correspondiente. Leer OLAT no accede directamente al puerto. Escribir en GPIO o en OLAT modifica el latch de salida que posteriormente modificará el puerto (la patilla) de salida.
0x15 0x1a OLATB

El proceso para utilizar el MCP23S17 desde un microcontrolador es muy simple, consiste en establecer las comunicaciones SPI, preparar la configuración base e ir intercambiando información (recibiendo el estado o solicitando un estado) ya sea por las condiciones que impone el programa, incluyendo otras lecturas externas o control del tiempo, o por las interrupciones que genere el MCP23S17.

A nivel de hardware, para la implementación más simple no es necesario ningún componente pasivo, basta con conectar la alimentación y la patilla de reset a nivel alto. Si hubiera varios MCP23S17 en la misma línea de comunicaciones SPI habría que establecer diferentes direcciones para cada uno de ellos con las patillas A0, A1 y A2 a nivel alto o bajo.

Para usarlo desde Arduino se puede trabajar con la librería SPI. El proceso de explotación es el habitual:

  1. preparar la librería con SPI.begin()
  2. establecer el pin CS (SS) como salida con pinMode()
  3. ponerlo a nivel alto con digitalWrite() para desactivar el bus SPI al inicio
  4. para cada escritura ponerlo a nivel bajo con digitalWrite()
  5. enviar la dirección, el registro y el valor con SPI.transfer()
  6. liberar las comunicaciones volviendo a poner CS (SS) a nivel alto con digitalWrite()

Como el acceso implica varios pasos se puede empaquetar en una función. En el siguiente ejemplo, que muestra cómo escribir en un puerto, la única razón para crear la función es hacer el código más legible pero, en general, es la forma correcta ya que simplificará el código al acceder al MCP23S17 desde distintos puntos del programa.

En el siguiente ejemplo se muestra cómo leer un puerto. Para ver el resultado de la operación de lectura se escribe en el puerto de salida. El montaje podría estar formado por una batería de pulsadores o conmutadores conectados al puerto B la misma batería de LED del ejemplo anterior en el puerto A.

Al contrario de lo que ocurría en el montaje del anterior ejemplo, en este caso sí son necesarios algunos componentes pasivos (resistencias) para establecer el valor por defecto (salvo que se obtenga en la entrada, por ejemplo, conmutando entre VDD y VSS). En mi caso, una resistencia conecta cada pin del puerto B a masa para que el valor sea cero salvo que se actúe sobre el pulsador, conectado a la alimentación positiva, en cuyo caso tomaría el valor uno.

Para no tener al microcontrolador esperando cada cierto tiempo para leer el estado del puerto se pueden utilizar las interrupciones. En el siguiente ejemplo se muestra la configuración de las interrupciones, activándolas con el registro GPINTENB, y cómo sería el código de Arduino que lee el estado del puerto B y lo refleja a la salida en el puerto A. Para verificar el funcionamiento correcto de la configuración de las interrupciones no se han utilizado todos los bits para generarla de forma que solamente los cuatro menos significativos la activan aunque al lanzarla se lea el estado de todo el puerto.

Víctor Ventura

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

Más entradas - Página web

Sígueme:
TwitterLinkedIn

Seguir Víctor Ventura:

Programador multimedia y web + IoT. Mejor con software libre.

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

2 Respuestas

  1. Adrian

    Muy buenas,

    Estoy intentando comunicarme con una placa que integra un MCP23S17 y un LCD (LCD mini click de MikroE), y la explicacion sobre el funcionamiento del integrado me parece muy completa y de mucha ayuda para entender mejor el datacheet; aun con todo esto tengo un pequeño problema:

    Estoy probando el primer programa de ejemplo que propones con un Arduino mega2560. Debido a que mi chip ya esta integrado en una placa (LCD click de MikroE), tengo que realizar algunos cambios en el programa (cambios detallados al final).

    El principal problema es que intento activar las señales GPA 7-4, pero al medir con el multimetro las patas del integrado no me aparece ningun valor de tension (de hecho, el valor se queda oscilando y no fijo a 0). Aunque no tengo resolucion para verlo al detalle, puedo detectar la bajada de la linea de CS, y que la linea MOSI parece enviar, por lo que intuyo que no es un problema fisico, si no mas bien del comando que estoy enviando.

    Los cambios de mi codigo son los siguientes:

    **Debido a que mis señales A2-0 estan internamente conectadas a GND en mi placa. Mi comando para escritura queda
    #define DIRECCION_ESCRITURA 0B01000000

    **Utilizo una linea de CS distinta en el pin 37
    #define PIN_MCP23S17 37

    **Como no quiero que el chip se resetee, he conectado la linea de RESET a Vcc

    **Como (de momento) no voy a leer, las dos lineas de interrupcion estan al aire

    **Envio un «encender todos los leds» y tras un tiempo los apago
    void loop()
    {
    Serial.print(«\nEnviar activar»);
    enviar_MCP23S17(GPIOA,0xFF);
    delay(4000);
    Serial.print(«\nEnviar des-activar»);
    enviar_MCP23S17(GPIOA,0x00); // Todos los bits del puerto a nivel bajo (apagar todos los LED)
    delay(4000);
    }

    Muchas gracias por tu ayuda. Un saludito 😉

  2. Salomo

    Un articulo muy interesante.

    Pero estoy haciendo un proyecto con Arduino en donde necesito poder intalar unos 10 chips MCP23017.
    Como solo dispone 3 bits de direcciones como muchopodre direccionar 8 chips.

    ¿Existe alguna solucion al respecto?

Deja un comentario

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