Pages: [1]   Go Down
Author Topic: PulseIn don't work well  (Read 2370 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi,
I have Arduino Duemilanove connecteed to PC. And Pulse Generator 8110A connected to PIN7.
Generator is set up to: Periode 1ms, Impuls width 60us, LeadEdg and TrailEdg 10ns, Amplitude 5V.

I use code below:
---------- ---------- ---------- ---------- ----------
int pin = 7;
unsigned long duration;

void setup()
{
  Serial.begin(256000);
  pinMode(pin, INPUT);
}

void loop()
{
  duration = pulseIn(pin, HIGH);
  Serial.println(duration);
}
---------- ---------- ---------- ---------- ----------
But results are very bad:
example: 60 60 60 60 58 60 60 60 53 53 58 60 60 60 60 60 60 60 60 60 60 58 60 60 60 60 60 60 60 60 60 60 58 60 60 60 60 60 60 60 60 60 60 58 60 60 60 57 53 53 60 60 60 60 60 60 58

Any idea, please?????

Logged

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 126
Posts: 8471
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Does Serial work that fast?

Maybe one end is not quite handling the speed.

______
Rob
Logged

Rob Gray aka the GRAYnomad www.robgray.com

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17259
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset



I saw basically the same kind of variations when using the micro() function to form a simple interrupt driven frequency counter. My gut says we are both just seeing the basic +/- timing resolution limits of 4us for this timing function used in the arduino. From the reference for micro() :

Quote
Returns the number of microseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 70 minutes. On 16 MHz Arduino boards (e.g. Duemilanove and Nano), this function has a resolution of four microseconds (i.e. the value returned is always a multiple of four).

My code:

Quote
// frequency counter
// Uses pin 2 interrupt to measure frequency of signal input
// Note micros() function has a 4 us resolution so 1000hz can be 996.02 or 1,000 at 1,000
// retrolefty 2/4/11

   
volatile unsigned long isrPeriod;
volatile unsigned long start_time;
volatile unsigned long timestamp;
volatile byte first = 1;
unsigned long speed;


void setup() {

  Serial.begin(57600);
  Serial.println ("Frequency counter starting");     // signal initialization done
  attachInterrupt(0, countP, RISING);
  delay(100);

  }  // End of setup


void loop() {
  delay(100);
  noInterrupts();
  long period = isrPeriod;
  interrupts();
  float freq = 1/(float(period) * .000001);
  speed = long(freq);
  Serial.print("Freq =  ");
  Serial.print(freq);
  Serial.print("   ");
  Serial.print(speed);
  Serial.println("  Hz.");
  delay(100);
 
}  // end of loop

void countP()
 {
   timestamp = micros();
   if (first)
    {
      start_time = timestamp;
      first = 0;
    }
    else
    {
      isrPeriod = timestamp - start_time;
      first = 1;     
    }
 }


Lefty
Logged

Offline Offline
Newbie
*
Karma: 1
Posts: 3
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The code for pulseIn from the wiring_pulse.c file in the Arduino library reads as follows:
Code:
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
// cache the port and bit of the pin in order to speed up the
// pulse width measuring loop and achieve finer resolution.  calling
// digitalRead() instead yields much coarser resolution.
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
uint8_t stateMask = (state ? bit : 0);
unsigned long width = 0; // keep initialization out of time critical area

// convert the timeout from microseconds to a number of times through
// the initial loop; it takes 16 clock cycles per iteration.
unsigned long numloops = 0;
unsigned long maxloops = microsecondsToClockCycles(timeout) / 16;

// wait for any previous pulse to end
while ((*portInputRegister(port) & bit) == stateMask)
if (numloops++ == maxloops)
return 0;

// wait for the pulse to start
while ((*portInputRegister(port) & bit) != stateMask)
if (numloops++ == maxloops)
return 0;

// wait for the pulse to stop
while ((*portInputRegister(port) & bit) == stateMask)
width++;

