Módulo de 4 dígitos LED de 7 segmentos con el 74HC595. ¡Incluye código!

Módulo de 4 dígitos LED de 7 segmentos con el 74HC595. ¡Incluye código!

(Read this article in english here here.)

Introducción

Un módulo muy requerido en nuestros proyectos es el de la pantalla. Y tipos de pantallas tenemos muchos, desde las tradicionales de 7 segmentos LED hasta las micropantallas OLED, pasando por las LCD de 16×2 y las gráficas. Cada una tiene sus pros y sus contras, y cuál utilicemos dependerá completamente del proyecto en el que estemos trabajando.

En este artículo te quiero mostrar cómo utilizar una pantalla de 7 segmentos LED utilizando al chip 74HC595 para controlar los segmentos y transistores MOSFET para controlar los dígitos.

Hardware

El chip 74HC595 es un registro de corrimiento que nos va a permitir reducir la cantidad de pines, de 8 a 3 (y quizás 4), para controlar los 8 segmentos de un dígito LCD de 7 segmentos. Vamos a necesitar el protocolo de comunicaciones seriales SPI de 3 líneas (aunque sólo vamos a usar 2) que está presente en prácticamente todos los microcontroladores del mercado.

Otra ventaja de utilizar al HC595 es que sirve como amplificador de corriente para cada segmento; recuerda que al final de cuentas cada uno de estos es un LED, y así protegemos al microcontrolador.

Como mencioné, los dígitos los controlaremos con transistores discretos MOSFET 2N7000, aunque también es posible utilizar transistores TBJ (transistores bipolares de juntura, como el BC547, BC337, etc). Y también podríamos utilizar transistores encapsulados (como el ULN2003), pero 4 transistores discretos es una mejor opción para una pantalla de 4 dígitos,  como la que te voy a presentar. 

Usar MOSFETs en lugar de TBJs tiene la ventaja de que no necesitamos la resistencia de base de estos últimos, y así ahorramos en componentes y espacio en el circuito impreso.

Diagrama

Da click en la imagen para que puedas verla en tamaño completo.

Los textos en azul son las líneas de Arduino que utilicé, y tú puedes variar algunos de ellos si así lo requieres, excepto D11 y D13, ya que estos son los pines correspondientes a las señales SCLK (señal de reloj, serial clock) y MOSI (señal de datos desde el micro hacia el chip, master out, slave in). En el diagrama son los pines 11 (SRCLK) y 14 (SER) del HC595, respectivamente.

Nota que utilicé etiquetas en lugar de «alambres» para conectar el HC595 a la pantalla. Esto nos permite mucha flexibilidad. Cuando realicé el primer prototipo en protoboard usé un mapa 1 a 1: la salida QA con el segmento a, la salida QB con el segmento b, etc. Esto funcionó muy bien ahí, pero para realizar el circuito impreso en una sola cara se convirtió en una pesadilla (que ya había anticipado). Cuando inicié el diseño del circuito impreso cambié el mapa de tal manera que el trazado de pistas no implicara utilizar puentes, o los menos posibles.

Detalle del mapeo de pines.

Acomodar los pines es muy sencillo y lo hacemos por software (en el archivo defs.hpp):

#define SEG_A  0x80
#define SEG_B  0x01
#define SEG_C  0x10
#define SEG_D  0x04
#define SEG_E  0x08
#define SEG_F  0x40
#define SEG_G  0x20
#define SEG_DP 0x02

LATCH

El pin 12 del HC595 es la señal de LATCH (RCLK en el diagrama, aunque cada fabricante le llama como quiere). Cuando vas inyectando los bits en el HC595 estos NO se hacen presentes a la salida inmediatamente, sino que se van guardando en un registro interno que NO está conectado a las salidas. Una vez que todos los bits están en su lugar es entonces que das la orden de que pasen del registro interno hacia las salidas, logrando colocar todos los bits en la salida en el mismo instante. 

La señal LATCH es la que transfiere los bits del registro interno hacia las salidas. Bastante conveniente. Esta señal es provista a través de un pin GPIO de tu microcontrolador y puedes utilizar el que tu quieras, siempre y cuando esté configurado como pin de salida.

En la imagen anterior podemos ver la señal de LATCH en color amarillo, mientras que la señal en azul es SCLK.

OE (Output Enable)

