Skip to main content

Frequency Counter using Interrupts PIC18f4580 Project


Frequency Counter 

using PIC18f4580 Project


This POST describes the construction of small frequency counter with a
cheap PIC18f4580 microcontroller with 16 x 2 LCD.  

Prerequisites:
  1. PIC18F4580 TIMER Programming.
  2. PIC18F4580 COUNTER Programming.
  3. PIC18F4580 Interfacing with 16x2 LCD.
  4. PIC18F4580 Interrupts Programming  ( we will cover in this POST)

Concept:

Frequency is the number of occurrences of a repeating event per unit time.
in our case we will measure a number of clocks generated by clock source per unit time.

In this project, LCD is used to display the frequency and PIC timer 1 to measure the input signal and Timer0 to generate an indication that one second has gone.
System software utilizes Timer-1 in the 16-bit counter mode to count the input signal and overflows of the counter are added to provide the aggregate count in multiples of 65536. Totaling the existing value of the counter at the conclusion provides the full count.

To get the events per second we need another timer which conveys the system that one second has elapsed. Timer0 is used for this service. Since the main clock is running at 10MHz then the processor clock (Fosc/4) is 2.5MHz which is the rate at which Timer0 operates using an internal clock.

we will use interrupts of both timers to accomplish our system software. Timer0 interrupts will indicate that one second has passed and at this point, we will accumulate the values of Timer1 which will be our events per seconds. The timer1 interrupt will increment our multiplier for each overflow of Timer1, keep in mind Timer1 is configured as a 16 bit counter.

From the above argument, we can realize that the part of interrupts in this project is very critical.

so first we will devote some time to understand the concept of interrupts and specifically interrupts of PIC18f4580 for Timer0 and Timer1.

Interrupts vs. polling

A single microcontroller is capable to serve many devices by using two ways :interrupts or polling.

In the interrupt methodology, whenever any device desires the microcontroller's service, the device notifies it by delivering a digital signal, upon receiving interrupt signal, the microcontroller stops all routines except ISR (interrupt service routine). The program related to the interrupt is termed the (ISR)or interrupt handler.

In polling, the microcontroller unceasingly monitors the logical condition of a given device; once the required condition is met, it performs the service.

After that, it moves on to observe successive device till all is entertained.

Although polling will monitor the logical condition of many devices and serve every of them as logical condition are true, it is not an effective way to use controller .

The advantage of interrupts is that the microcontroller will serve several devices (not all at an equivalent time, of course); every device will get the eye of the microcontroller supported the priority assigned to that.

The most vital reason that the interrupt methodology is preferred is that the polling methodology wastes a lot of the microcontroller's time by polling devices that don't want service.

 for instance, in our last post associated with timers and counters polling method was utilized:  
                                   while (INTCON.TMR0IF==0) 

                                            See Previous Posts and simulations

Here while loop waited till the timer rolled over, we couldn't do anything that's a waste of time . In contrast, if we tend to use the interrupt methodology, the microcontroller will perform alternative tasks, and once the TMR0IF flag is raised, the timer can interrupt the microcontroller in no matter it's doing.


Interrupt service routine

For every interrupt, microcontroller needs a piece of code which is known as ISR, whenever interrupt raised microcontroller executes ISR.

Generally, in most microprocessors, for each interrupt there's a hard and fast location in memory that holds the address of it's ISR. The cluster of memory locations put aside to carry the addresses of ISRs is termed the interrupt vector table.

 Upon initiation of an interrupt, the microcontroller goes through the subsequent  steps:

1. It finishes the instruction defined in ISR and saves the address of succeeding instruction (program counter) on the stack.

 2. It jumps to defined location in memory known as the interrupt vector table. The interrupt vector table is designed  to hold addresses so it  directs the microcontroller to the address of the interrupt service routine (ISR).

 3. The microcontroller gets the address of the ISR from the interrupt vector table and jumps to that. It starts to execute the interrupt service routine till it reaches the last instruction of the routine.

 4. Upon accomplishing the instruction, the microcontroller returns to the place wherever it had been interrupted. First, it gets the program counter (PC) address from the stack by removing the highest bytes of the stack into the Program counter register, Then execute code  from that address.

 To avail the interrupts, we have to take the later steps:

 1. Bit 07 (GIE) of the INTCON register should be set to HIGH to permit the interrupts to happen. 

               INTCON.GIE = 1; //instruction will allow user to avail all available interrupts

 2. If GIE = 1, every interrupt is permitted by setting to HIGH the interrupt enable