// convert the reading to microseconds. The loop has been determined
// to be 10 clock cycles long and have about 16 clocks between the edge
// and the start of the loop. There will be some error introduced by
// the interrupt handlers.
return clockCyclesToMicroseconds(width * 10 + 16);
}

pulseIn(pin,HIGH) runs a loop to wait for the pin to go low, then another to wait for it to go HIGH again, and a third to measure the amount of time it is HIGH for (and waits for a low).

It measures this time by counting the iterations of the last loop and multiplying it by the cycles per loop.

This is probably as good as you'll get from a software timer, but it is error prone and makes the CPU freewheel while it waits for the pulse (correct me if i'm wrong on that).

Your code looks correct.  It might not catch every pulse because some will go off while Serial.print does it's thing (Serial.print statements are expensive).  If you want to know exactly how many clock cycles  you pulses take, you use hardware interrupts and an interrupt service routine.  It's daunting at first, but I'm a microcontroller newbie and I used it for some sonars.

Here is some info I copied from a project report of mine
The Arduino Uno with the ATmega328p micro chip has a 16-bit timer, accessible through output pin 8 (pin 14 on the chip).  This timer can be set to increment, from 0x0000 to 0xFFFF, once every 1, 2, 8, 64, 256 or 1024 clock cycles.  By setting the right bits in certain registers, the chip will accept “interrupts” on pin 14, in the form of either a voltage drop or rise (called falling and rising edges), at which point, the contents of the timer register are copied into the input capture register (ICR1).  The interrupt can also trigger a software routine, so that ICR1 can be used immediately.

These registers and the bits that can be set are very carefully described in the long, 566 page version of the atmega328p specifcations at http://www.atmel.com/dyn/resources/prod_documents/doc8271.pdf, so we will focus on how to find the relevant information in that document (a summary of this document is at http://www.atmel.com/dyn/resources/prod_documents/8271S.pdf).  The related specifications of for the Seeeduino’s processor are available from the same website.

The registers relevant to the 16-bit timer are as follows:

    * TCNC1.  This is a 16-bit timer register.
    * TCCR1B: Timer/Counter Control Register B.  This register has flag-bits which determine the behaviour  of the Timer and Input Capture behaviour.  These bits are named as follows:
          o ICNC1:  Input capture noise canceller.  This measures the voltage input at pin 14 on four consecutive clock cycles to ensure that a detected edge is not noise.  It is not always necessary to set this (to 1).
          o ICES1:  When this is set to 1, an interrupt is triggered on a rising edge, and otherwise on a falling edge.
          o CS12, CS11, CS10:  These bits control the frequency with which the timer, TCNC1 is incremented (prescaling).
    * TCCR1A: We set this to 0 to override an initialization by the Arduino library which uses this timer for PWM.
    * ICR1:  This is the register to which TCNC1 is copied to when an interrupt is signalled.
    * TIMSK1:  Timer/Counter1 Interrupt Mask Register.  Setting the ICIE1 bit to 1 enables interrupt capture for timer 1.


This configuration works as follows for the Arduino Uno.  On the ATmega328p, ICP1 pin is called PB0 (14), which is connected to output pin 8 of the Arduino Uno.  On the Seeeduino Mega, ICP1 is available, but not as an Arduino Mega pin.  Instead, there is a direct connection to it, at PD4.  PD4 is an input pin by default, but in case it needs to be accessed, it’s mode can be set with
 //set PD4 pin to input.
 DDRD &= ~_BV(4);
 //set PD4 pin to low
 //PORTD &= ~_BV(4);
 //set PD4 pin to high
 //PORTD |= _BV(4);

Last but not least, the following should be included for these things to work.  
#include <avr/io.h>
#include <avr/interrupt.h>

