PWM to PWM converter

Yesterday I developed an Arduino based PWM to PWM converter. In my application, I need to lower the PWM frequency but maintain the duty cycle, my input PWM is at 2K Hz and my output PWM is at 10 Hz. I found that the puslein function was too unreliable so I wrote my own pulse length measuring code using the interrupt on IO 2 and because the Arduino’s PWM output can only be tweaked down to 31 Hz I also wrote my own low frequency PWM output code. In my application, I am actually running two inputs (IO 2 & 3) and two outputs simultaneously and it is working quite well considering all the jitter on my input signals (the fault of my 3rd party hardware on the input). Below is the code, I don’t know much about optimizing it for speed so I would appreciate hearing ideas on that and other improvements. Also, if you know of a method for doing this without a micro-controller I’d like to hear it. We did have something going with op-amps but it requires a lot of tuning. I’m wondering if something can be done with a frequency divider.

// PWM input 1 variables
byte pwmIn1IO = 2;                             // IO used to measure PWM input 1, IO 2 is also interrupt 0
volatile unsigned long pwmIn1PulseStartTime;   // store PWM pulse start time in uS
volatile long pwmIn1PulseElapsedTime;          // store PWM pulse length in uS
unsigned long pwmIn1Period = 500ul;            // used to calc pwmIn1Duty percentage, 500 uS = 2K Hz
byte pwmIn1Duty;                               // PWM pulse length converted to duty cycle in percent * 100
volatile boolean pwmIn1Ready;                  // used to flag the duty cycle calc after a completed PWM pulse measurement
unsigned long pwmIn1Timeout = 50;              // uS added to pwmIn1Period for the timeout
volatile boolean pwmIn1Invert = 1;             // used to invert input PWM readings
// PWM output 1 variables
byte pwmOut1Pin = 8;                    // IO for PWM ouput 1
unsigned long pwmOut1Period = 100000ul; // base period in uS for PWM out 1, 10Hz
unsigned long pwmOut1PulseTime = 0ul;   // pulse active time in uS
unsigned long pwmOut1PrevEndTime = 0ul; // previous PWM period end time in uS
boolean pwmOut1State = 0;               // tracks the current phase of the PWM output, ie active or inactive ("on" or "off")
boolean pwmOut1Invert = 0;              // used to invert PWM output, '0' means active is HIGH or 5V

void setup() {
  // set IO modes
  pinMode(pwmIn1IO, INPUT);
  digitalWrite(pwmIn1IO, HIGH);    // set internal pullup resistor
  attachInterrupt(0, pwmIn1Trigger, CHANGE);    // catch interrupt 0 (IO 2) changing state and send to pwmIn1Trigger()
  pinMode(pwmOut1Pin, OUTPUT);
  digitalWrite(pwmOut1Pin, pwmOut1Invert);
}

void loop(){

  // if a pulse has been measured
  if (pwmIn1Ready){
    pwmIn1Ready = false;
    if (pwmIn1PulseElapsedTime <= pwmIn1Period){    // to help deal with 0% or 100% duty cycle conditions, let the timeout code deal with it
      pwmIn1Duty = (int)(pwmIn1PulseElapsedTime * 100ul / pwmIn1Period); // calc duty cycle of input pwm
      pwmIn1Duty = constrain(pwmIn1Duty,0,100);
      pwmOut1PulseTime = pwmIn1Duty * pwmOut1Period / 100ul; // calc output pwm's pulse time in uS
    }
  }  
  // timeout code, if input pwm does not finish a measurement due to 0% or 100% duty cycle, ie. there are no pulses to trigger the interrupt
  if (micros() > pwmIn1PulseStartTime + pwmIn1Period + pwmIn1Timeout){  
    if (digitalRead(pwmIn1IO) == pwmIn1Invert){
      pwmOut1PulseTime = 0;              // set 0 duty cycle
    } else {
      pwmOut1PulseTime = pwmOut1Period;  // set max duty cycle
    }
    pwmIn1PulseStartTime = micros();     // reset pwm in pulse start time
  }  
  // if output pwm is "active", check for end of pulse time
  if (pwmOut1State){
    if (micros() >= (pwmOut1PrevEndTime + pwmOut1PulseTime)){
      digitalWrite(pwmOut1Pin, pwmOut1Invert);  // pulse "active" time has expired, toggle pwm output IO
      pwmOut1State = 0;    // set state tracking flag
    }
  }
  // if output pwm is "inactive" or "off", check for end of pwm period time
  if (!pwmOut1State){
    if (micros() >= (pwmOut1PrevEndTime + pwmOut1Period)){
      digitalWrite(pwmOut1Pin, !pwmOut1Invert);
      pwmOut1PrevEndTime = micros();  // originally I used (pwmOut1PrevEndTime += pwmOut1Period)
                                      // depends which is more important, steady frequency or accurate pwm "active" pulse length
      pwmOut1State = 1;
    }    
  }
}