Existe una cuarta señal de control que no es obligatoria ni imprescindible, pero que nos permitirá hacer cosas interesantes como controlar el brillo o hacer parpadear a la pantalla; ésta es la señal OE (output enable, activa en bajo). Si la pones a un 1 lógico los transistores a la salida del chip se desactivan, lo cual se traduce en una pantalla apagada. Si la pones a un 0 lógico, los transistores a la salida del chip se activan normalmente según los hayas programado. Si no piensas utilizarla, entonces déjala conectada de manera permanente a 0V.

IMPORTANTE: El valor de los bits NO se altera cuando OE está a un 1 lógico, solamente se desconectan los transistores de salida. Cuando pones OE de vuelta a un 0 lógico, lo que sea que hayas programado estará presente nuevamente en las salidas.

SRCLR

Para terminar el tema del HC595 quiero mencionar al pin 10 (SRCLR), el cual efectivamente dejé a 5V de manera permanente. Un 0 lógico en este pin pone los bits del registro interno a 0’s. Nosotros vamos a hacer lo mismo, pero por software, así que nos vamos a ahorrar esa línea.

Finalmente quiero mencionarte que el mismo circuito puede ser utilizado para módulos de 3 dígitos; basta con no uses ni el pin D4 ni instales el transistor Q4. Obviamente tendrás que avisarle al software de esta decisión, pero es muy fácil, como ya verás.

Prototipos

El primer prototipo en un protoboard. De aquí me surgió la idea de crear un módulo independiente que pudiera utilizar en mis otros proyectos:

En la siguiente imagen puedes ver el primer prototipo de circuito impreso. Para utilizar componentes de montaje tradicional es bastante compacto.

Prototipo funcionando con un Arduino UNO.

El circuito impreso, realizado de manera profesional, se ve así desde el punto de vista del artista:

Al final del artículo te explico cómo puedes obtener el diseño del circuito impreso en formato PDF para que hagas tus propios módulos.

En este diseño me fue imposible no usar puentes, pero dada la complejidad del mismo, 5 de ellos es un buen número. Recuerda que el diseño es de una sola cara para que todos podamos crear el nuestro en nuestro laboratorio utilizando componentes comunes.

Software

Escribí una pequeña clase para este módulo, la cual expone 5 operaciones fundamentales para la pantalla, incluyendo la de configuración:

class Display_HC595
{
private:
   const uint8_t* cathodes{nullptr};
   const uint8_t* digits{nullptr};
   uint8_t memory[ DISPLAY_HC595_CATHODES ];

public:
   Display_HC595();
   Display_HC595(Display_HC595&) = delete;
   Display_HC595& operator=(Display_HC595&) = delete;

   void clear();
   void print_str( uint8_t* str );
   void print_number( uint16_t num, uint8_t dp_pos, bool leading_zero = false );

   void begin( const uint8_t* cathodes, const uint8_t* digits );
   void update();
};
  • begin(): inicializa al módulo.
  • update(): actualiza el contenido de la pantalla y debe ser llamada a intervalos regulares. En el ejemplo a continuación lo llamo cada 5 milisegundos.
  • clear(): pone la pantalla en blanco. Útil cuando quieres hacerla parpadear.
  • print_str(): imprime una cadena de texto de 4 carácteres. No te preocupes, en el ejemplo te muestro cómo imprimir cadenas mucho más largas.
  • print_number(): imprime un número de hasta 4 dígitos. Tú puedes establecer dónde quieres el punto decimal, y también puedes decirle si quieres ceros a la izquierda.

Antes de escribir en el módulo debes inicializarlo. Para ello debes llamar a la función begin() con una tabla que represente cada uno de los diferentes símbolos, y con un arreglo que establezca los pines donde están conectados los dígitos. La tabla y el arreglo están definidos en el archivo principal display_hc595.ino:

