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;
}
}