Pages

Wednesday, August 30, 2017

Real time clock & calendar with PIC18F4550 and DS3231

Interfacing PIC18F4550 with DS3231 RTC
PIC18F4550 microcontroller with DS3231 RTC hardware circuit
The DS3231 is a low cost , extremely accurate real time clock with a built-in crystal oscillator. The characteristics of the DS3231 make it one of the best choices for real time clock chips.
This project shows how to build is simple real time clock and calendar (RTCC) using PIC18F4550 microcontroller and DS3231.
The DS3231 uses I2C protocol to interface with the master device which is in our example the PIC18F4550 which has one I2C module.
The I2C protocol uses only two lines: SCL (Serial Clock) and SDA (Serial Data) which are in the PIC18F4550 pin RB1 and pin RB0 respectively.
Hardware Required:
  • PIC18F4550 microcontroller
  • 1602 LCD screen
  • 10K ohm variable resistor
  • 2 x push button
  • 5V supply source
  • Breadboard
  • Jumper wires
DS3231 board contains the following components:
  • DS3231 RTC - datasheet
  •  2 x 4.7K ohm resistors
  • 0.1uF ceramic capacitor
  • 3V coin cell battery
Real time clock & calendar with PIC18F4550 and DS3231 circuit:
Interfacing PIC18F4550 with DS3231 RTC cirucit
The 1602 LCD has 7 data lines which are connected to pins RD0~6, the DS3231 SCL pin is connected to pin RB1 (#34) and SDA is connected to pin RB0 (#33) of the PIC18F4550 microcontroller.
In the circuit there are 2 push buttons (B1 & B2) connected to pin RB2 and pin RB3, the two push buttons are used to set the time as well as the calendar parameters (minutes, hours, date......). The button B1 selects the parameter and B2 increments the selected parameter.
The 3V cell battery is used as a backup to keep time and date running in case of main power failure. The circuit can work without this battery but its pin (#14) has to be grounded.
In this project the PIC18F4550 uses its internal oscillator and MCLR pin function is disabled.
Real time clock & calendar with PIC18F4550 and DS3231 C code:
The C code below was tested with CCS C compiler version 5.051.
The hardware I2C module of the PIC18F4550 is initialized and configured using the function below with a speed of 100KHz:
#use I2C(master, I2C1, FAST = 100000)
master: set the microcontroller to the master mode
I2C1: use first I2C module.
The DS3231 works with BCD format only and to convert the BCD to decimal and vise versa I used the following functions (example for minute variable):
minute = (minute >> 4) * 10 + (minute & 0x0F);                                  // Convert BCD to decimal
minute = ((minute / 10) << 4) + (minute % 10);                                   // Convert decimal to BCD
Code functions:
void DS3231_display() : this function prints the time and calendar on the 1602 LCD screen. Before the print it converts the all the wanted data from BCD format to decimal format.
int8 edit(parameter, x, y) : I used this function to edit time and calendar parameters. I used a variable named i to distinguish between the parameters:
i = 0, 1 : hours and minutes respectively
i = 2, 3, 4: date, month and year respectively.
After the edit of time/calendar the data have to be converted back to BCD format and written to the DS3231 (it had been converted from BCD format to decimal format by the function void DS3231_display() ).
void blink() : this small function works as a delay except that it is interrupted by the buttons B1 (connected to RB2) and B2 (connected to RB3). When called and without pressing any button the total time is 10 x 25ms = 250ms. With this function we can see the blinking of the selected parameter with a frequency of 2Hz. So a delay of 250ms comes after the print of the selected parameter and after that delay a 2 spaces is printed which makes the parameter disappears from the LCD and another 250ms delay comes after the print of the 2 spaces.
The complete C code is the one below.
/* Real time clock/calendar (RTCC) using PIC18F4550 & DS3231 CCS C code.
   Read DS3231 RTC datasheet to understand the code!
   Time & date parameters can be set using two push buttons connected to RB2 & RB3.
   http://ccspicc.blogspot.com/
   electronnote@gmail.com
*/

//LCD module connections
#define LCD_RS_PIN      PIN_D0
#define LCD_RW_PIN      PIN_D1
#define LCD_ENABLE_PIN  PIN_D2
#define LCD_DATA4       PIN_D3
#define LCD_DATA5       PIN_D4
#define LCD_DATA6       PIN_D5
#define LCD_DATA7       PIN_D6
//End LCD module connections

#include <18F4550.h>
#fuses NOMCLR, INTRC_IO, NOWDT,NOPROTECT,NOLVP
#use delay(clock = 8000000)
#include <lcd.c>
#use fast_io(B)
#use fast_io(D)
#use I2C(master, I2C1, FAST = 100000)

char time[]     = "TIME:  :  :  ";
char calendar[] = "DATE:  /  /20  ";
int8  i, second, minute, hour, date, month, year;
void DS3231_display(){
  // Convert BCD to decimal
  second = (second >> 4) * 10 + (second & 0x0F);
  minute = (minute >> 4) * 10 + (minute & 0x0F);
  hour = (hour >> 4) * 10 + (hour & 0x0F);
  date = (date >> 4) * 10 + (date & 0x0F);
  month = (month >> 4) * 10 + (month & 0x0F);
  year = (year >> 4) * 10 + (year & 0x0F);
  // End conversion
  time[12]     = second % 10 + 48;
  time[11]     = second / 10 + 48;
  time[9]      = minute % 10 + 48;
  time[8]      = minute / 10 + 48;
  time[6]      = hour   % 10 + 48;
  time[5]      = hour   / 10 + 48;
  calendar[14] = year   % 10 + 48;
  calendar[13] = year   / 10 + 48;
  calendar[9]  = month  % 10 + 48;
  calendar[8]  = month  / 10 + 48;
  calendar[6]  = date   % 10 + 48;
  calendar[5]  = date   / 10 + 48;
  lcd_gotoxy(1, 1);                              // Go to column 1 row 1
  printf(lcd_putc, time);                        // Display time
  lcd_gotoxy(1, 2);                              // Go to column 1 row 2
  printf(lcd_putc, calendar);                    // Display calendar
}
void blink(){
  int8 j = 0;
  while(j < 10 && input(PIN_B2) && input(PIN_B3)){
    j++;
    delay_ms(25);
  }
}
int8 edit(parameter, x, y){
  while(!input(PIN_B2));                         // Wait until button RB2 released
  while(TRUE){
    while(!input(PIN_B3)){                       // If button RB3 is pressed
      parameter++;
      if(i == 0 && parameter > 23)               // If hours > 23 ==> hours = 0
        parameter = 0;
      if(i == 1 && parameter > 59)               // If minutes > 59 ==> minutes = 0
        parameter = 0;
      if(i == 2 && parameter > 31)               // If date > 31 ==> date = 1
        parameter = 1;
      if(i == 3 && parameter > 12)               // If month > 12 ==> month = 1
        parameter = 1;
      if(i == 4 && parameter > 99)               // If year > 99 ==> year = 0
        parameter = 0;
      lcd_gotoxy(x, y);
      printf(lcd_putc,"%02u", parameter);        // Display parameter
      delay_ms(200);                             // Wait 200ms
    }
    lcd_gotoxy(x, y);
    lcd_putc("  ");
    blink();
    lcd_gotoxy(x, y);                            // Print two spaces
    printf(lcd_putc,"%02u", parameter);          // Print parameter
    blink();
    if(!input(PIN_B2)){                          // If button RB2 is pressed
      i++;                                       // Increament 'i' for the next parameter
      return parameter;                          // Return parameter value and exit
    }
  }
}
void main(){
  setup_oscillator(OSC_8MHZ);                    // Set internal oscillator to 8MHz
  set_tris_d(0);
  port_b_pullups(TRUE);                          // Enable PORTB internal pull-ups
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // LCD clear
  while(TRUE){
    if(!input(PIN_B2)){                          // If RB2 button is pressed
      i = 0;
      hour = edit(hour, 6, 1);
      minute = edit(minute, 9, 1);
      date = edit(date, 6, 2);
      month = edit(month, 9, 2);
      year = edit(year, 14, 2);
      // Convert decimal to BCD
      minute = ((minute / 10) << 4) + (minute % 10);
      hour = ((hour / 10) << 4) + (hour % 10);
      date = ((date / 10) << 4) + (date % 10);
      month = ((month / 10) << 4) + (month % 10);
      year = ((year / 10) << 4) + (year % 10);
      // End conversion
      // Write data to DS3231 RTC
      i2c_start();                               // Start I2C protocol
      i2c_write(0xD0);                           // DS3231 address
      i2c_write(0);                              // Send register address (time seconds register)
      i2c_write(0);                              // Reset sesonds and start oscillator
      i2c_write(minute);                         // Write minutes value to DS3231
      i2c_write(hour);                           // Write hours value to DS3231
      i2c_write(1);                              // Write day value (not used)
      i2c_write(date);                           // Write date value to DS3231
      i2c_write(month);                          // Write month value to DS3231
      i2c_write(year);                           // Write year value to DS3231
      delay_ms(200);                             // Wait 200ms
    }
    i2c_start();                                 // Start I2C protocol
    i2c_write(0xD0);                             // DS3231 address
    i2c_write(0);                                // Send register address (time seconds register)
    i2c_start();                                 // Restart I2C
    i2c_write(0xD1);                             // Initialize data read
    second = i2c_read(1);                        // Read seconds from register 0
    minute = i2c_read(1);                        // Read minutes from register 1
    hour   = i2c_read(1);                        // Read hours from register 2
    i2c_read(1);                                 // Read day from register 3 (not used)
    date   = i2c_read(1);                        // Read date from register 4
    month  = i2c_read(1);                        // Read month from register 5
    year   = i2c_read(0);                        // Read year from register 6
    i2c_stop();                                  // Stop I2C protocol
    DS3231_display();                            // Diaplay time & calendar
    delay_ms(50);                                // Wait 50ms
  }
}

The project should work as shown in the video below (in the video the used mcu is PIC16F877A).


PIC18F4550 + LM335 temperature sensor example

Interfacing PIC18F4550 with LM335 analog temperature sensor
As mentioned above, the LM335 is a 3-pin analog device which can measure temperature (converts temperature to analog voltage). This sensor requires an ADC to convert the analog data into digital one. this topic shows how to use PIC18F4550 microcontroller ADC module to measure the ambient temperature using the LM335 sensor.
The LM335 sensor has the following features (from LM335 datasheet):
  • Directly Calibrated to the Kelvin Temperature Scale
  • 1°C Initial Accuracy Available
  • Operates from 400 μA to 5 mA
  • Less than 1-Ω Dynamic Impedance
  • Easily Calibrated
  • Wide Operating Temperature Range
  • 200°C Overrange
  • Low Cost
The LM135 has a breakdown voltage directly proportional to absolute temperature at 10 mV/°K. If the LM335 output voltage for example is 3.03 (3030 mV) that means the temperature is: 303 °Kelvin = 30 °Celsius.
Hardware Required:
  • PIC18F4550 microcontroller
  • LM335 Temperature sensor - datasheet
  • 1602 LCD Screen
  • 10K ohm potentiometer or variable resistor
  • 2.2K ohm resistor
  • +5V Power supply source
  • Breadboard
  • Jumper wires
Interfacing PIC18F4550 with LM335 temperature sensor circuit:
Interfacing PIC18F4550 microcontroller with LM335 temperature sensor
The LM335 sensor has 3 pins (from left to right):
Pin 1 for calibration, not used in this example
Pin 2: output
Pin 3: GND (ground).
The output pin of the LM335 sensor is connected to analog channel 0 (AN0). I chose the 2.2K ohm because as written in the datasheet for optimum accuracy the current flows through the LM335 should be 1mA. For example if the temperature = 27°C, the output will be 3.00V and assume the supply voltage is exactly 5.00V that means the current flows through the sensor is ( 5 - 3)/2.2 = 0.90mA which is good enough. Also the value 2.2K is a standard value and well used.
The 1602 LCD screen is connected to pins RD0~6. The 10K variable resistor is used to adjust the brightness of the screen.
In this example the PIC18F4550 runs with its internal oscillator @ 8MHz and MCLR pin function is disabled.
Interfacing PIC18F4550 with LM335 sensor CCS C Code:
The following C code was tested with CCS PIC C compiler version 5.051.
/* Interfacing PIC18F4550 with LM335 analog temperature sensor CCS C code.
   Read LM335 datasheet to understand the code!
   http://ccspicc.blogspot.com/
   electronnote@gmail.com
*/

//LCD module connections
#define LCD_RS_PIN      PIN_D0
#define LCD_RW_PIN      PIN_D1
#define LCD_ENABLE_PIN  PIN_D2
#define LCD_DATA4       PIN_D3
#define LCD_DATA5       PIN_D4
#define LCD_DATA6       PIN_D5
#define LCD_DATA7       PIN_D6
//End LCD module connections

#include <18F4550.h>
#fuses NOMCLR, INTRC_IO
#device ADC=10
#use delay(clock = 8MHz)
#include <lcd.c>

char message1[] = "Temp =  00.0 C";
char message2[] =      "=  00.0 K";
signed int16 Kelvin, Celsius;
void main(){
  setup_oscillator(OSC_8MHZ);                    // Set internal oscillator to 8MHz
  setup_adc(ADC_CLOCK_INTERNAL);                 // ADC Module uses its internal oscillator
  setup_adc_ports(AN0);                          // Configure AN0 pin as analog
  set_adc_channel(0);                            // Select channel 0 (AN0)
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // Clear LCD
  while(TRUE){
    delay_ms(1000);                              // Wait 1 second
    Kelvin = read_adc() * 0.489;                 // Read analog voltage and convert it to Kelvin (0.489 = 500/1023)
    Celsius = Kelvin - 273;                      // Convert Kelvin to degree Celsius
    if(Celsius < 0){
      Celsius = abs(Celsius);                    // Absolute value
      message1[7] = '-';                         // Put minus '-' sign
    }
    else
      message1[7]  = ' ';                        // Put space ' '
    if (Celsius > 99)
      message1[7]  = 1 + 48;                     // Put 1 (of hundred)
    message1[8]  = (Celsius / 10) % 10  + 48;
    message1[9]  =  Celsius % 10  + 48;
    message1[12] = 223;                          // Degree symbol
    message2[2]  = (Kelvin / 100) % 10 + 48;
    message2[3]  = (Kelvin / 10)  % 10 + 48;
    message2[4]  = Kelvin  % 10 + 48;
    lcd_gotoxy(1, 1);                            // Go to column 1 row 1
    printf(lcd_putc, message1);                  // Display message1
    lcd_gotoxy(6, 2);                            // Go to column 6 row 2
    printf(lcd_putc, message2);                  // Display message2
  }
}

Real time clock/calendar with 2 alarms and temperature sensing using PIC16F877A and DS3231

Interfacing PIC16F877A with DS3231 RTC (2)
After I built a simple real time clock and calendar using PIC16F877A microcontroller and DS3231 RTC boards, I improved the previous project and I added alarm functions (alarm1 and alarm2) and temperature monitor.
The simple RTCC with PIC16F877A and DS3231 topic:
Simple real time clock and calendar using DS3231 and PIC16F877A
The DS3231 RTC has a built-in 2 alarm functions and a digital temperature sensor with an accuracy of ±3°C.
Hardware Required:
  • PIC16F877A microcontroller
  • 20x4 LCD screen
  • DS3231 RTC board
  • 8 MHz crystal
  • 2 x 22pF ceramic capacitor
  • 10K ohm resistor
  • 10K ohm variable resistor 
  • 330 ohm resistor
  • LED
  • 3 x push button
  • 5V supply source
  • Breadboard
  • Jumper wires
Project circuit:
Real time clock with 2 alarms and temperature sensing using PIC16F877A and DS3231 circuit
To simplify the circuit, I used the DS3231 board, this board basically contains the main chip which is the DS3231, pull-up resistors (4.7K) of SCL, SDA and INT/SQW lines and coin cell battery holder. There is also 24C32 EEPROM and some other resistors (not used in this project).
The DS3231 board is supplied with 5V as the microcontroller and the 2004 LCD, there are 3 data lined connected between this board and the PIC16F877A, SCL line is connected to RC3, SDA is connected to RC4 and INT line is connected to pin RB0 which is the external interrupt pin of the PIC16F877A. The DS3231 interrupts the microcontroller when there is an alarm.
In the circuit there are 3 push buttons: B1, B2 and B3. These buttons are used to set time, calendar and alarms. Time and calendar can be adjusted with B1 and B2, button B1 selects time or date parameter (time parameters: hours and minutes; calendar parameters: day, date, month and year) and B2 increments the selected parameter. The button B3 and B2 adjust alarm1 and alarm2 parameters (hours, minutes and ON/OFF), button B3 selects the parameter and B2 increments the selected parameter.
There is an LED connected to pin RB4, this LED is used as an alarm indicator, so if there is an alarm the DS3231 pulls down the INT pin which interrupts the microcontroller and the microcontroller turns the LED ON, here button B2 turns both the LED and the occurred alarm OFF.
In this project the MCU runs with 8MHz crystal oscillator.
Project C code:
The C code below was tested with CCS PIC C compiler version 5.051.
By reading the datasheet of the DS3231 RTC the code will be more easier!
The hardware I2C module of the MCU is initialized and configured using the following CCS C function with a speed of 100KHz:
#use I2C(master, I2C1, FAST = 100000)
master: set the microcontroller to the master mode
I2C1: use first I2C module.
The DS3231 works with BCD format only and to convert the BCD to decimal and vise versa I used the following functions (example for minute variable):
minute = (minute >> 4) * 10 + (minute & 0x0F);                                  // Convert BCD to decimal
minute = ((minute / 10) << 4) + (minute % 10);                                   // Convert decimal to BCD

Code functions:
void DS3231_read() : this function reads time and calendar data from the DS3231 (seconds, minutes, hours, day, date, month and year).
void DS3231_display() : displays time and calendar data, before displaying time and calendar data are converted from BCD format to decimal format. This function displays the calendar by calling a function named void calendar_display() .
void alarms_read_display() : basically this functions reads alarm1 and alarm2 minutes and hours. It also reads the DS3231 control register, status register and 2 temperature registers.
The other job of this function is to display alarms data (hours, minutes and status) and the temperature value. The alarm status are extracted from the control register.
int8 edit(parameter, x, y) : I used this function to edit time, calendar and alarm parameters except the day. I used a variable named i to distinguish between the parameters:
i = 0, 1 : time hours and minutes respectively
i = 2, 3, 4: date month, year respectively
i = 5, 6: alarms hours and minutes respectively
i = 7: alarms status (ON or OFF)
After the edit of time/calendar/alarms the data have to be converted back to BCD format and written to the DS3231.
The MCU turns the LED ON when it was interrupted by the DS3231, the DS3231 sends the interrupt signal (pulls down the INT line) when there has been an alarm. Button B2 resets and turns OFF the alarm. If both alarms are active, button B2 will resets and turns OFF the occurred alarm only and keeps the other as it is. To do that we've to detect which alarm was occurred which can be easily done by reading the status register of the DS3231 (A1IF and A2IF flag bits). Turning ON or OFF an alarm is done by writing to the control register (bits: INTCN, A1IE and A2IE). Always INTCN bit should be 1. I used the following line to write 1 to the INTCN bit and to turn OFF the occurred alarm:
i2c_write(4 | (!bit_test(status_reg, 0) & alarm1_status) | ((!bit_test(status_reg, 1) & alarm2_status) << 1));
alarm1_status and alarm2_status are 1-bit variables, for example if alarm1_status is 1 ==> alarm1 is ON and  if alarm1_status is 0 ==> alarm1 is OFF. The same thing for alarm2.
The complete C code is below.
/* Real time clock and calendar with 2 alarms and temperature sensing using
   PIC16F877A & DS3231 CCS C code.
   Read DS3231 RTC datasheet to understand the code!
   Time & date parameters can be set using two push buttons connected to RB1 & RB2.
   Alarm1 and alarm2 can be set using buttons RB3 and RB2.
   Pin RB4 becomes high when alarm occurred and button RB2 returns it to low and
   turn the occurred alarm OFF.
   DS3231 interrupt pin is connected to PIC16F877A interrupt pin RB0.
   http://ccspicc.blogspot.com/
   electronnote@gmail.com
*/

//LCD module connections
#define LCD_RS_PIN      PIN_D0
#define LCD_RW_PIN      PIN_D1
#define LCD_ENABLE_PIN  PIN_D2
#define LCD_DATA4       PIN_D3
#define LCD_DATA5       PIN_D4
#define LCD_DATA6       PIN_D5
#define LCD_DATA7       PIN_D6
//End LCD module connections

#include <16F877A.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP                       
#use delay(clock = 8MHz)
#use fast_io(B)
#use fast_io(D)
#include <lcd.c>
#use I2C(master, I2C1, FAST = 100000)

int1 alarm1_status, alarm2_status;
char time[]     = "  :  :  ",
     calendar[] = "      /  /20  ",
     alarm1[]   = "A1:   :  :00", alarm2[]   = "A2:   :  :00",
     temperature[] = "T:   .   C";
int8  i, second, minute, hour, day, date, month, year,
      alarm1_minute, alarm1_hour, alarm2_minute, alarm2_hour,
      status_reg;
#INT_EXT                                         // External interrupt routine
void ext_isr(void){
  output_high(PIN_B4);
  clear_interrupt(INT_EXT);
}
void DS3231_read(){                              // Read time & calendar data function
  i2c_start();                                   // Start I2C protocol
  i2c_write(0xD0);                               // DS3231 address
  i2c_write(0);                                  // Send register address (seconds register)
  i2c_start();                                   // Restart I2C
  i2c_write(0xD1);                               // Initialize data read
  second = i2c_read(1);                          // Read seconds from register 0
  minute = i2c_read(1);                          // Read minutes from register 1
  hour   = i2c_read(1);                          // Read hour from register 2
  day    = i2c_read(1);                          // Read day from register 3
  date   = i2c_read(1);                          // Read date from register 4
  month  = i2c_read(1);                          // Read month from register 5
  year   = i2c_read(0);                          // Read year from register 6
  i2c_stop();                                    // Stop I2C protocol
}
void alarms_read_display(){                      // Read and display alarm1 and alarm2 data function
  int8 control_reg, temperature_lsb;
  signed int8 temperature_msb;
  i2c_start();                                   // Start I2C protocol
  i2c_write(0xD0);                               // DS3231 address
  i2c_write(0x08);                               // Send register address (alarm1 minutes register)
  i2c_start();                                   // Restart I2C
  i2c_write(0xD1);                               // Initialize data read
  alarm1_minute = i2c_read(1);                   // Read alarm1 minutes
  alarm1_hour   = i2c_read(1);                   // Read alarm1 hours
  i2c_read(1);                                   // Skip alarm1 day/date register
  alarm2_minute = i2c_read(1);                   // Read alarm2 minutes
  alarm2_hour   = i2c_read(1);                   // Read alarm2 hours
  i2c_read(1);                                   // Skip alarm2 day/date register
  control_reg = i2c_read(1);                     // Read the DS3231 control register
  status_reg  = i2c_read(1);                     // Read the DS3231 status register
  i2c_read(1);                                   // Skip aging offset register
  temperature_msb = i2c_read(1);                 // Read temperature MSB
  temperature_lsb = i2c_read(0);                 // Read temperature LSB
  i2c_stop();                                    // Stop I2C protocol
    // Convert BCD to decimal
  alarm1_minute = (alarm1_minute >> 4) * 10 + (alarm1_minute & 0x0F);
  alarm1_hour   = (alarm1_hour   >> 4) * 10 + (alarm1_hour & 0x0F);
  alarm2_minute = (alarm2_minute >> 4) * 10 + (alarm2_minute & 0x0F);
  alarm2_hour   = (alarm2_hour   >> 4) * 10 + (alarm2_hour & 0x0F);
    // End conversion
  alarm1[8]     = alarm1_minute % 10  + 48;
  alarm1[7]     = alarm1_minute / 10  + 48;
  alarm1[5]     = alarm1_hour   % 10  + 48;
  alarm1[4]     = alarm1_hour   / 10  + 48;
  alarm2[8]     = alarm2_minute % 10  + 48;
  alarm2[7]     = alarm2_minute / 10  + 48;
  alarm2[5]     = alarm2_hour   % 10  + 48;
  alarm2[4]     = alarm2_hour   / 10  + 48;
  alarm1_status = bit_test(control_reg, 0);      // Read alarm1 interrupt enable bit (A1IE) from DS3231 control register
  alarm2_status = bit_test(control_reg, 1);      // Read alarm2 interrupt enable bit (A2IE) from DS3231 control register
  if(temperature_msb < 0){
    temperature_msb = abs(temperature_msb);
    temperature[2] = '-';
  }
  else
    temperature[2] = ' ';
  temperature_lsb >>= 6;
  temperature[4] = temperature_msb % 10  + 48;
  temperature[3] = temperature_msb / 10  + 48;
  if(temperature_lsb == 0 || temperature_lsb == 2){
    temperature[7] = '0';
    if(temperature_lsb == 0) temperature[6] = '0';
    else                     temperature[6] = '5';
  }
  if(temperature_lsb == 1 || temperature_lsb == 3){
    temperature[7] = '5';
    if(temperature_lsb == 1) temperature[6] = '2';
    else                     temperature[6] = '7';
  }
  temperature[8]  = 223;                         // Degree symbol
  lcd_gotoxy(11, 1);                             // Go to column 10 row 1
  printf(lcd_putc, temperature);                 // Display temperature
  lcd_gotoxy(21, 1);                             // Go to column 1 row 3
  printf(lcd_putc, alarm1);                      // Display alarm1
  lcd_gotoxy(38, 1);                             // Go to column 18 row 3
  if(alarm1_status)  lcd_putc("ON ");            // If A1IE = 1 print 'ON'
  else               lcd_putc("OFF");            // If A1IE = 0 print 'OFF'
  lcd_gotoxy(21, 2);                             // Go to column 1 row 4
  printf(lcd_putc, alarm2);                      // Display alarm2
  lcd_gotoxy(38, 2);                             // Go to column 18 row 4
  if(alarm2_status)  lcd_putc("ON ");            // If A2IE = 1 print 'ON'
  else               lcd_putc("OFF");            // If A2IE = 0 print 'OFF'
  
}
void calendar_display(){                         // Display calendar function
  switch(day){
    case 1:  strcpy(calendar, "Sun   /  /20  "); break;
    case 2:  strcpy(calendar, "Mon   /  /20  "); break;
    case 3:  strcpy(calendar, "Tue   /  /20  "); break;
    case 4:  strcpy(calendar, "Wed   /  /20  "); break;
    case 5:  strcpy(calendar, "Thu   /  /20  "); break;
    case 6:  strcpy(calendar, "Fri   /  /20  "); break;
    case 7:  strcpy(calendar, "Sat   /  /20  "); break;
    default: strcpy(calendar, "Sat   /  /20  "); break;
  }
  calendar[13] = year  % 10 + 48;
  calendar[12] = year  / 10 + 48;
  calendar[8]  = month % 10 + 48;
  calendar[7]  = month / 10 + 48;
  calendar[5]  = date  % 10 + 48;
  calendar[4]  = date  / 10 + 48;
  lcd_gotoxy(1, 2);                              // Go to column 1 row 2
  printf(lcd_putc, calendar);                    // Display calendar
}
void DS3231_display(){
  // Convert BCD to decimal
  second = (second >> 4) * 10 + (second & 0x0F);
  minute = (minute >> 4) * 10 + (minute & 0x0F);
  hour = (hour >> 4) * 10 + (hour & 0x0F);
  date = (date >> 4) * 10 + (date & 0x0F);
  month = (month >> 4) * 10 + (month & 0x0F);
  year = (year >> 4) * 10 + (year & 0x0F);
  // End conversion
  time[7]     = second % 10  + 48;
  time[6]     = second / 10  + 48;
  time[4]     = minute % 10  + 48;
  time[3]     = minute / 10  + 48;
  time[1]     = hour   % 10  + 48;
  time[0]     = hour   / 10  + 48;
  calendar_display();                            // Call calendar display function
  lcd_gotoxy(1, 1);                              // Go to column 1 row 1
  printf(lcd_putc, time);                        // Display time
}
void blink(){
  int8 j = 0;
  while(j < 10 && (input(PIN_B1) || i >= 5) && input(PIN_B2) && (input(PIN_B3) || i < 5)){
    j++;
    delay_ms(25);
  }
}
int8 edit(parameter, x, y){
  while(!input(PIN_B1) || !input(PIN_B3));       // Wait until button RB0 is released
  while(TRUE){
    while(!input(PIN_B2)){                       // If button RB2 is pressed
      parameter++;
      if(((i == 0) || (i == 5)) && parameter > 23)    // If hours > 23 ==> hours = 0
        parameter = 0;
      if(((i == 1) || (i == 6)) && parameter > 59)    // If minutes > 59 ==> minutes = 0
        parameter = 0;
      if(i == 2 && parameter > 31)               // If date > 31 ==> date = 1
        parameter = 1;
      if(i == 3 && parameter > 12)               // If month > 12 ==> month = 1
        parameter = 1;
      if(i == 4 && parameter > 99)               // If year > 99 ==> year = 0
        parameter = 0;
      if(i == 7 && parameter > 1)                // For alarms ON or OFF (1: alarm ON, 0: alarm OFF)
        parameter = 0;
      lcd_gotoxy(x, y);
      if(i == 7){                                // For alarms ON & OFF
        if(parameter == 1)  lcd_putc("ON ");
        else                lcd_putc("OFF");
      }
      else
        printf(lcd_putc,"%02u", parameter);      // Display parameter
      if(i >= 5){
        DS3231_read();                           // Read data from DS3231
        DS3231_display();                        // Display DS3231 time and calendar
      }
      delay_ms(200);                             // Wait 200ms
    }
    lcd_gotoxy(x, y);                            // Go to LCD x column and y row
    lcd_putc("  ");                              // Print two spaces
    if(i == 7) lcd_putc(" ");                    // Print space (for alarms ON & OFF)
    blink();                                     // Call blink function
    lcd_gotoxy(x, y);                            // Go to LCD x column and y row
    if(i == 7){                                  // For alarms ON & OFF
      if(parameter == 1)  lcd_putc("ON ");
      else                lcd_putc("OFF");
    }
    else
      printf(lcd_putc,"%02u", parameter);        // Display parameter
    blink();
    if(i >= 5){
      DS3231_read();
      DS3231_display();}
    if((!input(PIN_B1) && i < 5) || (!input(PIN_B3) && i >= 5)){
      i++;                                       // Increment 'i' for the next parameter
      return parameter;                          // Return parameter value and exit
    }
  }
}
void main(){
  output_b(0);
  set_tris_b(0x0F);                              // Configure RB0 ~ 3 as input pins
  set_tris_d(0);                                 // Configure all PORTD pins as outputs
  port_b_pullups(TRUE);                          // Enable PORTB internal pull-ups
  enable_interrupts(GLOBAL);                     // Enable global interrupts
  enable_interrupts(INT_EXT_H2L);                // Enable external interrupt with edge from high to low
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // LCD clear
  while(TRUE){
    if(!input(PIN_B1)){                          // If RB1 button is pressed
      i = 0;
      hour   = edit(hour, 1, 1);
      minute = edit(minute, 4, 1);
      while(!input(PIN_B1));                     // Wait until button RB0 released
      while(TRUE){
        while(!input(PIN_B2)){                   // If button RB2 button is pressed
          day++;                                 // Increment day
          if(day > 7) day = 1;
          calendar_display();                    // Call display calendar
          lcd_gotoxy(1, 2);                      // Go to column 1 row 2
          printf(lcd_putc, calendar);            // Display calendar
          delay_ms(200);
        }
        lcd_gotoxy(1, 2);                        // Go to column 1 row 2
        lcd_putc("   ");                         // Print 3 spaces
        blink();
        lcd_gotoxy(1, 2);                        // Go to column 1 row 2
        printf(lcd_putc, calendar);              // Print calendar
        blink();                                 // Call blink function
        if(!input(PIN_B1))                       // If button RB1 is pressed
          break;
      }
      date = edit(date, 5, 2);                   // Edit date
      month = edit(month, 8, 2);                 // Edit month
      year = edit(year, 13, 2);                  // Edit year
      // Convert decimal to BCD
      minute = ((minute / 10) << 4) + (minute % 10);
      hour = ((hour / 10) << 4) + (hour % 10);
      date = ((date / 10) << 4) + (date % 10);
      month = ((month / 10) << 4) + (month % 10);
      year = ((year / 10) << 4) + (year % 10);
      // End conversion
      // Write time & calendar data to DS3231 RTC
      i2c_start();                               // Start I2C protocol
      i2c_write(0xD0);                           // DS3231 address
      i2c_write(0);                              // Send register address (seconds address)
      i2c_write(0);                              // Reset seconds and start oscillator
      i2c_write(minute);                         // Write minute value to DS3231
      i2c_write(hour);                           // Write hour value to DS3231
      i2c_write(day);                            // Write day value
      i2c_write(date);                           // Write date value to DS3231
      i2c_write(month);                          // Write month value to DS3231
      i2c_write(year);                           // Write year value to DS3231
      i2c_stop();                                // Stop I2C
      delay_ms(200);                             // Wait 200ms
    }
    if(!input(PIN_B3)){                          // If RB3 button is pressed
      while(!input(PIN_B3));                     // Wait until button RB3 released
      i = 5;
      alarm1_hour   = edit(alarm1_hour, 25, 1);
      alarm1_minute = edit(alarm1_minute, 28, 1);
      alarm1_status = edit(alarm1_status, 38, 1);
      i = 5;
      alarm2_hour   = edit(alarm2_hour, 25, 2);
      alarm2_minute = edit(alarm2_minute, 28, 2);
      alarm2_status = edit(alarm2_status, 38, 2);
      alarm1_minute = ((alarm1_minute / 10) << 4) + (alarm1_minute % 10);
      alarm1_hour   = ((alarm1_hour / 10) << 4) + (alarm1_hour % 10);
      alarm2_minute = ((alarm2_minute / 10) << 4) + (alarm2_minute % 10);
      alarm2_hour   = ((alarm2_hour / 10) << 4) + (alarm2_hour % 10);
      // Write alarms data to DS3231
      i2c_start();                               // Start I2C
      i2c_write(0xD0);                           // DS3231 address
      i2c_write(7);                              // Send register address (alarm1 seconds)
      i2c_write(0);                              // Write 0 to alarm1 seconds
      i2c_write(alarm1_minute);                  // Write alarm1 minutes value to DS3231
      i2c_write(alarm1_hour);                    // Write alarm1 hours value to DS3231
      i2c_write(0x80);                           // Alarm1 when hours, minutes, and seconds match
      i2c_write(alarm2_minute);                  // Write alarm2 minutes value to DS3231
      i2c_write(alarm2_hour);                    // Write alarm2 hours value to DS3231
      i2c_write(0x80);                           // Alarm2 when hours and minutes match
      i2c_write(4 | alarm1_status | (alarm2_status << 1));      // Write data to DS3231 control register (enable interrupt when alarm)
      i2c_write(0);                              // Clear alarm flag bits
      i2c_stop();                                // Stop I2C
      delay_ms(200);                             // Wait 200ms
    }
    if(!input(PIN_B2) && input(PIN_B4)){         // When button B2 pressed with alarm (Reset and turn OFF the alarm)
      output_low(PIN_B4);                        // Turn OFF the alarm indicator
      i2c_start();                               // Start I2C
      i2c_write(0xD0);                           // DS3231 address
      i2c_write(0x0E);                           // Send register address (control register)
      // Write data to control register (Turn OFF the occurred alarm and keep the other as it is)
      i2c_write(4 | (!bit_test(status_reg, 0) & alarm1_status) | ((!bit_test(status_reg, 1) & alarm2_status) << 1));
      i2c_write(0);                              // Clear alarm flag bits
      i2c_stop();                                // Stop I2C
    }
    DS3231_read();                               // Read time and calendar parameters from DS3231 RTC
    alarms_read_display();                       // Read and display alarms parameters
    DS3231_display();                            // Display time & calendar
    delay_ms(50);                                // Wait 50ms
  }
}
// End of code
Videos:
The following video shows a simple hardware circuit of the project.


And the second video shows a simulation of the project with Proteus.


Monday, August 28, 2017

Interfacing PIC16F877A with LM35 temperature sensor

The LM35 temperature sensor is three pin device (VCC, OUT and GND) with an output voltage linearly related to Centigrade temperature. Since the LM35 output varies with dependent to the temperature we need ADC (Analog-to-Digital Converter) module to measure this voltage. The ADC module converts analog data into digital data.
The LM35 output has linear +10mV/°C scale factor means the following:
If the output voltage =   10mV ---> temperature =   1°C
If the output voltage = 100mV ---> temperature = 10°C
If the output voltage = 200mV ---> temperature = 20°C
If the output voltage = 370mV ---> temperature = 37°C
and so on.
LM35 Futures (from datasheet):
  • Calibrated Directly in ° Celsius (Centigrade)
  • Linear + 10 mV/°C Scale Factor
  • 0.5°C Ensured Accuracy (at +25°C)
  • Rated for Full −55°C to +150°C Range
  • Suitable for Remote Applications
  • Low Cost Due to Wafer-Level Trimming
  • Operates from 4 to 30 V
  • Less than 60-μA Current Drain
  • Low Self-Heating, 0.08°C in Still Air
  • Nonlinearity Only ±¼°C Typical
  • Low Impedance Output, 0.1 Ω for 1 mA Load
This topic shows how to interface the microcontroller PIC16F877A with LM35 analog temperature sensor.
Hardware Required:
  • PIC16F877A microcontroller
  • LM35 temperature sensor  -- datasheet
  • 1602 LCD screen
  • 8MHz crystal
  • 2 x 22pF ceramic capacitor
  • 10K ohm variable resistor
  • Breadboard
  • 5V voltage source
  • Jumper wires
Interfacing PIC16F877A with LM35 sensor circuit:
PIC16F877A with LM35 temperature sensor circuit
The output of the LM35 temperature sensor is connected to analog channel 0 (AN0) of the PIC16F877A.
In this example the microcontroller runs with crystal oscillator @ 8MHz.
Interfacing PIC16F877A with LM35 temperature sensor C code:
The C code below was tested with CCS PIC C compiler version 5.051.
Reading voltage quantity using the ADC gives us a number between 0 and 1023 (10-bit resolution), 0V is represented by 0 and 5V is represented by 1023. Converting back the ADC digital value is easy and we can use the following equation for that conversion:
Voltage (in Volts) = ADC reading * 5 / 1023
Multiplying the previous result by 100 (LM35 scale factor is 10mV/°C = 0.01V/°C) will gives the actual temperature:
Temperature(°C) = ADC reading * 0.489
where 0.489 = 500 / 1023
/* Interfacing PIC16F877A with LM35 analog temperature sensor CCS C code.
   Read LM35 datasheet to understand the code!
   http://ccspicc.blogspot.com/
   electronnote@gmail.com
*/

//LCD module connections
#define LCD_RS_PIN      PIN_D0
#define LCD_RW_PIN      PIN_D1
#define LCD_ENABLE_PIN  PIN_D2
#define LCD_DATA4       PIN_D3
#define LCD_DATA5       PIN_D4
#define LCD_DATA6       PIN_D5
#define LCD_DATA7       PIN_D6
//End LCD module connections

#include <16F877A.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP
#device ADC=10
#use delay(clock = 8MHz)
#include <lcd.c>

char temperature[] = " 00.0 C";
unsigned int16 temp;
void main(){
  setup_adc(ADC_CLOCK_INTERNAL);                 // ADC Module uses its internal oscillator
  setup_adc_ports(AN0);                          // Configure AN0 pin as analog
  set_adc_channel(0);                            // Select channel 0 (AN0)
  lcd_init();                                    // Initialize LCD module
  lcd_putc('\f');                                // Clear LCD
  lcd_gotoxy(3, 1);                              // Go to column 3 row 1
  printf(lcd_putc, "Temperature:");
  while(TRUE){
    delay_ms(1000);
    temp = read_adc() * 0.489;                   // Read analog voltage and convert it to degree celsius (0.489 = 500/1023)
    if (temp > 99)
      temperature[0]  = 1 + 48;                  // Put 1 (of hundred)
    else
      temperature[0]  = ' ';                     // Put space
    temperature[1]  = (temp / 10) % 10  + 48;
    temperature[2]  =  temp % 10  + 48;
    temperature[5] = 223;                        // Degree symbol
    lcd_gotoxy(5, 2);                            // Go to column 5 row 2
    printf(lcd_putc, temperature);               // Display LM35 temperature result
  }
}
Interfacing PIC16F877A with LM35 videos:
The video below shows a simple hardware circuit of our example.


And the following video shows the simulation.


PIC16F877A + LM35 Proteus simulation file download