Pages: [1]   Go Down
Author Topic: [solved] pulseIn trouble when reading square wave  (Read 822 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello to you all!

Let me start by stating that I'm a complete beginner to the Arduino platform and sadly only a novice in programming. My question to you all lies in my problem with getting the pulseIn function to read a complete low every time.

I have a personal hobby project with 4-pin PC fans that I control with a 25 kHz PWM created by altering Timer2 (which affects PWM pin 3 and 11) as following (per discussion in http://arduino.cc/forum/index.php/topic,18742.0.html):

Code:
TCCR2A = 0x23;
TCCR2B = 0x09;  // select clock
OCR2A = 79;  // aiming for 25kHz
OCR2B = 25;  // set the PWM duty cycle

This works great, fans are completely controllable and no high-pitch "clicking" sounds as when trying the default PWM speed (although it works).

My trouble lies in the following code:
Code:
    while(pulseDuration[i] < 1000 && retries < maxRetries)
      {
        digitalWrite(ledPin, HIGH); // For indication on oscilloscope
        pulseDuration[i] = pulseIn(fanPulse[i], LOW, 100000);
        digitalWrite(ledPin, LOW); // For indication on oscilloscope
        retries++;

        if (pulseDuration[i] != 0 && pulseDuration[i] < 1000)
          delay(5);
          
        if (pulseDuration[i] == 0)
          retries = 9999;
      }

As you can see I have a pin that turns on a LED when using pulseIn to find the length of the low part of my square wave. Hanging on an oscilloscope on both the "fanPulse"-pin and the "ledPin" I can try and make out the pulseIn time.

The retries variable is for me to see how many times it fails to give me a "good" read from the pulse, since every low has a length of ca 11000 uS with the above set duty cycle.

Here is my oscilloscope readings from where it fails two times and gives a "good" read the third time. This varies and sometimes it doesn't manage to give a "good" read in even 10 tries.
Purple is the tach from the fan and blue is the ledPin that starts right before pulseIn and end right after.


Am I approaching this the wrong way? I have, as you can see in the code, tried adding a slight delay to the pulseIn iterations to see if it helps skewing the start of reading but to no apparent avail.
Have I misunderstood the explanation from the reference http://www.arduino.cc/en/Reference/PulseIn that it waits for it to change to desired state and then times it?
It seems to me from the first reading that it starts in the middle of a LOW and ends at the beginning of a HIGH, which isn't really as I understood the reference :/

When increasing the duty cycle to 79 (100%), it seems to never miss a read, so I'm suspecting it has something to do with the timer2, but isn't the timer2 still set differently than default even though it's 100% duty cycle?

Complete code from my project is here:
Code:
const int controlPin = 3;
const int ledPin = 13;

int ledState = LOW;             // ledState used to set the LED
int ledStatus = LOW;
long previousLedMillis = 0;
long previousFanMillis = 0;

int fanPulse[] = {2, 4, 7, 8, 12};

unsigned long pulseDuration[] = {0, 0, 0, 0, 0};
unsigned long revTime[] = {0, 0, 0, 0, 0};

void setup()
{
  Serial.begin(115200);
  TCCR2A = 0x23;
  TCCR2B = 0x09;  // select clock
  OCR2A = 79;  // aiming for 25kHz
  OCR2B = 79;  // set the PWM duty cycle
  pinMode(controlPin, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)
  pinMode(ledPin, OUTPUT);
  digitalWrite(fanPulse[0], HIGH);
  digitalWrite(fanPulse[1], HIGH);
  digitalWrite(fanPulse[2], HIGH);
  digitalWrite(fanPulse[3], HIGH);
  digitalWrite(fanPulse[4], HIGH);
}

void ledBlink()
{
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousLedMillis > 1000) {
    // save the last time you blinked the LED
    previousLedMillis = currentMillis;  

    if (ledStatus == HIGH) // check to see if led should blink
    {
      // if the LED is off turn it on and vice-versa:
      if (ledState == LOW)
        ledState = HIGH;
      else
        ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

void readPulse(int dutyCycle)
{
  int retries = -1;
  int maxRetries = 10;
  unsigned long fanCycle[] = {0, 0, 0, 0, 0};

  unsigned long currentMillis = millis();
 
  if(currentMillis - previousFanMillis > 10000) {
    // save the last time you blinked the LED
    previousFanMillis = currentMillis;  

//    Serial.print("Fan | Rev. time (uS) | RPM     duty cycle: ");
    Serial.print("Fan | pulseIn value  | RPM     duty cycle: ");
    Serial.print(dutyCycle);
    Serial.println(" %");
    Serial.println("---------------------------");
  
    for (int i=0; i <= 4; i++)
    {
      while(pulseDuration[i] < 1000 && retries < maxRetries)
      {
        digitalWrite(ledPin, HIGH); // For indication on oscilloscope
        pulseDuration[i] = pulseIn(fanPulse[i], LOW, 100000);
        digitalWrite(ledPin, LOW); // For indication on oscilloscope
        retries++;

        if (pulseDuration[i] != 0 && pulseDuration[i] < 1000)
          delay(5);
          
        if (pulseDuration[i] == 0)
          retries = 9999;
      }
      
      if (pulseDuration[i] == 0)
        ledStatus = HIGH;
  
      revTime[i] = pulseDuration[i]*4;
    
      Serial.print(i);
      Serial.print("   |      ");
      Serial.print(pulseDuration[i]);
      Serial.print("        | ");
      if (9999 > retries > 0)
      {
        Serial.print(60000000/revTime[i]);
        Serial.print(" (retried ");
        Serial.print(retries);
        Serial.println(" times)");
      }
      else if (retries == 9999)
      {
        Serial.println("FAILED!");
      }
      else
      {
        Serial.println(60000000/revTime[i]);
      }
  
      retries = -1;
      pulseDuration[i] = 0;
    }
    Serial.println();
  }
}

void loop()
{
  OCR2B = 20;
  readPulse(OCR2B*100/79);
  ledBlink();
}

To summarize it, I'm not getting the expected reads from pulseIn, and I'm not really sure why as it seems the square wave is (almost) perfect.

My sincere thanks for any idea or input on the matter at hand.

-- Marcus
« Last Edit: November 25, 2012, 07:22:44 am by greem » Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Can you explain your objective? You are measuring the square wave to what purpose? Aren't you generating it anyway?
Logged

0
Offline Offline
Shannon Member
****
Karma: 160
Posts: 10416
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It looks like it works, you must have glitches on those signals I think (or have them connected to the wrong pins?)

I'd always make long constants explicitly long BTW, such as 100000L  (L for long) - though here the context allows
the compiler to get it right.
Logged

[ I won't respond to messages, use the forum please ]

Poole, Dorset, UK
Offline Offline
Edison Member
*
Karma: 25
Posts: 1872
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Two ideas - one Serial is not instant and may make a mess of your timing, you could try taking a batch of readings and then printing then all together.

Two - use a second arduino to act as "scope". DuaneB's blog has an example init some where http://rcarduino.blogspot.co.uk/


Mark
Logged

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

Can you explain your objective? You are measuring the square wave to what purpose? Aren't you generating it anyway?

Hi and thank you for your answer,

I'm controlling the speed of five PC fans that each have a tach output. I get two pulses per revolution and that is what I'm measuring.
The main purpose of the measurement is to make sure that fans haven't stopped, and the second purpose is to calculate the RPM of the fans.
I'm in control of the square waves frequency by the PWM output, but I'm not generating it.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm in control of the square waves frequency by the PWM output, but I'm not generating it.

I'm not sure I understand that, but anyway you will probably find that interrupts will capture fast-changing edges better than pulseIn.
Logged

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

I'm in control of the square waves frequency by the PWM output, but I'm not generating it.

I'm not sure I understand that, but anyway you will probably find that interrupts will capture fast-changing edges better than pulseIn.

I'll try to clarify myself smiley

The fans I'm using have four pins, +12V, 0V (ground), tacho (pulses twice as the fan turns a complete revolution) and lastly the PWM pin.

I control the speed of the fans by changing the PWM duty cycle on my Arduino's pin 3, hence the pulses I'm trying to measure changes frequency.
The pulses I'm measuring are always square waves with a 50% duty cycle, but the frequency changes with the fan speed.

As for interrupts I've been trying to read up on those, and as it seems I only have two external interrupts available on my UNO, and I'm trying to measure five separate fan pulses.


It looks like it works, you must have glitches on those signals I think (or have them connected to the wrong pins?)

Perhaps it's just like you say that I have glitches in my signal, and my (cheap) oscilloscope is too slow to be able to read glitches as fast as 2-3 uS as that is what I'm getting from the pulseIn() when it "errors".

I'd always make long constants explicitly long BTW, such as 100000L  (L for long) - though here the context allows
the compiler to get it right.

I'm not really sure I'm following you here, "long constants explicitly long"? Am I declaring my variables in a wrong manner?
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

As for interrupts I've been trying to read up on those, and as it seems I only have two external interrupts available on my UNO, and I'm trying to measure five separate fan pulses.

You can use pin-change interrupts to detect changes on any pins. More info on interrupts:

http://www.gammon.com.au/interrupts
Logged

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

As for interrupts I've been trying to read up on those, and as it seems I only have two external interrupts available on my UNO, and I'm trying to measure five separate fan pulses.

You can use pin-change interrupts to detect changes on any pins. More info on interrupts:

http://www.gammon.com.au/interrupts


Thank you! Now I'm going to try and implement pin-change interrupts instead of using pulseIn() smiley
Logged

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

Using pin-change interrupts solves my issues and makes it also a lot more usable! Thank you Nick Gammon!

Here is my current (not finished) code if anyone else is trying something like this. Currently it just sets the fan PWM duty cycle and every 5 seconds or so it prints the RPM to the serial. The goal is to poll the arduino from Python and also send over temps so that the arduino regulates the fan speed accordingly.

It uses the PinChangeInt library available at http://code.google.com/p/arduino-pinchangeint/
Code:

#include <PinChangeInt.h>

boolean toggle1 = 0;
boolean ERROR = 0;
int fanPin[] = {2, 4, 7, 8, 12};

// Create a quick function that the library will call whenever your pin(s) are interrupted:
unsigned long previousFanMillis[20] = {0};
unsigned long pulseTimeMillis[20] = {0};

uint8_t interrupted_pin;

void quicfunc() {
  interrupted_pin=PCintPort::arduinoPin;

  unsigned long currentMillis = millis();
  pulseTimeMillis[interrupted_pin] = currentMillis-previousFanMillis[interrupted_pin];
  previousFanMillis[interrupted_pin] = currentMillis;

}

void setup() {
cli();//stop interrupts
 
//set timer1 interrupt at 1Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10); 
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
 
sei();//allow interrupts
 
  pinMode(2, INPUT); digitalWrite(2, HIGH);
  pinMode(4, INPUT); digitalWrite(4, HIGH);
  pinMode(7, INPUT); digitalWrite(7, HIGH);
  pinMode(8, INPUT); digitalWrite(8, HIGH);
  pinMode(12, INPUT); digitalWrite(12, HIGH);
  pinMode(13, OUTPUT); digitalWrite(13, LOW);

  PCintPort::attachInterrupt(2, &quicfunc, FALLING);
  PCintPort::attachInterrupt(4, &quicfunc, FALLING);
  PCintPort::attachInterrupt(7, &quicfunc, FALLING);
  PCintPort::attachInterrupt(8, &quicfunc, FALLING);
  PCintPort::attachInterrupt(12, &quicfunc, FALLING);

  TCCR2A = 0x23;
  TCCR2B = 0x09;  // select clock
  OCR2A = 79;  // aiming for 25kHz
  OCR2B = 20;  // set the PWM duty cycle
  pinMode(3, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)

  Serial.begin(115200);
}

void printRPM() {
  Serial.print("--------------------------duty cycle: ");
  Serial.print(OCR2B*100/OCR2A);
  Serial.println(" %");
 
  for (int i=0; i <= 4; i++)
  {
    Serial.print("Fan ");
    Serial.print(i);
    Serial.print(": ");
    if (pulseTimeMillis[fanPin[i]] != 0) {
      Serial.print(60000/(pulseTimeMillis[fanPin[i]]*2));
      Serial.println(" RPM");
    } else {
      Serial.println("FAILED!");
    }
  }
}

void printParseable() {
  for (int i=0; i <= 4; i++)
  {
    Serial.print("<fan");
    Serial.print(i);
    Serial.print("=");
    if (pulseTimeMillis[fanPin[i]] != 0) {
      Serial.println(60000/(pulseTimeMillis[fanPin[i]]*2));
    } else {
      Serial.println(0);
    }
  }
}
 
unsigned long previousLoopMillis = 0;
void loop() {

  unsigned long currentMillis = millis();
  if (currentMillis-previousLoopMillis > 5000) {
    printParseable();
    previousLoopMillis = currentMillis;
  }
 
}

ISR(TIMER1_COMPA_vect){ //timer1 interrupt 1Hz

  unsigned long currentMillis = millis();
  for (int i=0; i <= 4; i++){
    if (currentMillis-previousFanMillis[fanPin[i]] > 5000)
    {
      ERROR=1;
      pulseTimeMillis[fanPin[i]] = 0;
    }
  }

  if (ERROR) {
    if (toggle1){
      digitalWrite(13,HIGH);
      toggle1 = 0;
    }
    else{
      digitalWrite(13,LOW);
      toggle1 = 1;
    }
  }
}
Logged

Pages: [1]   Go Up
Jump to: