Example 2 : Timer1 Input Capture Interrupt

The 16-bit timer1 is a synchronous timer. This means that it is clocked by the system clock, a prescaled system clock or an external clock which is synchronized with the system clock. To ensure that the 16-bit registers of the Timer1 are written and read simultaneously, a temporary register (Temp) is used. This makes it necessary to access these registers in a specific order. See the application note “AVR072: Accessing 16-bit I/O Registers” and the device datasheet for details. The correct way to access the registers is shown in following table.
Table 1. Accessing 16-bit Registers
Operation 1st Access 2nd Access
Read Low Byte High Byte
Write High Byte Low Byte
According to this, a read operation of a 16-bit register can look like this:
lds	r16,TCNT1L
lds	r17,TCNT1H

A write operation to this register has to access the registers in the opposite order:
sts	TCNT1H,r17
sts	TCNT1L,r16

The C Compiler automatically handles 16-bit I/O read and write operations in the correct order.

This example will show the implementation of a very simple use of the input capture event and interrupt. The port pin PB0 is the input capture pin (ICP1). If the value of this pin changes, a capture will be triggered; the 16-bit value of the counter (TCNT1) is written to the Input Capture Register (ICR1).

Measurement of an external signal’s duty cycle requires that the trigger edge is changed after each capture. Changing the edge sensing must be done as early as possible after the ICR1 Register has been read. This is to make sure that the change in sense configuration is done before the next falling edge occurs. After a change of the edge, the Input Capture Flag (ICF1) must be cleared by software (writing a logical one to the I/O bit location). For measuring frequency only, the clearing of the ICF1 Flag is not required (if an interrupt handler is used).

The following initialization routine shows how to set up such a system:

init_Ex2:
	ldi r16,(1<<CS11)|(1<<CS10)
	sts TCCR1B,r16	; timer clock = system clock/64
	ldi r16,1<<ICF1
	out TIFR1,r16	; Clear ICF1/clear pending interrupts
	ldi r16,1<<ICIE1
	sts TIMSK1,r16	; Timer/Counter1 Capture Event Interrupt
	ldi r16,1<<ICNC1
	sts TCCR1B,r16  ; Enable noise canceler
	cbi DDRB,PORTB0	; Set PB0/ICP1 as input
	ret

The corresponding C code looks like this:
void init_Ex2(void)
{
	/* Timer clock = I/O clock / 64 */
	TCCR1B = (1<<CS11)|(1<<CS10);
	/* Clear ICF1. Clear pending interrupts */
	TIFR1   = 1<<ICF1;
	/* Enable Timer 1 Capture Event Interrupt */
	TIMSK1  = 1<<ICIE1;
} 

In the next step, the interrupt service routine has to be implemented. This routine will be executed with every input capture event. The ISR will toggle PB5 on each capture event and cleat TCNT register before for next measurement.
ISR_TIM1_CAPT:
	push r16
	in r16,SREG
	push r16
	clr r16
	sts TCNT1H,r16	; Write Temp register
	sts TCNT1L,r16	; Clear the 16 bit register
	call TOGGLEPIN
	pop r16
	out SREG,r16
	pop r16
	reti

The corresponding C code looks like this:
ISR (TIMER1_CAPT_vect)
{
	/* Toggle a pin after input capture */
	PORTB  ^= (1 << USER_LED);
	/* Clear counter to restart counting */
	TCNT1 = 0;
}
Note: This implementation has one disadvantage: A timer overflow is not detected. Thus, if the duty cycle of the wave has a longer period than what can be covered the 16-bit counter, an overflow will happen before the next edge occurs. A global variable which is set in a timer overflow ISR can be used to sense whether a timer overflow has happened before the capture is performed. If this variable is set, then the effective capture value will be (0xFFFF + contents of ICR1).