Relógio em tempo real com PIC16F877A, SSD1306 OLED e DS1307

Tempo de leitura: 5 minutes

Este post mostra como construir um relógio em tempo real usando o microcontrolador PIC16F877A e o chip DS1307 RTC onde a hora e a data são exibidas em um display OLED com controlador SSD1306 (128×64 Pixel). Com a ajuda de uma bateria de célula tipo moeda de 3V, o DS1307 mantém o tempo funcionando mesmo se a fonte de alimentação principal for cortada. O compilador utilizado neste projeto é o CCS PIC C, diagrama de circuito de hardware e simulação Proteus estão abaixo.

O SSD1306 OLED usado neste projeto está configurado para funcionar em modo I2C, algumas placas SSD1306 OLED podem exigir pequenas modificações de hardware (para selecionar entre modo SPI ou modo I2C) como solda, colocação de jumpers…

Para simplificar o circuito do projeto, o display SSD1306 e o chip DS1307 RTC compartilham o mesmo barramento I2C, sempre o microcontrolador PIC16F877A fala com um dispositivo apenas porque seus endereços escravos I2C são diferentes.

 

Hardware Necessário:

  • Microcontrolador PIC16F877A
  • Tela OLED SSD1306 (128×64 Pixel)
  • DS1307 RTC — folha de dados
  • oscilador de cristal de 32,768 KHz
  • oscilador de cristal de 8 MHz
  • 2 x capacitor cerâmico de 22pF
  • 3 x resistor de 10k ohms
  • 2 x botão de pressão
  • bateria de célula tipo moeda 3V
  • fonte de alimentação 5V
  • Protoboard
  • Fios de ligação

O circuito:

A imagem a seguir mostra o diagrama de circuito do projeto.

(Todos os terminais aterrados são conectados juntos)

