LM393 on interrupt pin, not working 100%

I’ve got a strange issue with a LM393 which is hooked up to one of the interrupt pins of a Mega 2560. The LM393 is outputting a logical 1 or 0 depending if the CNY70 on the input detects something, where the 1 is supposed to trigger an interrupt.

When the LM393 outputs a logical 1, the interrupt routine (set as RISING) is triggered which only does one thing: setting a volatile variable HIGH. Back in the main loop, an if-statement is called (which can only be called with that variable set as HIGH) which does some calculating and outputs the result to Serial3 which is hooked up to a HC-06 bluetooth module. At the end of the if-statement, the variable is set as LOW after which the Arduino is waiting for another interrupt.

The interrupt routine is called when it’s supposed to, but unfortunately there’s something going wrong. The output is supposed to be a 1 to 4 digit number depending on the amount of time between interrupts. That number appears. (although it’s hard to check if it’s 100% accurate), but after each interrupt, that number is followed by a bunch of zero’s (each on a new line) and -4480 appears a couple of times as well. Then it goes quiet on Serial3, till the next interrupt comes and then the same thing happens again: expected value, followed by a bunch of zero’s and -4480.

At first I thought the sensor and LM393 were generating some false positives directly after the pulse that’s supposed to be generated. But after connecting the LM393 to a regular digital pin (set as input), I only saw ones and zero’s passing by when they were supposed to, I didn’t see any false positives.
I also tried setting up the internal pull down (and later pull up) resistor on that interrupt pin, didn’t change anything. Also checked for shorts, didn’t find any.

And now I’m at a loss, does anyone have any ideas what this could cause?

Sketch:

#define ms_per_hour	3.6e6
#define cycles_per_kwh	375

int W = 0;
long starttime;
long duration;
volatile int wattpulse = LOW;  
 
void setup(){
        // open serial port 3:
	Serial3.begin(9600);
        attachInterrupt(0, watt, RISING);
        starttime = millis();
}

void loop() {
        if (wattpulse == HIGH) {
          duration = millis() - starttime;
          starttime = millis();
          W = ms_per_hour / ((cycles_per_kwh * duration) / 1000); // calculate current power usage
          Serial3.println(W);
          wattpulse = LOW;
        }
}

void watt() {
        wattpulse = HIGH;
}

edit: And it just got weirder:
After using a 9v power supply instead of an USB-power supply, the output cleared up, just a little.

Now I’m getting the expected value when the output of the LM393 becomes 1, then the zeros again, and then another value precisely when the output of the LM393 becomes zero, followed by some zeros.

It looks like the interrupt is triggered not only on rising, but also on falling, while it’s set to trigger only on rising. That, and the zeros are still there, which I can’t explain.

edit2: After some more Googling, I put together a RC-filter that has cleared up most zeros (guessing debouncing was needed after all) but not all of them, plus the falling-edge still triggers an interrupt.

Back in the main loop, an if-statement is called

If statements are not called. Functions are. If statements are evaluated.

volatile int wattpulse = LOW;

wattpulse is only assigned two values - HIGH and LOW. Do you really need an int for that?

        if (wattpulse == HIGH) {
          duration = millis() - starttime;
          starttime = millis();
          W = ms_per_hour / ((cycles_per_kwh * duration) / 1000); // calculate current power usage
          Serial3.println(W);
          wattpulse = LOW;
        }

What is going to happen if the interrupt happens in the middle of all of this? What happens if duration is 0?

Why are you NOT printing some kind of identification with the value?

What is Serial3 connected to?

mills() returns an unsigned long, so change the type of both starttime and duration.

Mark

PaulS:

Back in the main loop, an if-statement is called

If statements are not called. Functions are. If statements are evaluated.

Ah, my bad ;)

PaulS: volatile int wattpulse = LOW;

wattpulse is only assigned two values - HIGH and LOW. Do you really need an int for that?

I got this from an example on this website, didn't think there was something wrong with it. See http://arduino.cc/en/Reference/AttachInterrupt Blame the guy/girl who wrote that ;)

