Sunday, February 7, 2010

Arduino Digital Pulse Counter

Measuring heart rate per minute is a part of my academic project for which I had to count digitally high pulses coming from the sensor. Having a Sanguino board (ATMEGA664P based) which is an Arduino variant, counting became easy with the peripherals like timers and interrupts.

Googling for arduino pulse counter mainly yielded results from forums which was of some use, but not satisfied which lead to this post with a generalized pulse counter. This has numerous applications. One quick example could be measuring RPM of a motor.

Here pulse is given as external interrupt and counted meanwhile a timer takes care of measuring interval.Knowledge of timers and interrupts in AVR architecture is required.AVRFreaks has an excellent tutorial on AVR timers.One of the best I would say.

Plunging into code directly :

It can be downloaded here.(remove the .txt to open in arduino IDE)

/***
File : Pulse.pde (opens in arduino IDE www.arduino.cc)
Description : Counts digital pulse on External Interrupt 0 for 1 minute and prints the value serially
Date : 6th February 2010
Author : Kishore Sheik Ahamed M R
Contact : kishoreinme@gmail.com
***/


int iTestPin=13; //Pin which generates test pulses, this is not needed when actual pulse is available for measurement
int iPulsePin = 10; //Pin serving as external interrupt (INT 0) to count the pulses
static int siPulseCounter=0; //Variable keeping track of pulse counts
static int siSecondCount=0; //Variable keeping track no. of seconds in the timer1 block

void SetupTimer1() //Setting up timer1 with appropriate register values
{

TCCR1A = 0; // No PWM is used.So set this to zero.
TCCR1B = 1<<CS12 | 0<<CS11 | 0<<CS10; // Input clock is set to F(cpu)/256
TIMSK1 = 1<<TOIE1; // Enable timer interrupt to detect overflow
/***
Set the timer count to overflow to in 1 second.
My board has a 16MHz oscillator.
Reload Timer Value = ((2 ^ Bits) - 1) - ((Input Frequency / Prescale) / Target Frequency)
= 65535 - ((16 x 10 ^6 Hz)/256)/1 Hz
= 65535 - 62500
= 3035
***/
TCNT1=0x0bdb; //3035 in hex
}


ISR(TIMER1_OVF_vect) // Timer1 ISR when it overflows
{

TCNT1=0x0bdb; //Reset Timer1 to count another second
siSecondCount++; //Increase second count
if(siSecondCount>=60) //If 60 Seconds are attained Print the Pulse count to serial port.
{

Serial.print("Pulse Count is ");
Serial.print(siPulseCounter,DEC); //Print as decimal value
siPulseCounter=0; //Reset counter values for next minute cycle
siSecondCount=0;
}
}

void setup(void)
{


pinMode(iPulsePin, INPUT); // Set Pulsepin to accept inputs
digitalWrite(iPulsePin, HIGH);
pinMode(iTestPin, OUTPUT); // Test signal generator pin as output. Can be ignored if the actual digital signal is available
attachInterrupt(0, count, RISING); // Caputres external interrupt 0 at iPulsepin at rising edge and calls funtion count defined below
Serial.begin(9600); // Begin Serial communication at baudrate 9600 bps
SetupTimer1(); // Call Setuptimer1 function
Serial.println(" Initialising, Please wait...");

}

void loop(void)
{


/*** Following 4 lines just generate pulses for testing purposes.
All the 4 lines Can be ignored if the actual digital signal is available ***/

digitalWrite(iTestPin, HIGH); // sets the iTestPin ON
delay(1000); // waits for a second
digitalWrite(iTestPin, LOW); // sets the iTestPin off
delay(1000); // waits for a second



}


void count() // Function called by AttachInterrupt at interrupt at int 0
{

siPulseCounter++; //increment the pulse count

}


/* End of Pulse.pde */





Now lets dissect the code.




int iTestPin=13;              //Pin which generates test pulses,actually not needed
int iPulsePin = 10; //Pin serving as external interrupt (INT 0) to count
static int siPulseCounter=0; //keeping track of pulse counts
static int siSecondCount=0; //keeping track no. of seconds





External Interrupt zero(0) is a pin numbered 10 in my Sanguino which counts the pulses, and Pin 13 genrates test pulses to count in the absence of actual pulses.



Seperate counters for keeping track of pulse count and time are declared static for unique use through out the program.



Next, Timer is set.





void SetupTimer1() //Setting up timer1 with appropriate register values
{

TCCR1A = 0; // No PWM is used.So set this to zero.
TCCR1B = 1<<CS12 | 0<<CS11 | 0<<CS10; // Input clock is set to F(cpu)/256
TIMSK1 = 1<<TOIE1; // Enable timer interrupt to detect overflow
TCNT1=0x0bdb;
}







