Calculating duty cycle of PWM signal - wrong data type?

Hello all,

This is probably something simple, but I can't figure it out.

I have a 100Hz PWM signal. I'm trying to measure the duty cycle to determine the state of the device sending the signal. To test this idea, i used this code;

const int PWMGenerator = 5;         //980Hz pulse generator arduino itself
const int PulseIN = 9;              // pulse counter pin
unsigned long ONCycle;              //oncycle variable 
unsigned long OFFCycle;             // offcycle variable got microsecond
unsigned long T;                    // total time to one cycle ONCycle + OFFcycle
unsigned long F;                    // Frequency = 1/T
float DutyCycle;                    // D = (TON/(TON+TOFF))*100 %

void setup()
{
  pinMode(PWMGenerator, OUTPUT);
  pinMode(PulseIN, INPUT);
  Serial.begin(115200);
  analogWrite(PWMGenerator,100);    //sample pulse 980Hz 
}
void loop()
{
  ONCycle = pulseIn(PulseIN, HIGH);
  OFFCycle = pulseIn(PulseIN, LOW);

  T = ONCycle + OFFCycle;
  DutyCycle = (ONCycle / T) * 100;
  F = 1000000 / T;                    // 1000000= microsecond 10^-6 goes to upper
  
  Serial.print("High Time = ");
  Serial.print(ONCycle);
  Serial.print(" us");
  Serial.print("\n");

  Serial.print("Low Time = ");
  Serial.print(OFFCycle);
  Serial.print(" us");
  Serial.print("\n");

  Serial.print("Pulse Width = ");
  Serial.print(T);
  Serial.print(" us");
  Serial.print("\n");
      
  Serial.print("Frequency = ");
  Serial.print(F);
  Serial.print(" Hz");
  Serial.print("\n");

  Serial.print("DutyCycle = ");
  Serial.print(DutyCycle);
  Serial.print(" %");
  Serial.print("\n");
  delay(1000);
}

The serial monitor spits out everything correctly, except for the duty cycle;

19:14:22.792 -> High Time = 396 us
19:14:22.792 -> Low Time = 620 us
19:14:22.792 -> Pulse Width = 1016 us
19:14:22.826 -> Frequency = 984 Hz
19:14:22.826 -> DutyCycle = 0.00 %

What am I missing? Using an Arduino Nano Every. I tried varies data types, but it always give me 0. Any ideas?

Thanks so much.

try

DutyCycle = ONCycle * 100 / T;

It is delivering 0 because of this :

(ONCycle / T)

Since T is greater than ONCycle, in integer arithmetic, the value is 0.

You could also use floating point arithmetic to solve this.

Thank both of you for your suggestions. I will give it a try!

@6v6gt - I am declaring DutyCycle as a float, right? Or should I do that differently? My understanding was that a float can handle decimal numbers.

Thanks for your help guys!

This:

( (float)ONCycle / T )

Should also work to force a floating point calculation. Simply defining DutyCycle as float is not enough to prevent it being treated as an integer calculation.

float DutyCycle = ( 3 / 6 ) ; // zero
float DutyCycle = ( 3 / 6.0 ) ; // 0.5

Thanks 6V6gt!

I tried this:

 DutyCycle = ONCycle  * 100 / T;

And that worked. So that is great.

Now I am trying to do it with the 100Hz signal, and I can't even read the on-time; Always return 0. Copied the code 1:1, so all the declarations should be good;

const int PWM_PIN = 9; 
unsigned long DutyCycle;

void setup()
{
  Serial.begin(115200);
  Serial.println("********** - System Initialized - **********");

  pinMode(PWM_PIN,INPUT);
}