PaulS:         if (wattpulse == HIGH) {           duration = millis() - starttime;           starttime = millis();           W = ms_per_hour / ((cycles_per_kwh * duration) / 1000); // calculate current power usage           Serial3.println(W);           wattpulse = LOW;         }

What is going to happen if the interrupt happens in the middle of all of this? What happens if duration is 0?

Technically, duration can't be zero. The sensor is in front of the marked spinning disc in the power meter. If duration is (close to) 0, then for the disc to spin around that fast, the current power usage would have to be in the MegaWatt range. I don't see that happening in this house :P

PaulS: Why are you NOT printing some kind of identification with the value?

Because it's the only thing being printed at this time, didn't think it was necessary.

PaulS: What is Serial3 connected to?

Have you read the startpost? ;)

holmes4: mills() returns an unsigned long, so change the type of both starttime and duration.

Mark

Done :)

holmes4: This duration = millis() - starttime;is the wrong way round, See "blink without delay".

Mark

Please be more specific, I don't see anything wrong with it, plus I've used it in other sketches without problems.

I don't see that happening in this house

Sorry. That's the wrong mind set. You are writing code. You need to make sure that the code can't do something stupid, like divide by 0.

In any case, it isn't just that duration could be 0.

(cycles_per_kwh * duration) / 1000

Suppose that duration is less than 2. 2 * 375 = 750. 750/1000 = 0.

Because it's the only thing being printed at this time, didn't think it was necessary.

The only thing that YOUR code is printing. How do you know that some library somewhere isn't printing to Serial3? None should, of course, but if you printed W=xxx, instead of xxx, then you'd KNOW that any line that contained W=xxx was yours, and that, if there were any, any line that didn't contain W=xxx was NOT yours.

Have you read the startpost?

Not closely enough, apparently.

This ... is the wrong way round, See "blink without delay".

No. Addition would be the wrong way around. Subtraction is correct. And, since millis() will (almost always) be larger than starttime, the code is correct. In the rare case (around rollover) where millis() is less than starttime, subtraction is still guaranteed to work.

Your circuit would inherently be noisy as it does’nt utilize capacitors anywhere. The signal at the blue wire (from photo-transistor sensor ckt?) would also inherently be noisy, especially if your sensor is aimed at an electro-mechanical meter’s disk to sense the dark spot. If its an electronic type meter with IRLED, then this signal would be more responsive, but would probably have switching noise near the comparator’s trigger level.

Your LM393N comparator circuit should be adapted to have some hysteresis and some decoupling and filtering with capacitors.

PaulS:

I don’t see that happening in this house

Sorry. That’s the wrong mind set. You are writing code. You need to make sure that the code can’t do something stupid, like divide by 0.

In any case, it isn’t just that duration could be 0.

(cycles_per_kwh * duration) / 1000

Suppose that duration is less than 2. 2 * 375 = 750. 750/1000 = 0.

Ah okay, when you put it like that…

PaulS:

Because it’s the only thing being printed at this time, didn’t think it was necessary.

The only thing that YOUR code is printing. How do you know that some library somewhere isn’t printing to Serial3? None should, of course, but if you printed W=xxx, instead of xxx, then you’d KNOW that any line that contained W=xxx was yours, and that, if there were any, any line that didn’t contain W=xxx was NOT yours.

Guess I’ll have to do something about that :slight_smile:

PaulS:

This … is the wrong way round, See “blink without delay”.

No. Addition would be the wrong way around. Subtraction is correct. And, since millis() will (almost always) be larger than starttime, the code is correct. In the rare case (around rollover) where millis() is less than starttime, subtraction is still guaranteed to work.

Okay :slight_smile:

An update: I just switched to software debounce instead of using the (not so perfect working) RC-filter. That works a whole lot better, but still getting 2 output values per pulse. One on the rising edge (which is supposed to happen) and one on falling edge.
Screenshot of BlueTerm:

