'Reading' PWM duty cycle

Can anyone help a simple mechanical engineer please (keep it simple please)....

I would like an analog circuit to read a PWM signal that is being used to drive a (nominally) 12v DC motor and report its duty cycle into an analog pin on the Arduino.

The PWM cycle frequency is constant during the measurement but from one run to the next it may change from say 50Hz to 200Hz.

The peak voltage of the PWM can change during the measurement from 5v to 15v but I won't be able to know its value. Current drawn by the motor will be no more than 2.5v during normal operation... typically even less and will go 'negative' when the motor goes into ovrerun.

I will be able to know when its receiving no voltage but I won't know when it's set to 100% or any other duty cycle

Is that enough info?

Any help appreciated, thank you.

Alan

unsigned long highTime pulseIn(inputPin, HIGH);
unsigned long lowTime pulseIn(inputPin, LOW);
unsigned long cycleTime = highTime + lowTime;
float dutyCycle = (float)highTime / float(cycleTime);

You may have to put in special cases for the timeouts you will get when the duty cycle is 0% or 100%.

acboother:
Current drawn by the motor will be no more than 2.5v

Just as by-the-way, current's not measured in volts. Even this civil engineer knows that :stuck_out_tongue:

JimboZA:

acboother:
Current drawn by the motor will be no more than 2.5v

Just as by-the-way, current's not measured in volts. Even this civil engineer knows that :stuck_out_tongue:

DOH! I know that but as you can see it was very late here in the UK. Current is measured in kgs in little plastic bags

acboother:
Current is measured in kgs in little plastic bags

You raisin a currant?

johnwasser:

unsigned long highTime pulseIn(inputPin, HIGH);

unsigned long lowTime pulseIn(inputPin, LOW);
unsigned long cycleTime = highTime + lowTime;
float dutyCycle = (float)highTime / float(cycleTime);



You may have to put in special cases for the timeouts you will get when the duty cycle is 0% or 100%.

So simple! If I can make it work... check out my logic in case I've messed up but...

... using 50Hz means the typical (50% duty) takes 10ms... and nearly 100% duty takes nearly 20ms... I'd have a real problem with the time out for 0% or 100% as this is where my algorithm is really working hard and has to respond quickly. I actually know 0% from the rest of the logic in the system but I don't know when 100% happens.

The rest of my sketch currently takes about 25-30ms for its entire cycle time (time around loop()) where it makes 100-400 analog readings and does some good algorithmic stuff. Adding in the time to waiting on pulse signals is going to be a significant burden where the sketch isn't doing anything useful.

I wonder if I could put the code on an interrupt so its triggered only when it gets a high pulse so I can avoid the waiting time waiting for that high pulse? At least that way the sketch is only waiting about for the low pulse to happen... unless i can then make a low pulse trigger an interrupt ... toggle the two interrupts... I wonder even more.

An analog in solution with a bit of electronics would be nice as well then I can keep my code a bit simpler if anyone still has that one tucked up their sleeve.

Cheers Alan

acboother:
I would like an analog circuit to read a PWM signal that is being used to drive a (nominally) 12v DC motor and report its duty cycle into an analog pin on the Arduino.

The peak voltage of the PWM can change during the measurement from 5v to 15v but I won't be able to know its value.

The first thing you need to do is to fix that. Feed that signal into a comparator so you have a known voltage coming out.

Feed the output of the comparator into a diode and from there into a capacitor and then to GND. You'll have to play around with the capacitor values until you find the right one. With the right capacitor the voltage at the junction between the diode and capacitor will be proportional to the PWM duty cycle.

If the PWM duty cycle doesn't vary rapidly then almost any capacitor will do (so long as it's a large enough value).

You might need a voltage divider to get the output from the capacitor into 0-5V range for the Arduino. It depends on how many volts the comparator is outputting.

Or...because you've got a microcontroller you can totally skip the analog part and just count how long it's HIGH and how long it's LOW. You're looking at relatively low frequency signals so it should be quite accurate. Just put a 22K resistor between the PWM signal and the Arduino pin, job done.

Untested code and not very elegant but something like this...

// Interrupt variables
volatile unsigned long fall_Time = 0;                   // Placeholder for microsecond time when last falling edge occured.
volatile unsigned long rise_Time = 0;                   // Placeholder for microsecond time when last rising edge occured.
volatile byte dutyCycle = 0;                            // Duty Cycle %
volatile unsigned long lastRead = 0;                    // Last interrupt time (needed to determine interrupt lockup due to 0% and 100% duty cycle)

void PinChangeISR0(){                                   // Pin 2 (Interrupt 0) service routine
  lastRead = micros();                                  // Get current time
  if (digitalRead(2) == LOW) {
    // Falling edge
    fall_Time = lastRead;                               // Just store falling edge and calculate on rising edge
  }
  else {
    // Rising edge
    unsigned long total_Time = rise_Time - lastRead;    // Get total cycle time
    unsigned long on_Time = fall_Time - rise_Time;      // Get on time during this cycle
    total_Time = total_Time / on_Time;                  // Divide it down
    dutyCycle = 100 / total_Time;                       // Convert to a percentage
    rise_Time = lastRead;                               // Store rise time
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(2,INPUT);
  Serial.println(F("ISR Pin 2 Configured For Input."));
  attachInterrupt(0,PinChangeISR0,CHANGE);
  Serial.println(F("Pin 2 ISR Function Attached."));
}

void loop() {
  static unsigned long oldLastRead = lastRead;
  Serial.print("Duty Cycle = ");
  if (oldLastRead != lastRead) {
    Serial.print(dutyCycle);
    oldLastRead = lastRead;
  }
  else { // No interrupt since last read so must be 0% or 100%
    if (digitalRead(2) == LOW){
      Serial.print("0");
    }
    else {
      Serial.print("100");
    }
  }
  Serial.println("%");
  delay(100);
}

Feed the output of the comparator into a diode and from there into a capacitor and then to GND. You'll have to play around with the capacitor values until you find the right one. With the right capacitor the voltage at the junction between the diode and capacitor will be proportional to the PWM duty cycle.

This is incorrect. The diode only sources current, so the capacitor simply charges to the peaks. If you have enough of a resistance discharging it and low enough capacitance, then you are still left with a huge ripple voltage and it still isn't the average voltage.

I answered a similar question here:
http://forum.arduino.cc/index.php?topic=267459.msg1888121#msg1888121

Or...because you've got a microcontroller you can totally skip the analog part and just count how long it's HIGH and how long it's LOW. You're looking at relatively low frequency signals so it should be quite accurate. Just put a 22K resistor between the PWM signal and the Arduino pin, job done.

I agree with that. That does seem to be what the OP is trying to do, but he's doing it in a way that is holding up the rest of the program.

I agree with that. That does seem to be what the OP is trying to do, but he's doing it in a way that is holding up the rest of the program.

I have a code version running now using interrupts on a generated square wave and like the cleanliness of the solution but the real PWM isn't actually very clean. It's both noisy and not square (due to back EMF from the DC motor).

Now improving algorithm (some average/smoothing may help) and will start looking into high frequency filtering... it's very slow going as each of these concepts has to be researched and tried etc starting from a pretty basic level. If you gave me a nut and bolt I'd know exactly which screwdriver to hit it with....