void loop()
{
    
  elapsedMicros = micros() - previousMicros;

  if ((elapsedMicros >= lowTime) && (highLow == 0))
  {
    previousMicros = previousMicros + lowTime;
    digitalWrite(pin8, HIGH);
    highLow = 1; // started high period
  }

  else if ((elapsedMicros >= highTime) && (highLow == 1))
  {
    previousMicros = previousMicros + highTime;
    digitalWrite(pin8, LOW);
    highLow = 0; // started low period
  }
  
  // "most of the time" (processor-wise) the above do nothing as the proper time hasn't elapsed
  // use that downtime to adjust the duty cycle

  if (digitalRead(button)==HIGH)
{
    
      if (StatusFlag == 2)                                                              // and the status flag is 2 (Current status is pass through)
          {                                         
          StatusFlag = 0;                                                               // Make status flag 0 (20% DC)
          lowTime = 8000;
          }                                                       
      
      else if (StatusFlag == 0)                                                         // otherwise...
          {
          StatusFlag = 1;                                                               // make status flag 1 (80% DC)
          lowTime = 2000;
          }
      
      else if (StatusFlag == 1)                                                         // otherwise...
          {
          StatusFlag = 2;                                                               // make status flag 2 (Pass Through)
          RGB_color(0, 0, 0);                                                           // LED is off
          lowTime = 1000;
          }      
          
    delay(300);                                                                       // wait a sec for the hardware to stabilize
    highTime = period - lowTime;

    OnTime = pulseIn(PWM_PIN, HIGH); 

    Serial.print("Measured High Time: ");
    Serial.print(OnTime);
    //Serial.println("%");
    
    }                                                           
}

Can't quite figure out why it reads 0. The signal going to pin 9 is a 100Hz, 0-5.5V PWM signal. Thanks for your suggestions.

elapsedMicros = micros() - previousMicros ;

Where, for example, are the two variables in the above statement defined ?

Thanks for your reply 6v6gt.

So, I stripped the code to the absolute bare bones;

byte pin8 = 8;
const int PWM_PIN = 9;
unsigned long currentMicros;
unsigned long previousMicros;                                 // timer for PWM generator switch hi/lo
unsigned long elapsedMicros;                                  // timer for PWM generator switch hi/lo

unsigned long previousMicrosSerial;                           // timer for serial print command
unsigned long elapsedMicrosSerial;                            // timer for serial print command

unsigned long period = 10000UL;                               // microseconds, 0.01S  1/.01 = 100 Hz
unsigned long highTime= 2000UL;                               // microseconds
unsigned long lowTime;                                        // microseconds
unsigned long interval = 5000000UL;                           // interval set to 5 seconds
byte highLow = 1;                                             // flag to show which state output is in
unsigned long OnTime;

void setup()
{
  Serial.begin(115200);
  Serial.println("********** - System Initialized - **********");

  pinMode(PWM_PIN,INPUT);
   
  pinMode (pin8, OUTPUT);
  digitalWrite (pin8, LOW);

  lowTime = period -  highTime;                               // with numbers above, 10000 - 2000 = 8000
  
  currentMicros = micros();
  previousMicros = currentMicros;
  elapsedMicrosSerial = micros();
  previousMicrosSerial = currentMicros;
}

void loop()
{
    
  elapsedMicros = micros() - previousMicros;
  elapsedMicrosSerial = micros()-previousMicrosSerial;
  
  if ((elapsedMicros >= lowTime) && (highLow == 0))
  {
    previousMicros = previousMicros + lowTime;
    digitalWrite(pin8, HIGH);
    highLow = 1; // started high period
    
  }

  else if ((elapsedMicros >= highTime) && (highLow == 1))
  {
    previousMicros = previousMicros + highTime;
    digitalWrite(pin8, LOW);
    highLow = 0; // started low period
  }

  if (elapsedMicrosSerial >= interval)
  {
  OnTime = pulseIn(PWM_PIN, HIGH);
  Serial.print("Measured On Time: ");
  Serial.print(OnTime);
  Serial.println(" us");
  
  previousMicrosSerial = previousMicrosSerial + interval;
  }
}

This generates the 100Hz PWM signal on pin 8, that I am then trying to read on pin 9. Hooking up the scope, I can see what goes wrong. The pulseIn command times out, because the processor does not keep generating the signal because it is busy executing the pulseIn command.

Ultimately, the 100Hz signal I am trying to read comes from my engine ECU, so it will be an external signal. I don't have anything to generate an external signal at the moment.

Is there another way to read this signal generated on pin 8 without interrupting the code?

Thanks!

