En este tutorial vamos a programar un módulo RTC.
El módulo utilizado es el DS3231
Este módulo incluye además un sensor de temperatura.
En una primera parte conectaremos el módulo al Arduino para configurarlo y comprobar que funciona correctamente. Conectamos un display para ver el resultado.
Más adelante añadiremos nuevas funciones al módulo, sensores de temperatura, humedad, etc.
El objetivo final será programar alarmas para que Arduino realice diariamente acciones concretas a la hora establecida.
Este código está modificado a partir de la librería de Petre Rodan disponible en GitHub. La librería es necesaria para que funcione el módulo, así que instaladla sin dilación.
Hay un vídeo con explicaciones en YouTube.
La conexión es muy sencilla, conectamos la alimentación a 5V, GND y los pines SDA y SDL a los pines A4 y A5 del Arduino.
Como indiqué mas arriba, este código es una modificación del original de Petre Rodan al que he añadido la función printDay para que el display muestre el día de la semana.
La primera vez que se carga el código hay que desmarcar la línea parse_cmd("T004600501052015",16);
En esta línea del código vamos a configurar el tiempo actual en el módulo RTC.
De izquierda a derecha el significado es: T significa Tiempo, los dos siguiente dígitos son los segundos, minutos, hora, día de la semana, día del mes, mes, año.
Esto sólo se hace la primera vez que se carga el sketch, luego se marca la línea para no sobre escribir el tiempo y enviamos el programa otra vez al Arduino. El módulo guarda el tiempo aunque se quede sin alimentación, gracias a la batería incluida, y será así por muchos años hasta que se agote.
/*
Test RTC. OK for DS3231SN
*/
#include <Wire.h>
#include "ds3231.h"
#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 8, 9); // pins for RS, E, DB4, DB5, DB6, DB7
#define BUFF_MAX 128
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);
Serial.println("GET time");
lcd.begin(16, 2);
lcd.clear();
// the first day of the week is monday=1
// (seconds,minutes,hour,week day,date, month,year)
// parse_cmd("T004600501052015",16);
}
void loop()
{
char in;
char tempF[6];
float temperature;
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);
lcd.clear();
lcd.setCursor(0,0);
printDay(t.wday);
//lcd.print(t.wday);
//lcd.print(":");
lcd.print(t.mday);
printMonth(t.mon);
lcd.print(t.year);
lcd.setCursor(0,1); //Go to second line of the LCD Screen
lcd.print(t.hour);
lcd.print(":");
if(t.min<10)
{
lcd.print("0");
}
lcd.print(t.min);
lcd.print(":");
if(t.sec<10)
{
lcd.print("0");
}
lcd.print(t.sec);
lcd.print(' ');
lcd.print(tempF);
lcd.print((char)223);
lcd.print("C ");
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: lcd.print(" Ene ");break;
case 2: lcd.print(" Feb ");break;
case 3: lcd.print(" Mar ");break;
case 4: lcd.print(" Abr ");break;
case 5: lcd.print(" May ");break;
case 6: lcd.print(" Jun ");break;
case 7: lcd.print(" Jul ");break;
case 8: lcd.print(" Ago ");break;
case 9: lcd.print(" Sep ");break;
case 10: lcd.print(" Oct ");break;
case 11: lcd.print(" Nov ");break;
case 12: lcd.print(" Dic ");break;
default: lcd.print(" Error ");break;
}
}
void printDay(int wday)
{
switch(wday)
{
case 1: lcd.print(" Lun ");break;
case 2: lcd.print(" Mar ");break;
case 3: lcd.print(" Mie ");break;
case 4: lcd.print(" Jue ");break;
case 5: lcd.print(" Vie ");break;
case 6: lcd.print(" Sab ");break;
case 7: lcd.print(" Dom ");break;
default: lcd.print(" Error ");break;
}
}
En el siguiente proyecto he añadido el sensor de temperatura y humedad DHT11, de esa manera complementamos las funciones del RTC y convertimos nuestro reloj en una estación meteorológica.
Como dije más arriba, el módulo DS3231 incluye un sensor de temperatura, pero el del DHT11 es un grado más preciso. En cualquier caso se puede usar uno u otro indistintamente.
El código que hay a continuación funciona con el sensor de temperatura del DHT11, al final de la página hay una versión que utiliza el sensor de temperatura del DS3231.
El funcionamiento es muy sencillo, cada cinco segundos el display muestra la fecha y la hora juntas, y en la siguiente pantalla la temperatura y la humedad.
/*
se ha añadido el sensor DHT11
para obtener medidas de humedad y temperatura
*/
#include "DHT.h"
#include <Wire.h>
#include "ds3231.h"
#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 8, 9); // pins for RS, E, DB4, DB5, DB6, DB7
#define DHTPIN 3 // what pin we're connected to
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
#define BUFF_MAX 128
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);
Serial.println("GET time");
dht.begin();
lcd.begin(16, 2);
lcd.clear();
// Serial.println("Setting time");
// (seconds,minutes,hour,week day,date, month,year)
// the first day of the week is monday=1
// parse_cmd("T004600501052015",16);
}
void loop()
{
int humedad = dht.readHumidity();
int temperatura = dht.readTemperature();
char in;
char tempF[6];
float temperature;
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);
//Go to first line of the LCD Screen
lcd.clear();
lcd.setCursor(0,0);
printDay(t.wday);
lcd.setCursor(11,0);
lcd.print(t.hour);
lcd.print(":");
if(t.min<10)
{
lcd.print("0");
}
lcd.print(t.min);
//Go to second line of the LCD Screen
lcd.setCursor(2,1);
lcd.print(t.mday);
printMonth(t.mon);
lcd.print(t.year);
delay(5000);
lcd.clear();
// check if returns are valid, if they are NaN (not a number) then something went wrong!
if (isnan(temperatura) || isnan(humedad)) {
lcd.setCursor(0, 0);
lcd.print("Failer read DHT");
} else {
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Humedad: ");
lcd.print(humedad);
lcd.print("%");
lcd.setCursor(3, 1);
lcd.print("Temp: ");
lcd.print(temperatura);
lcd.print((char)223);
lcd.print("C ");
}
prev = now;
delay(5000);
}
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: lcd.print(" Ene ");break;
case 2: lcd.print(" Feb ");break;
case 3: lcd.print(" Mar ");break;
case 4: lcd.print(" Abr ");break;
case 5: lcd.print(" May ");break;
case 6: lcd.print(" Jun ");break;
case 7: lcd.print(" Jul ");break;
case 8: lcd.print(" Ago ");break;
case 9: lcd.print(" Sep ");break;
case 10: lcd.print(" Oct ");break;
case 11: lcd.print(" Nov ");break;
case 12: lcd.print(" Dic ");break;
default: lcd.print(" Error ");break;
}
}
void printDay(int wday)
{
switch(wday)
{
case 1: lcd.print(" Lunes ");break;
case 2: lcd.print(" Martes ");break;
case 3: lcd.print(" Miercoles ");break;
case 4: lcd.print(" Jueves ");break;
case 5: lcd.print(" Viernes ");break;
case 6: lcd.print(" Sabado ");break;
case 7: lcd.print(" Domingo ");break;
default: lcd.print(" Error ");break;
}
}