Ask Larry

In a previous Ask Larry column in Dec. 2008 (www.rabbit.com/support/ask_larry/), I discussed how to measure the frequency of an input signal by measuring its period using Input Capture. In this article I aim to show how to use the Count feature of Input Capture and a Timer-based gate interrupt to measure frequency.

The code I have attached, MeasFreqGate.C, implements the code discussed in this article. Even though there are several similarities between this program and the previous one I will still review the entire program.

I will not be describing each statement in the program, since the program is well commented, but I will discuss the more important ones. I will save discussing the Interrupt Service Routines for last.

This article makes multiple references to the program code by line number. It will be easiest to follow my explanations if you print out the code (or view the source within the Dynamic C GUI) with the line numbers for quick reference as you read through the article.

One of the features of the Input Capture systems in the Rabbit 4000 and 5000 processors is that they can count transitions of the input signal – not just use them to start and stop an internal counter. In using this feature, to measure frequency you also need a method to “gate” the signal so that you get some number of transitions per unit of time. This value can be used to calculate the frequency of the input signal. This program uses Timer B1 to create this gate signal.

Line 103 is used to calculate the CPU main clock frequency. This is needed in order to determine the Timer B1 value required to generate the desired gate time.

CPUfreq = (long)freq_divider * BASEFREQ;

The value freq_divider is determined by the BIOS at run time and is a measure of the CPU frequency. When multiplied by the constant BASEFREQ yields the CPU frequency in Hertz. Using this value allows the program to be largely independent of the product on which it is run.

For test purposes the program creates two sources of square wave signals. Either one can be used for the signal to the Input Capture. One uses a “trick” of an SPI serial port and the other an output of Timer C. The calculation on line 104 determines the divisor value needed by the appropriate timer to generate a 70 KHz square wave from both of the outputs.

Lines 108 – 124 set up both serial port B and Timer C1 to generate the 70 KHz square wave. You can use either of these two signals connected to Input Capture 1 to test the system.

Lines 128 – 136 set up the Timer B interrupt:

SetVectIntern ( 0x0B, TimerBISR ); // point vector to Timer B ISR
WrPortI ( TBCSR, &TBCSRShadow, 3 ); // enable B0 interrupt and clock
WrPortI ( TBM1R, NULL, 0 ); // B0 match reg MSB = 0
WrPortI ( TBL1R, NULL, 0 ); // B0 match reg LSB = 0
WrPortI ( TBCR, NULL, 0x09); // Timer B clocked by pclk/16,
  // enable Timer B interrupt at IPL 1
TimerBstate = 2; // tell ISR to disable the clock
while ( BitRdPortI(TBCSR,0) ); // wait for Timer B clock disabled
Period = 16.0*1024.0 / (float)CPUfreq; // interrupt interval for Timer B

I chose Timer B1 because it is probably the least used timer in the Rabbit and is 10 bits whereas Timer A is eight bits and is used more extensively. Driving Timer B with pclk/16 enables Timer B to cause an interrupt every 16*1024 CPU clock cycles. I set the Timer B1 match register to 0, although it could be set to any value since the Timer runs continuously and a match will still only occur once every 1024 cycles no matter what is in the match register. The final statement in this group is a calculation of the actual Timer B1 interrupt interval based on the previously calculated CPU clock frequency and the number of clocks between interrupts.

Line 135 synchronizes Timer B so that it starts from a count of 0. When Timer B reaches a count of 0 it causes an interrupt. There is a state variable, TimerBstate, which was previously initialized to a value of 2 causing the ISR to stop the counter. This insures that the gate time is consistent.

Since we are discussing the gate time I want to skip down to lines 162 – 164:

// calculations required for the gate time
TimerBcount = (int)((float)GateTime/Period/1000.0); // nbr of interrupts required
ActualGateTime = TimerBcount * Period; // *** in seconds ***

