Proyecto 35 - Clock Thermometer w NXP SAA1064 4-digit Led Driver

2 de octubre de 2016

En las oficinas de un Servicio Técnico de cuyo nombre no quiero acordarme, no ha mucho tiempo, pasó un equipo de electromedicina para ser reciclado. Como buen juntamugre que soy, antes de retirar el susodicho equipo, le despojé de todo aquello que podía ser útil.

Entre las piezas del botín había una serie de integrados SAA1064. Según el datasheet, el SAA1064 es un driver para displays led de 7 segmentos, tiene interfaz I2C y es capaz de controlar hasta cuatro displays.

Buscando en internet encontré varias páginas con tutoriales para hacer funcionar este integrado con Arduino. Me documenté un poco más, reuní los componentes necesarios y aquí estamos con un nuevo proyecto con Arduino y el circuito integrado SAA1064.

SAA1064.jpg

Googleando un poco se pueden encontrar excelentes tutoriales de propósito general sobre el SAA1064. En el proyecto aquí descrito veremos cómo hacer un reloj-termómetro con cuatro displays de 7 segmentos, un integrado SAA1064 y el RTC DS3231.

A primera vista puede parecer que hacer un reloj-termómetro con cuatro displays de 7 segmentos es más sencillo que hacerlo con displays LCD. Pero mientras que un LCD necesita de seis hilos de conexión con Arduino -o dos hilos si usa el bus I2C- aquí cada display tiene 10 pines x 4 displays son 40 hilos, más dos transistores, el integrado SAA1064 y el RTC DS3231. Para ilustrar de lo que hablo, mira la imagen siguiente del circuito terminado.

Todo lo anterior añade complejidad al proyecto, tanto a nivel hardware como software...y también más horas de entretenimiento. Como he dicho en anteriores ocasiones, si todo saliera la primera, esto sería muy aburrido.

Para materializar este proyecto me he basado en los tutoriales disponibles en tronixstuff y en Arduino Adventures. Del primero saqué parte del código y del segundo el esquema.

Esquema

Como decía un poco más arriba, el esquema está basado en el propuesto en la página Arduino Adventures, pero adaptado a cuatro displays independientes. Curiosamente el circuito propuesto en la web  tronixstuff mostraba los dígitos intercambiados.

En el esquema puede verse que he utilizado dos displays de fabricantes diferentes, pero iguales pin a pin. No hay un motivo técnico para ello, simplemente son los displays de los que disponía en ese momento.

Como curiosidad una imagen tomada por mí, de los displays vistos con Rayos-X.

Se pueden ver los diodos led de cada segmento y la pista común a los ánodos.

TDSO-5150 HDSP-5551 7 SEGMENT DISPLAY.PNG
TDSO-5150 HDSP-5551 7 SEGMENT DISPLAY X-RAY.PNG

Sketch

El sketch lo he modificado partiendo del código del Proyecto 21. He borrado la parte correspondiente a las funciones printDay y printMonth ya que no vamos a mostrar el día de la semana ni el mes. También he quitado las líneas correspondientes al LCD.

Para mostrar la hora he utilizado la función saa1064.sayTime(t.min, t.hour) disponible gracias a la librería coolSAA.

Indicar la temperatura resultó más complicado, ya que junto a la cifra quería mostrar el símbolo de los grados y la letra C, es decir, ºC, para hacer la lectura más intuitiva.

La librería coolSAA es excelente para mostrar cualquier variable, pero no admite texto libre ni caracteres especiales. Así que partiendo del primer ejemplo que podéis encontrar en  tronixstuff he utilizado la función displayInteger(int num) para sacar en los dos dígitos de la izquierda la temperatura y en los dos de la derecha ºC.

Esta función recibe la variable ta, la descompone en decenas y unidades y se envían en forma de cuatro bytes -uno por cada dígito- al controlador SAA1064 junto con el símbolo º y la letra C.

Multiplicar la temperatura x 10 en el void loop tiene un doble motivo. Por un lado sirve para quitar los decimales de la variable temperature y así enviar un número entero en la función displayInteger(ta); si temperature = 26.5 entonces ta = 265. Al disponer solamente de dos dígitos para mostrar la temperatura, no tiene sentido utilizar decimales.

