Ask Larry

Dear Larry,

How can I use the Rabbit to measure the period of an input signal?

John A.

Hello John,

The easiest way to do this is to use the Input Capture (IC) system built-in to the Rabbit 3000, 4000, and 5000. (This is not available with the older Rabbit 2000 processors.)

The Input Capture system allows you to measure the time between a start event and a stop event. The start or stop can be from an external source or generated internally. In the example program I provide here, both the start and stop are from a single external source.

The IC system can be programmed to start and/or stop on either signal edge and can be set to a wide range of resolutions. The following equation governs the behavior of the Rabbit's input capture.

Resolution = (Half CPU Clock)/(Capture Divider)

The Resolution is calculated from the main CPU clock divided by 2 as shown above. The Capture Divider can be any integer value from 1 to 256.

In addition, one limitation of the IC system is that programming it to start and stop on the same edge will produce a result of 0. The purpose of this article is to show you how to work around this "limitation".

The attached program (MeasurePeriod.C) is a fully working sample of how to accomplish this. The following is a brief explanation of how the program works.

Line 21 defines the constant for calculating the CPU clock frequency.
Line 22 defines the desired resolution in usec.

#define	BASEFREQ      614400L   // constant for determining clock frequency
#define	RESOLUTION    .1        // define desired resolution in usec

Lines 74 & 75 calculate the frequency of the main CPU clock. When the system starts booting, the CPU uses the frequency of the RTC (32768Hz) to measure the frequency of the main oscillator. The proportionality constant is 614400. This calculation makes the program largely independent of the Rabbit product being used.

k = (long)freq_divider * BASEFREQ;     // calculate CPU clock frequency
CPUfreq = (float)k/1e6;                // make MHz

Lines 77 – 81 calculate and check the Timer A8 value for the defined resolution. The test insures that the resolution value is not too low. The range of resolutions is from 2/CPU clock to 512/CPU clock. Using an RCM3000 with a 29.5MHz CPU clock yields a resolution range of approximately 67.8ns to 17.4us, in 67.8ns steps. You need to choose the resolution based on the range of period values expected keeping in mind that the IC counter is 16 bits.

TAT8value = (int)(CPUfreq*RESOLUTION/2 - 1);     // calculate TA8 value
if ( TAT8value>255 )
   {   printf ( "Resolution too large\n\r" );
       exit(1);
   }

For example:

Resolution = 67.8ns
Longest period measurable = about 4ms
Equivalent to 250Hz

Lines 83 – 86 set up the LEDs and switches on the prototype board.

// set up for reading switches
WrPortI(PBDR, &PBDRShadow, PBDRShadow | 0x0C);	// LEDs off
WrPortI(PBDDR, &PBDDRShadow, PBDDRShadow | 0x0C); // bits 2,3 = output
WrPortI(PBDDR, &PBDDRShadow, PBDDRShadow & ~0x30); // bits 4,5 = input

Lines 88 – 92 set up Serial Port B in its SPI mode so that it generates a continuous, programmable clock signal. The frequency of the signal can be changed while the program is executing – see lines 138-143. This is the signal that is measured by the IC system.

// start the PB0 clock
Divisor = 500;
WrPortI ( SBDLR, NULL, Divisor-1 );		// low byte
WrPortI ( SBDHR, NULL, 0x80|((Divisor-1)>>8) );	// high byte
WrPortI ( SBCR, NULL, 0x8C );			// start the PB0 clock

Line 94 calculates the actual resolution as described above.

Resolution = ((TAT8value+1)*2) / CPUfreq;	// calculate actual resolution

Lines 95 & 96 calculate the low frequency limit based on the resolution.

Period = 65535*Resolution;     // longest possible period
 Freq = 1e6/Period;            // equivalent frequency

Lines 102 – 106 initialize the IC system – without starting it.

WrPortI ( TAT8R, NULL, TAT8value );     // set TA8

WrPortI ( ICCR, NULL, 0 );              // disable Input Capture interrupt
WrPortI ( ICS1R, NULL, 0x22 );          // start/stop = PC5
SetVectIntern( 0x1A, IC1ISR);           // set interrupt vector

Lines 114 – 123 set up for the “next” measurement and wait for it to complete. The most important thing to see here is that the measurement is initiated by starting on a rising edge but ignores the stop condition.

IC1count = 0;                          // init for next measurement

WrPortI ( ICCSR, NULL, 0x24 );         // interrupt on Start, clear the counter
WrPortI ( ICT1R, &ICT1RShadow, 0x40 | 0x10 | 0x04 );
     // counter runs from Start to Stop, latch on Stop,
     // Start on rising edge, ignore Stop
 RdPortI ( ICCSR );                   // insure no pending interrupts
WrPortI ( ICCR, NULL, 2 );	           // enable Input Capture interrupt

 while ( RdPortI ( ICCR ) & 3 );      // wait for reading to finish

Lines 124 & 125 read the count value from the IC system.

IC1count = RdPortI ( ICL1R );            // get low byte
IC1count += (RdPortI ( ICM1R )<<8);      // insert high byte

Lines 127 – 141 calculate and display the results based on the IC count value and check the switches on the prototype board to see if a change in frequency is wanted. The delay is there just to keep the measurements from occurring too quickly.

Period = Resolution*IC1count;                      // convert period count to usec
 MeasFreq = 1e6/Period;                            // calculate measured frequency
CalcFreq = CPUfreq*1e6/((float)(Divisor*2));
Error = ((MeasFreq-CalcFreq)/CalcFreq) * 100.0;
printf ( "%5d %9u %9.1f    %8.1f  %8.1f  %5.1f\r",
     Divisor, IC1count, Period, CalcFreq,  MeasFreq, Error );

MSdelay ( 100 );
// check switches
if ( !BitRdPortI ( PBDR, 4 ) && (Divisor > 50) )
     Divisor -= DIVISOR_DELTA;
if ( !BitRdPortI ( PBDR, 5 ) && (Divisor < 32767-DIVISOR_DELTA) )
     Divisor += DIVISOR_DELTA;
WrPortI ( SBDLR, NULL, Divisor-1 );                // low byte
WrPortI ( SBDHR, NULL, 0x80|((Divisor-1)>>8) );    // high byte

Pardon this brief interruption… Now for the ISR!

Just a general comment first – notice the numbers in the comments. These are the number of CPU clock cycles required to execute the instructions. I usually do this in ISRs so that I can tell how long an ISR takes to execute. This gives me an idea of the load on the CPU that the ISR has. For a more accurate calculation you should also include the interrupt latency which is a maximum of 29 clock cycles.

I have three rules for ISRs:

  • It must be in assembly language
  • It must be as short as possible – do nothing that can be done in the main program
  • Almost every line gets a meaningful comment – the only exceptions are those statements which should be extremely obvious.

The ISR has two distinct sections – Start condition interrupt and Stop condition interrupt.

Line 32 – save the only registers that are used during the ISR

           push  af                             ;10

Lines 33 – 35 determine which of the two interrupt conditions needs to be serviced

ioi        ld    a, (ICCSR)                     ;11 clear interrupt condition
           and   0x10                           ; 4 stop interrupt?
           jr    nz, IC1ISR_1                   ; 5 jump if yes

Lines 38 – 45 service the Start condition by setting the Stop condition and enabling the Stop interrupt

           ld    a, 0xD1                        ; 4 run until stop, latch on stop,
                                                    stop=rising edge
ioi        ld    (ICT1R), a                     ;12
           ld    a, 0x10                        ; 4 enable IC1 Stop interrupt
ioi        ld    (ICCSR), a                     ;12

           pop   af                             ; 7 restore the registers
           ipres                                ; 4 and the Interrupt Priority Level
           ret                                  ; 8 return to main program

Lines 49 – 54 service the Stop condition by disabling the IC interrupts.

IC1ISR_1:
           xor   a                              ; 2 a=0
ioi        ld    (ICCR), a                      ;12 disable Input Capture interrupts
                                                ;   and show measurement is done
           pop   af                             ; 7 restore the registers
           ipres                                ; 4 and the IPL
           ret                                  ; 8 return to main program

Summary

This program takes a single measurement of the period of a continuous pulse stream. If you do not know approximately what the period will be you may not know what resolution value to use. It would be a fairly simple modification to implement an auto-ranging algorithm. I would probably start with the smallest possible resolution (TAT8R=0) and keep incrementing it as long as the overflow bit (ICCSR:2) is set. Once a measurement is made that has this bit cleared you will then have a valid value.

With this technique you cannot easily measure higher frequencies very accurately. For instance, if you need to measure a 1MHz signal it period is 1us. The count value with 67.8ns resolution would be only 14. So, for higher frequencies it would be better to use the count feature of the IC system with an accurate gate mechanism. I will describe this in a near future article.

I hope this answers some of your questions about input capture with the Rabbit family and I hope you enjoy tinkering with this example program.

- 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