Adjustable Timer with Relay

Adjustable Timer with Relay

Project Overview

This adjustable timer displays seconds, minutes, and hours and is powered by a PIC16F 877A microcontroller. There are five buttons included: start/stop, up for incrementing digits, down for decrementing digits, and left and right for moving between digits during adjustment. I’ve included a relay circuit that will trigger when the timer is finished.

Introduction


A timer is a typical project but not really a simple one. Here, I designed a six-digit countdown timer that, when finished, will trigger a relay. The timer can be set to a maximum of 99 hours, 59 minutes, and 59 seconds and to a minimum of 1 second. This is useful as a timed switch for appliances or other electrical devices that draw power from mains.

Schematic Diagram

Adjustable Timer with Relay schematic

I wanted to use the least amount of components possible but I also don’t want to make the code too long. Thus, I settled to use one decoder even if it’s possible not to use one. The POV refresh rate is based on timer0 overflow with x32 prescale. This is using a 4 MHz crystal oscillator. Adjust the prescale through the OPTION register if you are using other oscillator frequencies.

Code

/*
 * File:   timer_relay.c
 * Author: Roland Pelayo
 * For Adjustable Timer with Relay Project
 * See full tutorial at: https://www.teachmemicro.com/adjustable-timer-relay
 * Created on April 5, 2018, 11:25 AM
 */

#pragma config FOSC = XT        // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#define _XTAL_FREQ 4000000
#include <xc.h>

//structs for the digits
struct _digits{
    int ones;
    int tens;
};
struct timer{
    struct _digits digits;          //a struct within a struct just because I wanted to :)
};

//declare struct objects for every tick
struct timer seconds = {9,5};
struct timer minutes = {9,5};
struct timer hours = {9,5};
//declare struct objects for updating
struct timer set_seconds = {9,5};
struct timer set_minutes = {9,5};
struct timer set_hours = {9,5};

bit start_timer;    //flag for starting or stopping the timer
int counter = 0;    //used for counting up/down digits during time adjustment
int shifter = 0;    //used for shifting digits left and right during time adjustment

int dummy;          //used to clear mismatch for RB Change interrupt

//Function for updating time every tick
void runTime(void){
     RB1 = !RB1;                    //make the dots blink for every tick
            seconds.digits.ones--;
            if(seconds.digits.ones < 0){
                seconds.digits.tens--;
                seconds.digits.ones = 9;
                if(seconds.digits.tens < 0){
                    minutes.digits.ones--;
                    minutes.digits.tens = 6;
                    if(minutes.digits.ones < 0){
                        minutes.digits.tens--;
                        minutes.digits.ones = 9;
                        if(minutes.digits.tens < 0){
                            hours.digits.ones--;
                            minutes.digits.tens = 6;
                            if(hours.digits.ones < 0){
                                hours.digits.tens--;
                                hours.digits.ones = 9;
                            }
                        }
                    }
                }
            }
            if(seconds.digits.ones == 0 && seconds.digits.tens == 0 && minutes.digits.ones == 0 && minutes.digits.tens == 0 && hours.digits.ones == 0 && hours.digits.tens == 0){
                RB2 = 1;
                start_timer = 0;
            }       
           set_seconds.digits.ones = seconds.digits.ones;
           set_seconds.digits.tens = seconds.digits.tens;
           set_minutes.digits.ones = minutes.digits.ones;
           set_minutes.digits.tens = minutes.digits.tens;
           set_hours.digits.ones = hours.digits.ones;
           set_hours.digits.tens = hours.digits.tens;
}

//Function for adjusting time
void adjustTime(int direction, int position){
    if(direction == 0){
        //decrement the digits for every button press
        switch(position){
            case 0:
                set_seconds.digits.ones--;
                if(set_seconds.digits.ones < 0){
                   set_seconds.digits.ones = 9;
                }
                break;
            case 1:
                set_seconds.digits.tens--;
                if(set_seconds.digits.tens < 0){
                   set_seconds.digits.tens = 5;
                }
                break;
            case 2:
                set_minutes.digits.ones--;
                if(set_minutes.digits.ones < 0){
                   set_minutes.digits.ones = 9;
                }
                break;
            case 3:
                set_minutes.digits.tens--;
                if(set_minutes.digits.tens < 0){
                   set_minutes.digits.tens = 5;
                }
                break;
            case 4:
                set_hours.digits.ones--;
                if(set_hours.digits.ones < 0){
                   set_hours.digits.ones = 9;
                }
                break;
            case 5:
                set_hours.digits.tens--;
                if(set_hours.digits.tens < 0){
                   set_hours.digits.tens = 9;
                }
                break;
        }
    }else{
       //increment the digits for every button press
       switch(position){
            case 0:
                set_seconds.digits.ones++;
                if(set_seconds.digits.ones > 9){
                   set_seconds.digits.ones = 0;
                }
                break;
            case 1:
                set_seconds.digits.tens++;
                if(set_seconds.digits.tens > 5){
                   set_seconds.digits.tens = 0;
                }
                break;
            case 2:
                set_minutes.digits.ones++;
                if(set_minutes.digits.ones > 9){
                   set_minutes.digits.ones = 0;
                }
                break;
            case 3:
                set_minutes.digits.tens++;
                if(set_minutes.digits.tens > 5){
                   set_minutes.digits.tens = 0;
                }
                break;
            case 4:
                set_hours.digits.ones++;
                if(set_hours.digits.ones > 9){
                   set_hours.digits.ones = 0;
                }
                break;
            case 5:
                set_hours.digits.tens++;
                if(set_hours.digits.tens > 9){
                   set_hours.digits.tens = 0;
                }
                break;
        } 
    }
}

