Saturday, September 16, 2017

PIC12F1822 + DS3231 + Remote Control


PIC12F1822 is a small microcontroller has only 8 pins which is not enough for building a real time clock using DS3231 with set buttons because the DS3231 needs 2 pins and the LCD screen needs 3 pins, therefore I've only 1 free pin. I connected that pin to an IR receiver and I used a remote control with a lot of buttons where I used only 3 buttons and the problem solved.
This post shows how did I build a simple real time clock using PIC12F1822, 1602 LCD and DS3231 with Car MP3 IR remote control (NEC protocol RC).
Before this project I made a NEC remote control decoder using PIC12F1822 MCU:
Extended NEC Protocol Decoder Using PIC12F1822 Microcontroller
Time and calendar are displayed on 1602 LCD with the help of a serial-in parallel-out shift register (74HC595, 74HC164, CD4094 .....) as what I made in the example below:
Interfacing PIC12F1822 microcontroller with LCD display
The DS3231 uses I2C protocol to interface with the master device which is in our example the PIC12F1822 MCU which has one I2C module.
The I2C protocol uses only two lines: SCL (Serial Clock) and SDA (Serial Data) which are in the PIC12F1822 pin RA1 and pin RA2 respectively.
Hardware Required:
  • PIC12F1822 microcontroller
  • DS3231 board - datasheet
  • 1602 LCD screen
  • 74HC595 shift register (74HC164 and CD4094 can be used)
  • IR receiver
  • 10K ohm variable resistor
  • 47uF capacitor
  • 5V supply source
  • Breadboard
  • Jumper wires
 The Circuit:
PIC12F1822 + DS3231 + Car MP3 remote control circuit
The IR receiver has 3 pins: GND, VCC and OUT. Pin OUT is connected to pin RA3 of the PIC12F1822.
The LCD screen with the 74HC595 shift register are connected to pins: RA0 (Enable), RA4 (Data) and RA5 (Clock).
The DS3231 board SCL and SDA are connected to pins RA1 and RA2 respectively.
In this project the PIC12F1822 uses its internal oscillator and MCLR pin is configured as a digital pin.
The Code:
The C code below was tested with CCS C compiler version 5.051.
The hardware I2C module of the PIC12F1822 MCU 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 2 functions below. Before displaying (after reading from DS3231), the data have to be converted from BCD to decimal, and before writing to the DS3231 (after editing the parameters) the data have to be converted from decimal to BCD:
int8 bcd_to_decimal(number)
int8 decimal_to_bcd(number)
Each function returns the converted value of the variable number.
void DS3231_display() : displays time and calendar data, before displaying time and calendar data are converted from BCD format to decimal format using the function bcd_to_decimal(number) .
int8 edit(x, y, parameter) : I used this function to edit time calendar parameters (minutes, hours, date, month and year). I used a variable named i to distinguish between the parameters:
i = 0, 1 : time hours and minutes respectively
i = 2, 3, 4: calendar date, month and year respectively
After the edit of time and calendar, the data have to be converted back to BCD format using the function decimal_to_bcd(number) and written back to the DS3231.
void blink() : this small function works as a delay except that it is interrupted by the IR remote control codes for buttons 1, 2 and 3. When called and without pressing any of the three 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 remote control buttons and their codes are shown in the image below:
Car MP3 IR remote control button codes 
I used only 3 buttons where button 1 selects the parameter which we want to set (hours, minutes, date, month or year). Button 2 increments the selected parameter and button 3 decrements it. The rest of buttons have no effect on the circuit.
Finally the interfacing of the LCD with the PIC12F1822 needs a small driver which can be found in the the following topic:
3-Wire LCD driver for CCS PIC C compiler
The complete C code is the one below.
/* Interfacing PIC12F1822 MCU with DS3231 with NEC IR remote control (Car MP3)
   http://ccspicc.blogspot.com/
   electronnote@gmail.com
*/

//LCD module connections
#define LCD_DATA_PIN  PIN_A5
#define LCD_CLOCK_PIN PIN_A4
#define LCD_EN_PIN    PIN_A0
//End LCD module connections

#include <12F1822.h>
#fuses NOMCLR INTRC_IO PLL_SW
#use delay(clock=32000000)
#use fast_io(A)
#include <3WireLCD.c>                            // 3-Wire LCD driver source file
#define IR_Sensor PIN_A3
#use I2C(master, I2C1, FAST = 100000)

