A veces un Arduino queda corto de pines, y se nos presenta la necesidad de ampliar la cantidad de salidas digitales. La manera más usual es con un registro de desplazamiento (Shift Register, en inglés), que convierte los datos en serie en salidas paralelas. Esto será de utilidad en aquellas placas como Arduino UNO, Nano, Micro, etc, que a veces resultan un poco limitados en este sentido.
El chip 74HC595 —para algunos un misterioso integrado de 16 patas que viene incluido en muchos kits de inicio para Arduino— tiene una ventaja esencial ante otros chips del mismo tipo: tiene un registro que mantiene el dato en los pines de salida sin variación mientras se desplazan los datos dentro del chip.
Adicionalmente, tiene la posibilidad de desconectar las salidas de este registro de los pines de salida del chip, por medio de la entrada de control OE (Ouput Enable = Habilitación de Salidas), dejándolos en un estado de alta impedancia, o tercer estado. No usaremos esta opción aquí, pero en futuros artículos veremos la importancia de esta posibilidad.
Otro chip opcional para este uso, aunque de 16 bits y también con registro de salida, es el circuito integrado 74LS673, pero es más difícil de encontrar en el mercado.
Al utilizar el circuito integrado 74HC595, ocupamos solamente 3 salidas digitales en la placa Arduino, pero obtenemos 8 salidas digitales adicionales. Este 3 x 8 en pines no parece una gran mejora (ganamos 5 salidas), pero si se agregan más chips, la ampliación puede llegar a ser importante.
Poniendo más chips conectados en serie se pueden obtener otras 8 salidas más por cada chip agregado, y la cantidad de pines ocupados en el Arduino sigue igual: solamente tres. Con tres chips tendremos 24 salidas, con 8 chips tendremos 64, y con 32 chips tendremos una ampliación de 256 nuevas salidas.
Para calcular la cantidad de salidas que obtendremos, hay que multiplicar la cantidad de chips por 8.
En teoría, se puede poner una cantidad indefinida de chips en serie y obtener centenares de salidas adicionales. Sólo se debe tener en cuenta que los datos no se desplazan instantáneamente, debido a los tiempos de programa. Supongamos una cantidad de 32 bytes a poner en las salidas de 32 chips —256 bits—. Colocar todos esos datos en los registros de salida de 32 chips 74HC595 implica un tiempo que puede ser sustancial y prohibitivo para algunas aplicaciones que requieren salidas con variaciones rápidas, sin que importe si se cambia un único bit en las salidas, o los 256 bits juntos. Para aplicaciones sin tiempos críticos, como encendido de leds, artefactos a través de relés, displays de segmentos o control de motores, no existe ningún problema de tiempos.
La mayoría de los chips de registro de desplazamiento pueden manejarse elevadas frecuencias en MHz para el desplazamiento, todos superan la máxima velocidad de envío de datos en serie de un Atmega328P, ya que para hacerlo debemos usar una secuencia de instrucciones de programa. Algunas hojas de datos nos muestran frecuencias de desplazamiento de 100 MHz, 36 MHz, y similares. Un Arduino estándar, con cristal de 16 MHz, y aún más teniendo que usar una secuencia de instrucciones para enviar cada bit, no superará nunca esas frecuencias para el desplazamiento de los datos. De modo que no se nos presentarán problemas de límite de velocidad al usar estos chips.
Registro de desplazamiento 74HC595
Las salidas son Q0 a Q7 (pata 15, y de 1 a 7), y las tres entradas que van conectadas a las salidas de la placa Arduino son DS = entrada de datos (14), SCTP, para ingresar un pulso que deja fijos los datos en el registro de salida (12), y SHCP, que es el pulso de reloj, o clock, que hace desplazar los datos en el registro de desplazamiento (11).
Estas son las señales más importantes de comprender y profundizar.
Luego está la entrada de voltaje de 5V (16), la conexión a tierra (8), y Q7S (9), la salida que se utiliza para continuar la conexión de más chips en serie.
La hoja de datos (datasheet) permite ver con más detalle los datos del chip.
Por la entrada de reloj SHCP se ingresan pulsos que indican el tiempo preciso en que debe ocurrir el ingreso de bits por la entrada DS, y el desplazamiento en el registro.
La entrada de datos es DS.
Y la señal SCTP es un pulso que traspasa los datos del registro de desplazamiento al de salida, y de allí a las salidas digitales.
Las línea SHCP es la señal de reloj. La segunda línea, DS, es la secuencia de datos que queremos hacer llegar a las salidas Q1-Q7.
Con el pulso de reloj el registro de desplazamiento del 74HC595 desplaza los que entraron anteriormente hacia el interior del registro e ingresa el valor que hayamos puesto en el pin de Arduino que está conectado a su entrada DS. Este bit es parte de la secuencia de datos de 8 bits (o sea que este suceso ocurrirá 8 veces).
Para el primer pulso el bit 0, para el segundo pulso el bit 1, y así sucesivamente hasta completar los 8 bits de un byte.
En ese momento los datos del registro de desplazamiento aún no estarán en las salidas, es necesario que la señal STCP reciba un pulso. Cuando STCP sube de nivel bajo a nivel alto, los datos que tenemos en el registro de desplazamiento pasan a estar disponibles en las salidas Q0 – Q7.
En el ejemplo del gráfico de tiempos de arriba, estamos poniendo a nivel ALTO (HIGH) el bit que aparecerá en Q7, y en nivel BAJO (LOW) el resto.
El manejo de las líneas de Arduino
En principio y para mejor comprensión deberíamos dar una mirada a la función shiftOut(). Esta función se ocupará de ir poniendo de a un bit por vez en la salida del Arduino hasta completar los 8 bits del byte que vamos a transferir.
La función shiftOut()
Sintaxis: shiftOut (pinDato, pinClock, ordenBits, valor)
Desplaza un byte de datos (llamado valor) de a un bit por vez hacia el pin indicado en pinDato. Según lo que se indique en el parámetro ordenBits de la función, el desplazamiento comienza desde el bit más significativo (es decir, el que se ubica más a la izquierda en un byte, en la representación estándar) o el menos significativo (más a la derecha). Cada bit se escribe hacia un pin de datos (pinDato), y después se genera un pulso en el pin de reloj en pinClock (alto, luego bajo), lo que indica que el bit está disponible.
Nota: hay que asegurarse que el pin de reloj (pinClock) esté preparado en valor BAJO antes de la llamada a shiftOut(). Por ejemplo, con una llamada a digitalWrite(pinClock, LOW).
Esta es una implementación que utiliza software. Veremos también más adelante la forma de usar la biblioteca SPI, que provee una implementación por hardware más rápida, aunque debemos recordar que solo funciona en los pines definidos para esta interfaz.
Parámetros de la función shiftOut():
■ pinDato: el pin en el que se coloca cada bit
■ pinClock: el pin para producir el pulso una vez que se haya establecido el valor correcto en pinDato
■ ordenBits: indica en qué orden se desplazarán los bits hacia el pin de salida; ya sea MSBFIRST o LSBFIRST (el bit más significativo primero, o el bit menos significativo primero)
■ valor: los datos a desplazar. (formato byte)
Circuito de ejemplo:
Código de ejemplo:
Supongamos que deseamos poner en los pines de salida un byte con el valor B10101010, o sea así:
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 |
/****************************************************************/ // Código que muestra como concretar la salida de un byte /****************************************************************/ int pinDato = 9; // Pin conectado a DS pin 14 de 74HC595 int pinClock = 12; // Pin conectado a SHCP pin 11 de 74HC595 int pinRegistro = 11; // Pin conectado a STCP pin 12 de 74HC595 byte datos; void setup() { // configura los pines de salida que se usan en el bucle principal pinMode(pinRegistro, OUTPUT); pinMode(pinClock, OUTPUT); pinMode(pinDato, OUTPUT); digitalWrite(pinRegistro, LOW); } void loop() { // enviamos un dato con el bit 7 (MSB) en 1 // y el bit 0 (LSB) en 0 para comprobar si // usamos la direccion correcta (MSBFIRST) datos = B10101010; shiftOut(pinDato, pinClock, MSBFIRST, datos); // enviamos el dato y pulsamos para que pase // al registro de salida y lo podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO } |
Más programas 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 |
/****************************************************************/ // Código para mostrar el avance de un contador // en el registro de desplazamiento 74HC595 /****************************************************************/ int pinDato = 9; // Pin conectado a DS pin 14 de 74HC595 int pinClock = 12; // Pin conectado a SHCP pin 11 de 74HC595 int pinRegistro = 11; // Pin conectado a STCP pin 12 de 74HC595 void setup() { // configura los pines a la salida porque se usan en el bucle principal pinMode (pinDato, OUTPUT); pinMode (pinClock, OUTPUT); pinMode (pinRegistro, OUTPUT); } void loop() { // rutina que cuenta de 0 a 255 for (int j = 0; j <256; j++) { // pone a BAJO pinRegistro y lo mantiene bajo durante el tiempo que este transmitiendo digitalWrite(pinRegistro, LOW); // envia el dato shiftOut(pinDato, pinClock, MSBFIRST, j); // retorna el pinRegistro ALTO para sacar los datos hacia las salidas digitalWrite(pinRegistro, HIGH); delay(200); // breve espera para poder ver el conteo } } |
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 |
/****************************************************************/ // Código que muestra la salida de una secuencia de datos /****************************************************************/ int pinDato = 9; // Pin conectado a DS pin 14 de 74HC595 int pinClock = 12; // Pin conectado a SHCP pin 11 de 74HC595 int pinRegistro = 11; // Pin conectado a STCP pin 12 de 74HC595 byte datos; void setup() { // configura los pines de salida que se usan en el bucle principal pinMode(pinRegistro, OUTPUT); pinMode(pinClock, OUTPUT); pinMode(pinDato, OUTPUT); digitalWrite(pinRegistro, LOW); } void loop() { // enviamos un dato con el bit 7 (MSB) en 1 // y el bit 0 (LSB) en 0 para comprobar si // usamos la direccion correcta (MSBFIRST) datos = B10101010; shiftOut(pinDato, pinClock, MSBFIRST, datos); // enviamos el dato y pulsamos para que pase // al registro de salida y lo podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO delay(3000); // esperamos para poder ver el dato // ahora encendemos el bit 0 (LSB) y // dejamos los otros tal como estaban datos = B10101011; shiftOut(pinDato, pinClock, MSBFIRST, datos); // enviamos el dato y pulsamos para que pase // al registro de salida y podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO delay(3000); // esperamos para poder ver el dato // este dato enciende los 4 bits mas significativos datos = B11110000; shiftOut(pinDato, pinClock, MSBFIRST, datos); // enviamos el dato y pulsamos para que pase // al registro de salida y podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO delay(3000); // esperamos para poder ver el dato // este dato enciende los 4 bits menos significativos datos = B00001111; shiftOut(pinDato, pinClock, MSBFIRST, datos); // enviamos el dato y pulsamos para que pase // al registro de salida y podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO delay(3000); // esperamos para poder ver el dato } |
El envío de datos en una función propia
El programa anterior repite varias veces un código idéntico para el envío del dato. Es la opción perfecta para convertir esta parte repetida del código en una función propia. Además de reducir la longitud de comandos del programa y hacerlo mucho más legible, creamos una función que nos servirá para cualquier otro programa que envíe dato a un integrado 74HC55. El funcionamiento del envío de datos en este programa a continuación produce exactamente la misma secuencia y funcionamiento del programa.
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 |
/****************************************************************/ // Código que muestra la salida de una secuencia de datos utilizando // una funcion creada por nosotros para ponerlos en el 74HC595 /****************************************************************/ int pinDato = 9; // Pin conectado a DS pin 14 de 74HC595 int pinClock = 12; // Pin conectado a SHCP pin 11 de 74HC595 int pinRegistro = 11; // Pin conectado a STCP pin 12 de 74HC595 void setup() { // configura los pines de salida que se usan en el bucle principal pinMode(pinRegistro, OUTPUT); pinMode(pinClock, OUTPUT); pinMode(pinDato, OUTPUT); digitalWrite(pinRegistro, LOW); } void loop() { // enviamos un dato con el bit 7 (MSB) en 1 // y el bit 0 (LSB) en 0 para comprobar si // usamos la direccion correcta (MSBFIRST) SalidaDatos(B10101010); delay(3000); // esperamos para ver el dato // ahora encendemos el bit 0 (LSB) y // dejamos los otros tal como estaban SalidaDatos(B10101011); delay(3000); // esperamos para ver el dato // este dato enciende los 4 bits mas significativos SalidaDatos(B11110000); delay(3000); // esperamos para ver el dato // este dato enciende los 4 bits menos significativos SalidaDatos(B00001111); delay(3000); // esperamos para ver el dato } /* nuestra funcion */ void SalidaDatos(int dato) { // enviamos el dato shiftOut(pinDato, pinClock, MSBFIRST, dato); // emitimos un pulso para que pase // al registro de salida y lo podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO } |
Diagrama para salida de 16 bits con dos 74HC595
No hay diferencia importante entre los circuitos de cada chip, se ven idénticos; el único cambio es de dónde recibe la información de entrada en DS cada uno: el primero desde la salida digital 11 del Arduino, el segundo desde la salida Q7S del primero. Y así se podría continuar la cadena, agregando de a 8 bits a las salidas. Como se puede observar en el diagrama, las señales de control y clock, STCP y SHCP, de los pines 11 y 12 de los 74HC595, se repiten en paralelo en ambos chips, y así continuaría en toda la cadena si se agregasen más salidas, como se puede ver en el diagrama mostrado más arriba con 4 chips 74HC395.
Programa de ejemplo para 16 bits:
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 |
// **************************************************************** // Enviar 16 bits de datos a dos chips 74HC595 // La función shiftOut escribe en la salida de a 1 byte (8 bits), // si queremos enviar 16 bits se requiere que la operación // se haga en dos pasos para enviar los dos bytes // **************************************************************** int pinDato = 9; // Pin conectado a DS pin 14 de 74HC595 int pinClock = 12; // Pin conectado a SHCP pin 11 de 74HC595 int pinRegistro = 11; // Pin conectado a STCP pin 12 de 74HC595 int datosALTO = B11001100; int datosBAJO = B11110000; void setup() { // configura los pines como salida pinMode (pinDato, OUTPUT); pinMode (pinClock, OUTPUT); pinMode (pinRegistro, OUTPUT); } void loop() { // envia la parte alta del byte shiftOut(pinDato, pinClock, MSBFIRST, datosALTO); // envia la parte baja del byte shiftOut(pinDato, pinClock, MSBFIRST, datosBAJO); // emitimos un pulso para que pase // al registro de salida y lo podamos ver digitalWrite(pinRegistro,HIGH); // pulso ALTO digitalWrite(pinRegistro,LOW); // pulso BAJO } |
En próximas notas veremos estos circuitos controlados por la biblioteca SPI, en otra el uso del chip serie a paralelo 74LS673 para obtener 16 salidas y en otra nota veremos el uso de un chip paralelo a serie 74HC165 para obtener una ampliación de 8 entradas.
ARTÍCULOS RELACIONADOS:
■ Arduino: Entradas y salidas – Manipulación de puertos
■ Ingresar lectura de varios sensores a través de un único pin analógico
■ Arduino UNO R3 – Conectándolo al mundo exterior