Line 163 calculates the number of times that Timer B must interrupt in order to create the requested gate time. Obviously there will be some truncation with the resolution being the value calculated for the Timer B Period. Line 164 calculates the actual gate time. This value will be used in calculating the measured frequency.

Back to lines 138 – 144 to set up the Input Capture Interrupt:

// set up the Input Capture  
SetVectIntern ( 0x1A, IC1_ISR ); // point to IC1 ISR
WrPortI ( ICCR, NULL, 0x42 ); // set IC1 for Input Count, IPL = 2
WrPortI ( ICS1R, NULL, 0x90 ); // use Port E bit 3 for Start
BitWrPortI ( PEDDR, &PEDDRShadow, 0, 3 ); // PE3 = input
WrPortI ( TAT8R, NULL, 0 ); // set low pass filter to allow
  // highest frequency

The interrupt is required to count the number of times the Input Capture overflows. The basic Input Capture counter is 16 bits, allowing a count of up to 65535. With a gate time of 1 second, this would allow a maximum frequency capability of only 65 KHz. This program effectively extends the count to 24 bits – allowing a maximum frequency of about 16 MHz. However, there is a mechanism that keeps the maximum frequency lower:

There is a two period digital low pass filter on the input circuit driven by Timer A8 which is driven by pclk/2. With TAT8R set to 0, this limits the input frequency to a maximum of CPUclock/8.

Lines 146 – 148 print the heading for the main loop which starts on line 150. Lines 152 – 160 change the frequency of the Rabbit generated square wave. If you like, you can insert some code between lines 151 and 152 which modify the value of Divisor.

Lines 166 – 176 enable the two interrupts and wait for end of gate:

// enable Timer B and Input Capture ISRs  
WrPortI ( PBDR, &PBDRShadow, 0x08 ); // DEBUG_IC_INT: PB3 = 1, PB2 = 0
TimerBstate = 1; // tell ISR to count interrupts
WrPortI ( TBL1R, NULL, 0 ); // required to re-enable interrupt
WrPortI ( TBCSR, &TBCSRShadow, 3 ); // enable B0 interrupt and clock
CaptureOverflow = 0; // initialize
WrPortI ( ICCSR, NULL, 0x14 ); // enable stop interrupt, clear IC1 count
WrPortI ( ICT1R, NULL, 0x80 | 0x20 | 0x04 );  
  // run cont., latch on Start
  // Start on rising edge
while ( RdPortI ( ICT1R ) & 0x04 ); // wait for IC1 disable

Line 167 is for debugging purposes. It simply sets PB3 high and PB2 low. It allows you to “see” the Input Capture overflows relative to the start of the gate time. If you decide to look at these signals on an oscilloscope you will see that an Input Capture interrupt occurs almost immediately. This is because the Input Capture system is set to interrupt on the stop signal which is set to occur when the counter has a value of 0. Since the count is already 0 (line 172), an interrupt is generated as soon as the interrupt is enabled.

Line 176 waits for the gate signal to indicate that it is done. The Timer B ISR clears ICT1R bit 4 when it has counted down TimerBcount to 0.

Lines 178 – 180 calculate and print the reported values.

calcFreq = CPUfreq/(Divisor*2);
measFreq = (long)((1.0/ActualGateTime) * (float)CaptureCount);
printf("%5d %5d %7ld %11ld %10ld\r", Divisor, GateTime, CaptureCount, calcFreq, measFreq);

Now for the ISRs. I will not describe every line of code since these are well documented; however, I will make some comments on some of the more important statements. One feature of most ISRs I write is that of showing the number of CPU clock cycles required to execute the instruction. I do this so I can calculate how long it takes to execute the ISR. This allows me to determine whether or not the ISR is placing too much of a burden on the CPU based on how often the ISR is executed. For both of these ISRs this is not an issue since they do not get executed very often.

Timer B ISR: The main purpose of this ISR is to disable itself once the Gate time has completed.

Lines 42 – 47 initialization:
  push af   ; 10 save registers

