Go Down

Topic: Measure of Duty Cicle of a PWM signal (Read 195 times) previous topic - next topic

Rolaro

Hi,

I want to read de the duty cicle of a PWM signal with Arduino.
I would like to use interrupts to detects the change of high to low and low to high.
I worry if it's possible to use the timers of arduino as counters to read the time which the signal is in high and low.

Thanks!.

Riva

Maybe something like the attached will help as a starting point.
Don't PM me for help as I will ignore it.

Rolaro

Maybe something like the attached will help as a starting point.
Yes, thanks, with that i can detect the change of flank, the problem is that micros() function can be too slow to read the duty cicle with high accuracy.

ard_newbie


What is the frequency range of your PWM signal ?

Riva

Yes, thanks, with that i can detect the change of flank, the problem is that micros() function can be too slow to read the duty cicle with high accuracy.
If micros() is to slow for the resolution your looking for then you need to move to a faster MCU or some external hardware to do the timing. IIRC the AVR micro() function updates in steps of 4 so your getting errors to begin with without the code overhead.
Don't PM me for help as I will ignore it.

ChrisTenone

#5
May 18, 2018, 06:44 am Last Edit: May 18, 2018, 08:58 am by ChrisTenone
Yes, thanks, with that i can detect the change of flank, the problem is that micros() function can be too slow to read the duty cicle with high accuracy.
Hi Rolaro,

You are correct = the function micros() always comes back with a value that is a multiple of 4! This imposes a limit of 250KHz on even a hypothetical no-time sketch. But of course the sketch time adds significantly to the overhead. The maximum frequency and duty cycle precision are adversely affected.

However, this code is not ideal. It spends way too much time in the interrupt service routine! Plus it accesses three volatile variables. Volatile variables are read/written slower than non-volatile variables. ISR code can access byte sized arguments (aka "atomic" variables) in a single cycle, so they need not be declared volatile, as they can't change mid-cycle.

The ISR should be super simple. Like set a single atomic variable and exit. Do everything else in the loop() function. The following code does this and reads a frequency on pin 2 (and ground!) up to 13KHz.

Code: [Select]

// UNO, pwm signal to pin 2 + common ground
// 050518 clh 2630/252 show frequency and duty cycle

const byte inputPin = 2;  // INT.0

boolean inputState = false;
boolean lastInputState = false;

unsigned long pulseLength = 0;
unsigned long lastPulseLength = 0;
unsigned long startMicros;
unsigned long timeBetween = 0;
unsigned long lastTimeBetween = 0;
unsigned long stopMicros;

unsigned long previousDisplayMillis = millis();
long displayMillis = 1000L;

void setup() {
  pinMode(inputPin, INPUT_PULLUP);  // pin <-- 100R <-- vibePin

  Serial.begin(115200);

  attachInterrupt(digitalPinToInterrupt(inputPin), setInputState, CHANGE);
}

void setInputState() {
//  inputState = digitalRead(inputPin);
  inputState = PIND & 0b0100;
}

void loop() {

  if (inputState != lastInputState) {
    if (pulseDone()) {
      lastPulseLength = pulseLength;
    } else {
      lastTimeBetween = timeBetween;
    }
    lastInputState = inputState;
  }

  // --------- Run this code every second ------------------
  if (millis() - previousDisplayMillis >= displayMillis) {
    previousDisplayMillis += displayMillis;

    unsigned long period = (lastPulseLength + lastTimeBetween);
    long frequency = 1000000L / period;
    byte dutyCycle = ((100L * lastPulseLength) / period) + 1;

    Serial.print("Frequency: ");
    Serial.print(frequency);
    Serial.print(" Hz | Duty Cycle: ");
    Serial.print(dutyCycle);
    Serial.println('%');
  }
}

// If pulseDone() is true, the variable pulseLength is valid.
// If false, then timeBetween is valid, but pulseLength is 0.
boolean pulseDone () {
  unsigned long nowMicros = micros();  // resolution of micros() is 4┬Ás, so *
  if (inputState) {             // rising edge of the pulse.
    startMicros = nowMicros;    // start the microseconds clock
    pulseLength = 0UL;

    timeBetween = (nowMicros - stopMicros);
    return false;
  }
  else {                        // falling edge - stop clock
    stopMicros = nowMicros;    // start the microseconds clock
    timeBetween = 0UL;

    pulseLength = (nowMicros - startMicros);
    return true;
  }
}
.sig

ChrisTenone

#6
May 18, 2018, 07:46 am Last Edit: May 18, 2018, 08:45 am by ChrisTenone Reason: - fix code error
Also, if you just check frequency, skipping duty cycle, and completely forgo using interrupts, you can measure up to 60-ish KHz. With a regularly timed event, the interrupt code that runs behind the scenes interferes, and actually slows things down near the physical limits of the machine.

Code: [Select]

const byte inputPin = 2;  // INT.0

boolean inputState = false;
boolean lastInputState = false;
long count = 0UL;

unsigned long previousDisplayMillis = millis();
const long displayMillis = 500L;

void setup() {
  pinMode(inputPin, INPUT_PULLUP);

  Serial.begin(115200);
}

void loop() {
  inputState = PIND & 4;  //  faster than: inputState = digitalRead(inputPin);
  if (inputState != lastInputState) {
    count++;
    lastInputState = inputState;
  }

  // --------- Run this code every *half* second ------------------
  if (millis() - previousDisplayMillis >= displayMillis) {
    previousDisplayMillis += displayMillis;

    Serial.print(count);   //   *** SERIAL PRINT ***
    Serial.print(" Hz");   //   *** SERIAL PRINT ***
    Serial.print('\n');    //   *** SERIAL PRINT ***
    
    count = 0UL;

  }
}
.sig

Go Up