constexpr uint8_t digits_array[18] =
{
   SEG_A + SEG_B + SEG_C + SEG_D + SEG_E + SEG_F, // 0
   SEG_B + SEG_C,
   SEG_A + SEG_B + SEG_G + SEG_E + SEG_D,
   SEG_A + SEG_B + SEG_G + SEG_C + SEG_D,
   SEG_F + SEG_G + SEG_B + SEG_C,
   SEG_A + SEG_F + SEG_G + SEG_C + SEG_D,
   SEG_A + SEG_C + SEG_D + SEG_E + SEG_F + SEG_G,
   SEG_A + SEG_B + SEG_C,
   SEG_A + SEG_B + SEG_C + SEG_D + SEG_E + SEG_F + SEG_G,
   SEG_A + SEG_F + SEG_G + SEG_B + SEG_C + SEG_D, // 9

   SEG_A + SEG_B + SEG_C + SEG_E + SEG_F + SEG_G, // A
   SEG_C + SEG_D + SEG_E + SEG_F + SEG_G,
   SEG_A + SEG_D + SEG_E + SEG_F,
   SEG_B + SEG_C + SEG_D + SEG_E + SEG_G,
   SEG_A + SEG_D + SEG_E + SEG_F + SEG_G,
   SEG_A + SEG_E + SEG_F + SEG_G,                 // F

   SEG_G,                                         // -
   SEG_F + SEG_E + SEG_A + SEG_B + SEG_G,         // P 
};

constexpr uint8_t cathodes_array[DISPLAY_HC595_CATHODES] = { 3, 4, 5, 6 };

Como puedes ver solamente establecí los números del 0 al 9 y las letras de la A al F, y un par de símbolos extras. Lo hice así porque es lo que necesito para un proyecto más grande en el que estoy trabajando, pero tú puedes crecer la tabla para incluir toda la tabla ASCII; es fácil, pero laborioso. Ya lo he hecho para otros proyectos similares, pero por lo pronto esa tabla me sirve.

Por otro lado, en el arreglo de los dígitos:

constexpr uint8_t cathodes_array[DISPLAY_HC595_CATHODES] = { 3, 4, 5, 6 };

establecí que quiero utilizar los pines de Arduino D3, D4, D5 y D6 para controlar los 4 dígitos. Tú puedes cambiarlos a tu conveniencia, según tu diseño y microcontrolador que estés utilizando. Por cierto, esta pantalla puede ser utilizada con con cualquier otra tarjeta con cambios mínimos.

¡Ah! También los pines asociados a la señal LATCH y OE puedes ponerlos en los pines que tú gustes (en el archivo defs.hpp):

#define DISPLAY_HC595_LATCH_PIN 9   // Arduino: D9
#define DISPLAY_HC595_OE_PIN    10  // Arduino: D10

En el mismo archivo puedes establecer si el módulo es de 3 o 4 dígitos (inclusive de 2):

#define DISPLAY_HC595_CATHODES 4  // 4 digits module

Constructor

El constructor está vacío porque las respectivas inicializaciones las hice en la declaración de la clase (C++11 lo permite y es bastante limpio y conveniente). También establecí que no se puedan realizar copias de objetos tipo Display; no tiene sentido ya que los proyectos normalmente tienen una sola pantalla. Si tu proyecto tuviera dos o más pantallas tendrás que crear objetos separados, pero no copias.

Método update()

Esta función, como lo mencioné, es la encargada de escribir en la pantalla y utiliza el protocolo SPI. Este protocolo permite que dos o más dispositivos se “cuelguen” de sus líneas MOSI, MISO y SCLK, pero cada uno tiene que tener una forma de ser discriminado, ya que no se debe escribir en todos al mismo tiempo. Normalmente cada dispositivo que puede ser conectado al bus SPI tiene una línea de activación. En el caso del HC595 dicha línea es la señal LATCH.

Si tu proyecto usa a SPI con algún otro dispositivo deberás configurar su respectiva línea de activación.

Ejemplo

Odio el súper-loop de Arduino porque nos obliga a programar de manera horrible, pero con tal de hacer el ejemplo en sketch y tener la posibilidad de compartirlo con ustedes, mis amables lectores, me metí, con mucho gusto, en un par de problemas.

*En el proyecto más grande en el que estoy trabajando (también en Arduino), y del cual saqué la idea de realizar este artículo, utilizo al sistema operativo en tiempo real FreeRTOS, el cual no solamente simplifica la programación, sino que la hace más limpia y permite escalarla sin mayores inconvenientes.

Básicamente tengo 3 bases de tiempo:

  • Una de 5 milisegundos, la cual es la base de tiempo del sistema (system tick),
  • Una de 100 milisegundos,
  • Y una de 1000 milisegundos.