El segundo motivo tiene que ver con la precisión en la medida. Eliminar los decimales resta exactitud a la hora de indicar la temperatura en el display. Si por ejemplo la variable temperature es 26.9ºC, el display indicará 26 aunque estemos más cerca de los 27ºC que de los 26ºC. La función displayInteger descompone ta en centenas, decenas y unidades. Las centenas y decenas corresponden con los dos dígitos a mostrar de la temperatura. Y las unidades son los decimales que utilizamos para redondear la medida. Entre 26.0 y 26.4 el display muestra 26ºC y entre 26.5 y 26.9 el display muestra 27ºC con lo que tenemos 0.5ºC de exactitud.

/*

*

* Proyecto 35

* Clock-Thermometer with 4 x 7 Segment Displays, DS3231 RTC and SAA1064 LED Driver I2C Interface

* https://sites.google.com/site/angmuz/home

*

*

*/

#include <Wire.h>

#include "ds3231.h"

#define BUFF_MAX 128

#include <cool_SAA1064.h> // enable I2C bus

SAA1064 saa1064;

byte saa = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)

// these are the byte representations of pins required to display each digit 0~9 then A~F, and blank digit

int digits[17]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113, 0};

uint8_t time[8];

char recv[BUFF_MAX];

unsigned int recv_size = 0;

unsigned long prev, interval = 1000;

void setup()

{

   Serial.begin(9600);

   Wire.begin();

   DS3231_init(DS3231_INTCN);

   memset(recv, 0, BUFF_MAX);

   delay(500);

   initDisplay();

      

   // the first day of the week is monday=1

   // (seconds,minutes,hour,week day,date, month,year)

   // parse_cmd("T001422126092016",16);

}

// turns on dynamic mode and adjusts segment current to 12mA

void initDisplay()

{

Wire.beginTransmission(saa);

Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte

Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current

Wire.endTransmission();

}

void clearDisplay()

{

Wire.beginTransmission(saa);

Wire.write(1); // start with digit 1 (right-hand side)

Wire.write(0); // blank digit 1

Wire.write(0); // blank digit 2

Wire.write(0); // blank digit 3

Wire.write(0); // blank digit 4

Wire.endTransmission();

}

// displays the temperature

void displayInteger(int num)

{

int hundred, ten, one;

// breakdown number into columns

hundred = num/100;

ten = (num-(hundred*100))/10;

one = num-((hundred*100)+(ten*10));

if (one<=4)

{

  Wire.beginTransmission(saa);

  Wire.write(1);

  Wire.write(digits[ten]);

  Wire.write(digits[hundred]);

  Wire.write(B00111001); // C

  Wire.write(B01100011); // º

  Wire.endTransmission();

  delay(10);  

}

else

if (one>4)

{

  ten++;

  Wire.beginTransmission(saa);

  Wire.write(1);

  Wire.write(digits[ten]);

  Wire.write(digits[hundred]);

  Wire.write(B00111001); // C

  Wire.write(B01100011); // º

  Wire.endTransmission();

  delay(10);

}

}

void loop()

{

   char in;

   char tempF[6];

   float temperature;

   int ta;

   char buff[BUFF_MAX];

   unsigned long now = millis();

   struct ts t;

   // show time once in a while

   if ((now - prev > interval) && (Serial.available() <= 0)) {

       DS3231_get(&t); //Get time

       parse_cmd("C",1);

       temperature = DS3231_get_treg(); //Get temperature

       dtostrf(temperature, 5, 1, tempF);

       

       saa1064.sayTime( t.min, t.hour );

       delay(3000);

       

       ta=temperature*10;        

       displayInteger(ta);

       delay(3000);

               

       prev = now;

       

       

   }

   

   if (Serial.available() > 0) {

       in = Serial.read();

       if ((in == 10 || in == 13) && (recv_size > 0)) {

           parse_cmd(recv, recv_size);

           recv_size = 0;

           recv[0] = 0;

       } else if (in < 48 || in > 122) {;       // ignore ~[0-9A-Za-z]

       } else if (recv_size > BUFF_MAX - 2) {   // drop lines that are too long

           // drop

           recv_size = 0;

           recv[0] = 0;

       } else if (recv_size < BUFF_MAX - 2) {

           recv[recv_size] = in;

           recv[recv_size + 1] = 0;

           recv_size += 1;

       }

   }

}

