Project Overview
This adjustable timer displays seconds, minutes and hours and is powered by a PIC16F877A microcontroller. There are five buttons included: start/stop, up for incrementing digits, down for decrementing digits, left and right for moving between digits during adjustment. I’ve included a relay circuit which will trigger when the timer is finished.
Introduction
A timer is a common project but not really a simple one. Here, I designed a six-digit countdown timer which, 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
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
[the_ad id="3059"]/* * 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.
Project Files
Of course, the schematic, code and simulation files are free to download.
I hope you find this project useful!