Reloj de cuarzo LCD con backup y alarma

Este circuito es un simple reloj despertador digital realizado en base a un mcu muy económico y potente (en este caso se eligió por su tamaño y precio, ya que se puede aprovechar mucho mas en otras aplicaciones)
El pic utilizado es el PIC16F883 configurado con oscilador interno y sin interrupciones.
El mismo obtiene los pulsos de reloj y la hora del RTC (Reloj en Tiempo Real) DS1302, este integrado de la firma Maxim nos provee Hora y Fecha, la posibilidad de configurar  y de mantener los datos con una pila de BackUp (en el circuito usamos una CR2032) esto es por si se desconecta de la alimentación.
De esta forma no perderemos la hora, una posible mejora seria la de guardar la alarma en la eeprom del pic por si se desconecta la energía, es muy sencillo, solo es necesario utilizar las instrucciones write_eeprom y read_eeprom, con esas instrucciones una graba el valor seteado como alarma y al momento de compararlo con la hora actual la tome de ahí .
También es posible utilizar interrupción por activación externa para el seteo de la hora, seria mas eficiente.
(estas son mejoras que se me van ocurriendo en el momento) el programa lo eh desarrollado en el recreo de la universidad y con una hora de empeño que me sobraba entre dos materias por esto no lo eh optimizado bien, también decidí subirlo asi porque para los que no tienen mucho conocimiento de C, les resultara mas fácil ver unos cuantos IF sencillos.
El circuito presenta un LCD de 1x16 (es decir una linea/renglón de 16 caracteres, no parecía necesario uno mas grande pero es posible poner otro mas grande y simplemente con la función lcd_gotoxy(X,Y) es posible posicionar el string en donde queramos (Renglón y carácter).
Como se puede notar el circuito se trato de hacer lo mas economicamente posible utilizando MCU económico, LCD el mas chico dentro de lo que nos sea útil, el RTC cuesta un poco mas que el pic, es posible no utilizar el RTC pero habría que utilizar el cristal de 32768KHz con algún arreglo de compuertas como oscilador e ingresarlo en una interrupción por timer del mcu para marcar el tiempo. Pero no viene mal ya que con el RTC también se puede aprender a utilizar el protocolo I2C el cual esta detallado en el código fuente, como también el código de manejo del LCD.
La única librería que resta es la del propio pic pero si lo tienen que compilar ya deberían contar con ella, posible mente también cuenten con la del DS1302.C y LCD.C (siendo así pueden eliminar el código de estos dos del fuente (el de color gris) e incluir las dos librerías, así quedara mas corto el código de programa).
El funcionamiento de este reloj se basa en tres comandos, un interruptor que decide si el reloj esta en modo normal o modo configuración dos pulsadores, uno para incrementar la hora actual, y otro para incrementar la alarma, Si posicionamos el interruptor en normal, el reloj comenzara a funcionar normalmente con la descripción "HORA HH:MM:SS   " en cambio si posicionamos el interruptor en configuración se mostrara   "HORA 00:00:00   " en este estado si presionamos el botón de tiempo aparecerá "ST" quedando de la siguiente manera "HORA 00:00:00 ST" y si lo mantenemos presionado comenzara a incrementar horas y minutos, lo mismo pasa para configurar la alarma, si presionamos el pulsador de alarma mostrara   "HORA 00:00:00 AL"  y luego comenzara a incrementar. Una vez seteado nuevamente ponemos el interruptor en normal y comenzara a funcionar el reloj desde la hora seteada, y en el caso de que la hora de alarma sea igual a la hora actual se encenderá un led. (se puede agregar otra mejora como un interruptor donde esta el led para apagar la alarma... o bien un circuito de alarma sonora temporizado (todo esto se puede hacer con un 555 y un buzzer activo o bien con el mismo pic se puede hacer una subrutina donde se enciende el Led que encienda por un tiempo y luego corte.
También me falto en el diseño ponerle la resistencia a Vcc que alimenta el backlight del lcd, eso nos sirve para poder ver el reloj de noche, claro que si  v a estar en la oscuridad tal vez con un interruptor inversor podemos optar por dos resistencias dif, una que encienda mas brillante el backlight a modo dimmer. eso queda a elección.
El circuito funciona perfectamente pero como les eh comentado es posible mejorarlo y es sencillo mejorarlo también.


ACTUALIZACIÓN:
Se le agrego el control de luminosidad para el backlight con un interruptor inversor para poder elegir entre dos luminosidades diferentes.
Se agrego la grabación de la alarma en la EEPROM interna del mcu para que se retenga si se corta la energía.
Se cambio el display de un renglón por uno de dos renglones ya que el precio es casi el mismo y de esta forma mostramos en la linea 2 el horario de la alarma.





El código fuente (con el control de LCD y DS1302 incluido):
NOTA: el código en Gris es el de las librerías (DS1302.C y LCD.C) que si lo compilan con CCS solo tienen que incluirlas en el encabezado del programa junto a los demas Include las librerías DS1302.C y LCD.C (yo las he modificado levemente para sacarles algunas cosas que interesaban y cambiarle algunas configuraciones, pero es lo mismo si utilizan las de CCS)


#include <16F883.h>
#device adc=8
#FUSES NOWDT
#FUSES INTRC_IO
#FUSES NOPUT
#FUSES MCLR
#FUSES NOBROWNOUT
#FUSES NOLVP
#FUSES NOCPD
#FUSES NOWRT
#FUSES NODEBUG
#FUSES NOPROTECT
#use delay(int=4000000)
#define RTC_RST    PIN_A0
#define RTC_SCLK   PIN_A1
#define RTC_IO     PIN_A2
#include 
#include 
void main()
{
int HH, MIN, SS;
int HHSet, MMset;
int HHAlarm, MMAlarm;
int i;
   lcd_init();
   lcd_putc("Electgpl Clock");
   delay_ms(1000);
   rtc_init();
   lcd_init();
   while(1)
   {
   if (input(PIN_A5)==1)
      {
      if (input(PIN_A4)==1) 
         {
         lcd_gotoxy(15,1);
         lcd_putc("ST");
         rtc_set_datetime(0,0,0,0,HHSet,MMSet);
         MMSet = MMSet + 1;
         if (MMSet > 59)
            {
            delay_ms(100);
            MMSet = 0;
            HHSet = HHSet + 1;
            }  
            if (HHSet > 23)
               {
               HHSet = 0;
               } 
         lcd_gotoxy(1,1);
         printf(lcd_putc,"HORA %02d:%02d:00   ",HHSet,MMSet);               
         }
      else
         {
         if (input(PIN_A3)==1) 
            {
            lcd_gotoxy(15,1);
            lcd_putc("AL");
            MMAlarm = MMAlarm + 1;
            if (MMAlarm > 59)
               {
               delay_ms(100);
               MMAlarm = 0;
               HHAlarm = HHAlarm + 1;
               }  
               if (HHAlarm > 23)
                  {
                  HHAlarm = 0;
                  } 
            lcd_gotoxy(1,1);
            printf(lcd_putc,"HORA %02d:%02d:00   ",HHAlarm,MMAlarm);    
            write_eeprom(8,HHAlarm);
            write_eeprom(9,MMAlarm);
            } 
         } 
      }   
   else     
      {
      rtc_get_time(HH,MIN,SS);
      lcd_gotoxy(1,1);
      printf(lcd_putc,"HORA %02d:%02d:%02d   ",HH,MIN,SS);
      HHAlarm=read_eeprom(8);
      MMAlarm=read_eeprom(9);
      lcd_gotoxy(1,2);
      printf(lcd_putc,"ALARMA  %02d:%02d   ",HHAlarm,MMAlarm);
      if((HH == HHAlarm) && (MIN == MMAlarm))
         output_high(pin_a6);
      else
         output_low(pin_a6);       
      }
   }   
}
#ifndef RTC_SCLK
#define RTC_SCLK PIN_A1
#define RTC_IO   PIN_A3
#define RTC_RST  PIN_A2
#endif
void write_ds1302_byte(BYTE cmd) 
{
   BYTE i;
   for(i=0;i<=7;++i) 
   {
      output_bit(RTC_IO, shift_right(&cmd,1,0) );
      output_high(RTC_SCLK);
      output_low(RTC_SCLK);
   }
}
void write_ds1302(BYTE cmd, BYTE data) 
{
   output_high(RTC_RST);
   write_ds1302_byte(cmd);
   write_ds1302_byte(data);
   output_low(RTC_RST);
}
BYTE read_ds1302(BYTE cmd) 
{
   BYTE i,data;
   output_high(RTC_RST);
   write_ds1302_byte(cmd);
   for(i=0;i<=7;++i) 
   {
      shift_right(&data,1,input(RTC_IO));
      output_high(RTC_SCLK);
      delay_us(2);
      output_low(RTC_SCLK);
      delay_us(2);
   }
   output_low(RTC_RST);
   return(data);
}
void rtc_init() 
{
   BYTE x;
   output_low(RTC_RST);
   delay_us(2);
   output_low(RTC_SCLK);
   write_ds1302(0x8e,0);
   write_ds1302(0x90,0xa4);
   x=read_ds1302(0x81);
   if((x & 0x80)!=0)
     write_ds1302(0x80,0);
}
int get_bcd(BYTE data)
{
   int nibh;
   int nibl;
   nibh=data/10;
   nibl=data-(nibh*10);
   return((nibh<<4)|nibl);
}
int rm_bcd(BYTE data)
{
   int i;
   i=data;
   data=(i>>4)*10;
   data=data+(i<<4>>4);
   return data;
}
void rtc_set_datetime(BYTE day, BYTE mth, BYTE year, BYTE dow, BYTE hr, BYTE min) 
{
   write_ds1302(0x86,get_bcd(day));
   write_ds1302(0x88,get_bcd(mth));
   write_ds1302(0x8c,get_bcd(year));
   write_ds1302(0x8a,get_bcd(dow));
   write_ds1302(0x84,get_bcd(hr));
   write_ds1302(0x82,get_bcd(min));
   write_ds1302(0x80,get_bcd(0));
}
void rtc_get_date(BYTE& day, BYTE& mth, BYTE& year, BYTE& dow) 
{
   day = rm_bcd(read_ds1302(0x87));
   mth = rm_bcd(read_ds1302(0x89));
   year = rm_bcd(read_ds1302(0x8d));
   dow = rm_bcd(read_ds1302(0x8b));
}
void rtc_get_time(BYTE& hr, BYTE& min, BYTE& sec) 
{
   hr = rm_bcd(read_ds1302(0x85));
   min = rm_bcd(read_ds1302(0x83));
   sec = rm_bcd(read_ds1302(0x81));
}
void rtc_write_nvr(BYTE address, BYTE data) 
{
   write_ds1302(address|0xc0,data);
}
BYTE rtc_read_nvr(BYTE address) 
{
    return(read_ds1302(address|0xc1));
}
struct lcd_pin_map 
{                 
BOOLEAN rs;
BOOLEAN rw;
BOOLEAN enable;
BOOLEAN unused;
int     data : 4;         
} lcd;
#if defined(__PCH__)
#if defined use_portb_lcd
   #byte lcd = 0xF81
#else
   #byte lcd = 0xF83
#endif
#else
#if defined use_portb_lcd
   #byte lcd = 6
#else
   #byte lcd = 8
#endif
#endif
#if defined use_portb_lcd
   #define set_tris_lcd(x) set_tris_b(x)
#else
   #define set_tris_lcd(x) set_tris_d(x)
#endif
#define lcd_type 2
#define numero_caracteres 20
#define lcd_line_two 0x40
BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6};
struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; 
struct lcd_pin_map const LCD_READ = {0,0,0,0,15};
BYTE lcd_read_byte() 
{
   BYTE low,high;
   set_tris_lcd(LCD_READ);
   lcd.rw = 1;
   delay_cycles(1);
   lcd.enable = 1;
   delay_cycles(1);
   high = lcd.data;
   lcd.enable = 0;
   delay_cycles(1);
   lcd.enable = 1;
   delay_us(1);
   low = lcd.data;
   lcd.enable = 0;
   set_tris_lcd(LCD_WRITE);
   return( (high<<4) | low);
}
void lcd_send_nibble( BYTE n ) 
{
   lcd.data = n;
   delay_cycles(1);
   lcd.enable = 1;
   delay_us(2);
   lcd.enable = 0;
}
void lcd_send_byte( BYTE address, BYTE n ) 
{
   lcd.rs = 0;
   while ( bit_test(lcd_read_byte(),7) ) ;
   lcd.rs = address;
   delay_cycles(1);
   lcd.rw = 0;
   delay_cycles(1);
   lcd.enable = 0;
   lcd_send_nibble(n >> 4);
   lcd_send_nibble(n & 0xf);
}
void lcd_init() 
{
   BYTE i;
   set_tris_lcd(LCD_WRITE);
   lcd.rs = 0;
   lcd.rw = 0;
   lcd.enable = 0;
   delay_ms(15);
   for(i=1;i<=3;++i) 
   {
      lcd_send_nibble(3);
      delay_ms(5);
   }
   lcd_send_nibble(2);
   for(i=0;i<=3;++i)
      lcd_send_byte(0,LCD_INIT_STRING);
}
void lcd_gotoxy( BYTE x, BYTE y) 
{
   BYTE address;
}

4 comentarios:

  1. Hola Sebastian. He usado ese mismo módulo RTC para la misma aplicación, pero con Arduino. Imagino que sabrás que adelanta 1 minuto a la semana, aproximadamente. ¿Por software se puede retrasar ese minuto? El hermano mayor, el DS3231 es más preciso, por lo visto, pero no lo tengo todavía.

    Gracias y saludos.

    ResponderEliminar
    Respuestas
    1. Hola como estas?, donde leíste eso? yo no recuerdo que me adelantara tanto, me adelantaba un poco ahora que me decis, pero ni a palos 1 hora por semana, igual si es una hora por semana siempre, es lo de menos porque le haces una corrección por software.
      El problema es cuando la variación no es estable cuando una semana de ta 1h, otra semana te da media hora, etc... si siempre es 1h, se puede corregir por software. Es algo muy común en estos casos.
      Saludos.

      Eliminar
    2. Hola Sebastián. En otro foro que participo les ocurre lo mismo. Y en otros que he consultado igual. Aunque hay gente que le atrasa, a mi me adelanta 1 minuto cada semana aproximadamente. Todos me recomiendan el ds3231 que trae compensación de temperatura, pero mientras pensaba ajustar el mio por software. La diferencia son unos euros pero la precisión es de algunos segundos al año.

      Gracias de nuevo.
      Saludos.

      Eliminar
    3. Si, puede ser, yo recuerdo que me algo adelantaba pero no era mucho, osea no era tanto como una hora por semana, eso es un valor muy grande. :S lo voy a mirar la semana que viene a ver que dice, pero por ahora viene andando bien, lo tengo en el escritorio este reloj.
      Saludos!

      Eliminar