EmbeddedRelated.com
Memfault Beyond the Launch

LED Blinker Using a Timer/Counter

Stephen Friederichs April 2, 2013 Coded in C for the ATMega328P

This code uses a 16-bit hardware timer/counter to generate the timing to blink an LED at 1Hz. The code assumes a clock frequency of 8MHz.  The code assumes the LED is on Port D, pin 7.

/**@file timer_blinker.c
   @brief A more advanced LED blinker using a timer
   @author Stephen Friederichs
   @date 3/28/13
   @note This code assumes that the LED is active high (pin sources current)   
*/   

/**@def F_CPU
   @brief Clock frequency = 8MHZ - this is set by fuses and registers, not by this define
   @note Always define this before including delay.h!
*/
#define F_CPU 8000000
	
/**@include io.h
   @brief Include for AVR I/O register definitions
*/
#include <avr/io.h>

/**@include stdint.h
   @brief Include for standard integer definitions (ie, uint8_t, int32_t, etc)
*/
#include <stdint.h>

/**@include delay.h
   @brief Include for delay functions such as _delay_ms() and _delay_us()
*/
#include <util/delay.h>

/* Basic bit manipulation macros - everyone should use these.  Please, steal these! Don't not use them and
 don't rewrite them yourself!
*/
#define SET(x,y) x |= (1 << y)
#define CLEAR(x,y) x &= ~(1<< y)
#define READ(x,y) ((0x00 == ((x & (1<<y))>> y))?0x00:0x01)
#define TOGGLE(x,y) (x ^= (1<<y))

int main(void)
{
	/*Initialization Code*/
	
	/*	ATMega328 Datasheet Table 14-1 Pg 78
		Configure PD7 for use as Heartbeat LED
		Set as Output Low (initially)
	*/
	SET(DDRD,7);	//Direction: output
	CLEAR(PORTD,7);	//State: Lo

	/*	TCCR1A - ATMega328 Datasheet Section 16.11.1 pg 132
		No waveform generation is required on this timer, so set all
		ports to normal operation
	*/	
	TCCR1A = 0x00;
		
	/*	TCCR1C - ATMega328 Datasheet Section 16.11.3 pg 135 
		This register is only used for output compare.  
		There's no output compare in this application so this can be all 0's
	*/
	TCCR1C = 0x00;
		

	/*	TCCR1B
		
		Note: I've disabled the CKDIV8 fuse so that the clock source is 8MHz
	
		ATMega328 Datasheet Section 16.11.2 pg 134 - TCCR1A
		No input capture used - bits 7:6 are 0
		No waveform generation used - bits 4:3 are 0
		Clock source select is bits 2:0 but are not yet set - wait until the 
		main loop is ready to start
		
		As per ATMega328 Datasheet Section 16.9.1 page 123, setting the timer 
		to Normal mode causes the counter to count up until it reaches 0xFFFF
		at which point it will overrun and start back at 0.  To configure this
		timer/counter to produce a period of 500ms we need to start counting 
		at a value that causes it to reach 65535 in 500ms. 
		
		What is that value?

		With a clock prescaler of 256 each count of the timer is roughly
		(1/8MHz)*256 = 32uS
		500ms / 32us /tick = 15625 ticks /500ms
		The counter counts up to 65535, so to determine what value we have to
		start at we subtract 15635 from 65536:
		65536-15625 = 49910
	*/

	#define TIMER1_PERIOD 49910
		
	TCNT1 = TIMER1_PERIOD;

	/*	Flash the LED for a second to show that initialization has successfully 
		occurred
	*/
	SET(PORTD,7);
	_delay_ms(1000);
	CLEAR(PORTD,7);
	
	/*	Start the timer/counter
		ATMega328 Datasheet Section 16.11.2 Pg 135 - TCCR1B
		No Waveform generation: bits 4:3 = 0
		No input capture: bits 7:6 = 0
		Clock select: ClkIO/256 - bits 2:0 = 100b = 0x04
	*/	
	TCCR1B = 0x04;	//This starts the counter/timer
	
	while(1)
	{
		/*	Handle the Heartbeat LED
			When the timer/counter reaches 65535 the 500ms period will have 
			elapsed and TIFR1 bit 1 will be '1'
		*/
		if(READ(TIFR1,0))
		{
			/*	ATMega328 Datasheet Section 16.11.9 pg137
				Setting TIFR1 bit 1 clears the overflow flag 
			*/
			SET(TIFR1,0);	
				
			/*	Toggle the LED to flash it at 1Hz*/ 
			TOGGLE(PORTD,7);
			
			/*	Reload the timer/counter count value to the previous value
				so that the period remains the same
			*/
			TCNT1 = TIMER1_PERIOD;
		}
		
		/*	Now you can do useful work here - no delay loops!*/
	
	}

}

Memfault Beyond the Launch