La de 5 la uso para actualizar a la pantalla; y las de 100 y 1000 para hacer correr un contador a esas velocidades, respectivamente.

En el ejemplo he incluído 3 sub ejemplos:

  • Un contador de tiempo con resolución de 1 segundo,
  • Un contador de tiempo con resolución de 100 milisegundos,
  • La impresión de un texto largo, el cual se va desplazando hacia la izquierda.

Estos sub ejemplos están dentro de una máquina de estados para que se ejecuten uno después de otro. ¡Odio el súper-loop!

Variables estáticas

Cuando estudies el código notarás que muchas de mis variables están marcadas como static, y esto es porque:

NO USO VARIABLES GLOBALES DONDE NO ES NECESARIO

Un error garrafal de la plataforma Arduino, que se ha extendido como la plaga, es el uso indiscriminado de las variables globales. Bueno, pues yo no las uso si no están debida y científicamente justificadas, como es el caso del objeto display de la clase Display; cuando un objeto representa un módulo de hardware, entonces sí podemos, y debemos, hacerla global. En casi todos los demás casos no se justifican.

Display_HC595 display;

Enteros estándar

Otra característica que siempre uso en mis programas embebidos son los enteros con tamaño estándar: uint8_t, uint16_t, uint32_t, etc. Para utilizar estas definiciones debemos incluir al archivo de encabezado <stdint.h>.

Si te pregunto ¿cuántos bits ocupa un entero (int) en el Arduino UNO y cuántos bits ocupa un entero en el Arduino Due, sabrás la respuesta? La primera tiene un chip de 8 bits y el entero ocupa 16 bits, mientras que la segunda es un chip de 32 bits y el entero usa 32 bits. 

Personalmente prefiero usar tamaños estándar ya que me permiten portar mis programas de un chip a otro más o menos fácil. Por ejemplo, un mismo programa que empiezo en el Arduino UNO lo cargo en el Arduino Due para poderlo depurar (con el depurador GDB), y también, muy seguido, escribo una versión de alguna función (como print_str() y print_number()) en la PC y luego la uso con mis microcontroladores. Por eso me es muy importante que los programas sean portables.

Código fuente

El código fuente completo de este artículo lo puedes descargar desde aquí.

¿Qué sigue?

  • Si quieres el mismo ejemplo utilizando al sistema operativo FreeRTOS (usando a mi proyecto KleOS), házmelo saber en los comentarios para escribirlo.
  • Brillo. La idea de haber expuesto la línea OE del HC595 es para controlar el brillo (o también hacer que la pantalla parpadée). Aunque ya lo tengo hecho, no lo agregué en este artículo, pero sí lo haré más adelante. Si estás interesado házmelo saber en los comentarios.
  • Más dígitos. Para tener más dígitos podemos encadenar dos módulos de 3 o 4 pantallas, pero el conteo de los pines a utilizar crece de forma lineal y es entonces que debemos pensar en chips auxiliares que codifiquen las líneas líneas.

Fiverr LinkedIn YouTube Instagram Facebook


Por un par de dólares puedes obtener el PCB de este diseño en PDF para que puedas construirlo por tí mismo utilizando herramientas y técnicas caseras, como transferencia por toner y plancha o enmicadora. Serás redigirido a una e-store para que estés tranquilo con tu compra.

Después de la compra recibirás a vuelta de correo un PDF con el esquemático, una guía de colocación de los componentes, el PCB propiamente dicho en dos versiones, con las perforaciones a tamaño real y en tamaño disminuído, para que utilices el que mejor te convenga; también están incluídos la capa de componentes y una guía de dimensiones.


¿Ya conoces mi curso gratuito de Arduino en tiempo real, utilizando el Arduino UNO o el Arduino Due y FreeRTOS?


¿Te gustaría ser de los primeros en recibir las actualizaciones a mi blog? ¡Suscríbete, es gratis!


Fco. Javier Rodríguez
Escrito por:

Fco. Javier Rodríguez

Soy Ingeniero Electrónico con 20+ años de experiencia en el diseño y desarrollo de productos electrónicos de consumo y a medida, y 12+ años como profesor. Egresado de la UNAM, también tengo el grado de Maestro en Ingeniería por la misma universidad. Mi perfil completo lo puede encontrar en: https://www.linkedin.com/in/fjrg76-dot-com/

Ver todas las entradas

3 COMENTARIOS