The odd thing is: The first value seems about right, this value also increases from ~300 to ~2700 watts if I turn on the oven to increase the powerusage which makes the disk in the meter spin around faster causing a shorter pulse time. While the other value becomes negative when the time between pulses gets shorter. Power usage down (longer pulse time) → the 2nd/fallingedge values get positive again and a whole lot higher than what’s correct.

I don’t suppose you can think of something that could trigger an interrupt on both rising and falling edge while rising is set?

#define ms_per_hour	3.6e6
#define cycles_per_kwh	375

int W = 0;                     // variable to store value watt
unsigned long starttime;       // variable to store starttime of puls
unsigned long duration;        // variable to store duration value
volatile int wattpulse = LOW;
 

long debouncing_time = 15; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;


void setup(){
        // open serial port 3 (bluetooth):
	Serial3.begin(9600);
        attachInterrupt(0, debounceInterrupt, RISING);
        starttime = millis();
}

void loop() {
        if (wattpulse == HIGH) {
          duration = millis() - starttime;
          starttime = millis();
          W = ms_per_hour / ((cycles_per_kwh * duration) / 1000); // calculate current power usage
          Serial3.println(W);
          wattpulse = LOW;
        }
}

void watt() {
        wattpulse = HIGH;
}

void debounceInterrupt() {
        if((long)(micros() - last_micros) >= debouncing_time * 1000) {
        watt();
        last_micros = micros();
        }
}

dlloyd:
Your circuit would inherently be noisy as it does’nt utilize capacitors anywhere. The signal at the blue wire (from photo-transistor sensor ckt?) would also inherently be noisy, especially if your sensor is aimed at an electro-mechanical meter’s disk to sense the dark spot. If its an electronic type meter with IRLED, then this signal would be more responsive, but would probably have switching noise near the comparator’s trigger level.

Your LM393N comparator circuit should be adapted to have some hysteresis and some decoupling and filtering with capacitors.

The sensor is a CNY70, so yes, phototransistor. I’m using both the ir-LED and phototransistor in that sensor to detect the marking, so no separate ir-LED and phototransistor. As for the meter in question: see attachment.

As for the capacitors, I eventually used a RC-filter to get rid of the noise, but that was only partially successful. Now I’m using software-debounce which works great straight away. Just leaves the problem of the interrupt on both rising and falling edge, which should only happen on rising.

As for the capacitors, I eventually used a RC-filter to get rid of the noise, but that was only partially successful. Now I'm using software-debounce which works great straight away. Just leaves the problem of the interrupt on both rising and falling edge, which should only happen on rising.

OK, so it appears you're confident with the hardware as is. It would be good to assume there would be a bit of noise or glitches at both the leading and trailing edge of the pulse.

Typical software debounce code will ONLY deal with the LEADING edge of a signal and not ignore "bounces" on the trailing edge. Since this seems to be where the problem is, I would use a software debounce solution that works on both edges of the pulse. This exists on this forum and elsewhere, but I don't have a link handy right now.

EDIT: A software solution won't prevent the interrupt from firing at each end of the pulse (if noisy at each end), but your code could determine when the input is stable to start/stop your tests.

Huh? :~ So increasing the debouncing-time in the sketch (which I just tried, set it to 100ms) doesn't help on the falling edge at all? Is there any way to figure out if it is noise triggering the interrupt on the falling edge or something else?

As for workarounds (just in case the cause is hard to find/fix), I don't suppose something as simple as a digitalread before the if and a 2nd argument in the if (checking if pin is still registering a 1 after the falling-edge interrupt) would do the job? edit: just tried that, didn't help.

edit2: "Fixed" it by using a counter: (counter % 2) != 0) as argument in the if and counter++ in the interrupt routine, now I can at least continue with the project. :)

Your counter solution is a great idea. This will allow you to adjust the debounce interval on both sides of the pulse based on iteration intervals (number of readings).

A while back, I was experimenting with debouncing here and did something similar. Only I ended up storing the reads in a mini shift register (byte), then tested for required bit patterns.

Good luck with your project.

EDIT:

edit2: “Fixed” it by using a counter: (counter % 2) != 0) as argument in the if and counter++ in the interrupt routine, now I can at least continue with the project. smiley