int1  nec_ok = 0;
int8  i, second, minute, hour, date, month, year;
int32 nec_code;
void nec_remote_read(){
  int8 j;
  int16 count = 0;
  // Check 9ms pulse (remote control sends logic high)
  while(!input(IR_Sensor) && (count < 9500))
    count = GET_TIMER1();
  if((count > 9499) || (count < 8500))
    return;
  // Check 4.5ms space (remote control sends logic low)
  SET_TIMER1(0);
  count = 0;
  while((input(IR_Sensor)) && (count < 5000))
    count = GET_TIMER1();
  if((count > 4999) || (count < 1500))
    return;
  if(count < 3000){                              // Check if previous code is repeated
    SET_TIMER1(0);
    count = 0;
    while(!input(IR_Sensor) && (count < 650))
      count = GET_TIMER1();
    if((count > 649) || (count < 500))
      return;
    // Check if the repeated code is for button 2 or 3
    if((nec_code == 0x40BF807F || nec_code == 0x40BF40BF)){
      nec_ok = 1;                                // Decoding process is finished with success
      disable_interrupts(INT_RA3);               // Disable the external interrupt
      return;
    }
  }
  // Read message (32 bits)
  for(j = 0; j < 32; j++){
    SET_TIMER1(0);
    count = 0;
    while(!input(IR_Sensor) && (count < 650))
      count = GET_TIMER1();
    if((count > 649) || (count < 500))
      return;
    count = 0;
    SET_TIMER1(0);
    while((input(IR_Sensor)) && (count < 1800))
      count = GET_TIMER1();
    if( (count > 1799) || (count < 400))
      return;
    if( count > 1000)                            // If space width > 1ms
      bit_set(nec_code, (31 - j));               // Write 1 to bit (31 - j)
    else                                         // If space width < 1ms
      bit_clear(nec_code, (31 - j));             // Write 0 to bit (31 - j)
  }
  disable_interrupts(INT_RA3);
  nec_ok = 1;
  return;
}
#INT_RA                                          // RA port interrupt on change
void ra_isr(void){
  SET_TIMER1(0);
  SETUP_TIMER_1(T1_INTERNAL | T1_DIV_BY_8);      // Configure Timer 1 to increment every 1 us  
  nec_remote_read();
  SETUP_TIMER_1(T1_DISABLED);                    // Stop Timer1 module
  clear_interrupt(INT_RA);
}
int8 bcd_to_decimal(number){                     // Convert BCD to decimal function
  return((number >> 4) * 10 + (number & 0x0F));
}
int8 decimal_to_bcd(number){                     // Convert decimal to BCD function
  return(((number / 10) << 4) + (number % 10));
}
void ds3231_display(){
  // Convert data from BCD format to decimal format
  second = bcd_to_decimal(second);
  minute = bcd_to_decimal(minute);
  hour   = bcd_to_decimal(hour);
  date   = bcd_to_decimal(date);
  month  = bcd_to_decimal(month);
  year   = bcd_to_decimal(year);
  // End conversion
  lcd_goto(13, 1);   
  printf(lcd_out,"%02u", second);                // Display seconds
  lcd_goto(10, 1);   
  printf(lcd_out,"%02u", minute);                // Display minutes
  lcd_goto(7, 1);   
  printf(lcd_out,"%02u", hour);                  // Display hours
  lcd_goto(7, 2);   
  printf(lcd_out,"%02u", date);                  // Display date
  lcd_goto(10, 2);   
  printf(lcd_out,"%02u", month);                 // Display month
  lcd_goto(15, 2);   
  printf(lcd_out,"%02u", year);                  // Display year
}
void _reset(){
  nec_ok = 0;                                    // Reset decoding process
  enable_interrupts(INT_RA3_H2L);                // Enable RA3 interrupt (High to low)
}
void blink(){
  int8 j = 0;
  while((!nec_ok || (nec_code != 0x40BF00FF) && (nec_code != 0x40BF40BF) && (nec_code != 0x40BF807F)) && (j < 10)){
    j++;
    delay_ms(25);
  }
}
int8 edit(x, y, parameter){
  _reset();
  while(TRUE){
    if(nec_ok && nec_code == 0x40BF40BF){        // If button RB1 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;
    }
    if(nec_ok && nec_code == 0x40BF807F){
      if(i == 0 && parameter < 1)
        parameter = 24;
      if(i == 1 && parameter < 1)
        parameter = 60;
      if(i == 2 && parameter < 2)
        parameter = 32;
      if(i == 3 && parameter < 2)
        parameter = 13;
      if(i == 4 && parameter < 1)
        parameter = 100;
      parameter--;
    }
    lcd_goto(x, y);
    printf(lcd_out,"%02u", parameter);
    if(nec_ok){
      delay_ms(200);
      _reset();
    }
    blink();
    lcd_goto(x, y);
    lcd_out("  ");                              // Display two spaces
    blink();
    if(nec_ok && nec_code == 0x40BF00FF){
      lcd_goto(x, y);
      printf(lcd_out,"%02u", parameter);
      i++;                                       // Increment 'i' for the next parameter
      return parameter;                          // Return parameter value and exit
    }
  }
}
void main() {
  setup_oscillator(OSC_8MHZ | OSC_PLL_ON);       // Set internal oscillator to 32MHz (8MHz and PLL)
  set_tris_a(0x0E);                              // Configure RA1, RA2 & RA3 as inputs
  port_a_pullups(8);                             // Enable pin RA3 internal pull-up
  lcd_initialize();                              // Initialize LCD module
  lcd_cmd(LCD_CLEAR);                            // LCD Clear
  enable_interrupts(GLOBAL);                     // Enable global interrupts
  clear_interrupt(INT_RA);                       // Clear RA IOC flag bit
  enable_interrupts(INT_RA3_H2L);                // Enable RA3 interrupt (High to low)
  lcd_goto(1, 1);  lcd_out("TIME:   :  :");
  lcd_goto(1, 2);  lcd_out("DATE:   /  /20");
  while(TRUE){
    if(nec_ok == 1){                             // If a NEC remote code was received
      if(nec_code == 0x40BF00FF){                // If the remote code is for button 1
        i = 0;
        hour   = edit(7, 1, hour);
        minute = edit(10, 1, minute);
        date   = edit(7, 2, date);
        month  = edit(10, 2, month);
        year   = edit(15, 2, year);
        // Convert decimal to BCD
        minute = decimal_to_bcd(minute);
        hour   = decimal_to_bcd(hour);
        date   = decimal_to_bcd(date);
        month  = decimal_to_bcd(month);
        year   = decimal_to_bcd(year);
        // End conversion
        // Write data to DS3231 RTC
        i2c_start();                             // Start I2C protocol
        i2c_write(0xD0);                         // DS3231 address
        i2c_write(0);                            // Send register 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(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
      }
      _reset();                                  // Call _reset function
    }
    i2c_start();                                 // Start I2C protocol
    i2c_write(0xD0);                             // DS3231 address
    i2c_write(0);                                // Send register address
    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
    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();                            // Display time & calendar
    delay_ms(50);
  }
}
// End of code.
Small video: