23 de octubre de 2016
En la primera parte de este tutorial vimos cómo hacer un reloj con la librería SevSeg. Con dicha librería resulta muy fácil mostrar variables numéricas en un display de 7 segmentos, sin embargo no permite mostrar texto libre, es decir, si por ejemplo hacemos un termómetro no podríamos mostrar el símbolo º ni la letra C, o cualquier combinación de caracteres que se nos ocurra.
Utilizando un Shift Register 74HC595 vamos a tener un mayor control sobre el display. Podremos iluminar los segmentos a voluntad y en todas las combinaciones posibles.
Como siempre, lo primero es comprobar que el display funciona correctamente. Para ello montamos el siguiente circuito y con un sencillo sketch verificamos que se iluminan todos los segmentos del display.
A diferencia del circuito utilizado en la primera parte, donde las resistencias van en los cátodos comunes de cada dígito, aquí las resistencias van colocadas en cada uno de los siete segmentos del display.
He utilizado un Arduino NANO v3 en lugar del UNO R3 porque el NANO tiene una mayor corriente de salida en los pines digitales. Según las especificaciones de ambas tarjetas el modelo NANO tiene una corriente máxima de 40mA en cada I/O PIN, mientras que el UNO tiene una corriente máxima de 20mA. Esto implica que para no sobrepasar la corriente máxima, cuando los 7 segmentos están iluminados, por ejemplo para representar el número 8, cada segmento toca a 20mA / 7seg =2,85mA/seg en el UNO y 40mA / 7 seg =5,7mA/seg en el NANO, por lo que se iluminarán más en este último.
Tened en cuenta, según el esquema, cada uno de los ánodos comunes de cada dígito está conectado a un pin digital del Arduino, y la corriente de los 7 segmentos pasa por el correspondiente pin del Arduino. Por eso se divide la corriente máxima del pin entre 7, y se calcula la resistencia adecuada para no sobrepasar ese valor.
En caso de utilizar un Arduino UNO R3, hay que poner resistencias de 1K2 para que cada segmento no sobrepase los 2,85mA. Esto tiene el inconveniente de que el segmento se va a iluminar muy poco.
Una solución es colocar un transistor para alimentar el display como se muestra a continuación. Utilizar transistores no afecta al sketch y se pueden colocar resistencias más pequeñas 180-220 Ohm en los segmentos, para que se iluminen con más intensidad.
Mediante el siguiente sketch, lo que hacemos es iluminar de uno en uno los siete segmentos de cada dígito y a continuación se muestran los números del 0 al 9. Mira el vídeo demostrativo.
Para ello se hace un HIGH solo al pin del dígito que queremos que se ilumine y con el bucle for se le envían al dígito los 18 bytes que contiene el array digit[18].
Dependiendo del valor del byte enviado, así se iluminan los segmentos. Esto es 0b11111111 donde 0b1 es igual en todos los bytes y dependiendo si ponemos 0 ó 1 se ilumina el segmento correspondiente 0b1gfedcba
En este ejemplo he utilizado siete bits de los ocho disponibles. Fíjate que el primer bit después de 0b siempre es 1 (y en el esquema Q7 no está conectado) y los otros siete bits cambian de valor según lo que queramos mostrar. Esto es porque el display aquí utilizado es de siete segmentos, sin punto decimal. Hay una explicación más detallada aquí.
A continuación el sketch.
/*
* Proyecto 36.1
* Testing 5-Digit 7-Segment Display with 74HC595
* Scan Digit Mode
* Este sketch ilumina uno por uno todos los segmentos de cada dígito de uno en uno
* Angel M. https://sites.google.com/site/angmuz
* Public domain
*/
int digitPins[5] = {2,3,4,5,6};
int latchPin = 12; // Arduino Pin 12 to 74HC595 Pin 12 (Latch)
int dataPin = 13; // Arduino Pin 13 to 74HC595 Pin 14 (Data)
int clockPin = 11; // Arduino Pin 11 to 74HC595 Pin 11 (Clock)
int i =0;
int digitScan = 0;
// Cada segmento se enciende con LOW por ser de ánodo común
const byte digit[18] = {
0b11111111, // BLANK
0b11111110, // seg a
0b11111101, // seg b
0b11111011, // seg c
0b11110111, // seg d
0b11101111, // seg e
0b11011111, // seg f
0b10111111, // seg g
0b11000000, // 0
0b11111001, // 1
0b10100100, // 2
0b10110000, // 3
0b10011001, // 4
0b10010010, // 5
0b10000010, // 6
0b11111000, // 7
0b10000000, // 8
0b10010000};// 9
void setup() {
Serial.begin(9600);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
}
void loop(){
for(int j=0; j<5; j++)
{
digitalWrite(digitPins[j], LOW);
}
digitalWrite(digitPins[digitScan], HIGH);
for (i=0; i<18 ;i++)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digit[i]);
digitalWrite(latchPin, HIGH);
delay(300);
}
digitScan++;
if(digitScan>4) digitScan=0;
}
Para materializar este pequeño reloj-termómetro, sólo hay que añadir al circuito anterior un módulo RTC DS3231.
Partiendo del código para el reloj de la primera parte, se quitan las líneas correspondientes a la librería SevSeg, y se añaden las líneas para controlar el 74HC595 del ejemplo 2.1. que vimos al principio, y alguna cosa más :-)
Con las funciones displayHour y displayTemp mostramos la hora y la temperatura en el display. Para ello descomponemos las variables t.hour, t.min y temperature en números simples y sacamos cada cifra en el dígito correspondiente mediante digitBuffer[i].
El quinto dígito del display lo utilizamos para iluminar los : cuando se muestra la hora. Y cuando se muestra la temperatura en los dos dígitos de la izquierda, en los de la derecha mostramos ºC.
También he incluido un pequeño juego de luces con el quinto dígito al final de la función displayTemp.
/*
* Proyecto 36.1 - Tiny Clock-Termometer with 5-Digit 7-Segment
* Small DVD Display, DS3231 RTC Module & 74HC595 Shift Register
*
* Angel M. https://sites.google.com/site/angmuz
* Public domain
*
*/
#include <Wire.h>
#include "ds3231.h"
#define BUFF_MAX 128
uint8_t time[8];
char recv[BUFF_MAX];
unsigned int recv_size = 0;
int digitPins[5] = {2,3,4,5,6};
int latchPin = 12; // Arduino Pin 12 to 74HC595 Pin 12 (Latch)
int dataPin = 13; // Arduino Pin 13 to 74HC595 Pin 14 (Data)
int clockPin = 11; // Arduino Pin 11 to 74HC595 Pin 11 (Clock)
int i = 0;
int digitScan = 0;
int digitBuffer[5] = {0};
const byte digit[14] = {
0b11000000, // 0
0b11111001, // 1
0b10100100, // 2
0b10110000, // 3
0b10011001, // 4
0b10010010, // 5
0b10000010, // 6
0b11111000, // 7
0b10000000, // 8
0b10010000, // 9
0b11101111, // :
0b11111111, // BLANK
0b11000110, // C
0b10011100};// º
void setup()
{
//Serial.begin(9600);
Wire.begin();
DS3231_init(DS3231_INTCN);
memset(recv, 0, BUFF_MAX);
//Serial.println("GET time");
// the first day of the week is monday=1
// (seconds,minutes,hour,week day,date,month,year)
// parse_cmd("T003922420102016",16);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
}
void loop()
{
char in;
char tempF[6];
float temperature;
char buff[BUFF_MAX];
unsigned long now = millis();
struct ts t;
DS3231_get(&t); //Get time
parse_cmd("C",1);
temperature = DS3231_get_treg(); //Get temperature
dtostrf(temperature, 5, 1, tempF);
displayHour(t.hour, t.min);
displayTemp(temperature);
/*
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);
}
}
// función para mostrar la hora y minutos
void displayHour(int hora, int minutos)
{
int decHora, udsHora, decMins, udsMins;
// descomponemos la hora decenas y unidades
decHora = hora/10;
udsHora = hora-(decHora*10);
// descomponemos los minutos decenas y unidades
decMins = minutos/10;
udsMins = minutos-(decMins*10);
// con este bucle mostramos la hora durante 5seg (4x1250=5000ms)
for (int h=0; h<1250; h++)
{
digitBuffer[4] = 10; //:
digitBuffer[3] = udsMins; //unidades de minuto
digitBuffer[2] = decMins; //decenas de minuto
digitBuffer[1] = udsHora; //unidades de hora
digitBuffer[0] = decHora; //decenas de hora
updateDisp();
delay(4);
}
}
// función para mostrar la temperatura
void displayTemp(int temp)
{
int decTemp, udsTemp;
// descomponemos la temperatura decenas y unidades
decTemp = temp/10;
udsTemp = temp-(decTemp*10);
// con este bucle mostramos la temp. durante 5seg (4x1250=5000ms)
for (int t=0; t<1250; t++)
{
digitBuffer[4] = 11; //BLANK
digitBuffer[3] = 12; //C
digitBuffer[2] = 13; //º
digitBuffer[1] = udsTemp; //unidades de temp.
digitBuffer[0] = decTemp; //decenas de temp.
updateDisp();
delay(4);
}
// juego de luces con los segmentos del dígito nº5
for (int x=1; x<8; x++)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digit[x]);
digitalWrite(latchPin, HIGH);
delay(200);
}
}
void updateDisp(){
for(int j=0; j<5; j++)
{
digitalWrite(digitPins[j], LOW);
}
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, B11111111);
digitalWrite(latchPin, HIGH);
//delayMicroseconds(100);
digitalWrite(digitPins[digitScan], HIGH);
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, digit[digitBuffer[digitScan]]);
digitalWrite(latchPin, HIGH);
digitScan++;
if(digitScan>4) digitScan=0;
}