using external interrupts to measure PWM

Hello,

I've got a standalone project using and ATmega328P-PU where I want to measure the pulse length of an external (not generated by the ATmega itself) PWM square signal, and I want my circuit to do something, switch a relay, if the pulse length goes below 13 milliseconds.

Am I correct in assuming that this is going to require both external interrupt pins on my Atmega to be used?

I imagine that one of the two interrupt pins would watch for a "rising" signal, and the other interrupt pin would watch for a "falling" signal, and the pulse length would then simply be measured as the time difference between the last "rising" event and the last "falling" event?

And do I just connect both interrupt pins to the signal source using the same wire? Or do I need to put resistors between the two interrupt pins or something?

Am I correct in assuming that this is going to require both external interrupt pins on my Atmega to be used?

Why? If you use CHANGE as the interrupt type, your ISR will be called on both the RISING and FALLING edges. It's easy to determine whether the call was for the RISING edge (the pin is now HIGH) or the FALLING edge (the pin is now LOW).

So you're saying, just measure the time difference between changes? Wouldn't that mean that the ATmega won't know if it's measuring signal peaks or signal troughs?

Just to illustrate, here's the square signal I want to use to tell my ATmega when to switch the relay:

The "high to low ratio" of the signal appears to stay the same at different pulse widths. What changes is the time length that the signal stays "high". The "lows" are constantly about 1.85 times the length of the "highs".

Can't you use pulseIn() command and just use 1 pin to measure high & low durations?

Check the pin in the ISR and you'll know if it is a peak or not.

CrossRoads:
Can't you use pulseIn() command and just use 1 pin to measure high & low durations?

Yes but it is blocking.

CrossRoads:
Can't you use pulseIn() command and just use 1 pin to measure high & low durations?

From what I've read, this would mean that the Atmega couldn't be doing much else in the mean time.

Just to outline the project:

I've built a muting device for my car. It switches the stereo to mute, using a relay as part of my circuit, when the car is in reverse, so that I can hear the beeping of the parking sensors without having to turn down the volume knob. I want the mute to stay activated (i.e. the relay switched to mute) for 20 seconds after I've put the car back out of reverse. So that the stereo doesn't come back on all the time while you are still maneuvering back and forth. But if you put the car out of reverse and are pulling back into moving traffic, I want the mute to be deactivated as soon as the car reaches about 20 kph (~12.5 mph). Because it'd be annoying if you're already back in traffic but still have to wait 20 seconds for your music to come back on.

Many current-model cars have this as standard, and I want to make a device that does the same thing for my somewhat older car. I've already built the circuit, it's ready to use, the only thing that's still missing is a few lines of code for the ATmega to incorporate the PWM speed signal.

So far so good, I've measured the speed signal of my car at different speeds, which is the PWM curve shown in the picture above, and at 20 kph (12.5 mph), the pulse length happens to be pretty exactly 13 ms. So that's going to be my cutoff. If the pulse gets shorter than 13 ms, it means the car is doing more than 20 kph.

Another question: Does the interrupt have to be defined in the setup section of your code? I just think it'd be unnecessary if the interrupts monitor the speed signal constantly, even if you're just idly going down a highway for hours. Could it be done so that the interrupt only watches the PWM signal while the mute is activated?

And one more question: In the picture above, you can see that there's a bit of noise, and that the curve isn't a crisp rectangular shape but has a few "wobbles" in it. Will the ATmega be able to work with this and still tell the highs from the lows with accuracy?

So you're saying, just measure the time difference between changes? Wouldn't that mean that the ATmega won't know if it's measuring signal peaks or signal troughs?

Between appropriate changes, yes. You can tell when the change is to HIGH, and record that as toHigh. You can tell when the change is to LOW. Record that as toLow. When one of the changes happens (you need to decide whether it is the change to HIGH or the change to LOW that is important), subtract the later change time from the earlier change time.

Does the interrupt have to be defined in the setup section of your code?

The interrupt is external. The interrupt handler is usually attached in setup(). There is generally no reason to be detaching and reattaching the handler.

I just think it'd be unnecessary if the interrupts monitor the speed signal constantly

Why does it matter? The Arduino is constantly checking for serial input, for clock ticks, and myraid of other "background" events.

In the picture above, you can see that

Actually, I can't see your picture, because of where it is hosted.

Will the ATmega be able to work with this and still tell the highs from the lows with accuracy?

That's why you test. Presumably, the signal doesn't happen just once while you are backing up. So, average the times over a dozen cycles, to get an average time.

ok sorry... so at the bottom of this post, there's a picture of the PWM signal I got at 20 kph/12.5 mph:

That looks pretty darned square to me.

ok, so I have started typing up an interrupt service routine to measure pulse length widths whenever a change is detected by the involved interrupt pin.

Here's my code so far... it's untested yet.

As ISRs are still new territory for me, my question is more if so far, I still have any grave misunderstandings about the nature of them:

// ISR, triggered by interrupt CHANGE

void DeactMute(){

//ISR is only executed if the mute is activated:
	
if(MuteWasOn){
  
//Checking state of PWM vehicle speed signal (a.k.a the GALA signal)
  
  if(digitalRead(pwm) == HIGH && !countBegin){

    
//Milliseconds are counted from the moment that the signal goes "HIGH"
	
            time = millis();
            MillisAtHighChange = time;
	
//We've started counting, so, note to ourselves:

            countBegin = true;
	
    }


//If the change is to "LOW", we compare the two time markers of the "LOW" and "HIGH" changes:

else if (digitalRead(pwm) == LOW){
	
            time = millis();
            MillisPulseLength = time - MillisAtHighChange;

//We've stopped counting, so countBegin goes back to "false":
            countBegin = false;


//If the pulse width is shorter than 13 milliseconds and therefore the car's speed greater than
//20 kph (12.5 mph), the radio mute is deactivated.

            if(MillisPulseLength<=13) GALAdeact = true;
				
}
}
}