//Function for checking buttons for adjusting time
void checkButtons(){
    __delay_ms(200);
    if(!RB3){
        shifter++;
        if(shifter > 5) shifter = 0;
    }else if(!RB7){
        shifter--;
        if(shifter < 0) shifter = 5;
    }else if(!RB5){
        adjustTime(1, shifter);     
    }else if(!RB6){
        adjustTime(0, shifter);
    }else{;}
    
    //the updating digits must be the same as the adjusted digits
    switch(shifter){
        case 0:
            seconds.digits.ones = set_seconds.digits.ones;
            break;
        case 1:
            seconds.digits.tens = set_seconds.digits.tens;
            break;
        case 2:
            minutes.digits.ones = set_minutes.digits.ones;
            break;
        case 3:
            minutes.digits.tens = set_minutes.digits.tens;
            break;
        case 4:
            hours.digits.ones = set_hours.digits.ones;
            break;
        case 5:
            hours.digits.tens = set_hours.digits.tens;
            break;
    }
}
void main(void) {
    INTCON = 0b10101000;        //Use RB Change interrupt
    OPTION_REG = 0b00000100;    //      Prescale 
    TRISB = 0b11111000;         //RB1, RB2 outputs, RB3-RB7 inputs
    TRISC = 0;                  //All PORTC output
    TRISD = 0;                  //All PORTD output
    RB2 = 0;                    //relay pin is initialized as cleared
    PORTC = 0;                  //initialize PORTC and PORTD as cleared
    PORTD = 0;
    
    while(1){  
        //When Start/Stop is pressed, the start_timer variable is toggled
        
        //Start Timer
        while(start_timer){
            __delay_ms(1000);   //ticks every second
            runTime();          //update digits per the specified delay above
        }
        
        //Stop/Pause Timer
        while(!start_timer){
            checkButtons();    //adjust time
        }
    }
    return;
}

//Function to turn on the dot as indicator of the digit currently adjusting
void setDot(void){
    if(!start_timer){
        if(shifter == counter-1){
            RB1 = 1;
        }else{
            RB1 = 0;
        }
    }                
}

//Interrupt service routine: The interrupts used are RB Change and Timer Interrupt. The shifting of digits is done every time
// timer0 overflows. The RB change interrupt is used for starting and stopping the timer.
void interrupt isr(void){
    //RB Change interrupt is triggered
    if(RBIF){
        dummy = PORTB;   
        if(!RB4){
            if(!start_timer){
                start_timer = 1;
            }else{
                start_timer = 0;
            }
        }
        RBIF = 0;   //manual clearing of interrupt flag
    }
    //Timer Overflow interrupt is triggered
    if(TMR0IF){
            //switch digits for every interrupt. Read about POV for better understanding
            counter++;        
            switch(counter){
                case 1:
                    PORTC = 0b11011111;
                    PORTD = seconds.digits.ones;
                    setDot();
                    break;
                case 2:
                    PORTC = 0b11101111;
                    PORTD = seconds.digits.tens;
                    setDot();
                    break;
                case 3:
                    PORTC = 0b11110111;
                    PORTD = minutes.digits.ones;
                    setDot();
                    break;
                case 4:
                    PORTC = 0b11111011;
                    PORTD = minutes.digits.tens;
                    setDot();
                    break;
                case 5:
                    PORTC = 0b11111101;
                    PORTD = hours.digits.ones;
                    setDot();
                    break;
                case 6:
                    PORTC = 0b11111110;
                    PORTD = hours.digits.tens;
                    setDot();
                    break;
                case 7:
                    counter = 0;
                    break;
            }
            TMR0IF = 0;     //manual clearing of interrupt flag
    }
}

The project was coded using Microchip XC8. There are some peculiarities with my code, especially the use of structs. You can actually remove the structs and just use simple variables. I just find the structs more readable given the number of variables I had to use.

I used two interrupts in this code: timer overflow and RB change interrupt. The timer overflow interrupt is used for POV while the RB change interrupt is responsible for starting or stopping the timer. The adjustments are not done with interrupts but by simple button testing. The debounce problem is handled by a delay after every button pressed.

 

Leave a Reply

Your email address will not be published. Required fields are marked *