Os dois botões B1 e B2 servem para ajustar a hora e a data. O botão B1 está conectado ao pino RB0 (#33) e o B2 está conectado ao pino RB1 (#34). Os pull-ups internos para pinos PORTB estão habilitados no código.

Neste projeto o microcontrolador PIC16F877A roda com oscilador de cristal de 8MHz.

O código:

O código C abaixo é para o compilador CCS C, foi testado com a versão 5.051.

Para poder compilar o código C abaixo sem erros, é necessário um driver para o display OLED SSD1306, seu nome é SSD1306.C, seu link de download está abaixo:
Driver de exibição OLED SSD1306

após o download, adicione o arquivo de driver à pasta do projeto ou pasta de drivers do compilador CCS C.

O DS1307 RTC e o display OLED SSD1306 compartilham o mesmo barramento I2C, o endereço DS1307 I2C é 0xD0 e o endereço I2C do display SSD1306 é 0x7A.

Código CCS C:

/**************************************************************************************

 Real time clock/calendar using PIC16F877A microcontroller, SSD1306 OLED display
      (128x64 Pixel) and DS1307 RTC chip.
 C Code for CCS C compiler
 Crystal oscillator used @ 8MHz

***************************************************************************************/

// SSD1306 OLED reset pin definition
#define SSD1306_RST    PIN_D4

#define button1        PIN_B0                       // Button B1 is connected to RB0 pin
#define button2        PIN_B1                       // Button B2 is connected to RB1 pin


#include <16F877A.h>
#fuses HS, NOWDT, NOPROTECT, NOLVP
#use delay(clock = 8MHz)
#use fast_io(B)
#use I2C(MASTER, I2C1, FAST = 100000, stream = SSD1306_STREAM)  // initialize I2C

// include SSD1306 OLED driver source code
#include <SSD1306.c>    

// Variables declaration
char Time[] = "  :  :  ";
char Date[] = "  /  /20  ";
int8 i, second, minute, hour, w_day, day, month, year;

// Function for display day of the week
void display_day() {

  SSD1306_GotoXY(7, 1);
  switch(w_day){
    case 1:  SSD1306_PutC(" SUNDAY  "); break;
    case 2:  SSD1306_PutC(" MONDAY  "); break;
    case 3:  SSD1306_PutC(" TUESDAY "); break;
    case 4:  SSD1306_PutC("WEDNESDAY"); break;
    case 5:  SSD1306_PutC("THURSDAY "); break;
    case 6:  SSD1306_PutC(" FRIDAY  "); break;
    default: SSD1306_PutC("SATURDAY ");
  }

}

void DS1307_display() {
  // Convert BCD to decimal
  second = (second >> 4) * 10 + (second & 0x0F);
  minute = (minute >> 4) * 10 + (minute & 0x0F);
  hour   = (hour   >> 4) * 10 + (hour   & 0x0F);
  day   = (day     >> 4) * 10 + (day    & 0x0F);
  month  = (month  >> 4) * 10 + (month  & 0x0F);
  year   = (year   >> 4) * 10 + (year   & 0x0F);
  // End conversion

  Time[7] = second % 10 + '0';
  Time[6] = second / 10 + '0';
  Time[4] = minute % 10 + '0';
  Time[3] = minute / 10 + '0';
  Time[1] = hour   % 10 + '0';
  Time[0] = hour   / 10 + '0';
  Date[9] = year   % 10 + '0';
  Date[8] = year   / 10 + '0';
  Date[4] = month  % 10 + '0';
  Date[3] = month  / 10 + '0';
  Date[1] = day    % 10 + '0';
  Date[0] = day    / 10 + '0';

  SSD1306_GotoXY(6, 3);
  printf(SSD1306_PutC, Date);   // Print date
  SSD1306_GotoXY(7, 8);
  printf(SSD1306_PutC, Time,);  // Print time

}

void blink_parameter() {
  int8 j = 0;
  while(j < 10 && input(button1) && input(button2)) {
    j++;
    delay_ms(25);
  }
}

int8 edit(int8 x_pos, int8 y_pos, int8 parameter) {
  delay_ms(50);
  while(!input(button1));                        // Wait for button B1 release

  while(true){
    while(!input(button2)) {                     // If button B2 is pressed
      parameter++;
      if(i == 0 && parameter > 31)                   // If date > 31 ==> date = 1
        parameter = 1;
      if(i == 1 && parameter > 12)                   // If month > 12 ==> month = 1
        parameter = 1;
      if(i == 2 && parameter > 99)                   // If year > 99 ==> year = 0
        parameter = 0;
      if(i == 3 && parameter > 23)                   // If hours > 23 ==> hours = 0
        parameter = 0;
      if(i == 4 && parameter > 59)                   // If minutes > 59 ==> minutes = 0
        parameter = 0;

      SSD1306_GotoXY(x_pos, y_pos);
      printf(SSD1306_PutC, "%02u", parameter);
      delay_ms(200);                                    // Wait 200ms
    }

    SSD1306_GotoXY(x_pos, y_pos);
    SSD1306_PutC("  ");
    blink_parameter();
    SSD1306_GotoXY(x_pos, y_pos);
    printf(SSD1306_PutC, "%02u", parameter);
    blink_parameter();

    if(!input(button1)){                       // If button B1 is pressed
      i++;                                           // Increament 'i' for the next parameter
      return parameter;                              // Return parameter value and exit
    }
  }
}

// main function
void main(void) {

  port_b_pullups(TRUE);                      // Enable PORTB internal weak pull-ups

  delay_ms(1000);

  // Initialize the SSD1306 OLED with an I2C addr = 0x7A (default address)
  SSD1306_Init(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);

  // clear the whole display
  SSD1306_ClearDisplay();

  SSD1306_GotoXY(9, 6);
  SSD1306_PutC("TIME:");

  while (TRUE) {

    if(!input(button1)) {   // If button B1 is pressed
      i = 0;
      delay_ms(50);
      while(!input(button1));  // Wait for button B1 release

      while(TRUE) {

        while(!input(button2)) {   // While button B2 pressed
          w_day++;                 // Increment w_day
          if(w_day > 7) w_day = 1;
          display_day();           // Call display_day function
          delay_ms(200);           // Wait 200 ms
        }

        SSD1306_GotoXY(7, 1);
        SSD1306_PutC("         ");
        blink_parameter();        // Call blink_parameter function
        display_day();            // Call display_day function
        blink_parameter();        // Call blink_parameter function
        if(!input(button1))       // If button B1 is pressed
          break;
      }

      day    = edit(6, 3, day);         // Edit date
      month  = edit(9, 3, month);      // Edit month
      year   = edit(14, 3, year);      // Edit year
      hour   = edit(7, 8, hour);       // Edit hours
      minute = edit(10, 8, minute);     // Edit minutes

      // Convert decimal to BCD
      minute = ((minute / 10) << 4) + (minute % 10);
      hour   = ((hour   / 10) << 4) + (hour   % 10);
      day   = ((day     / 10) << 4) + (day    % 10);
      month  = ((month  / 10) << 4) + (month  % 10);
      year   = ((year   / 10) << 4) + (year   % 10);
      // End conversion

      // Write data to DS1307 RTC
      i2c_start(SSD1306_STREAM);          // Start I2C
      i2c_write(SSD1306_STREAM, 0xD0);    // DS1307 address
      i2c_write(SSD1306_STREAM, 0);       // Send register address
      i2c_write(SSD1306_STREAM, 0);       // Reset sesonds and start oscillator
      i2c_write(SSD1306_STREAM, minute);  // Write minute value to DS1307
      i2c_write(SSD1306_STREAM, hour);    // Write hour value to DS1307
      i2c_write(SSD1306_STREAM, w_day);   // Write day of week value to DS1307
      i2c_write(SSD1306_STREAM, day);     // Write day value to DS1307
      i2c_write(SSD1306_STREAM, month);   // Write month value to DS1307
      i2c_write(SSD1306_STREAM, year);    // Write year value to DS1307
      i2c_stop(SSD1306_STREAM);           // Stop I2C

    delay_ms(200);   // Wait 200ms

    }

    // Read current time and date
    i2c_start(SSD1306_STREAM);              // Start I2C
    i2c_write(SSD1306_STREAM, 0xD0);        // DS1307 address
    i2c_write(SSD1306_STREAM, 0);           // Send register address
    i2c_start(SSD1306_STREAM);              // Restart I2C
    i2c_write(SSD1306_STREAM, 0xD1);        // Initialize data read
    second = i2c_read(SSD1306_STREAM, 1);   // Read seconds from register 0
    minute = i2c_read(SSD1306_STREAM, 1);   // Read minuts from register 1
    hour   = i2c_read(SSD1306_STREAM, 1);   // Read hour from register 2
    w_day  = i2c_read(SSD1306_STREAM, 1);   // Read day of week from register 3
    day    = i2c_read(SSD1306_STREAM, 1);   // Read day from register 4
    month  = i2c_read(SSD1306_STREAM, 1);   // Read month from register 5
    year   = i2c_read(SSD1306_STREAM, 0);   // Read year from register 6
    i2c_stop(SSD1306_STREAM);               // Stop I2C

  display_day();      // Display day of the week
  DS1307_display();   // Diaplay time & Date

  delay_ms(50);       // Wait 50ms

  }

}
// End of code.

Simulação PIC16F877A + SSD1306 OLED + DS1307 RTC Proteus:

O vídeo abaixo mostra a simulação do projeto com o Proteus ISIS. Observe que o circuito de simulação não é o mesmo que o circuito de hardware real, o circuito de hardware do projeto é mostrado acima.

Download do arquivo de simulação Proteus (para versão 8.6 e superior):
PIC16F877A + SSD1306 OLED + DS1307