ioi ld a, (TBCSR)   ; 11 clear the interrupt
  ld a, (TimerBstate)   ; 9 get state variable
  dec a   ; 2 update TimerBcount?
  jr nz, TimerBISR_Try2   ; 5 jump if no, TimerBstate > 1

Line 44: Most Rabbit peripherals require that the ISR access one of the registers in order to clear the interrupt condition.

Lines 45 – 47: set up to perform the operation required by the value of TimerBstate

Lines 50 – 57 update the TimerBcount value to determine whether or not the gate time is completed.

ioi ld (TBL1R), a   ; 12 (a=0) required to re-enable interrupt
  push HL   ; 10
  ld HL, (TimerBcount)   ; 11 get count value
  dec HL   ; 2 update
  ld (TimerBcount), HL   ; 13 save
  bool HL   ; 2 done (HL=0)?
  pop HL   ; 7
  jr nz, TimerBISRexit   ; 5 jump if no

As noted, line 50 is required to re-enable the timer B interrupt. This is done by writing a data value to the least significant byte of the Timer B1 count register.

Notice that there is a push HL and a corresponding pop HL in this section. Rather than cause all Timer B interrupts to save and restore HL I do it here because this is the only part of the ISR that requires the use of HL.

The bool HL is required because the dec HL instruction does not set the condition codes and I need to know whether or not TimerBcount has decremented to 0. If it has, the gate time has expired and the ISR will drop through to the next section. If it has not, the ISR will be exited.

Lines 59 – 69 handle the processing for the end of the gate time.

ioi ld a, (ICL1R)   ; get LSB of count
  ld (CaptureCount), a   ; save it
ioi ld a, (ICM1R)   ; get MSB of count
  ld (CaptureCount+1), a   ; save it
  ld (TimerBcount), HL   ; 13 save
  ld a, (CaptureOverflow)   ; get overflow count
  dec a   ; adjust for initial 0 value
  ld (CaptureCount+2), a   ; insert into total count
TimerBISR_Try2:
  xor a   ; 4 a = 0
ioi ld (TBCSR), a   ; 12 disable Timer B clock and ISR
ioi ld (ICT1R), a   ; 12 disable Input Capture counter

Lines 59 – 65 are executed only once per measurement cycle so I violated one of my own “rules” for ISRs: do not do anything that is not required by the ISR. These lines read the Input

Capture value, save it to the global variable and adjust it for the number of times the counter overflowed.

Lines 67 – 69 disable both the Timer B and Input Capture interrupts

Lines 71 – 74 contains the ISR exit code: restore any registers that are used and then return to the main program.

TimerBISRexit:
  pop af ; 7
  ipres   ; 4
  ret   ; 8

Input Capture ISR: update the overflow counter.

Lines 78 – 83 update the Input Capture overflow count.

ioi ld a, (ICCSR)   ; clear the interrupt
  ld a, 0x80 | 0x20 | 0x04   ; set up to
ioi ld (ICT1R), a   ; re-enable IC1 interrupt
  ld a, (CaptureOverflow)   ; get current overflow count
  inc a   ; update it
  ld (CaptureOverflow), a   ; save it

Lines 84 – 89 are here for debugging purposes only. They get compiled only if the macro DEBUG_IC_INT is defined. Lines 90 – 92 contain the typical ISR exit code.

This program is fairly generic in that it will run on any Rabbit 4000 or 5000 product. You may need to select different ports for the internally generated square waves or for the pin used for the Input Capture.

I hope that reading this has been a positive experience and that you can use some of the ideas in your applications.

- Larry C.

Larry Cicchinelli is Rabbit’s Technical Support Manager. He has 30 years of embedded experience, and is considered one of the foremost authorities on Rabbit products. Larry and his staff offer comprehensive technical support to Rabbit customers.

Submit your questions for Larry via email at AskLarry@rabbit.com

Read more Ask Larry Answers