(IE) flag bit for that interrupt. In microcontroller we have multiple interrupts for different peripherals like for serial communication (UART), analog to digital converter(ADC) and etc. In case of timer, we need to enable Timer0(TMR0IE). It should be noted that if GIE = zero, no interrupt are going to be active, although the corresponding interrupt  bit is high.

 NTCON.TMR0IE=1; //enable Timer 0 interrupt

 3. PEIE also need to configure as logic high to avail Timer1

PIE1.TMR1IE = 1;//enable Timer1 interrupt


Rollover timer flag and interrupt

The timer flag is HIGH when the timer rolls over. In previous posts, we showed how to monitor the timer flag with the instruction 

while (INTCON.TMR0IF==0); In polling TMR0IF, we have to wait until TMR0IF is raised. 
we have already discussed the method that  keep controller busy is not an efficient way, to resolve this issue and to optimize our code we utilize interrupt . 
 
If the timer interrupt in the interrupt register is enabled, TMR0IF is raised whenever the timer rolls over and the microcontroller jumps to the interrupt vector table to service the ISR. 

By using interrupt methodology microcontroller is free to perform other tasks until it is notified that the timer has rolled over.
In the case of Timer0, the INTCON register contains the TMR0IE bit, and PIEI (peripheral interrupt enable) holds the TMR1IE bit. 

Frequency Counter Project Code using miKroC



1.  void TO_ISR(void);

2.  void init(void);

3.  void Tl_ISR(void);



4.  char Freq_txt[11];

5.  unsigned int tmr1_Val=0;
6.  unsigned int OverNumb=0;
7.  unsigned long Freq=0;
8.  bit flag;

9.  // LCD module connections
10.          sbit LCD_RS at RB4_bit;
11.          sbit LCD_EN at RB5_bit;
12.          sbit LCD_D4 at RB0_bit;
13.          sbit LCD_D5 at RB1_bit;
14.          sbit LCD_D6 at RB2_bit;
15.          sbit LCD_D7 at RB3_bit;

16.          sbit LCD_RS_Direction at TRISB4_bit;
17.          sbit LCD_EN_Direction at TRISB5_bit;
18.          sbit LCD_D4_Direction at TRISB0_bit;
19.          sbit LCD_D5_Direction at TRISB1_bit;
20.          sbit LCD_D6_Direction at TRISB2_bit;
21.          sbit LCD_D7_Direction at TRISB3_bit;
22.          // End LCD module connections

23.          char txt1[13] = "Frequency = ";



24.          void interrupt() {
25.          if(INTCON.TMR0IF==1){
26.          TO_ISR();
27.          }
28.          if(PIR1.TMR1IF ==1){
29.          Tl_ISR();
30.          }

31.          }

32.          void Tl_ISR(void)
33.          {
34.          OverNumb++;
35.          PIR1.TMR1IF = 0;  //clear TF1


36.          }
37.          void TO_ISR(void){

38.          TMR0H = 0x67;
39.          TMR0L = 0x69;
40.          INTCON.TMR0IF=0; //clear TFO
41.          INTCON.GIE = 0;
42.          flag = 1;
43.          }