Additional note … I would ensure here that the debouncing doesn’t effect the measurement timing. Otherwise, this would create some additional jitter and fluctuations in your results. I would ensure that the measurements are still taken at each RISING interrupt and the debounce intervals be considered IGNORE intervals for disregarding additional interrupt(s).

I was trying out Digi-Key's schemeit and decided to re-interperet bartgrefte's circuit diagram.

Since the reflective light from the disk is normally present and decreases temporarily as the meter disk's black mark passes by, then the phototransistor output of the sensor is normally turned on, driving the signal low at the comparator's + terminal.

I'm not sure how the pushbutton was originally connected, but would suggest moving it to where shown below (untested), where pressing the pushbutton will turn off the sensor's IRLED. This should simulate the black mark detection and eliminate any possibility of interference due to combining the two control signals.

Actually, I don't see where any external or internal pullups are required as the 1K (R5) serves this purpose. Perhaps software debouncing isn't required either.

I think that the 220K resistor adds negative feedback. When the output is high, it raises the voltage on the inverting input, tending to drive the output back to low again; when the output is low, it lowers the voltage on the inverting input, tending to drive the output back high again. If the voltage divider is set at 50% - if the 100K potentiometer is set at 8K1 - my back-of-the envelope calculations say that the voltage on the inverting input will be either 2.54, output high, or 2.45, output low, in the steady-state condition.

That's not typical for a digital input. Typically, a small amount of positive feedback is applied, to drive the non-inverting input a bit higher when the output is high, and a bit lower when the output is low, to add some hysteresis.

If the input transition is slow or noisy, negative feedback will tend to make it switch more than once during an input transition. Here's how I see it, with the input and the output high in the steady state:

  • If the input drops slowly through 2.54V, the output will switch to low.
  • The open-collector output will actively ground the RC circuit on the output, and the output will be at 0V very quickly. The RC circuit on the output won't provide any filtering. The voltage on the inverting input will then quickly become 2.45V.
  • The voltage on the non-inverting input, dropping slowly through 2.54V, now at, say, 2,5V, will then be higher than the voltage on the inverting input, and the output will switch high again. If the input doesn't make its transition faster than the output of the LM393, there will be unwanted transitions on the 393's output. Depending on how slowly the input changes, it might make many transitions. The 393 is designed to make fast transitions on its output. My guess is that it will change considerably faster than the input will.

I think the circuit would operate better without negative feedback. My vote is to remove the 220K resistor, and see if it helps. You might consider putting a resistor of 10K or so between the sensor and the non-inverting input, and connecting the 220K from the 393's output to the non-inverting input. That would add positive feedback, making the 393's state a little bit "sticky," to use a technical term, and a little bit resistant to excessive switching. I also think that you want the transitions on the 393's output to be as fast as they can be, to avoid confusion about the output's logic level. The output capacitor makes the low-to-high transition slower. If it's really in the circuit, I'd recommend removing it.

Well, I completely forgot about this topic :-[

The debounce-code did help, but still got some false-positives. Increasing the debounce-time only caused the Arduino to miss pulses because the time the marking needed to completely pass the sensor got so short with high power usage that the debounce-code blocked it...

So I checked the last message in this topic and followed tmd3's tip about the 220k resistor (that did end up on the circuit board) and disconnected it. Then I removed the debounce-code and got very few false positives (lasting a couple of ms each), which might have been caused by the USB-powersupply, noticed a while a go that there are more false positives with the Arduino powered through USB instead of a standard 9v DC adapter But to be sure I put the debounce-code back in the sketch with only 20ms of debounce time.

And it's looking good so far :)

edit: Crap, still a false positive every now and then. Only I can't "fix" those with debounce-code since those false positives last more then a second (according to the millis function).

I ended up putting a resistor between the output and non-inverting input, like tmd3 said. Unfortunately the 220k resistor I hooked up caused the output to stay high after becoming high. Then I hooked up a resistor with a much higher value (1M) and that seems to have done the trick, for the most part.

There are almost no false positives anymore, but still not all gone.