Proyecto 34.2 - Reloj-Termómetro-Calendario-Higrómetro
En el Proyecto 34.1 vimos una introducción a los displays VFD. Con un módulo RTC DS3231 hicimos un sencillo Reloj-Termómetro aprovechando la temperatura proporcionada por el sensor interno del RTC.
Para completar las funciones del display, vamos a añadir un sensor de humedad DHT11. Este sensor también proporciona la temperatura, pero al igual que el RTC DS3231 tiene una tolerancia de +- 2ºC, por lo que únicamente lo utilizaremos para medir la humedad relativa del aire.
Para medir la temperatura con mayor exactitud, voy a emplear un sensor LM35 de National Semiconductor.
El LM35 es un sensor de temperatura de precisión, con una exactitud de 0.5ºC, es lineal en la respuesta y tiene la ventaja de estar directamente calibrado en grados centígrados.
Antes de continuar, una observación: el LM35 se conecta inversamente, es decir, la patilla GND se conecta a +5V y la patilla +VS se conecta a GND. Esto es porque a grandes rasgos el sensor es un diodo zener y si lo conectamos atendiendo al nombre de las patillas lo que estamos haciendo en polarizar directamente el diodo y provocando un cortocircuito en la alimentación, lo que puede dañar la fuente o el puerto USB de nuestro ordenador.
Vista superior y frontal del display montado sobre un soporte.
Esquema
Como veis en el esquema, el LM35 se conecta inversamente. La salida se conecta directamente a la entrada analógica A1 del Arduino.
El sensor DHT11 se conecta de igual forma que en ejemplos anteriores.
El trimmer de 100K es para ajustar la tensión de referencia del canal analógico a 1.0 voltios. Esto es porque el sensor LM35 proporciona una salida máxima de 1.0 voltios, por lo que si dejamos que aRef sea 5V estamos perdiendo exactitud en la medida.
Recordad que Arduino tiene un conversor A/D en las entradas analógicas. Por defecto convierte los 0-5 voltios de variación a la entrada analógica en un valor digital 0-1023. Como el LM35 varía entre 0 y 1V, estamos perdiendo mucha información, por eso modificamos el valor del pin Analog Reference a 1.0 voltios.
Puedes consultar una explicación más detallada aquí.
Para alimentar el circuito he utilizado una fuente externa de 5V. Se puede utilizar un cargador de móvil. Cuanto más próxima sea la tensión a 5V, más exactas serán las medidas.
Sketch
He programado el sketch de forma que la información se muestra en tres pantallas. La primera muestra el día de la semana y la fecha, tras 5 segundos se muestra la hora y la temperatura y a continuación la hora y la humedad relativa.
/*
Precision Thermometer, Digital Clock-Day-Date-Humidity sensor
with Arduino, RTC DS3231SN, DHT11 Humidity sensor, LM35 Temp. sensor
& VFD Module ISE ELECTRONICS CU169SCPB-T21A
Angel M. https://sites.google.com/site/angmuz
Domingo 19 Jun
13:14 T:24.3ºC
13:14 H:32%
*/
#include "DHT.h"
#include <Wire.h>
#include "ds3231.h"
#define DHTPIN 3 // DHT11 connected to pin 3
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
#include <SoftwareSerial.h>
SoftwareSerial VFD(6,7); // RX, TX. Pin 6 not connected
#define BUFF_MAX 128
uint8_t time[8];
char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev, interval = 1000;
// parametros para LM35
float tempC;
int reading;
int tempPin = 1;
int media;
int cnt;
void setup()
{
analogReference(EXTERNAL); // set aRef to 1.0V
Serial.begin(9600);
VFD.begin(19200);
resetVFD();
VFDclearsceen();
VFD.write(0x16); // underline cursor off
Wire.begin();
DS3231_init(DS3231_INTCN);
memset(recv, 0, BUFF_MAX);
dht.begin();
//parse_cmd("TssmmhhDddmmaaaa",16);
//parse_cmd("T001523715052016",16); // RTC setup,
}
void resetVFD()
// performs a software reset on the VFD controller
{
VFD.write(0x1B); // ESC
VFD.write(0x49); // software reset
}
void VFDclearsceen()
// moves cursor to top-left and clears display
{
VFD.write(0x0E); // clear display
VFD.write(0x0C); // form feed - cursor to top-left
}
void moveCursor(byte position)
// moves the cursor - top row is 0~15
// vertical scroll mode must be turned off if used
{
VFD.write(0x1B); // ESC
VFD.write(0x48); // move cursor
VFD.write(position); // location
}
void loop()
{
int humedad = dht.readHumidity();
char in;
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);
// dia de la semana
VFDclearsceen();
moveCursor(0);
printDay(t.wday);
// dia del mes, mes
VFD.print(t.mday);
printMonth(t.mon);
delay(5000);
// check if returns are valid, if they are NaN (not a number) then something went wrong!
if (isnan(humedad)) {
VFDclearsceen();
moveCursor(0);
VFD.print("Failer read DHT");
} else {
// calculo de la temperatura. con LM35
media=0; // inicializamos el valor de la media
// medimos la temperatura 10 veces y la almacenamos en tmedia
for ( cnt=0; cnt<10; cnt++)
{
reading = analogRead(tempPin);
media=media+reading;
delay(100);
}
// calculamos la media de las medidas
media=media/cnt;
tempC = media / 10.24;
// hora y temperatura
VFDclearsceen();
if(t.hour<10)
{
moveCursor(1);
VFD.print(t.hour);
VFD.print(":");
}
else
{
moveCursor(0);
VFD.print(t.hour);
VFD.print(":");
}
if(t.min<10)
{
VFD.print("0");
}
VFD.print(t.min);
// temperatura
moveCursor(7);
VFD.print(" T:");
VFD.print(tempC,1);
VFD.print((char)149);
VFD.print("C");
delay(500);
// the two dots of the hour blink each 500ms
for (int z=0; z<6; z++){
moveCursor(2);
VFD.print(" ");
delay(500);
moveCursor(2);
VFD.print(":");
delay(500);
}
// hora y humedad
VFDclearsceen();
if(t.hour<10)
{
moveCursor(1);
VFD.print(t.hour);
VFD.print(" ");
}
else
{
moveCursor(0);
VFD.print(t.hour);
VFD.print(" ");
}
if(t.min<10)
{
VFD.print("0");
}
VFD.print(t.min);
// Humedad
moveCursor(7);
VFD.print(" H:");
VFD.print(humedad);
VFD.print("%");
delay(500);
// the two dots of the hour blink each 500ms
for (int z=0; z<6; z++){
moveCursor(2);
VFD.print(":");
delay(500);
moveCursor(2);
VFD.print(" ");
delay(500);
}
}
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);
}
}
void printMonth(int month)
{
switch(month)
{
case 1: VFD.print(" Ene");break;
case 2: VFD.print(" Feb");break;
case 3: VFD.print(" Mar");break;
case 4: VFD.print(" Abr");break;
case 5: VFD.print(" May");break;
case 6: VFD.print(" Jun");break;
case 7: VFD.print(" Jul");break;
case 8: VFD.print(" Ago");break;
case 9: VFD.print(" Sep");break;
case 10: VFD.print(" Oct");break;
case 11: VFD.print(" Nov");break;
case 12: VFD.print(" Dic");break;
default: VFD.print(" Error");break;
}
}
void printDay(int wday)
{
switch(wday)
{
case 1: VFD.print("Lunes ");break;
case 2: VFD.print("Martes ");break;
case 3: VFD.print("Miercoles ");break;
case 4: VFD.print("Jueves ");break;
case 5: VFD.print("Viernes ");break;
case 6: VFD.print("Sabado ");break;
case 7: VFD.print("Domingo ");break;
default: VFD.print("Error ");break;
}
}
Una imagen del sketch en funcionamiento.
Links:
Tutorial about the VFD Modules, by John Boxall
Arduino library for DS3231 RTC, by Petre Rodan
LM35 Higher Resolution, Arduino Playground
Descarga el proyecto en un .zip aquí abajo. Incluye el esquema, sketch y el datasheet LM35.