44.          void init(void){
45.          ADCON1 = 0xff;
46.          CMCON = 0x7;

47.          TRISD = 0;
48.          TRISA.trisA0 = 0;
49.          TRISC.RC0 = 1;


50.          INTCON.TMR0IF=0; //clear TFO
51.          INTCON.TMR0IE=1; //enable TimerO interrupt
52.          INTCON.GIE = 1;  //enable all interrupts globally

53.          T0CON = 0X5;
54.          TMR0H = 0x67;
55.          TMR0L = 0x69;
56.          T0CON.TMR0ON = 1;


57.          T1CON=0x2;
58.          TMR1H=0;
59.          TMR1L=0;
60.          T1CON.TMR1ON=1; //turn on Timerl
61.          PIE1.TMR1IE = 1;//enable TimerO interrupt
62.          PIR1.TMR1IF = 0;  //clear TF1


63.          Lcd_Init();                        // Initialize LCDa
64.          Lcd_Cmd(_LCD_CLEAR);               // Clear display
65.          Lcd_Cmd(_LCD_CURSOR_OFF);          // Cursor of
66.          Lcd_Out(1,1,txt1);
67.          }



68.          void main(){
69.          init();

70.          while(1){

71.          if(flag==1){
72.          tmr1_Val = (TMR1H << 8) + TMR1L; //get high and low from                 timer1
73.          Freq = (OverNumb*65536) + tmr1_Val;  //calculate frequency
74.          LongWordToStr(Freq,Freq_txt);
75.          lcd_out(2,1, Freq_txt);    // Write text in first row
76.          lcd_out_cp("Hz");
77.          flag=0;
78.          TMR1H=0;
79.          TMR1L=0;
80.          INTCON.GIE = 1;  //enable all interrupts globally

81.          }

}

82.          }

Proeteus Simulation for Frequency Counter Project



Explanation of Code: 

Line 1,2 and 3 is declaration tells the compiler about a function's name for initialization of timers,LCD and interrupts. 

Line 4 is declaration statement and we are creating an array of size 11 and an character for the value of measured frequency.
Line 5 is declaration statement for the variable an unsigned integer of where we store the TIMER1L and TIMER1H Values when TIMER0 interrupt raise which indicates that one has elapsed.

Line 6 is declaration statement for the variable an unsigned integer of where we  store the values which is generated by an increment caused by overflow of TIMER1.



Line 7 is declaration statement for the variable an unsigned long of where we  store the value of calculated frequency.



Line 8 is declaration statement for the variable bit of where we  store the value of flag which is 1 when TIMER0 interrupt occur.


Line 9-22 is declaration statement for the interfacing of LCD with PIC18F4580 and pin Direction configuration.

Line 23 is declaration statement and we are creating an array of size 13 and an character to display the text "Frequency = " on LCD.

Line 24-31 is the start of definition of interrupt function which is the first function where the execution starts. here void specifies that function does not return anything.function deals with both interrupts of TIMERand TIMER1.if TIMER0 interrupt occur,TO_ISR() will execute whic is also a function and T1_ISR() for the interrupt of TIMER1.  

Line 32-36 is the start of definition of interrupt service routine for TIMER1.when ever timer1 overflows variable OverNumb get increment ,we will use it as multiplier of 63336. PIR1.TMR1IF is TIMER1 overflow flag and it must be clear by software for next counting.  


Line 37-43 is the start of definition of interrupt service routine for TIMER0. timer0 overflows when one second has gone.here we reload timer0 and disable global interrupt with  INTCON.GIE = 0.we also clear Timer0 overflowflag for next counting.flag is set to 1 to get  the point where we will accumulate the values of Timer1 for calculation of frequency in main() function. 

Line 44-67 is the start of definition of init() function ,deals with the Registers set-up required to PIC18f4580 to perform frequency measurement.

The ADCON0 register, controls the operation of the A/D module.ADCON1 = 0xff will disable the Analog to digital converter.


The CMCON register selects the comparator input and output configuration. CMCON = 0x7 will off all comparators.



TRISD  = 0,portd configure as an output port for debugging purpose same for TRISA.trisA0 = 0; 

TRISC.RC0 = 1 pin RC0 configured as input to count an events coming from clock source.

T0CON = 0X5; see my Previous post of one second delay
TMR0H = 0x67;
TMR0L = 0x69;
T0CON.TMR0ON = 1;



T1CON=0x2 Timer1 configured as 16 bit counter with no  prescaler

PIE1.TMR1IE = 1 Timer1 interrupt is enable