carguy:
The "high to low ratio" of the signal appears to stay the same at different pulse widths. What changes is the time length that the signal stays "high". The "lows" are constantly about 1.85 times the length of the "highs".

Then it's not PWM. You are describing FM. You may as well look at only rising or falling edges, and measure the total period. Everything you are doing to differentiate rising and falling edges is a waste of brain cells, memory, and processing time.

aarg:
Then it's not PWM. You are describing FM. You may as well look at only rising or falling edges, and measure the total period. Everything you are doing to differentiate rising and falling edges is a waste of brain cells, memory, and processing time.

But what do you think of my ISR then, which I posted above? It kind of seems to me like an efficient way to calculate the time between rising and falling.

carguy:
But what do you think of my ISR then, which I posted above? It kind of seems to me like an efficient way to calculate the time between rising and falling.

What's the use of an efficient way to do the wrong thing? A mistake which forces an approach that is way more complicated than it needs to be.

So, unless you want to change that, I'd have to say I don't like it much.

Also, although pulseIn() is a blocking routine, the pulses are so short that it may not affect the rest of your sketch, so you might be able to use it.

ok I think I get what you're driving at...

So the LOWs are always a constant 1.85 times longer than the HIGHs, that would mean that the time between two "rising" edges at 13ms/20 kph is 13 * 2.85 = 37.05 ms

So if I use 37 or 38 milliseconds as my cutoff below which I want to deactivate the mute (the signal gets "faster" with increasing vehicle speed), that means I only have to calculate the time between two RISING events? Does it not matter to the interrupt pin that there was a LOW period in the mean time?

carguy:
ok I think I get what you're driving at...

So the LOWs are always a constant 1.85 times longer than the HIGHs, that would mean that the time between two "rising" edges at 13ms/20 kph is 13 * 2.85 = 37.05 ms

So if I use 37 or 38 milliseconds as my cutoff below which I want to deactivate the mute (the signal gets "faster" with increasing vehicle speed), that means I only have to calculate the time between two RISING events? Does it not matter to the interrupt pin that there was a LOW period in the mean time?

You've got it. To answer the last question, no, when it is configured to respond to rising edges, everything else is a "don't care".

By the way, I suggest making the ISR only measure the period. You should perform the threshold logic in the main program.

You also have to deal with boundary conditions, like when the speed=0.

aarg:
You also have to deal with boundary conditions, like when the speed=0.

IIRC, the signal isn't "flat" when the car is standing still at zero speed. I'd have to go back to hooking the car's speed signal up to my oscilloscope again, but I think both when you've only got the ignition on and when the engine is on but the car is standing still, you get a similar looking signal with different cycle widths.

So far, I've got my muting device designed so that it will only get power from the car battery when the ignition is on, because I see no point in having it "always-on", if the engine/ignition isn't on and you're not actually maneuvering the car.

ok so now I have radically simplified the ISR portion of my sketch:

void setup(){

...
...
...
...


 attachInterrupt(pwm, DeactMute, RISING);

}


...
...
...
...


void DeactMute(){

	
if(MuteWasOn){
 

 
  if(!countBegin){

	
       time = millis();
       MillisAtFirstChange = time;
       countBegin = true;
	
    }

  
  else{
	
 
       time = millis(); 
       CycleLength = time - MillisAtFirstChange;
       countBegin = false;
				
     }


}
}

The threshold logic is now within the script's loop() function, also as you suggested. Still haven't uploaded and tested the sketch, need to even out a few little "niggles" in the loop first before that will make any sense.

ok so now that my ISR for the vehicle speed-sensitive interrupt is done and working, I've had the idea to use the second interrupt pin to watch for changes of the car stereo's volume knob. My car stereo has a rotary encoder to set the volume, and I've looked at the signal that the rotary encoder produces with an oscilloscope, and it will probably be enough to simply solder a wire onto one of the two positive terminals of the rotary enoder and then have the Atmega's interrupt pin listen for CHANGE events. And then when the volume knob is turned, the mute will shut itself off (but reactivate itself again when it senses that the vehicle is moving backwards again).

To keep the interrupt from reacting to random one-time signal spikes, I want to make it so that you have to turn the volume knob/rotary encoder five notches before the self-shutoff kicks in.

As ISRs should apparently be kept brief, I am wondering if what I have so far is a bit much to be put inside this ISR:

//ISR for volume change events on second interrupt pin

void VolumeChange(){


if(muteOn){
 

//ISR is only called if the car is in reverse and the mute has been activated


 if(VolumeCounter == 0){
 
 timeVolume = millis();
 
 }
 
 timeNow = millis();
 
 timeCutoff = timeNow - timeVolume;


// If more than two seconds pass between everytime you turn the knob one notch, the counter 
// goes back to zero and the CHANGE event is treated as an isolated, irrelevant signal spike
 

 if(timeCutoff < 2000){
 
 VolumeCounter++;
 
 if(VolumeCounter>=5){
 
 VolumeCounter = 0;
 VolumeInterrupt = true;
 }
 
 else if (VolumeCounter <=5 && timeCutoff > 2000){
 
 VolumeCounter = 0;
 VolumeInterrupt = false;
 
 }
} 
}
}

Should I put the counting logic elsewhere in my sketch, or will it be ok to leave it like this?

Should I put the counting logic elsewhere in my sketch, or will it be ok to leave it like this?

There is not that much going on in the ISR. The one thing that concerns me is the multiple calls to millis(). While an ISR is executing, interrupts are disabled, so millis() always returns the same time.