Impossible to vary square wave FREQUENCY output based on a PWM input?

Hi @trentonp

Here's one way to measure an input channel from an RC receiver using the attachInterrupt() function. It's non-blocking, meaning that it continously reads the incoming pulse width around the loop(). Using the micro() function gives the input an accuracy of around 4us on AVR based Arduinos, (1us on ARM based ones):

// Sketch to calculate pulsewidth from an input signal in microseconds

#define INPUT_PIN 10                      // Arduino input pin number

volatile unsigned long isrPulsewidth;     // Define Interrupt Service Routine (ISR) pulsewidth
unsigned long pulseWidth;                 // Define Pulsewidth variable

void setup() 
{
  pinMode(INPUT_PIN, INPUT);              // Set the input pin
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), calcPulsewidth, CHANGE);   // Run the calcPulsewidth function on signal CHANGE
}

void loop() 
{
  noInterrupts();                         // Turn off interrupts
  pulseWidth = isrPulsewidth;             // Copy the isr pulsewidth to the pulsewidth variable
  interrupts();                           // Turn on interrupts
  // Work with pulsewidth here ...
  // ...
}

void calcPulsewidth()                    // Interrupt Service Routine (ISR)
{
  static unsigned long pulseStartTime;   // Start time variable
  
  if (digitalRead(INPUT_PIN) == HIGH)    // If the change was a RISING edge
  {
    pulseStartTime = micros();           // Store the start time (in microseconds)
  }
  else                                   // If the change was a FALLING edge
  {        
    isrPulsewidth = micros() - pulseStartTime;    // Calculate the pulsewidth
  }
}

If you require a resolution of more than 4us, timer 2 can be employed instead of micros() to increase this to 1us if required.

The conversion from 1ms-2ms receiver input pulse to PWM frequency output can be achieved using the Arduino map() and constrain() functions.

Regarding the PWM output, if you're after a 50% duty-cycle that changes frequency, this can be achieved at low frequencies using the Arduino Uno's 16-bit timer 1 in Fast PWM Mode with OCR1A as TOP.

Fast PWM Mode with OCR1A as TOP has the advantage over Clear Timer On Compare Match (CTC) mode, since the OCR1A register is double buffered, meaning that changes to the output only occur on a timer update, or in other words only when the timer overflows. This prevents glitches from appearing on the output waveform.

Here's the code to do that:

// Output 1Hz, 50% duty-cycle on D9 using timer 1 in "Fast PWM mode with OCR1A as TOP"
// Calculate OCR1A value = frequency(CPU) / (2 * precaler * frequency(PWM)) - 1
void setup() 
{ 
  pinMode(9, OUTPUT);                                 // Set D9 as an output                       
  TCCR1A = _BV(COM1A0) | _BV(WGM11) | _BV(WGM10);     // Set timer 1 in Fast PWM mode with OCR1A as TOP
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS12);       // Enable timer clock with divide by 256 prescaler 
  OCR1A = 31249;                                      // 1Hz PWM: OCR1A = 16MHz / (2 * 256 * 1Hz) - 1 = 31249
  //OCR1A = 445;                                        // 70Hz PWM: OCR1A = 16MHz / (2 * 256 * 70Hz) - 1 = 445 
}

void loop() {}
1 Like