void parse_cmd(char *cmd, int cmdsize)

{

   uint8_t i;

   uint8_t reg_val;

   char buff[BUFF_MAX];

   struct ts t;

   //snprintf(buff, BUFF_MAX, "cmd was '%s' %d\n", cmd, cmdsize);

   //Serial.print(buff);

   // TssmmhhWDDMMYYYY aka set time

   if (cmd[0] == 84 && cmdsize == 16) {

       //T355720619112011

       t.sec = inp2toi(cmd, 1);

       t.min = inp2toi(cmd, 3);

       t.hour = inp2toi(cmd, 5);

       t.wday = inp2toi(cmd, 6);

       t.mday = inp2toi(cmd, 8);

       t.mon = inp2toi(cmd, 10);

       t.year = inp2toi(cmd, 12) * 100 + inp2toi(cmd, 14);

       DS3231_set(t);

       Serial.println("OK");

   } else if (cmd[0] == 49 && cmdsize == 1) {  // "1" get alarm 1

       DS3231_get_a1(&buff[0], 59);

       Serial.println(buff);

   } else if (cmd[0] == 50 && cmdsize == 1) {  // "2" get alarm 1

       DS3231_get_a2(&buff[0], 59);

       Serial.println(buff);

   } else if (cmd[0] == 51 && cmdsize == 1) {  // "3" get aging register

       Serial.print("aging reg is ");

       Serial.println(DS3231_get_aging(), DEC);

   } else if (cmd[0] == 65 && cmdsize == 9) {  // "A" set alarm 1

       DS3231_set_creg(DS3231_INTCN | DS3231_A1IE);

       //ASSMMHHDD

       for (i = 0; i < 4; i++) {

           time[i] = (cmd[2 * i + 1] - 48) * 10 + cmd[2 * i + 2] - 48; // ss, mm, hh, dd

       }

       byte flags[5] = { 0, 0, 0, 0, 0 };

       DS3231_set_a1(time[0], time[1], time[2], time[3], flags);

       DS3231_get_a1(&buff[0], 59);

       Serial.println(buff);

   } else if (cmd[0] == 66 && cmdsize == 7) {  // "B" Set Alarm 2

       DS3231_set_creg(DS3231_INTCN | DS3231_A2IE);

       //BMMHHDD

       for (i = 0; i < 4; i++) {

           time[i] = (cmd[2 * i + 1] - 48) * 10 + cmd[2 * i + 2] - 48; // mm, hh, dd

       }

       byte flags[5] = { 0, 0, 0, 0 };

       DS3231_set_a2(time[0], time[1], time[2], flags);

       DS3231_get_a2(&buff[0], 59);

       Serial.println(buff);

   } else if (cmd[0] == 67 && cmdsize == 1) {  // "C" - get temperature register

       //Serial.print("temperature reg is ");

       //Serial.println(DS3231_get_treg(), DEC);

   } else if (cmd[0] == 68 && cmdsize == 1) {  // "D" - reset status register alarm flags

       reg_val = DS3231_get_sreg();

       reg_val &= B11111100;

       DS3231_set_sreg(reg_val);

   } else if (cmd[0] == 70 && cmdsize == 1) {  // "F" - custom fct

       reg_val = DS3231_get_addr(0x5);

       Serial.print("orig ");

       Serial.print(reg_val,DEC);

       Serial.print("month is ");

       Serial.println(bcdtodec(reg_val & 0x1F),DEC);

   } else if (cmd[0] == 71 && cmdsize == 1) {  // "G" - set aging status register

       DS3231_set_aging(0);

   } else if (cmd[0] == 83 && cmdsize == 1) {  // "S" - get status register

       Serial.print("status reg is ");

       Serial.println(DS3231_get_sreg(), DEC);

   } else {

       Serial.print("unknown command prefix ");

       Serial.println(cmd[0]);

       Serial.println(cmd[0], DEC);

   }

}


Links

Arduino Adventures: Playing with 7 segment LED displays

tronixstuff: Tutorial Arduino & the SAA1064 Led Driver

An arduino library driving the NXP SAA1064: coolSAA1064 library

Petre Rodan: Arduino library for DS3231 RTC

Puedes descargar el proyecto aquí abajo. Incluye el esquema, sketch y datasheet.