You could use a pin change interrupt, configured for pin 9. The interrupt service routine should be quick to execute and cannot, for example, use Serial.print() , delay() etc. Usual is to just set a flag in the Interrupt service routine and, in the loop, act on that flag, then unset it.

https://playground.arduino.cc/Main/PinChangeInterrupt/

What’s wrong with interrupting the code?
You could timestamp the rising and falling edges in an ISR, and skip pulseIn altogether.

I tried following with an external 2V 100 Hz signal (just realized my scope can generate a signal!)

Modified the code as follows;

byte pin8 = 8;
const int PWM_PIN = 9;
unsigned long currentMicros;
unsigned long previousMicros;                                 // timer for PWM generator switch hi/lo
unsigned long elapsedMicros;                                  // timer for PWM generator switch hi/lo

unsigned long previousMicrosSerial;                           // timer for serial print command
unsigned long elapsedMicrosSerial;                            // timer for serial print command

unsigned long period = 10000UL;                               // microseconds, 0.01S  1/.01 = 100 Hz
unsigned long highTime= 2000UL;                               // microseconds
unsigned long lowTime;                                        // microseconds
unsigned long interval = 5000000UL;                           // interval set to 5 seconds
byte highLow = 1;                                             // flag to show which state output is in
unsigned long OnTime;

void setup()
{
  Serial.begin(115200);
  Serial.println("********** - System Initialized - **********");

  pinMode(PWM_PIN,INPUT);
   
  pinMode (pin8, OUTPUT);
  digitalWrite (pin8, LOW);

  lowTime = period -  highTime;                               
  
  currentMicros = micros();
  previousMicros = currentMicros;
  elapsedMicrosSerial = micros();
  previousMicrosSerial = currentMicros;
}

void loop()
{
    
  elapsedMicros = micros() - previousMicros;
  elapsedMicrosSerial = micros()-previousMicrosSerial;
  
  if ((elapsedMicros >= lowTime) && (highLow == 0))
  {
    previousMicros = previousMicros + lowTime;
    digitalWrite(pin8, HIGH);
    highLow = 1; // started high period
    
  }

  else if ((elapsedMicros >= highTime) && (highLow == 1))
  {
    previousMicros = previousMicros + highTime;
    digitalWrite(pin8, LOW);
    highLow = 0; // started low period
  }

  if (elapsedMicrosSerial >= interval)
  {
    readPWM();
  }
}

void readPWM()
{
  OnTime = pulseIn(PWM_PIN, HIGH);
  Serial.print("Measured On Time: ");
  Serial.print(OnTime);
  Serial.println(" us");
  
  previousMicrosSerial = previousMicrosSerial + interval;
}

I have attached screen captures of the input signal and the serial monitor output.

I'll read up on the pin interrupt 6v6gt! Thanks.

TheMemeberFormerlyKnowAsAWOL; I am not familiar with "ISR". I'll do a search.

Thanks

PWMSig.JPG

SerialOutput.JPG

PWMSig.JPG

SerialOutput.JPG

I tried something else. Turned on the PWM on pin 5, and connected it to pin 9.

Changed the code so that every time a button is pressed, a random number is generated to create a different duty cycle of the PWM signal on pin 5;

  randNumber = random(255);
  analogWrite(PWMGenerator,randNumber);
  Serial.print("Random number: ");
  Serial.println(randNumber);

Then I read the OnTime, OffTime, and calulate the Pulse width, frequency and duty cycle;

  ONCycle = pulseIn(PWM_PIN, HIGH);
  OFFCycle = pulseIn(PWM_PIN, LOW);

  T = ONCycle + OFFCycle;
  DutyCycle = (ONCycle  * 100) / T;
  F = 1000000 / T;                        // 1000000 = microsecond 10^-6 goes to upper
  
  Serial.print("High Time = ");
  Serial.print(ONCycle);
  Serial.print(" us");
  Serial.print("\n");

  Serial.print("Low Time = ");
  Serial.print(OFFCycle);
  Serial.print(" us");
  Serial.print("\n");

  Serial.print("Pulse Width = ");
  Serial.print(T);
  Serial.print(" us");
  Serial.print("\n");
      
  Serial.print("Frequency = ");
  Serial.print(F);
  Serial.print(" Hz");
  Serial.print("\n");

  Serial.print("DutyCycle = ");
  Serial.print(DutyCycle);
  Serial.print(" %");
  Serial.print("\n");

