Para expandir la capacidad de entradas digitales se utilizan registros de desplazamiento (Shift Register en inglés) con entradas en paralelo y salida serie.
Para este ejemplo, utilizamos tres pines digitales de la placa Arduino en conjunto con el circuito integrado 74HC165 (puede ser, también, 74LS165), que nos aportará 8 entradas.
Diagrama de pines:
Se pueden conectar varios de estos chips en cadena, lo que le nos aporta 16, 24, 32 o más entradas, sin usar ningún pin digital adicional de Arduino, simplemente conectando la salida QH de un registro a la entrada de datos serie SER del otro.
En el diagrama que sigue se ve cómo es la conexión completa de dos 74HC165. Los conjuntos de llaves de corredera, llamados DIP Switch en inglés, representan a las diversas entradas que se podrían ingresar. Si son llaves que se cierran (pulsadores, microswitches, relés, salidas de colector abierto), corresponde el circuito tal como se lo ve, con un resistor de polarización (pull-up) conectado a +5V. Si se trata de salidas digitales de otros chips y placas con salidas compatible con lógica TTL, no es necesario colocar el resistor.
Con línea de puntos se indica la conexión para continuar la cadena indefinidamente. La señal CLK (pata 2) va unida entre chips, e igual la señal SH/LD (pata 1), con la única salvedad de que hay que leer la hoja de datos del microcontrolador (según cuál sea) para saber cuántas entradas de este tipo de chip TTL HC se le pueden conectar a una salida digital de Arduino.
La tabla que sigue, y el diagrama interno del chip, los agrego para ofrecer más información y mejor comprensión del funcionamiento. Se pueden encontrar más detalles en su hoja de datos.
Diagrama de tiempo
La señal SH/LD se mantiene en ALTO durante las operaciones de desplazamiento. Un pulso a BAJO en este pin ingresa nuevos datos desde las entradas al registro de desplazamiento. Cuando el pulso vuelve a ALTO, queda todo dispuesto para aplicar pulsos de CLK y desplazar uno a uno los bits del registro hacia la salida serie QH. Si hemos usado un solo chip 74HC165, con 8 pulsos se habrán leído todas las entradas a través de QH (luego de haberlo conectado a una entrada digital del Arduino). Cada chip adicional requiere 8 pulsos más. En los programas se puede entender claramente cómo se ingresan los datos a variables dentro de nuestro microcontrolador.
Leer una de las entradas del 74HC165 – Diseño del programa:
■ Se aplica un pulso de alto a bajo y luego de regreso a alto en SH/LD, y así las entradas ingresan al registro de desplazamiento (condición “LOAD”, o de carga).
■ Aplicar a la salida CLK la cantidad de pulsos necesarios, desplazando los datos, para que el bit que deseamos leer quede ubicado en la línea de salida QH.
■ Leer el valor del pin digital de entrada del Arduino.
Primer programa de prueba
Este programa lee de a un pin e indica su estado en el LED incluido en el Arduino UNO, pin 13 = LED_BUILTIN. Para determinar qué entrada queremos leer para conocer su estado, se lo indicamos tipeando los números 1 a 8 desde el teclado de la computadora a la que esté conectado el Arduino por USB.
Incluso, usando comparaciones con IF se podría elegir como comando cualquier caracter. Puede ser “a”, “b”, “c”, o “A”, “B”, “C”, o lo que usted elija. Si la entrada proviene de sensores de puertas o ventanas abiertas, o de luces encendidas, se puede usar, por ejemplo, “C” para cocina, “B” para baño, “P” para pasillo, “p” para patio, “E” para entrada, “H” para una habitación y “h” para otra habitación. Y así. Además, estas letras de comando se pueden enviar a través de un módulo Bluetooth HC-06 o HC-05 y controlar desde el teléfono celular, recibiendo la respuesta por el mismo medio.
En este programa —para simplificar— se eligió tipear un número desde el teclado para leer cada entrada. Lo más interesante es la función que lee las entradas y las desplaza hacia la salida. Se le envía como parámetro un valor numérico y entero de 1 a 8, y devuelve un 1 o un 0 según el estado de la entrada. No es difícil ampliar el código hasta la cantidad de entradas que desee.
Agregando esta función a su programa, puede usarla del modo que usted quiera.
Utilizamos este circuito, que es el mismo que servirá para todos los programas excepto el de manejo por SPI, que requiere conectar dos señales del circuito a las entradas MOSI y SCLK de esta interfaz, como luego veremos.
Disculpen si tiene un aspecto un poco antiestético, pero ocurre que es difícil cablear un conjunto de llavecitas DIP al chip 74HC165 con el programa de dibujo sin hacer cruces de cables, y que sea todo visible. Ya verán en la foto que el circuito real, en la protoboard, quedó más prolijo.
Programa para leer de a una entrada
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 |
/* Este programa no requiere el uso de bibliotecas */ int pinCargarDatos = 8; // Pin SH/LD: desplazar-cargar en paralelo del 165 int pinDato = 11; // Pin QH: salida serie del 165 int pinClock = 12; // Pin CLK: clock del 165 // esta funcion devuelve 1 o 0 segun el estado del pin int leeBit(int pinLeer){ int valorBit; // Pulso de carga paralela, guarda estado de las entradas digitalWrite(pinCargarDatos, LOW); digitalWrite(pinCargarDatos, HIGH); // pulso carga datos // Lee el valor de bits de la salida QH del 74HC165 for(int i = 0; i < pinLeer; i++) { valorBit = digitalRead(pinDato); // leer el pin deseado digitalWrite(pinClock, HIGH); digitalWrite(pinClock, LOW); // pulso desplaza datos } return(valorBit); } void setup() { Serial.begin(9600); // Inicializar los pines digitales pinMode(LED_BUILTIN, OUTPUT); pinMode(pinCargarDatos, OUTPUT); pinMode(pinClock, OUTPUT); pinMode(pinDato, INPUT); digitalWrite(pinClock, LOW); digitalWrite(LED_BUILTIN, LOW); } void loop() { int numeroPin; char datoSerie; if (Serial.available() > 0) // espero el caracter tipeado { datoSerie = Serial.read(); // lo leo numeroPin = 8-(datoSerie-48)+1; // char ASCII en numero if (leeBit(numeroPin) == 1) { digitalWrite(LED_BUILTIN,HIGH); } else { digitalWrite(LED_BUILTIN,LOW); } } } |
Segundo ejemplo: ingresar todas las entradas a variables
El siguiente programa permite listar en el Monitor Serie el estado de los pines de entrada del circuito del ejemplo, o de una cadena de chips para ampliar 16, 24, 32 o más entradas. Tampoco necesita biblioteca. El listado se actualiza cada vez que cambia una entrada. Los comentarios explican en detalle su funcionamiento.
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
/* SN74HC165N_shift_reg * Traducido y modificado de https://playground.arduino.cc/ * Programa para ingresar valores de 8 bits desde un 74HC165 * (registro de desplazamiento entrada paralela/salida serie. * Este programa demuestra la lectura de 8 estados digitales * de un registro de desplazamiento 74HC165 usando solamente * 3 pines digitales del Arduino. * Se pueden conectar en cadena mas chips uniendo la salida serie * (pin QH) de un registro de desplazamiento a la entrada serie * (pin SER) del siguiente. * Por supuesto usted puede conectar en cadena tantos chips como * quiera y seguir usando solo 3 pines de Arduino (aunque hay que * ingresarlos en diferente variables unsigned long). */ // Definir la cantidad de chips que estan conectados en cadena. #define CANTIDAD_CHIP 1 // Ancho de los datos (cuantas lineas de entrada). #define ANCHO_DATOS CANTIDAD_CHIP * 8 // Ancho del pulso para cargar el registro de desplazamiento #define ANCHO_PULSO 5 // Espera opcional entre lecturas del registro de desplazamiento #define ESPERA_DEL_CICLO 1 // Debera cambiar "int" a "long" si la cantidad // CANTIDAD_CHIP es mayor que 2 #define TIPO_VAL_BYTES unsigned int int pinCargarDatos = 8; // Pin SH/LD: desplazar-cargar en paralelo del 165 int pinDato = 11; // Pin QH: salida serie del 165 int pinClock = 12; // Pin CLK: clock del 165 TIPO_VAL_BYTES valoresPin; TIPO_VAL_BYTES viejosValoresPin; // Esta funcion es una rutina de "desplazamiento hacia adentro" que // lee datos en serie desde el registro de desplazamiento de los chips // y arma el estado de los pines en un unsigned integer (o long integer) TIPO_VAL_BYTES lee_registros_desplaz() { long valorBit; TIPO_VAL_BYTES valorBytes = 0; // Pulso de carga paralela para guardar el estado de las entradas digitalWrite(pinCargarDatos, LOW); // pulso carga datos a BAJO delayMicroseconds(ANCHO_PULSO); // duracion pulso digitalWrite(pinCargarDatos, HIGH); // pulso carga datos a ALTO // Lazo que lee el valor de cada bit de la salida serie del 74HC165 for(int i = 0; i < ANCHO_DATOS; i++) { valorBit = digitalRead(pinDato); // poner el bit correspondiente en valorBytes valorBytes |= (valorBit << ((ANCHO_DATOS-1) - i)); // Pulso de Clock (el flanco de subida desplaza el siguiente bit). digitalWrite(pinClock, HIGH); delayMicroseconds(ANCHO_PULSO); digitalWrite(pinClock, LOW); } return(valorBytes); } // Mostrar la lista de datos con su estado void mostrar_valores_pines() { Serial.print("Estado pines:\r\n"); for(int i = 0; i < ANCHO_DATOS; i++) { Serial.print(" Pin-"); Serial.print(i); Serial.print(": "); if((valoresPin >> i) & 1) Serial.print("ALTO"); else Serial.print("BAJO"); Serial.print("\r\n"); } Serial.print("\r\n"); } void setup() { Serial.begin(9600); // Inicializar los pines digitales pinMode(pinCargarDatos, OUTPUT); pinMode(pinClock, OUTPUT); pinMode(pinDato, INPUT); digitalWrite(pinClock, LOW); // inicializa CLK en BAJO digitalWrite(pinCargarDatos, LOW); // inicializa carga datos a BAJO // Leer y mostrar los estados de los pines la 1ra vez valoresPin = lee_registros_desplaz(); mostrar_valores_pines(); viejosValoresPin = valoresPin; } void loop() { // Lee el estado de todos los bytes valoresPin = lee_registros_desplaz(); // Si hay un cambio de estado, muestra los que cambiaron if(valoresPin != viejosValoresPin) { Serial.print("*Detectado cambio de valor de Pin*\r\n"); mostrar_valores_pines(); viejosValoresPin = valoresPin; } delay(ESPERA_DEL_CICLO); } |
El Monitor Serie mostrará este mensaje, y se renovará cada vez que se cambie el valor de una entrada.
Biblioteca ShiftIn
Esta es una biblioteca que permite leer 8 o más entradas. Y también, si bien la biblioteca tiene una función que define 4 pines, se pueden usar sólo 3 pines al Arduino. Además, se puede conectar en cadena varios registros de desplazamiento utilizando la misma cantidad de pines digitales, o sea 3. El cableado es con la misma configuración que en el diagrama de protoboard mostrado arriba.
Instalación fácil (importar zip)
La forma más fácil de instalar esta biblioteca es descargar la última versión y luego importarla. No tiene que descomprimirlo. Simplemente abra su IDE de Arduino y navegue a Programa > Incluir Librería > Añadir Biblioteca .ZIP… y luego seleccione el archivo zip, que puede bajar desde aquí.
Instalación manual
Por supuesto también se puede instalar esta biblioteca manualmente. Para hacerlo, descargue la versión más reciente y descomprímala. Luego tiene que copiar la carpeta ShiftIn (NO la carpeta ShiftIn-x.y.z) y colocarla en la carpeta de la biblioteca Arduino:
Windows: Documentos\Arduino\libraries\
Mac and Linux: Documents/Arduino/libraries/
Después de esto solo hay que reiniciar el IDE de Arduino.
Uso de ShiftIn
Si ha instalado esta biblioteca, puede incluirla navegando a Programa > Incluir Librería > ShiftIn. Esto agregará la línea #include <ShiftIn.h> a su programa (por supuesto, también puede escribir esta línea manualmente).
Ahora se puede usar esta biblioteca:
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 |
/* Este programa es ejemplo de la libreria ShiftIn.h que se encuentra aqui https://github.com/InfectedBytes/ArduinoShiftIn */ #include <ShiftIn.h> // Inicia una instancia de ShiftIn un 1 chip. // El numero define la cantidad de chips 74HC165 conectados en serie // Si usted arma un circuit con 2 chips debe escribir: ShiftIn<2> shift; ShiftIn<1> shift; void setup() { // iniciar serie. El programa muestra en Monitor Serie Serial.begin(9600); // declar pines: // pLoadPin (SH/LD = 8), clockEnablePin (CLK INB = 9), // dataPin (SER = 11), clockPin (CLK = 12) // si bien la librería usa 4 pines solo usamos 3 // no hace falta usar CLK INB en el circuito, va a GND shift.begin(8, 9, 11, 12); } void displayValues() { for(int i = 0; i < shift.getDataWidth(); i++) Serial.print( shift.state(i) ); // obtiene el estado de la entrada i Serial.println(); } void loop() { // lee los valores. Retorna VERDADERO // si alguna entrada ha cambiado y // muestra los valores en Monitor Serie if(shift.update()) displayValues(); delay(1); } |
Los datos en el Monitor Serie se verán así:
Si usted necesita usar dos shift registers, sólo tiene que cambiar la declaración de ShiftIn<1> shift; a ShiftIn<2> shift;, y así sucesivamente.
El diagrama para 2 chips – 16 entradas es este:
Detalle de las funciones de la biblioteca ShiftIn
Dependiendo de la cantidad de chips, esta biblioteca utilizará diferentes tipos de datos. Si solo está utilizando un chip, el tipo ShiftType será un unsigned byte (uint8_t). Para dos chips será un unsigned int (uint16_t). Para tres o cuatro chips será un unsigned long (uint32_t) y para 5 a 8 chips será un unsigned long long (uint64_t). La biblioteca todavía no maneja más de ocho chips.
Esta función debe ser llamada en la función de configuración. Se utiliza para indicar a la biblioteca los pines que debe usar.
void begin(int ploadPin, int clockEnablePin, int dataPin, int clockPin)
GetPulseWidth() define el retardo para el pin de clock en microsegundos. Este valor está fijado en 5 us y en general no habrá necesidad de cambiarlo, aunque teniendo en cuenta el tiempo de programa transcurrido entre la ejecución de el inicio del pulso y de su final, puede ser tan pequeño como 1 us, incluso 0.
uint8_t getPulseWidth()
void setPulseWidth(uint8_t value)
Retorna la cantidad de entradas (bits en el estado)
uint16_t getDataWidth()
Retornan VERDADERO si ha cambiado alguna entrada durante la última lectura.
boolean hasChanged()
boolean hasChanged(int i) // lo mismo de arriba, pero solo para la entrada i
Retornan el estado completo del actual y el último grupo de bits
ShiftType getCurrent()
ShiftType getLast()
Retornan el estado de una sola entrada en el grupo actual de bits y el último grupo de bits
boolean state(int i)
boolean last(int i)
Indica cuando una entrada ha cambiado. En el ejemplo de los esquemas, se ha presionado o se ha soltado un pulsador
boolean pressed(int id) // no estaba presionado en la última lectura, pero ahora sí
boolean released(int id) // estaba presionado en la última lectura, pero ahora fue liberado
Esta función (la función para actualizar) debe ser llamada una vez por cada grupo de bits. Leerá todos los valores de los shift registers y retornará el nuevo estado.
ShiftType read()
Esta función es básicamente la misma que la función read, pero retorna VERDADERO si ha cambiado de estado alguna entrada, y FALSO en el caso contrario
boolean update()
Ingresando datos por SPI
Conectando el 74HC165 a la interfaz SPI se puede leer rápidamente 8 entradas digitales, ingresando los 8 bits en una sola instrucción de SPI. Se los puede encadenar para leer 16, 24, 32 o más entradas a la vez.
Podría usarse, por ejemplo, para examinar la configuración de un interruptor DIP de 8 interruptores (donde se fijaría la configuración del dispositivo).
En el 74HC165 se tiene que pulsar el pin de «cargar» (pin 1 del chip) para que el registro ingrese las entradas externas en sus registros internos.
La habilitación del chip, en esta prueba, es fija. El pin de activación de chip (/CLK INH) está a tierra, ya que el chip también puede funcionar estando siempre habilitado. Tenga en cuenta que la línea MISO siempre está activa, por lo que no es posible compartir este chip con otros dispositivos SPI utilizando el hardware SPI. Se puede solucionar agregando un circuito selector, pero no lo describiré en este artículo ya que si necesita usar otro dispositivo SPI, puede utilizar los otros ejemplos de programa, sin necesidad de SPI.
Código de programa
Es fácil de leer desde el registro. La biblioteca SPI se ocupará de todo.
Los pasos importantes son (dentro de la función de bucle):
1. Aplique un pulse al pin de carga paralela para cargar el registro desde las entradas.
2. Haga una transferencia SPI para leer el registro del chip.
Si desea leer más de 8 interruptores, simplemente use más registros, conecte en paralelo los pines 1, 2 y 15 de los chips, y la salida de cada chip «anterior» en la secuencia (QH) a la entrada del siguiente (SER).
Para leer cuatro bancos de interruptores, se cambia la sección de lectura del programa:
digitalWrite(LATCH, LOW);
digitalWrite(LATCH, HIGH);
bancoEntradas1 = SPI.transfer(0);
bancoEntradas2 = SPI.transfer(0);
bancoEntradas3 = SPI.transfer(0);
bancoEntradas4 = SPI.transfer(0);
NOTA: OBSERVE QUE HAY QUE CAMBIAR, EN EL DIAGRAMA DEL PROTOBOARD DE ARRIBA, LOS PINES QUE IBAN A SALIDA DIGITAL 11 Y SALIDA DIGITAL 12 DEL ARDUINO. LA CONEXIÓN QUE ESTABA EN 12 SE DEBE PASAR A 13, Y LA QUE ESTABA EN 11 SE DEBE PASAR A 12.
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 57 58 |
/* Programa Demo para leer desde 74HC165 Autor: Nick Gammon Fecha: 23 Marzo 2013 Conexiones para Uno y similares: Chip pin 1 (SH/LD) conectado a LATCH (8) queda igual Chip pin 2 (CLK) conectado a SCK (13) antes en 12 Chip pin 9 (QH) conectado a MISO (12) antes en 11 Conexiones para Mega2560: Chip pin 1 (SH/LD) conectado a LATCH (8) Chip pin 2 (CLK) conectado a (52) Chip pin 9 (QH) conectado a (50) */ #include <SPI.h> const byte LATCH = 8; void setup() { SPI.begin(); Serial.begin(9600); Serial.println ("Comienza la prueba de entradas"); pinMode(LATCH, OUTPUT); digitalWrite(LATCH, HIGH); } byte bancoEntradas1; // agregar declaracion: byte bancoEntradas2; // agregar declaracion: byte bancoEntradas3; // agregar declaracion: byte bancoEntradas4; byte bancoEntradas1Viejo; // estado previo void loop() { digitalWrite (LATCH, LOW); digitalWrite (LATCH, HIGH); // pulso en carga paralela bancoEntradas1 = SPI.transfer(0); // donde se debe agregar: bancoEntradas2 = SPI.transfer(0); // donde se debe agregar: bancoEntradas3 = SPI.transfer(0); // donde se debe agregar: bancoEntradas4 = SPI.transfer(0); byte mascara = 1; for (int i = 1; i <= 8; i++) { if ((bancoEntradas1 & mascara) != (bancoEntradas1Viejo & mascara)) { Serial.print ("Llave "); Serial.print (i); Serial.print (" ahora "); Serial.println ((bancoEntradas1 & mascara) ? "cerrada" : "abierta"); } // fin de "el bit ha cambiado" mascara <<= 1; } // final para cada bit bancoEntradas1Viejo = bancoEntradas1; delay (10); } |
Resultados en Monitor Serie. Muestra una indicación del estado al inicio, y luego lista cada cambio que se produce en las entradas:
Otras opciones
Por último, existen dos librerías relacionadas, llamadas bitBangedSPI y bitBangedSPIfast, que permiten operar las múltiples entradas desde 74HC165 con un SPI implementado por software, y dejar libre el módulo SPI de hardware para otros dispositivos. Entiendo que es lo que hicimos con los programas de ejemplo anteriores, pero les dejo la inquietud de investigarlos y probarlos.