PWM frequency and duty cycle using input capture on ATmega2560

Nick Gammon has two timer sketches for the Atmega2560, one that calculates duty cycle and the other the frequency of a PWM signal. Has anyone combined these two sketches to provide both in the same program? The two sketches are located at: Gammon Forum : Electronics : Microprocessors : Timers and counters

What is the frequency range? Here is someone already done measurements.

The range of PWM signals is between 100 and 500 max. Does the sketch you referenced use input capture? I believe it uses regular interrupts, whereas the Nick Gammon sketches use input capture that is supposed to be more efficient. e.g. ICP1 on logical pin 83 on the atmega2560.

davidcyr2000:
The range of PWM signals is between 100 and 500 max. Does the sketch you referenced use input capture? I believe it uses regular interrupts, whereas the Nick Gammon sketches use input capture that is supposed to be more efficient. e.g. ICP1 on logical pin 83 on the atmega2560.

I think, one of the Nick Gammon's example is written for atmega2560, but another for atmega328 (UNO board). ICP1 -> PD4 is not routed on Mega board. >:(
Though it still would be possible to use input capture Timer4 or Timer5 (ICP4 & ICP5), pin 48 & 49.
I read on this forum someone pose same question and was able to successfully adapt Gammon's code to ICP4. Not sure, if it's measure duty cycle, may be only frequency.
500 Hz is quite low, you sure you can't do it with input change interrupt? 2 milliseconds period, and if arduino spend ~2 usec to jump into ISR the worst case scenario you 'd have 0.1% error, it's not much, isn't it ?

This is what I come up with, to get maximum precision out of arduino's ICP feature.

/*
  Using Input Capture to measure frequency & duty cycle 
  on Arduino Mega2560.

  Reads a PWM signal on pin 49, prints the result over Serial Monitor.
  Commands to activate printout - "d".
  Range 1.00 Hz <---> 86.00 kHz
  
  Released into the public domain.

  Created by Anatoly Kuzmenko, April 2018
  k_anatoly@hotmail.com
*/

           String       in_String         =     "";        
           boolean      end_input         =  false;  
           uint8_t      adres_reg         =      0;         
           uint8_t      debug_osm         =      0;


  volatile int32_t      period_hi         =      0;
  volatile int32_t      period_lo         =      0;

  volatile uint8_t      tmr_overf         =      0;
  volatile uint8_t      f_capture         =      0; // flag

           float        duty_cycl         =    0.0;
           float        freq_cntr         =    0.0;

void setup()
{
  Serial.begin(115200);
  in_String.reserve(200);

  init_tmr4();
}

void loop()
{
  int32_t  tempr = 0;
  char *   pEnd;
  
  if(debug_osm) {
    if(f_capture) {
            
      uint32_t summ = period_hi + period_lo;
      
      duty_cycl = period_hi;
      duty_cycl *= 100.0;  // %
      duty_cycl /= summ;
      freq_cntr = 16000000.0 /summ;

      Serial.print(F("\n\tFreq: "));
      Serial.print(freq_cntr, 6);      
      Serial.print(F("\tduty cycle: "));
      Serial.print(duty_cycl, 4);      
      f_capture = 0;
      }  
    delay(500);
    }

  serialEvent(); 

  if( end_input) {
    char cmd = in_String[0];
    in_String[0] = '+';
    
    if( cmd == 'd' ) {
      debug_osm = 1 - debug_osm;
      if(debug_osm) Serial.print(F("\nDebug aktiv."));
      else          Serial.print(F("\nDebug de-aktiv."));
      }
                        
    in_String = "";
    end_input= false;
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    in_String += inChar;
    if (inChar == '\n') {
      end_input= true;
    }
  }
}

And timer4 tab:

void init_tmr4(void)
{  
  TCCR4A = 0;
  TCCR4B = 0;

  TCCR4B |= (1<< CS40); // set prescaler to 16 MHz

  TCCR4B |= (1<<ICNC4); // input noise canceler on
  TCCR4B |= (1<<ICES4); // input capture edge select (lo-hi) 

  TIMSK4 |= (1<<TOIE4); // Overflow Interrupt Enable 
  TIMSK4 |= (1<<ICIE4); // InCaptureInterrupt Enable   
}

ISR(TIMER4_OVF_vect) {
  tmr_overf++;  
}

ISR(TIMER4_CAPT_vect) {
  static uint16_t last_v = 0;
         uint16_t curr_v = ICR4;
         uint32_t accuml = 0;

  accuml  = curr_v + tmr_overf *65536UL;  
  accuml -= last_v;    
  last_v  = curr_v;
  tmr_overf = 0;        

  if(TCCR4B &  (1<<ICES4)) {  // lo-hi
    TCCR4B &= ~(1<<ICES4);    // input capture edge select (hi-lo) next 
    period_lo = accuml;
    f_capture = 1;       
    }
  else {  // hi-lo
    TCCR4B |= (1<<ICES4);    // input capture edge select (lo-hi) next 
    period_hi = accuml;
    }
}

Enjoy!

Freq_duty_public_1d.zip (1.82 KB)

Works like a charm! Changed to ICP1, digital pin 83, but otherwise ran it as is. Now I will have to figure out how you did it! :slight_smile: Thank you very much!

[BTW, both of Nick Gammon's sketches work fine on the 2560, but I simply wanted to get both frequency and duty cycle in the same sketch. My hardware is already made, so I am stuck with the ICP1 Digital pin 83 for input, and "they say" this pin is not one of the pins available for interrupts. ("Mega2560 DIGITAL PINS USABLE FOR INTERRUPTS 2, 3, 18, 19, 20, 21") I made a PCB using the atmega2560 chip, so I have access to ICP1. There is also a lot of other processing going on at the same time, so I was trying to minimize load on the system by using input capture.]