This seems to work great! Here is a read out of the serial monitor;

16:10:30.112 -> ********** - System Initialized - **********
16:10:32.672 -> CAN Message Received
16:10:32.672 -> Switch Status: 0
16:10:32.706 -> Message sent!
16:10:32.984 -> Random number: 232
16:10:32.984 -> High Time = 865 us
16:10:32.984 -> Low Time = 92 us
16:10:32.984 -> Pulse Width = 957 us
16:10:32.984 -> Frequency = 1044 Hz
16:10:32.984 -> DutyCycle = 90.00 %
16:10:44.976 -> CAN Message Received
16:10:44.976 -> Switch Status: 0
16:10:44.976 -> Message sent!
16:10:45.284 -> Random number: 19
16:10:45.284 -> High Time = 65 us
16:10:45.284 -> Low Time = 944 us
16:10:45.284 -> Pulse Width = 1009 us
16:10:45.284 -> Frequency = 991 Hz
16:10:45.284 -> DutyCycle = 6.00 %

Hopefully it will still work when the system is in the car and receives a 100Hz PWM signal!

Thanks for the help all!

There's something wrong with the calculation of DutyCycle:

shelbygt350:
16:10:32.984 -> High Time = 865 us
16:10:32.984 -> Low Time = 92 us
16:10:32.984 -> DutyCycle = 90.00 %

DutyCycle = 865 / (865 + 92) * 100% = 90.3866 % !

shelbygt350:
16:10:45.284 -> High Time = 65 us
16:10:45.284 -> Low Time = 944 us
16:10:45.284 -> DutyCycle = 6.00 %

DutyCycle = 65 / (944 + 65) * 100% = 6.442 % !

Hi Erik (Goedemiddag)

I noticed that the duty cycle calculation is not 100% precise. It always returns a round number for some reason. However, for what I am doing, it does not matter. I need to know if the DC is either 20% or 80%, so a ball park number is good enough.

Do you have an idea why it is spitting out rounded numbers?

Thanks!

It is putting out truncated values because you’re doing integer arithmetic.

Hi TheMemberFormerlyKnownAsAWOL

Thanks for your reply. I understand what you are saying, but I don't quite know where in the code that happens. The only thing I can think of is;

const int PWM_PIN = 9;

Is that the issue? If not, would you please point me in the right direction? I am pretty new to this stuff and learning.

Thanks!

unsigned long ONCycle;              //oncycle variable
unsigned long OFFCycle;   // INTEGER variable
unsigned long T;                 // INTEGER variable
unsigned long F;                 // INTEGER variable
float DutyCycle;                   

...

  T = ONCycle + OFFCycle; // INTEGER arithmetic
  DutyCycle = (ONCycle / T) * 100; // INTEGER arithmetic
  F = 1000000 / T;    // INTEGER arithmetic

Try casting the integer expression values in the DutyCycle calculation to “float”

  DutyCycle = (ONCycle / T) * 100; // INTEGER arithmetic

No, use:

  DutyCycle = 100 * ONCycle / T; // INTEGER arithmetic

Always do multiplication first, to avoid truncation error (unless you anticipate overflow).

Thanks TheMemberFormerlyKnownAsAWOL and aarg!

I didn't realize that a long and integer are similar (but 32 bit vs 64 bit). I will assign these variables as float and test the code.

Aarg, I appreciate your suggestion on doing multiplications first. That sounds like a good rule to go by when coding.

Thanks!

shelbygt350:
I didn’t realize that a long and integer are similar (but 32 bit vs 64 bit). I will assign these variables as float and test the code.

Don’t think of “int” as simply “integer”.

“long” is an integer.
“char” is an integer.
“uint64_t” is an integer.

Think of “int” as “nasty, implementation-defined integer that may bite me on the ass if I don’t think carefully about it”.

I do.

Aarg, I appreciate your suggestion on doing multiplications first. That sounds like a good rule to go by when coding.

…but be careful that that multiplication doesn’t wrap-around too.