Proyecto 35 - Clock Thermometer w NXP SAA1064 4-digit Led Driver
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.
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.
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.