You can run the following code (this does compile as an Arduino sketch, but I have not tested it.  It may also contain errors, since I don't have an arduino with me right now):

Code:
#include <avr/io.h>
#include <avr/interrupt.h>

//this works for the Uno and probably the demilanove.
//there may only be one such pin on your board, see above on
//how to find it
#define TIMERINTERRUPTPIN 8
int onHigh = -1;
int inPulse = 0;

void setup(){
//enable interrupts
  sei();
  //irrelevant when subtracting gaps
  //  TCNT1 = 0; //initial timer value
  //ICIE1 is the input capture flag
  TIMSK1 = _BV(ICIE1); // enable input capture interrupt for timer 1
  //prescaler of 64
  TCCR1B |= _BV(CS10) | _BV(CS11);
  //start timer on rising edge,
  TCCR1B |= _BV(ICES1);
  TCCR1A = 0;//stops TCCR1A from being used as a pwm pin

  //not needed
  //TCCR1B |= _BV(ICNC1);//noise cancellor bit.  adds 4 cycles to ICR1
}

//do these need to be volatile?
//the size, uint16_t is because we are reading from a 16-bit register
volatile uint16_t rise = 0;
volatile uint32_t gap = 0;


void loop(){
  //you'll need to do a little math to get gap to be a useful number.
  //this is a combination of the clock speed, 16mHz and the prescaler 64.
  //So I think the conversion to clock cycles is gap*64.  So the number of microseconds
  //is gap*64/16.  Be careful not to exceed the size of the type (that's why gap is a
  //uint32_t instead of uint16_t
  Serial.print(gap*64/16);  
  
}

ISR(TIMER1_CAPT_vect){
  if(onHigh == -1){//initial value of onHigh, only here when we are on a rise
    onHigh = 1;
    //skip this pulse, wait for a fall
    TCCR1B &= ~_BV(ICES1);
  }else if(onHigh == 1 && inPulse == 0){
    //we are on HIGH, so this must be the end of that HIGH.
    onHigh == 0;
    //wait for a rising edge
    TCCR1B |= _BV(ICES1);
  }else if(onHigh == 0 && inPulse == 0){
    //now we are starting a pulse we want to measure.  record the time
    onHigh = 1;
    inPulse = 1;
    //and wait for a falling edge
    rise = ICR1;
    TCCR1B &= ~_BV(ICES1);
  }else if(onHigh == 1 && inPulse == 1){
    //we were on HIGH and in the pulse, so this must be the falling edge.
    //record the time, measure the gap.  make sure you use "gap" before it is overwritten
    onHigh = 0;
    inPulse = 0;
    gap = ICR1-rise;
    //wait for a rising edge
    TCCR1B |= _BV(ICES1);
  }
}


Cheers,
Alejandro
http://alejandroerickson.com
http://tomokupuzzle.com
« Last Edit: February 12, 2011, 06:00:43 pm by alejandroerickson » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12740
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

@alejandroerickson...

Thank you for the post.  Two things about the code...

1. rise does not need to be volatile.  There is no harm in having it volatile but there is probably a small performance improvement removing volatile.

2. In loop, gap should only be accessed with interrupts disabled.
Logged

Offline Offline
Newbie
*
Karma: 1
Posts: 3
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

noted.  thanks for the tips!

my thought would be run loop for a while and gather "gap"s in a buffer.  when the buffer is full, report the values and quit.  that way you wouldn't risk missing as much stuff while you are printing "gap" and disabling interrupts.

I guess in the code I posted, you would do

Code:
cli();//disable interrupts
uint32_t printgap = gap;
sei();//enable interrupts
Serial.print(printgap);
« Last Edit: February 12, 2011, 06:58:19 pm by alejandroerickson » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 197
Posts: 12740
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Exactly.
Logged

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17259
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Or you could use the Arduino predefined way:

Quote
noInterrupts();  //disable interrupts
uint32_t printgap = gap;
interrupts();     //enable interrupts
Serial.print(printgap);

Lefty
Logged

Timisoara
Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hey guys,
   Alejandro, thx for the code and explanation, i'm integrating it in a school project, just one question, can your code be addapted for pulses from HALL sensors?, and measure the time between pulses?
Logged

Pages: [1]   Go Up
Jump to: