Following the request of some of our readers, in this tutorial we will cover how to program capture mode for timer module of ARM7 LPC2148 microcontroller along with a frequency counter example using capture input. In my previous LPC2148 Timer tutorial we saw how to setup and program the timer module.
A quick recap:
ARM LPC214x MCUs have two 32-bit-Timer blocks. Each Timer block can be used as a ‘Timer’ or as a ‘Counter’. A timer has a Timer Counter(TC) and Prescale Register(PR) associated with it. TC stores the current value of the count and PR stores a prescalar value. When Timer is Reset and Enabled, TC is set to 0 and incremented by 1 every ‘PR+1’ clock cycles. When it reaches its maximum value it gets reset to 0 and hence restarts counting. PR is used to set the Timer Resolution. When the timer is used in ‘Counter’ mode, and external signal is used to increment the value of TC.
Capture Channels and Input pins
Each timer block has 4 Capture Channels (CAPn.0 to CAPn.3, n=Timer number) associated with it. Using these Capture channels we can take a snapshot(i.e. capture) of the current value of TC when a signal edge is detected. These channels are mapped to device pins. This mapping of Capture Channel to pins is as given below:
Timer0 | Timer1 | ||
---|---|---|---|
Channel | Pin | Channel | Pin |
CAP0.0 | P0.2/P0.22/P0.30 | CAP1.0 | P0.10 |
CAP0.1 | P0.4 | CAP1.1 | P0.11 |
CAP0.2 | P0.6/P0.16/P0.28 | CAP1.2 | P0.17/P0.19 |
CAP0.3 | P0.29 | CAP1.3 | P0.18/P0.21 |
Using Capture Inputs in ARM7 LPC214x
When using Capture Inputs we use Timer Block in Normal ‘Timer Mode‘ or ‘Counter Mode‘.
- In Timer Mode, the Peripheral clock is used as a clock source to increment the Timer Counter(TC) every ‘PR+1’ clock cycles. Whenever a signal edge(rising/falling/both) event is detected, the timestamp i.e. the current value of TC is loaded into corresponding Capture Register(CRx) and optionally we can also generate an interrupt every time Capture Register is loaded with a new value. This behavior is configured using CCR.
- In Counter Mode, external signal is used to increment TC every time an edge(rising/falling/both) is detected. This behavior is configured using CTCR. Corresponding bits for Capture channel must be set to zero in CCR. (See register explanation given below)
Here is a simple diagram depicting the capture process. Dashed arrows(for both diagrams) signifiy the events.
Capture Related Registers:
Before we start with programming, first lets see the registers. Since I have already discussed main Timer Registers in my LPC2148 Timer tutorial, we will only have a look at registers relating to capture.
For CR0:
- Bit 0: Capture on CAPn.0 rising edge. When set to 1, a transition of 0 to 1 on CAPn.0 will cause CR0 to be loaded with the contents of TC. Disabled when 0.
- Bit 1: Capture on CAPn.0 falling edge. When set to 1, a transition of 1 to 0 on CAPn.0 will cause CR0 to be loaded with the contents of TC. Disabled when 0.
- Bit 2: Interrupt on CAPn.0 event. When set to 1, a CR0 load due to a CAPn.0 event will generate an interrupt. Disabled when 0.
Similarly bits 3-5, 6-8, 9-11 are for CR1, CR2 & CR3 respectively.
2) CR0 – CR3 – Capture Registers: Each capture register is associated with a Capture Pin. Depending on the settings in CCR, CRn can be loaded with current value of TC when a specific event occurs.
3) CTCR Count Control Register: Used to select between Timer or Counter Mode.
Bits[1:0] – Used to select Timer mode or which Edges can increment TC in counter mode.
- [00]: Timer Mode. PC is incremented every Rising edge of PCLK.
- [01]: Counter Mode. TC is incremented on Rising edges on the CAP input selected by Bits[3:2].
- [10]: Counter Mode. TC is incremented on Falling edges on the CAP input selected by Bits[3:2].
- [11]: Counter Mode. TC is incremented on Both edges on the CAP input selected by Bits[3:2].
Bits[3:2] – Count Input Select. Only applicable if above bits are not [00].
- [00]: Used to select CAPn.0 for TIMERn as Count input.
- [01]: Used to select CAPn.1 for TIMERn as Count input.
- [10] & [11]: Reserved.
Frequency Counter using LPC2148 Timer Capture
Methods to Measure frequency of an external signal
We will cover two methods of Measuring Unknown Signal Frequency:
- By Gating/Probing – In this method we define a Gating Interval in which we count the number of pulses. What is Gating Time? – Gating Time is amount of time for which we probe the input signal. Once we know the no.of. pulses, we can easily deduce the frequency using Gating time. Here we use the external signal as clock source to increment Timer Counter (TC). The value in TC gives the number of pulses counted per Gating Time. This method relies on selecting a proper Gating Time to get valid and accurate results.
- By measuring Period using Interrupts – In this method we use an Interrupt Service Routine to find out the time between 2 consecutive pulses i.e. period of the Input signal at that particular instant. This is done using an ISR, but ISR itself is main limiting factor for the maximum frequency which can be measured. Compared to first one, this method can give accurate results, given frequency is below measurement limit.
For measuring Square wave Signal Frequency, external hardware is not required unless the Pulse HIGH Voltage level is > 3.3 Volts, in which case a Voltage divider or Buffer is mandatory. To measure other Signal types like Saw-tooth, Sine wave we will require something that can provide Hysteresis which will define the HIGH & LOW Voltage Threshold, thereby converting it into a Square signal, to detect any of the edges of the unknown signal. This can be done using a Schmitt Trigger Buffer (either Inverting or Non-Inverting – doesn’t matter).
For both of examples given below, we will use Timer1 module to measure frequency. Capture Channel CAP1.0 is used as capture input. CAP1.0 is mapped to Pin P0.10 on LPC214x, hence we select CAP1.0 alternate function for P0.10.
To generate a square wave signal, I have used LPC214x’s inbuilt PWM module which is configured with 0.05 us resolution. PWM2 output channel is used which gives output on Pin P0.7. Refer my LPC2148 PWM Tutorial for more on PWM. You can also use any external source like a function generator or IC-555 based generator. The example project linked below also contains retargeted printf() for KEIL which redirects its output to UART0.
Schematic
1. Frequency Counter Example using Gating/Probing:
In this example we will, we use external signal as clock source for Timer1. Every positive going edge will increment the Timer1 Counter (T1TC) by 1. To count the number of pulses, we start the timer and wait until Gating time. After that we stop the time and read the value in T1TC which gives the no.of. pulses per gating time. For this example I have chosen a gating time of 1 seconds. Obviously, we can get more accurate results using a higher Gating time. For our purpose 1 second is enough to measure signals upto 30Mhz using PCLK=60Mhz. Given Gate time is in ms, the following equation can be used to find the frequency from counted pulses:
Source Code Snippet
/*(C) Umang Gajera- www.ocfreaks.com
ARM7 LPC2148 Input Capture Tutorial - Example 1 for frequency counter using ARM KEIL
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License: GPL.*/
#include <lpc214x.h>
#include <stdio.h> //visit https://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/
#include "lib_funcs.h" //OCFreaks LPC214x Tutorials Library Header
void initPWM(void); //To generate Test Square wave
unsigned int pulses = 0;
#define GATE_TIME_MS 1000 //Probing/Gating Time in ms
int main(void)
{
initClocks(); //Set PCLK = CCLK = 60Mhz - used by: UART, Timer & PWM
initUART0(); //Initialize UART0 for retargeted printf()
initTimer0(); //Init Timer for delay functions
//Assuming PCLK = 60Mhz
PINSEL0 |= (1<<21); //Select CAP1.0 for P0.10
T1CTCR = 0x1; //Increment TC on rising edges of External Signal for CAP1.0
T1PR = 0; //0.01667us res, 1 clock cycles @60Mhz = 0.01667 us
T1TCR = 0x02; //Reset Timer
T1CCR = (1<<0); //Capture on Rising Edge(0->1)
T1TCR = 0x01; //Enable timer1
initPWM(); //To generate square wave
float FreqKhz = 0;
printf("OCFreaks.com - Measuring Frequency using LCP2148 Timer Capture Ex 1:\n");
while(1)
{
T1TCR = 0x1; //Start Timer2
delayMS(GATE_TIME_MS); //'Gate' signal for defined Time (1 second)
T1TCR = 0x0; //Stop Timer2
pulses = T1TC; //Read current value in TC, which contains no.of. pulses counted in 1s
T1TCR = 0x2; //Reset Timer2 TC
FreqKhz = (double)pulses/GATE_TIME_MS;
if(FreqKhz >= 1000.0) //Display Freq. In MHz
{
printf("Frequency = %0.4f MHz\n", FreqKhz/1000.0);
}
else //Display Freq. in KHz
{
printf("Frequency = %0.2f KHz\n", FreqKhz);
}
}
//return 0; //This won't execute normally
}
void initPWM(void)
{
//Refer : https://www.ocfreaks.com/lpc2148-pwm-programming-tutorial/
/*Assuming that PLL0 has been setup with CCLK = 60Mhz and PCLK also = 60Mhz.*/
/*This is as per the Setup & Init Sequence given in the tutorial*/
PINSEL0 |= (1<<15); // Select PWM2 output for Pin0.7
PWMPCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
PWMPR = 3-1; //3 Clock cycles @60Mhz = 0.05us resoultion
PWMMR0 = 4; //4x0.05 = 0.2us - period duration i.e. 5MHz frequency
PWMMR2 = 2; //2x0.05 = 0.1us - pulse duration i.e. width
PWMMCR = (1<<1); // Reset PWMTC on PWMMR0 match
PWMLER = (1<<2) | (1<<0); // update MR0 and MR2
PWMPCR = (1<<10); // enable PWM2 output
PWMTCR = (1<<1) ; //Reset PWM TC & PR
//Now, the final moment - enable everything
PWMTCR = (1<<0) | (1<<3); // enable counters and PWM Mode
//PWM Generation goes active now!!
}
Download Example 1 Project Files:
2. Frequency Counter Example using Period Measurement:
Here, a timer resolution of 0.05 micro-seconds is selected by using Prescaler value of 2 (3-1) and PCLK=CLK=60Mhz. We configure the CCR so that the capture occurs for rising edges and an interrupt is also generated. The Timer1 interrupt handler function is the at core of this example where we are measuring the period of the square wave signal. Refer my ARM7 LPC2148 Interrupt Tutorial for more. Here we use 3 global variables viz.. period, previous & current. We just update period with the difference of current and previous. But since the timer counter (TC) is free running, an overflow is bound to occur. So, we need to take care of this condition by detecting an overflow condition which is simply when current is less than previous. In this situation the time difference is calculated as:
where, TC_MAX = Maximum value of TC and OVF_CNT = Number of times overflow occurred. Since TC is 32bit, its max value in our case is 0xFFFFFFFF. Also, since we are not measuring extremely low frequency signals OVF_CNT will be at max 1. Another thing is that, if OVF_CNT is >=2 then we will need a datatype of long long (8 bytes) to store the result since we won't be able to store the result in int which is 4 bytes for KEIL ARM compiler.
Hence, the equation boils down to:
Maximum value for frequency of external signal that can be correctly measured depends on the PCLK, Prescalar (PR) and the Latency of Timer Interrupt Routine execution. Out of the three, the main limiting factor is the interrupt latency. For example code given below, I was able to successfully able to measure square wave signals of upto 1Mhz. This frequency limit is more than enough for real-life applications like tachometer interfacing which I will cover in another tutorial.
Source Code Snippet
/*(C) Umang Gajera- www.ocfreaks.com
ARM7 LPC2148 Input Capture Tutorial - Example 2 for frequency counter using ARM KEIL
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License: GPL.*/
#include <lpc214x.h>
#include <stdio.h> //visit https://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/
#include "lib_funcs.h" //OCFreaks LPC214x Tutorials Library Header
int main(void)
{
initClocks(); //Set PCLK = CCLK = 60Mhz - used by: UART, Timer & PWM
initUART0(); //Initialize UART0 for retargeted printf()
initTimer0(); //Init Timer for delay functions
//Assuming PCLK = 60Mhz
PINSEL0 |= (1<<21); //Select CAP1.0 for P0.10
T1CTCR = 0x0; //Run in Timer Mode
T1PR = 3-1; //3 Clock cycles @60Mhz = 0.05us resolution
T1TCR = 0x02; //Reset Timer
T1CCR = (1<<0) | (1<<2); //Capture on Rising Edge(0->1) and generate an interrupt
T1TCR = 0x01; //Enable timer1
VICIntEnable |= (1<<5) ; //Enable TIMER1 IRQ
VICVectCntl0 = (1<<5) | 5; //5th bit must 1 to enable the slot. Refer: https://www.ocfreaks.com/lpc2148-interrupt-tutorial/
VICVectAddr0 = (unsigned) timer1ISR;
initPWM(); //Generate Test square wave
printf("OCFreaks.com - Measuring Frequency using Timer Capture\n");
double frequencyMhz = 0;
while(1)
{
frequencyMhz = (1.0 / (period*0.05) ) * 1000; //0.05 is Timer resolution in us
printf("Frequency = %0.2f Khz\n",frequencyMhz);
delayMS(500); //2 Udpates per second
}
//return 0; //This won't execute normally
}
__irq void timer1ISR(void)
{
current = T1CR0;
if(current < previous) //TC has overflowed
{
period = 0xFFFFFFFF + current - previous;
}
else
{
period = current - previous;
}
previous = T1CR0;
T1IR = (1<<4); //write back to clear the interrupt flag
VICVectAddr = 0x0; //Acknowledge that ISR has finished execution
}
void initPWM(void)
{
//Refer : https://www.ocfreaks.com/lpc2148-pwm-programming-tutorial/
/*Assuming that PLL0 has been setup with CCLK = 60Mhz and PCLK also = 60Mhz.*/
/*This is as per the Setup & Init Sequence given in the tutorial*/
PINSEL0 |= (1<<15); // Select PWM2 output for Pin0.7
PWMPCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
PWMPR = 3-1; //3 Clock cycles @60Mhz = 0.05us
PWMMR0 = 20; //20x0.05 = 1us - period duration i.e. 1MHz frequency
PWMMR2 = 10; //10x0.05 = 0.5us - pulse duration i.e. width
PWMMCR = (1<<1); // Reset PWMTC on PWMMR0 match
PWMLER = (1<<2) | (1<<0); // update MR0 and MR2
PWMPCR = (1<<10); // enable PWM2 output
PWMTCR = (1<<1) ; //Reset PWM TC & PR
//Now, the final moment - enable everything
PWMTCR = (1<<0) | (1<<3); // enable counters and PWM Mode
//PWM Generation goes active now!!
}
In the Frequency Counter Program given above, you can increase the values for PWMMR0 and PWMMR2 to measure other lower frequencies. Trying measure frequencies >1Mhz will yield in incorrect measurement.