The interval 1 minute to be measured is actually a very loooooooonnngg time for a chip running at 16MHz. Thus the timer is calibrated such that it counts 1 sec which is executed 60 times. 16 bit long Timer1 is suitable for this purpose



If you lack knowledge on timers , please refer to the tutorial referenced above.Firstly the timer1 is given a prescalar of 256 so that it approximately yielded 1.04 secs for a full run from 0x0000 to 0xFFFF.



This is achieved by setting prescalar in TCCR1B as 0100 for CS12 CS11 and CS10 and loading the Timer count with value 0xbdb in hex or 3550 in decimal which is calculated as follows:



Timer Value = ((2 ^ Bits) - 1) - ((chip Input Frequency / Prescale) / Target Frequency )

= (2^16-1) - ((16 x 10 ^6 Hz)/256)/1 Hz


= 65535 - 62500


= 3035.



The Timer1 interrupt is enabled by setting TOIE1




ISR(TIMER1_OVF_vect)  // Timer1 ISR when it overflows
{

TCNT1=0x0bdb; //Reset Timer1 to count another second
siSecondCount++; //Increase second count
if(siSecondCount>=60) //If 60 Seconds are attained Print the Pulse count to serial port.
{
Serial.print("Pulse Count is ");
Serial.print(siPulseCounter,DEC); //Print as decimal value
siPulseCounter=0; //Reset counter values for next minute cycle
siSecondCount=0;
}
}





Here comes the Timer1 ISR (Interrupt service Routine).What does it do? Simple. After every second,the timer1 overflows and generates an interrupt which increments the second counter by 1. If it is equal to 60, the static pulse count is printed serially and counting variables are reset.




void setup(void)
{
pinMode(iPulsePin, INPUT); // Set Pulsepin to accept inputs
digitalWrite(iPulsePin, HIGH);
pinMode(iTestPin, OUTPUT); // Test signal generator pin as output. Can be ignored if the actual digital signal is available
attachInterrupt(0, count, RISING); // Caputres external interrupt 0 at iPulsepin at rising edge and calls funtion count defined below
Serial.begin(9600); // Begin Serial communication at baudrate 9600 bps
SetupTimer1(); // Call Setuptimer1 function
Serial.println(" Initialising, Please wait...");

}





Straight forward arduino code to set I/O ports and serial communication except attachInterrupt(0, count, RISING);



This code just capture external interrupt zero (pin 10 as declared above) at the rising edge of pulse applied and calls a user defined funtion named count().




void loop(void)
{
digitalWrite(iTestPin, HIGH); // sets the iTestPin ON
delay(1000); // waits for a second
digitalWrite(iTestPin, LOW); // sets the iTestPin off
delay(1000); // waits for a second
}





The above lines just generate pulses for testing purposes.This loop can be empty if actual signal is available.




void count()   

siPulseCounter++; //increment the pulse count

}






This is the count() function mentioned above for the AttachInterrupt code.Its almost the ISR for the interrupt incrementing a counter each time when a pulse occurs and there ends the code.Connect the test pulse generating pin and the interrupt pin physically.



The point to be noted here is if the test signal generation code is removed, all actions happen just by hardware interrupts or in other words the code does not demand processors attention every time (expect the initializations) which can be utilized for any other purpose.



Hope the post was useful. cheers :-)

10 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi, I tried out your code, but all I get is the message "Pulse count is 0" repeated every minute, so it seems that the test pulse is not getting to the pulse pin. You don;t mention any physical link between pin 10 and pin 13; is this required?

    ReplyDelete
  3. Right I've found that if I attach pin 2 (interrupt 0) to pin 13 (test pin) I can see the test function working. If this is what you intended, it would probably help to add it to the text of your blog above.

    ReplyDelete
  4. @Headboy99 :

    there should be a physical connection n thanks for mentioning it...i hav added it to the post

    ReplyDelete
  5. I am not familiar with physical connection. For me also it showing pulse count 0. How to connect it?
    best website hosting

    ReplyDelete
  6. What a fantastic post regarding Digital Timer. I like it a lot and the detail given is also very particular and help me a lot to know more about it. We also supply Digital Timer and you can contact us at Digital Timer .

    ReplyDelete
  7. Thanks a lot , God bless you brother.

    ReplyDelete
  8. Using a portable Fluke device will enable you to measure the values of current at frequent intervals and take necessary action.www.themultimeterguide.com

    ReplyDelete