// runs when interrupt is triggered
void pwmIn1Trigger(){
  if (digitalRead(pwmIn1IO) != pwmIn1Invert){
    pwmIn1PulseStartTime = micros();
  } else {
    pwmIn1PulseElapsedTime = micros() - pwmIn1PulseStartTime; // may glitch during timer wrap-around
    pwmIn1Ready = true;
  }
}

Hi, Your code looks somehow familiar ?

Duane B

rcarduino.blogspot.com

I got some ideas for the input PWM pulse width measuring interrupts from here, http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1247084671/all, reply #14 I think. The rest I hammered out myself.

Hi, Now I feel like a clown, that post is about 3 years older than my blog post, but has almost identical code. I guess sometimes, there is only one way to skin a cat.

This is the post I was thinking of -

http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html

You mention noisy input but some of this may be due to you not protecting you shared variable. You use pwmIn1Ready to indicate to loop that a new pulse is available, but you do not take any measures in your ISR to prevent it overwriting the value of pwmIn1PulseElapsedTime while loop is reading it. Have a look through this link to get an idea what I am referring too -

http://rcarduino.blogspot.com/2012/04/how-to-read-multiple-rc-channels-draft.html

There are lots of optimizations you could make, for example using a timer to generate the output pulses in the background, but if it works, how low level do you want your code to get ?

Duane B

rcarduino.blogspot.com

For PWM to PWM without a comtroller: Using just an inductor diode RC, you produce a proportional DC level from your input PWM pulses. ( http://www.arduino.cc/playground/Main/RegulatedPositiveVoltageBooster )

For output you use a linear sawtooth generator made with an op amp generating constant current to a capacitor in the feedback loop. A comparator op-amp resets the sawtooth when it reaches the full DC level voltage.

A 2nd comparator produces a hi output iff the DC voltage is greater than the sawtooth voltage.

The result is PWM output at the frequency of the sawtooth and with widths of the same duty cycle as the input PWM.

Techylah:
For PWM to PWM without a comtroller:
Using just an inductor diode RC, you produce a proportional DC level from your input PWM pulses.
( http://www.arduino.cc/playground/Main/RegulatedPositiveVoltageBooster )

For output you use a linear sawtooth generator made with an op amp generating constant current to a capacitor in the feedback loop.
A comparator op-amp resets the sawtooth when it reaches the full DC level voltage.

A 2nd comparator produces a hi output iff the DC voltage is greater than the sawtooth voltage.

The result is PWM output at the frequency of the sawtooth and with widths of the same duty cycle as the input PWM.

That sounds similar to what we tried prior to going the Arduino route. We were trying to figure out how to do it with only 1 quad op-amp but it seemed that we needed to buffer the input PWM so we were at 5 op-amps; 2 input buffers, 1 saw-tooth generator and 2 more output comparators.