-1

I don't how to eliminate the effects of small oscillations when pressing a switch. I have a primitive switch and a led and I tried to create an interrupt that makes the LED blink when the switch is pressed. I did it in such a way that the interrupt fires on the falling edge of the input signal.


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

ISR(INT0_vect){
    PORTD ^= 1;
}

int main(void)
{
    DDRD = 1;//we set for input D port
    PORTD |= 1<<2;//pull-up for interrupt pin
    EICRA &= ~3;//clear EICRA and activate INT0 on LOW
    EICRA |= 2;//activate INT0 on falling edge
    SREG |= 1<<7;//I bit from SREG has to be enabled
    EIMSK |= 1;//enable INT0
    while (1) 
    {
        EIFR |= 1;//I've tried to clear the corresponding flag, but in vain
    }
}

pauk
  • 350
  • 4
  • 15
  • There are countless articles how to do this in firmware, or hardware, using Schmitt triggers, etc. Do you own google search. This is one simple step beyond blinking an LED. – TomServo Apr 04 '21 at 02:45
  • Please do not do as suggested below, this is terrible advice. Learn how to do proper software debouncing, or get a handful of 74-series Schmitt trigger chips and experiment. – TomServo Apr 04 '21 at 02:53

2 Answers2

-1

To debounce correctly, you must measure time in some way and you can do two things:

  1. Ensure that a certain thing is stable (this is not actually debouncing). For example, if your button had long wires, you could be worried of noises which could pollute the signal in. So you read the signal one time, and then a second time a little later, to check that the signal is still there and it was not noise. Perfect would be to monitor the signal for a period of time and, only if the signal didn't change in that period of time, take it as valid.

  2. Proper debouncing. When a signal from inactive state goes to active, you must ignore shorts falls (active-inactive-active) of the signal.

Your button gives you a low level when pressed, and you can ignore the noise. So, as soon the signal goes low, you know the button has been pressed. This is the perfect scenario for an interrupt. But, if the button bounces, it will lower and rise the signal very quickly - too much quickly for a human being, but not too much for an MCU. What you have to do is to ignore further "pressures" for a while, for example 1 millisecond, or 5, or 1/100 of a second, or so. Then, you have to count time (another interrupt, presumably).

Suppose you have an interrupt routine that fires every millisecond. In that routine, decrement a counter (if it is > 0). When the button is pressed, and the counter is zero, accept the button press and set the counter to a value. Further pressures of the button will be ignored, until the counter will "slowly" decay to zero thanks to the other routine (in interrupt) which measures time.

This is just an idea, there are many many ways to do just the same or even better (noise canceling).

In your case, you could also just sleep() (or do a delay loop) inside the interrupt routine you already have (not beautiful), or disable that interrupt for a given time (so you must measure time).

  • First of all thanks for the explanation. Nevertheless, I tried to do a trick and to clear the interrupt corresponding flag. I don't know why it does not work. Also, I've tried to put a delay within the ISR, again, no improvement. I really don't know how could I tackle this issue. Also, how could a fire another interrupt within an interrupt? Is there a way to do this? – pauk Apr 02 '21 at 15:14
  • Moreover, a delay cannot work as long as the edge-triggered external interrupts are of type 1, that is to say, when an interrupt is handled the bit for enabling the global interrupts is cleared, thus no other interrupts could be caught, but then, when an interrupt is encountered in this situation, a flag from an interrupt flag register is set so that the interrupt request will be posed immediately after the current interrupt is handled. – pauk Apr 02 '21 at 15:22
  • @pauk Beware of the difference between *interrupt flag* and *interrupt enable*. You should not clear the flag (request), you should disable the IRQ0 interrupt. True that the global interrupt in cleared, but as soon it enabled again, a pending flag is honored... so two flags must be managed, and with the correct order. – linuxfan says Reinstate Monica Apr 02 '21 at 15:26
  • Consequently, I tried to clear the interrupt specific flag so as to ignore the interrupt requests which came across whilst the previous interrupt was being handled by the CU. – pauk Apr 02 '21 at 15:26
  • Sorry, I don't have the manual at hands but: an enabled peripheral can set a flag; when the flag is true, an enabled interrupt can call the handler. If you want to completely cancel a pending interrupt you must first cancel interrupts, then disable the peripheral, then clear the request flag. Things can be different on different MCUs - may be that disabling INT0 (peripheral) the flag gets also cleared. – linuxfan says Reinstate Monica Apr 02 '21 at 15:30
  • Indeed, after the ISR is executed, a RETI instruction is required, but, after an interrupt is executed AFAIK it will necessarily be executed another instruction, different from an interrupt service routine. – pauk Apr 02 '21 at 15:31
  • I think you should disable INT0 in EIMSK, then clear the flag in EIFR (is there the request flag?) – linuxfan says Reinstate Monica Apr 02 '21 at 15:36
  • Here is provided more information about those registers: https://www.arxterra.com/11-atmega328p-external-interrupts/ . What could I say is that I have for those pins a register for direction, by which I specify when should an interrupt fire when encountering a LOW, rising edge, falling edge or any edge, EIMSK by which I enable a specific interrupt related to a pin, the status register, of which I's bit enables or disables globally all the interrupts and EIRF which is the register I was talking about, that contains those flags and could trigger some interrupts which couldn't be caught so far. – pauk Apr 02 '21 at 15:45
  • Even though, why should I disable INT0 in EMISK firstly? As long as the flag is cleared, even if the EMISK's bit is set so that interrupts from INT0 could be caught, why would there be pending interrupts anymore? – pauk Apr 02 '21 at 15:49
  • You can clear interrupt flag in EIFR (is there, I checked), and disable interrupt globally, but as long INT0 in EIMSK is enabled, as soon interrupt are free to fire they will. You must stop INT0, so new falling edges will not latch an interrupt request. The clear the interrupt request. After the wait (debounce) period is elapsed, you re-enable INT0 in EIMSK. – linuxfan says Reinstate Monica Apr 02 '21 at 15:49
  • Interrupts are *latched* in order to not miss the next interrupt while servicing the first one. Anyway, try to waste time in your interrupt handler - a couple milliseconds, that should be enough. Even if it is not beautiful, it will work in your simple program; maybe, clear the interrupt request after the delay. – linuxfan says Reinstate Monica Apr 02 '21 at 15:50
  • Well, as I said, while an interrupt is being handled, EIMSK's corresponding bit is cleared, thus, no other interrupts will be caught. Also, again, as I said before, I tried to put a delay within the ISR, but no results. – pauk Apr 02 '21 at 16:00
  • Now, I did a trick and it works, but in a peculiar way. My ISR is: ``PORTD ^= 1;_delay_ms(100);EIFR |= 1;``, but it behaves as if it were fired by any change, both the rising and the falling edge. – pauk Apr 02 '21 at 18:12
  • Perhaps I should post another article about this issue. – pauk Apr 02 '21 at 18:26
  • 3
    _Do not_ use a delay in an interrupt routine. – Clifford Apr 03 '21 at 07:32
  • Why? How am I suppose to do a debouncing? – pauk Apr 03 '21 at 17:03
  • @pauk Why? I explained that in my comment to the same issue in linuxfan's answer. How?: Any number of ways, but with care. I posted an answer but linuxfan spotted a flaw, the solution was more complex and relief on relative priorities if interrupts, without the means to test it or a lot of work wading through reference manuals, I was not prepared to post it. – Clifford Apr 06 '21 at 07:21
-1

The corrected code is:

#define F_CPU 16000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

ISR(INT0_vect){
    PORTD ^= 1;
    _delay_ms(10);
    EIFR |= 1;
}

int main(void)
{
    DDRD = 1;//we set for input D port
    PORTD |= 1<<2;//pull-up for interrupt pin
    EICRA &= ~3;//clear EICRA and activate INT0 on LOW
    EICRA |= 1;//activate INT0 on change
    SREG |= 1<<7;//I bit from SREG has to be enabled
    EIMSK |= 1;//enable INT0
    while (1) 
    {
        
    }
}

I mention that it still has issues when trying to trigger on rising or falling edge the circuit. When trying to do so I notice that the circuit behaves as if it were fired by any type of edge.

pauk
  • 350
  • 4
  • 15
  • I am starting to be confused... it is not clear what you want to achieve. The code you show should actually do what you want: debounce (for 10 ms) the button press. If you want, take a look at https://www.avrfreaks.net/forum/when-should-i-touch-eifr – linuxfan says Reinstate Monica Apr 03 '21 at 06:26
  • Clearing the flag is effective in the sense that, while waiting 10 ms, the flag can raise again because of a bounce - but that will not reenter the IRQ0 handler because the interrupt are disabled. Then, you clear the flag (avr is strange because you must write 1 to clear!) . It works. Instead, by touching EIMSK you *prevent* the flag to be set - it is nearly the same. Well, there is a difference: when IRQ0 handler is invoked, that flag is automatically cleared; at that point, the flag could be set again *before* you have the time to disable IRQ0. Nasty... but maybe it can rarely happen. – linuxfan says Reinstate Monica Apr 03 '21 at 06:34
  • 1
    _Do not_ put a delay in an interrupt! – Clifford Apr 03 '21 at 07:29
  • 1
    If you put a delay in an interrupt you block normal execution and also any lower or equal priority interrupts (or all interrupts if nesting is disabled). As such using the interrupt becomes pointless and you'd do better to poll and debounce the switch in the normal thread. The problem being that the debounce delay will adversely affect the timing of the entire system, defeating the purpose of using an interrupt in the first place. It is just bad practice. – Clifford Apr 03 '21 at 08:32
  • I don't think so because: firstly, whether I would poll, then the MCU has to perform one task, namely to poll the switches, and no other task could be performed meanwhile, also, if I would choose the polling technique, why should I be concerned with debouncing, as long as the poll is done periodically, with a delay established so that no oscillations are encountered. Also, what software method is supposed to be used in such cases, because debouncing is a frequent issue, and I suppose that it is not required extra-hardware in order to avoid this. – pauk Apr 03 '21 at 17:12
  • Also, I found the problem. It was due to the fact that by doing so - putting a delay instruction within the ISR - I avoided reading oscillations during the falling edge, but not throughout the rising edge, that's why the MCU detected an interruption request even if a rising edge came across and not a falling edge. – pauk Apr 03 '21 at 17:17
  • Also, I know that an interrupt service routine should be as short as possible, but I don't have any other feasible ideas in order to do this. – pauk Apr 03 '21 at 17:18
  • Last but not least, maybe I should post another article based on this issue. Perhaps this answer will save time for other people. Nevertheless, even if, as I said, I don't like this solution, I have no other ideas, so I would be interested in possible new solutions. Thanks! – pauk Apr 03 '21 at 17:20
  • @Clifford a delay in an interrupt handler blocks normal (main) execution, but if interrupts are re-enabled, other interrupts are not blocked. All depends on the application (in this case, all the application does is toggle an output). The main problem here is that for managing push-buttons, a polling approach would be better. But in this program, that simply triggers an event when a button is pressed, also a delay in an interrupt can work. Let's beginners understand step after step. – linuxfan says Reinstate Monica Apr 06 '21 at 07:06
  • @linuxfansaysReinstateMonica Interrupt nesting on AVR can be disabled. I am not saying it won't work in this instance; rather that it is bad practice. As a one-off you get away with it, but if you wanted a ubiquitous reusable solution that will work when reused in other applications or when this application becomes more complex, it is a poor solution. – Clifford Apr 06 '21 at 07:12
  • @Clifford there were two problems here: one was debouncing, the other was understanding the internals of IRQ0. I thought it was not the best solution to jump directly to "perfect" code which does not use the hardware capabilities of an IRQ. There are a few 'bits' to manage in the interrupt system, why not explore (and understand) them? About the interrupt nesting, I seem to remember that on AVR there are no levels, all boils down to (globally) enabling or disabling them. – linuxfan says Reinstate Monica Apr 06 '21 at 07:20
  • @linuxfansaysReinstateMonica There is definitely interrurpt nesting. I recall it is disabled by default. The reason I did not post my reworked solution is because it had hardware dependencies that I chose not to take the time to verify and lacked the means to test. It has been a long time since I developed for AVR. I could not guarantee without a lot of work that it was not (still) flawed. – Clifford Apr 06 '21 at 07:27