Line 68-81 is the start of definition main program you already know that when timer0 generates interrupt after one second we set flag, in the main prog we use this flag and when it is set we start our calculations ,we get the timer1 data from its registers and store it in tmr_1Val register then we calculate totlal count by multiply 65536( timer1 is configures 16 bit in counter mode) with addition of Timer low byte register.Since whole action is performed after one second elapsed so we got events per unit time which is frequency of events.




Frequency Counter Project Code using CSS




#include <flex_lcd.c>


int8 overnumber=1;
int16 count;
int8 datatest=126;
char Freq_txt[17];
int1 flag=0;


#INT_TIMER0
void  T0_ISR(void)
{

flag=1;

disable_interrupts(INT_TIMER0);
disable_interrupts(GLOBAL);

set_timer0(0x6769);
}

#INT_TIMER1
void Tl_ISR (void)
{
  set_timer1(0);
  overnumber++;
}



void main()
{

   setup_adc_ports(NO_ANALOGS|VSS_VDD);
   setup_adc(ADC_OFF|ADC_TAD_MUL_0);
   setup_psp(PSP_DISABLED);
   setup_spi(SPI_SS_DISABLED);
   setup_wdt(WDT_OFF);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_64);
   setup_timer_1(T1_EXTERNAL|T1_DIV_BY_1|T1_EXTERNAL_SYNC);
   setup_timer_2(T2_DISABLED,0,1);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   enable_interrupts(INT_TIMER0);
   enable_interrupts(INT_TIMER1);
   enable_interrupts(GLOBAL);
   set_timer0(0x6769);
   set_tris_c(0xff);     // make porta as  an input
   set_timer1(0);       //set count to zero

   lcd_init();  // Always call this first.
   lcd_gotoxy(2,1);
   lcd_putc("\f Frequency = ");
    delay_ms(2000);
   
  
  

while(1){
 if (flag==1){
   
   count = get_timer1()*overnumber; //calculate frequency
  
    lcd_gotoxy(1,2);
   printf(lcd_putc,"%Lu Hz", count);
   
 
   set_timer1(0);
   flag= 0;
   enable_interrupts(INT_TIMER0);
   enable_interrupts(GLOBAL);
 
}





}




}



           Friends for further learning about PIC18 checkout

Comments

  1. HI i'm getting error in ( LongWordToStr(Freq,Freq_txt) ) this line.can you tell me which library file i need to choose from library manager in mickro c ,to correct this error.

    ReplyDelete

Post a Comment

Popular posts from this blog

How to use SPI Debugger

  SPI Debugger Hi Friends, in this post, we will discuss how to use SPI Debugger available in proteus for serial peripheral interface (SPI) which is a built-in feature of PIC microcontroller to communicate and data exchange between PIC and other devices. SPI Debugger Introduction to SPI Guys, SPI can be considered as a programmable shift  register, The SPI module is a synchronous serial I/O port that shifts a serial bit stream of variable length and data rate between the PIC and other peripheral devices. Here “synchronous” means that the data transmission is synchronized to a clock signal. To avoid further delay, let's see some important connections and different configurations to effectively use SPI Debugger. How to use SPI Debugger in MASTER & SLAVE configuration Select the virtual instrument and from the instrument, list selects "SPI Debugger". SPI Debugger Insert two SPI Debugger on the working area, select any one of ...

PIC18f4580 Timer0 calculation using miKroC , CSS and proteus

PIC18f4580 TIMER PROGRAMMING The PIC18f4580 has four timers. they're named as Timers zero, one, two, and three. they will be used either as timers to come up with a time delay or as counters to count events happening outside the microcontroller. First, we see however Timers zero is employed to come up with time delays. Every timer wants a clock pulse to tick. The clock supply will be internal or external. If we have a tendency to use the inner clock supply, then 1/ fourth of the frequency of the crystal oscillator on the OSC1 and OSC2 pins (Fosc/4) is fed into the timer. Therefore, it's used for time delay generation and for that reason is termed a timer. By selecting the external clock choice, we have a tendency to feed pulses through one among the PIC18's pins: this is often known as a counter. GIF  taken from  https://exploreembedded.com Basic registers of the timer Majority of t timers in 18F are 16 bits wide. Because the PIC 18 has an 8- bit ...