In this tutorial we will go through the programming of input capture mode for timer module of ARM Cortex-M3 LPC1768 microcontroller along with a frequency counter example using capture input. In my previous LPC1768 Timer tutorial we saw how to setup and program the timer module.
Capture Channels and Input pins
Each timer block in LPC176x has 2 Input Capture Channels (CAPn.0 & CAPn.1, n=Timer number). 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 | Timer2 | Timer3 | ||||
---|---|---|---|---|---|---|---|
Ch. | Pin | Ch. | Pin | Ch. | Pin | Ch. | Pin |
CAP0.0 | P1.26 | CAP1.0 | P0.18 | CAP2.0 | P0.4 | CAP3.0 | P0.23 |
CAP0.1 | P1.27 | CAP1.1 | P0.19 | CAP2.1 | P0.5 | CAP3.1 | P0.24 |
Using Capture Inputs in LPC176x
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) signify the events.
Capture Related Registers:
Since I have already discussed main Timer Registers in my LPC1768 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, are for CR1.
2) CR0 & CR1 – 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 LPC1768 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 Timer2 module to measure frequency. Capture Channel CAP2.0 is used as capture input. CAP2.0 is mapped to Pin P0.4 on LPC1768, hence we select CAP2.0 alternate function for P0.4. On mbed platform P0.4 is labelled as p30 and P2.0 as p26. Schematic is also same for both.
To generate a square wave output, we can use LPC176x’s inbuilt PWM module, configured with 0.02 us resolution. PWM1.1 output channel is used which gives output on Pin P2.0. Refer my LPC1768 PWM Tutorial for more on PWM. You can also use any external source like a function generator or IC-555 based generator. The example projects linked below also contain 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 Timer2. Every positive going edge will increment the Timer2 Counter (LPC_TIM2->TC) 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 LPC_TIM2->TC 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 50Mhz using TIMER2_PCLK=100Mhz. 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
LPC176x Input Capture Tutorial - Example 1(Using Gating) for frequency counter using ARM KEIL
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License: GPL.*/
#include <lpc17xx.h>
#include <stdio.h> //for printf() - https://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/
#include "ocf_lpc176x_lib.h" //contains initUART0(), initTimer0() & putc() to retarget printf()
void initPWM(void);
unsigned int pulses = 0;
#define GATE_TIME_MS 1000 // Probing/Gating Time in ms
int main(void)
{
//SystemInit(); //Gets called by Startup code, sets CCLK=100Mhz, PCLK=25Mhz
initUART0(); //Initialize UART0 for uart_printf() - see ocf_lpc176x_lib.c
initTimer0(); //For delayMS() - see ocf_lpc176x_lib.c
/*Using CCLK = 100Mhz and PCLK_TIMER2 = 100Mhz.*/
LPC_SC->PCONP |= (1<<22); //Power-Up Timer2 module. It is disabled by default.
LPC_SC->PCLKSEL1 |= (1<<12); //Set bits[13:12] = [01] to select PCLK_TIMER2 = CCLK i.e. 100Mhz in our case.
LPC_PINCON->PINSEL0 |= (1<<9) | (1<<8); //Set Bits[9:8] = [11] to Select CAP2.0 for P0.4
LPC_TIM2->CTCR = 0x1; //Increment TC on rising edges of External Signal for CAP2.0
LPC_TIM2->PR = 0; //Using lowest PR gives most accurate results
LPC_TIM2->CCR = 0x0; //Must be [000] for selected CAP input
LPC_TIM2->TCR = 0x2; //Reset & Disable Timer2 Initially
initPWM(); //To generate square wave signal
float FreqKhz = 0;
printf("OCFreaks.com - LPC1768x Frequency Counter Example 1:\n");
while(1)
{
LPC_TIM2->TCR = 0x1; //Start Timer2
delayMS(GATE_TIME_MS); //'Gate' signal for defined Time (1 second)
LPC_TIM2->TCR = 0x0; //Stop Timer2
pulses = LPC_TIM2->TC; //Read current value in TC, which contains no.of. pulses counted in 1s
LPC_TIM2->TCR = 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/lpc1768-pwm-programming-tutorial/
/*Using CCLK = 100Mhz and PCLK_PWM1 = 100Mhz.*/
//By default PWM1 block is powered-on
LPC_SC->PCLKSEL0 |= (1<<12); //Set bits[13:12] = [01] to select PCLK_PWM1 = CCLK i.e. 100Mhz in our case.
LPC_PINCON->PINSEL4 |= (1<<0); // Select PWM1.1 output for Pin2.0
LPC_PWM1->PCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
LPC_PWM1->PR = 0; //PR+1 = 0+1 = 1 Clock cycle @100Mhz = 0.01us = 10ns
LPC_PWM1->MR0 = 4; //4x0.01 = 0.04us - period duration i.e. 25Mhz Test frequency
LPC_PWM1->MR1 = 2; //2x0.01 = 0.02us - pulse duration (50% duty cycle)
LPC_PWM1->MCR = (1<<1); // Reset PWMTC on PWMMR0 match
LPC_PWM1->LER = (1<<1) | (1<<0); // update MR0 and MR2
LPC_PWM1->PCR = (1<<9); // enable PWM1.1 output
LPC_PWM1->TCR = (1<<1) ; //Reset PWM TC & PR
LPC_PWM1->TCR = (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.02us (or 20ns) is selected by using Prescaler value of 1 with PCLK=CCLK=100Mhz. We configure CCR so that the capture occurs for rising edges and an interrupt is also generated.
The main logic of this frequency counter example lies in the Timer2 interrupt handler function itself where we are measuring the period of the square wave signal. 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:
Hence, inside ISR we will have to add additional code to indicate this condition using a flag. Inevitably this will increase the interrupt latency, but never the less we will be able reject 'over-the-limit' frequencies without stalling the code inside main(). While testing the code given below I was able to measure frequencies around 833Khz or 0.83Mhz without stalling code inside main.
Source Code Snippet
/*(C) Umang Gajera - www.ocfreaks.com
LPC176x Input Capture Tutorial - Example 2 for Frequency counter using ARM KEIL
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
License: GPL.*/
#include <lpc17xx.h>
#include <stdio.h> //for printf() - https://www.ocfreaks.com/retarget-redirect-printf-scanf-uart-keil/
#include "ocf_lpc176x_lib.h" //contains initUART0(), initTimer0() & putc() to retarget printf()
void initPWM(void);
unsigned int period = 0;
unsigned int previous = 0;
unsigned int current = 0 ;
int limitFlag = 0;
#define TIMER_RES 0.02 //Depends on Timer PCLK and PR. Used to convert measured period to frequency.
int main(void)
{
//SystemInit(); //Gets called by Startup code, sets CCLK=100Mhz, PCLK=25Mhz
initUART0(); //Initialize UART0 for uart_printf() - see ocf_lpc176x_lib.c
initTimer0(); //For delayMS() - see ocf_lpc176x_lib.c
/*Using CCLK = 100Mhz and PCLK_TIMER2 = 100Mhz.*/
LPC_SC->PCONP |= (1<<22); //Power-Up Timer2 module. It is disabled by default.
LPC_SC->PCLKSEL1 |= (1<<12); //Set bits[13:12] = [01] to select PCLK_TIMER2 = CCLK i.e. 100Mhz in our case.
LPC_PINCON->PINSEL0 |= (1<<9) | (1<<8); //Set Bits[9:8] = [11] to Select CAP2.0 for P0.4
LPC_TIM2->CTCR = 0x0;
LPC_TIM2->PR = 1; //PR+1 = 1+1 = 2 clock cycles @ 100Mhz = 0.02us res
LPC_TIM2->TCR = 0x02; //Reset Timer
LPC_TIM2->CCR = (1<<0) | (1<<2); //Capture on Rising Edge(0->1) and generate an interrupt
LPC_TIM2->TCR = 0x01; //Enable timer1
NVIC_EnableIRQ(TIMER2_IRQn); //Enable TIMER2 IRQ
initPWM(); //To generate square wave
printf("OCFreaks.com - Frequency Counter Example 2\n");
while(1)
{
if(limitFlag)
{
printf("Input Frequency limit reached!\n");
NVIC_EnableIRQ(TIMER2_IRQn); //Try to measure signal frequency again
delayMS(500);
}
else
{
printf("Frequency = %0.2f Khz\n",((1.0/(period*TIMER_RES)) * 1000)); //Convert to frequency, 0.02 is Timer resolution
delayMS(500); //2 Udpates per second
}
}
//return 0; //This won't execute normally
}
void TIMER2_IRQHandler(void)
{
LPC_TIM2->IR |= (1<<4); //Clear Interrupt Flag
current = LPC_TIM2->CR0;
if(current < previous) //TC has overflowed
{
period = 0xFFFFFFFF + current - previous;
}
else
{
period = current - previous;
}
previous = current; //LPC_TIM2->CR0;
if(period < 60)
{
NVIC_DisableIRQ(TIMER2_IRQn);
limitFlag = 1;
}
else limitFlag = 0;
}
void initPWM(void)
{
//Refer: https://www.ocfreaks.com/lpc1768-pwm-programming-tutorial/
/*Using CCLK = 100Mhz and PCLK_PWM1 = 100Mhz.*/
//By default PWM1 block is powered-on
LPC_SC->PCLKSEL0 |= (1<<12); //Set bits[13:12] = [01] to select PCLK_PWM1 = CCLK i.e. 100Mhz in our case.
LPC_PINCON->PINSEL4 |= (1<<0); // Select PWM1.1 output for Pin2.0
LPC_PWM1->PCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
LPC_PWM1->PR = 1; //PR+1 = 1+1 = 2 Clock cycles @100Mhz = 0.02us
LPC_PWM1->MR0 = 80; //80x0.02 = 1.6us - period duration i.e. 625Khz Test frequency
LPC_PWM1->MR1 = 40; //40x0.02 = 0.8us - pulse duration (50% duty cycle)
LPC_PWM1->MCR = (1<<1); // Reset PWMTC on PWMMR0 match
LPC_PWM1->LER = (1<<1) | (1<<0); // update MR0 and MR2
LPC_PWM1->PCR = (1<<9); // enable PWM1.1 output
LPC_PWM1->TCR = (1<<1) ; //Reset PWM TC & PR
LPC_PWM1->TCR = (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 LPC_PWM1->MR0 and LPC_PWM1->MR1 to measure other lower frequencies. The program will reject frequencies above 833.3 Khz.
Download Example 2 Project Files: