Measuring PWM with Arduino due the efficient way

I want to read RC receiver outputs with Arduino due; since time is valuable in my project i think it's best to use due's internal registers to measure PWM.
i need help to setup pin interrupts to measure PWM every time that the output of receiver changes using direct register programming.
by the way i know little to nothing about using due's internal interface registers to get the job done.

thank you.

To measure a PWM frequency and duty cycle, you can code a Timer Counter in capture mode. Then pull frequency and duty from Timer internal counters in the TC Handler or by polling TC satus register in loop().

Sam3x datasheet, Timer Counter section 36 is your best friend.

Here is an example sketch I did to measure a simple PWM frequency and duty cycle produced by an analogWrite in 12_bit resolution:

/**************************************************************************************/
/*                        Capture PWM frequency and duty cycle                        */
/*      Hook a jumper between pin 2 (TIOA0) and pin A7 (TIOA1)                        */
/**************************************************************************************/

volatile uint32_t CaptureCountA, CaptureCountB, TimerCount;
volatile boolean CaptureFlag;


void setup() {
  Serial.begin(250000);                                   // initilize serial port to 250000 baud

/**************          Generate simply a PWM signal                   ***************/
  analogWriteResolution(12); // From 0 to 2exp12  - 1 = 4095
  analogWrite(7, 1024);                                   // Duty cycle is 25% with 12 bits resolution
  
/*************         Capture a PWM frequency and duty cycle          ****************/
  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // Timer Counter 0 channel 1 IS TC1, TC1 power ON

  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // capture mode, MCK/2 = 42 MHz, clk on rising edge
                              | TC_CMR_ABETRG              // TIOA is used as the external trigger
                              | TC_CMR_LDRA_RISING         // load RA on rising edge of trigger input
                              | TC_CMR_LDRB_FALLING;       // load RB on falling edge of trigger input
                              
  // If you want to capture PWM data from TC1_Handler()
  TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB
  
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC counter and enable

  NVIC_EnableIRQ(TC1_IRQn);                                // Enable TC1 interrupts

  Serial.println("Timer Capture");

}

void loop() {
  if (CaptureFlag) {
    CaptureFlag = 0;
    printf("\r %d , %d \n", CaptureCountA, CaptureCountB);
  }
  
}

// Note that you could either test status register by polling in loop()
void TC1_Handler() {

  //Registers A and B (RA and RB) are used as capture registers. They are loaded with
  //the counter value TC_CV when a programmable event occurs on the signal TIOA1.
  //TimerCount = TC0->TC_CHANNEL[1].TC_CV;            // save the timer counter register, for testing

  uint32_t status = TC0->TC_CHANNEL[1].TC_SR;       // Read & Save satus register -->Clear status register

  // If TC_SR_LOVRSRA is set, RA or RB have been loaded at least twice without any read
  // of the corresponding register since the last read of the Status Register,
  // We are losing some values,trigger of TC_Handler is not fast enough !!
  //if (status & TC_SR_LOVRS) abort();

  // TODO: calculate frequency and duty cycle from data below *****************
  if (status & TC_SR_LDRAS) {  // If ISR is fired by LDRAS then ....
    CaptureCountA = TC0->TC_CHANNEL[1].TC_RA;        // get data from capture register A for TC0 channel 1
  }
  else { /* if (status && TC_SR_LDRBS)*/  // If ISR is fired by LDRBS then ....
    CaptureCountB = TC0->TC_CHANNEL[1].TC_RB;         // get data from capture register B for TC0 channel 1

    CaptureFlag = 1;                      // set flag indicating a new capture value is present
  }

}

Hi there @alireza_73,

Have a look to tc_lib available at GitHub - antodom/tc_lib: This is a library to take advantage of different functionalities available on Timer Counter (TC) modules in Arduino Due's Atmel ATSAM3X8E microcontrollers. I have built this library initially for measuring an RC signal with a DUE.

Have a look to example capture_test, using a capture object. In the example the PWM signal of an analogWrite() is measured.

I hope it helps.

Hi antodom,

Great Library !!

I wanted to know if it was possible to measure PWM frequencies well above 1 MHz, and I ended up by giving up interruptions (too slow). By polling status register in loop(), one can measure frequencies / duty cycles (if duty cycles are "slowly" moving) up to 1.5 MHz and Serial.print results once every 10000 measurments.

This implies to discard a few wrong measures when TC_SR_LOVRSRA/RB is set, but not so much.

Hi there @ard_newbie,

@HermannSW (thansk HermannSW) has already done some testing to know the limits of tc_lib. In this post you have a summary:

https://forum.arduino.cc/index.php?topic=393842.msg3086694#msg3086694

I think they can help you to have a better idea about tc_lib, they report measurements of signals with frequencies greater than 20Mhz.

Best.

Thanks for the information antodom :slight_smile: .

I understand that you can measure higher frequencies provided you can accept a large overrun (+100), good to know.

BTW for testing purposes, a DUE can provide frequencies with square signals identical to a PWM with a 50% duty cycle: PMC_PCK[0], PMC_PCK[1] and PMC_PCK[2].

PMC_PCKx are clock signals, divided by a prescaler from 1 to 64 resulting in square signals from Slow clock / 64 = 32768/64 Hz(the slowest) to UPLL/1 = 480/1 MHz(the fastest).

Hi again @ard_newbie,

You are right, the faster the frequency, the more overuns you will have, in fact, using tc_lib you can set how many overuns you can tolerate (second parameter of capture objects' member function config()).

Good suggestion, the use of the PLL output as a digital signal with high frequencies for testing purposes usign the DUE.

Best.

Hi @antodom, I have a question about using your library.

I am using an SMT172 thermometer, and to get accurate measurements I need to use measurements of multiples of 8 individual duty cycles, measured from the falling edge. Is there a way to use your library for this, or do I need to check for falling edges and then input that time (x2) as the capture window in your library?

Sorry if this is the wrong place and a stupid question, new to both the forum and to the Due.

Hi there @mrmaxen,

I do not know if I understood well your question. Do you mean measuring in 8 different duty cycles inside the same period of a PWM signal?, or you just need to measure at a specific frequency 8 times?. At which frequency?.

Best.

Thanks for replying!

In the data-sheet for the SMT172 it says that it uses Dynamic Element Matching (DEM) because the period of individual duty cycles can vary, but that the average period of 8 duty cycles is very precise.
So to answer your question, i guess I want to measure 8 different duty cycles in the same period of a PWM signal.

If I've understood your library correctly, I need to know the capture window before hand, which in my case would be the time it would take for exactly 8 pulses to occur. My problem is that I am unsure as to how I can find the value of my capture window. Please correct me if I'm wrong.

Hi again @mrmaxen,

According to SMT172's data sheet you need to measure eight consequent duty cycles of a PWM signal with a frequency ranging from 1 to 4 kHz. I would use a capture window of at least about twice the period of the minimum possible frequency, so a capture window of about 2 msecs (500 Hz), I think it is enough. Just accumulate 8 duty cycles and calculates the average using the equation in the data sheet. Take into account that, in its current version, tc_lib does not guaranteed that all duty cycle readings are consequent, but I think that this will be no problem, given those frequencies.

I hope it helps.