Author Topic: Why even bother with an interrupt for DIO0?  (Read 2838 times)

nemail

  • NewMember
  • *
  • Posts: 8
Why even bother with an interrupt for DIO0?
« on: April 04, 2020, 07:21:19 PM »
Hi,

why do we even need to use a hardware interrupt of the MCU for DIO0 handling?
I'm currently changing stuff to polling the Atmega pin so I can use the interrupt for other stuff...

response on github from Felix Rusu (https://github.com/LowPowerLab/RFM69/issues/137):
Quote
Because we need to be notified of transceiver events (like packet received) as soon as possible.
Please don't open any more issues if you just have a question, there is a forum for that.

My response to that:
yeah, that's what interrupts are for. But why do we need to know of transceiver events as soon as possible?
In my project, which is entirely non-blocking, it doesn't matter (in my opinion). I've changed the code to polling the pin on the 328p where DIO0 is connected (instead of using interrupts) and everything is working as expected.

Input on under what circumstanced issues could occur, would be very appreciated.

Thanks

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #1 on: April 04, 2020, 07:47:32 PM »
OK let's do this.  :)

So the difference between polling and interrupts is like this:
- polling is the kids asking you "are we there yet" on a roadtrip, every minute. It makes a lot more sense for the kids to use interrupts - request to be notified once when they "are there", and do other useful things with their available time.

To spin it differently also permit me the rhetoric follow up to your question: The entire planet is run by microchips with interrupt controllers and it is generally highly expensive to design chips - if we should not bother with interrupts, why then have the designers of this chip, or many others like it, bothered to design chips around interrupts? Obviously no answer is required.

For closure, here's an excerpt from the SX1231H transceiver datasheet:
An interrupt (PayloadReady) is also generated on DIO0 as soon as the payload is available in the FIFO.
If you poll, you will likely miss that event, and new packets will then erase the packet whose event your code did not bother to intercept because it was doing something else (got interrupted maybe :o) in between polls. Or if you block on receive, then you will never receive new packets, either way, you miss packets.

Maybe in your application that doesn't matter because the radio is low priority, or you're implementing a lossy protocol. Or are brute force polling that interrupt to make sure you don't miss anything (instead of ... obviously ... just telling it to tell you when to poll it).
In my lib I use an interrupt to flush the transceiver FIFO in internal buffers, and free up the transceiver for the next packet ready, as soon as possible.

FWIW - you can use other pins as interrupts, even all other purpose pins, using PCINTs.

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #2 on: April 05, 2020, 11:12:46 AM »
Hi there,

thanks for explaining interrupts, I'm sure someone out there who doesn't know about them will find that post very useful :-)

Maybe I have to explain that I wanted to get rid of the interrupt for the RFM69 module because I need both hardware interrupts of the Atmega328p in my project for other stuff, not just out of the blue without any reason.

I have solved it like this:
- new Process() function which polls the DIO0 pin and gets called in the main loop regularly
- Calling Process() in receiveDone()

New bool variable at the top of RFM69.cpp:
Code: [Select]
bool LastPinState;

New "Process" function:
Code: [Select]
void RFM69::Process(void)
{
// Detect RISING edge "interrupt"
if (LastPinState == LOW && digitalRead(_interruptPin) == HIGH)
{
LastPinState = HIGH;
_haveData = true;
return;
}

// Detect FALLING edge "interrupt"
if (LastPinState == HIGH && digitalRead(_interruptPin) == LOW)
{
LastPinState = LOW;
return;
}
}

Calling "Process()" from receiveDone:
Code: [Select]
bool RFM69::receiveDone()
{
Process();

if (_haveData)
{
_haveData = false;
interruptHandler();
}
...

I really can't see where this could cause problems, delays, missed packets, etc.
You have to call receiveDone() anyway if you are waiting for data. Because the pin is polled directly in receiveDone() there is no difference to the interrupt here.
The interrupt would have set the _haveData bool to true much earlier maybe, but that bool isn't queried until we call receiveDone() anyway. So again, I can see absolutely no difference here, except for the fact that we are waisting CPU cycles for reading the pin register continuously. Especially the digitalRead() may be a hog but if you really want (or need) to, you could work around that as well by directly reading from the register. The interrupt is being kept high on the RFM69 anyway, as long as the FIFO isn't cleared. So you don't miss anything, you do not even get to read the data later because you poll the pin right before you look for data.

Don't get me wrong, I don't want to convince anyone to stop using an interrupt for the radio here. I just figured that I could make a better use of the interrupts in my particular project and simply wasn't able to wrap my head around why polling the pin instead of using interrupts for the could cause any issues.

@PCINT: they trigger on every pin change, so you can't configure them to only trigger on a rising edge. You'd have to keep track of the previous state here. Doable, but the library doesn't support it, just like it doesn't support polling. If I'd be better at GIT, I'd create a push request which implements defines where you can choose from INT, PCINT and polling behaviour...


edit:
The Process() routine might come in handy anyway, if one would rewrite the library to be non-blocking. Currently blocking happens quite a few times but this would be avoidable if the library would be a proper state machine. Good code (I'm talking about any application which may use the library) should be non-blocking as well anyway, so I don't see any issues here if we would have to call Process() to keep the state machine going. If the user-made code is slow or blocking you would miss packets anyway, if the FIFO isn't read and cleared fast enough, so blocking within the library brings no benefit in my opinion. That is what retries are made for, anyway, to resend packets which have been missed by the receiver.
« Last Edit: April 05, 2020, 11:21:01 AM by nemail »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #3 on: April 10, 2020, 01:33:20 PM »
Quote
I have solved it like this:
- new Process() function which polls the DIO0 pin and gets called in the main loop regularly

Wait, you still use the interrupt pin. So how is that shared between the library and other code?

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #4 on: April 10, 2020, 02:16:11 PM »
Huh? I think there was a misunderstanding. I was talking about the interrupt pin on the Atmega328p. DIO0 of the radio is now connected to a random IO pin of the Atmega. So I can use INT0 and INT1 of the Atmega for other stuff.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #5 on: April 10, 2020, 04:59:22 PM »
Ok that was not clear, since _interruptPin means ... interrupt  :'(
A better name would be ... "DIO0Pin".

Anyway, how do you wake your MCU from sleep when the radio receives a packet, by the polling method?

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #6 on: April 10, 2020, 05:34:02 PM »
I do not, because the radio sleeps as well. Is the radio module able to receive packets even when asleep?
If so, that would absolutely be a valid use case for the Atmega Interrupt pin.

edit:
Quote
Ok that was not clear, since _interruptPin means ... interrupt
You are correct, I didn't rename that variable yet. However it is not an interrupt pin on the Atmega anymore.
« Last Edit: April 10, 2020, 08:44:11 PM by nemail »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #7 on: April 13, 2020, 09:03:59 AM »
The radio may be in RX (always ON RX) or Listen mode (which was dubbed buggy but that's irrelevant to this discussion), independent of the MCU. And it has the ability to wake the MCU through interrupts.
BTW there are many other interrupt modes for this transceiver, which are not currently used in my RFM69 library, albeit they could be useful in more advanced uses of the transceiver.

You mention "good code" and "proper state machine". Then you mention polling as the answer to all the problems of the interrupts and blocking library code.
While a proper state machine could be implemented, it would be much more spaghetti like, and confuse everyone who wants to make sense of this library. It is much easier to implement a state machine in a deterministic environment where you don't have interrupts happening at random times.
On some architectures like ESP there is the yield() call exactly for this blocking problem. On small chips like AVRs we have a choice, to block while the transceiver changes states, to ensure we're ready to transmit or that a packet was transmitted. Those are really the places where it "blocks". We can implement some sleep and other things to squeeze more sleep out of that but I believe otherwise the interrupts are the better approach to using the packet ready which I see as an event you don't want to miss, like after you just polled.

If you like polling I propose a better, pin-less approach, why don't you poll the IRQ registers of the transceiver, then you can check all the interrupts not just 1. You have another free GPIO.  ;)

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #8 on: April 13, 2020, 09:41:13 AM »
I'm not saying polling is always better than using interrupts. In this particular case, where we're not using the listen mode and sending radio + MCU to sleep ASAP, pin polling is better than interrupts because we can use the interrupt pins of the AVR for other stuff.
It is just that using an interrupt pin like you're doing it in your library, with the current features implemented, not using listen mode, just doesn't make any big sense, except from saving cycles for regularly polling a pin.
Speaking of saving cycles: polling the IRQ registers would certainly use more time to process than to simply poll the GPIO of the AVR.
Although it would be a feasable solution if you want to check all the interrupts like you mentioned.

My previous question, which you didn't quite answer, was whether the radio can receive messages while it is asleep. That would be the only reason for me to hook it up to an interrupt pin of the AVR (if I want the radio to sleep by default as well) but I doubt that it can do that (receiveing messages while asleep), at least when not using listen mode.

Regarding spaghetti code, I guess that's a question of how it is implemented. I still think, the non-blocking way would be better. You usually don't want your code to stop anywhere and wait for a timeout or something like that. You could fire a callback instead, which would notify you about the timeout. Also, if the library was implemented non-blocking, we could make use of the interrupt and maybe call the radio.process() function (which doesn't currently exist, obviously), more often in the code so radio stuff would be processed more often in between the code, than now. That way, interrupt events would get processed faster than now.

Of course, I understand that this approach probably wouldn't be as easy to understand by all the Arduino beginners out there but on the other hand, I'm seeing a great opportunity to educate people. Good, comprehensive examples and documentation can really push people forwards.

That all being said, I guess a good approach would be to make the interrupt pin in your library optional and to let people choose whether they want to poll or not.

On small chips like AVRs we have a choice, to block while the transceiver changes states, to ensure we're ready to transmit or that a packet was transmitted. Those are really the places where it "blocks". We can implement some sleep and other things to squeeze more sleep out of that but I believe otherwise the interrupts are the better approach to using the packet ready which I see as an event you don't want to miss, like after you just polled.

Wait. You are still talking about not wanting to miss the packet ready event.
First of all, you cannot miss the event, according to my understanding, because the DIO0 pin STAYS HIGH (it is active high) until you clear the FIFO buffer (by e.g. reading it). There is NO way you gonna miss that event.
Second, which stays valid even if I'd be wrong in the first part: if one is calling the function radio.receiveDone(), this function looks whether the _haveData boolean has been set by the ISR. How often do you call radio.receiveDone()? I'm doing it in my main loop in one of my stuff-processing functions. receiveDone() is being called in every loop cycle and between that, all other code is being executed. So the chance of "missing" the event (which isn't even possible according to my first statement) is just as high as if you would directly poll the GPIO whether it is in a HIGH state or not.

The only way of missing the event would be, if the DIO0 pin would go LOW again, before you had a chance to poll it. But that, according to my understanding of the datasheet, is not possible because DIO0 will stay high until you clear the FIFO buffer (as I already mentioned). Depending on the amount of code the AVR has to process, it can take quite a while until the AVR gets to read the FIFO buffer ANYWAY, because your ISR only sets a flag and doesn't trigger FIFO reading in any direct way. So IF there was any expiry of the FIFO buffer or the dataready event, we would hit it anyway, regardless of interrupt or pin polling.

All that only applies if you don't want to wake up your MCU using the radio, using an interrupt pin of the MCU.

edit:
And again, because I think there is still a very basic misunderstanding - you wrote earlier:
Quote
In my lib I use an interrupt to flush the transceiver FIFO in internal buffers, and free up the transceiver for the next packet ready, as soon as possible.

YES, you are setting the flag _haveData almost instantaneously but the point in time where the data is being read from the FIFO and therefore also when it is cleared, depends solely on the user code because one needs to call receiveDone(). Depending on the code, this can (should not but can) take ages.
Same applys to polling the DIO pin, except, that the pin gets polled in every loop cycle PLUS when checking (receiveDone()). So the data read operation from the FIFO happens exactly as fast as if you would use the interrupt pin. Absolutely no difference.
« Last Edit: April 13, 2020, 09:49:38 AM by nemail »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #9 on: April 13, 2020, 06:41:43 PM »
In a perfect world I would keep the conversation going, but the TLDR is that:
- I created the library mainly for Moteinos and other hardware and infrastructure I build around it
- the interrupt therefore will remain in place, at least for now

FWIW I did answer your wake from sleep question, first line in my previous post:

The radio may be in RX (always ON RX) or Listen mode (which was dubbed buggy but that's irrelevant to this discussion), independent of the MCU. And it has the ability to wake the MCU through interrupts.

So ... that is useful. Can't do that with polling  :-X

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #10 on: April 13, 2020, 06:48:13 PM »
so the only part profiting from interrupts is the buggy listen mode.
you're right, that wouldn't work with polling. also saving cpu cycles with not having to poll is a good thing. although if you need both 328p interrupt pins and you're not going to use listen mode, there is absolutely nothing wrong with polling the pin (if you have some spare cpu cycles to spend on that).

that's my conclusion and I hope it is not that far off of yours.
regardless whether you're going to change your library or not. it is your code, nobody can or will try to force you.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6866
  • Country: us
    • LowPowerLab
Re: Why even bother with an interrupt for DIO0?
« Reply #11 on: April 13, 2020, 07:51:46 PM »
IMO polling is useful if you have to go with the 328p and you really can't use any of its dedicated hardware interrupts, and can't get around the PCINT's limitations, and you never have the need to wake the MCU from an interrupt - even then, like I mentioned above - I would simply not use any GPIO at all, instead I would just poll the IRQ register (i believe that's even faster than a digitalRead if my memory serves me well from last time I looked at this interrupt and SPI traffic on a logic analyzer). So its not to be discarded. However I think the easier path to all those when you can use an interrupt is ... to use the interrupt, that is what it was designed for and makes your life easy.

As the sole maintainer and gate keeper of the library I have to worry about much more than just verifying that a change passes checks and may be a good idea.
The ripple effects from any change can have a lot of impact that may backfire in ways it's sometimes unpredictable.
For the topic at hand, it may seem like it's just a simple pull request and a merge, but to restructure it around polling (or optional polling) would require a lot of changes, including in the sub libraries, including all the examples would need to change or add a whole new set, and document the changes, then deal with the confused (read angry) crowd that now opens N new threads here about "why bother with polling when the interrupt worked perfectly well??". I will happen and I don't look forward to it, the gain vs effort seems not worth it.

FWIW the MoteinoMEGA has 3 interrupts, and the MoteinoM0 (SAMD21) has far more interrupts. Sometimes its easier to just use a different chip than to restructure a library.

lemonforest

  • NewMember
  • *
  • Posts: 16
  • Country: us
  • breaking all the things
Re: Why even bother with an interrupt for DIO0?
« Reply #12 on: June 05, 2020, 10:20:11 PM »
so the only part profiting from interrupts is the buggy listen mode.

I don't understand how you've come to this conclusion.  A sleeping uC can be woke from an interrupt.  A global short could hold your last pinchange ISR so that when you check register flags, you'd know if it used to be high or low and infer what state caused the pinchange ISR. If you wanted to sleep and not using interrupts, you'd find yourself using the wdt which itself uses an internal interrupt to wake or reset.  If I somehow decided that I needed several uCs performing a Fibonacci sequence race, I could use an interrupt to indicate to all the others that someone already finished and they can quit now instead of waiting to ask if someone else had already finished x minutes/hours later. Button debounce can be handled cleaner with a pcint, especially if you've forgone the RC debounce goodies.  I'm not trying to say that there isn't a valid argument for using polling for digital inputs, just that you haven't presented it.  Parallel input reading sounds like a good case for polling granted I think I'd still copy the port registers.  Wire's digitalRead/digitalWrite are kinda on the slow side in comparison iirc.

I don't really know how else to drive home that polling is okay sometimes but you'll eventually find yourself stuck between a rock and a hard place if you think of it as blasphemous.  If not for interrupts, my day would would be an endless cycle of picking up my phone to see if i have an incoming call, walking to the bathroom to find out if I need to crap, walking to the mail drop to see if the mail has arrived yet, then eventually sitting back down in my chair only to pick up my phone again.

nemail

  • NewMember
  • *
  • Posts: 8
Re: Why even bother with an interrupt for DIO0?
« Reply #13 on: June 05, 2020, 10:57:18 PM »
hi

i guess you got me wrong. I'm not against interrupts in general. Solely under the described circumstance it seems pointless to me.
Paint me a scenario, where the DIO0 interrupt will be of use with this library.

Listen Mode is buggy. So what remains is to put the mcu to sleep and let the radio stay awake. wooohoo, my mcu is pulling 3µA instead of 20mA from the battery but the radio is still draining several (if not tens of) milliamperes because of not sleeping so that it can receive packets and wake up the mcu. FAIL. for low power applications, this scenario is useless.

lemonforest

  • NewMember
  • *
  • Posts: 16
  • Country: us
  • breaking all the things
Re: Why even bother with an interrupt for DIO0?
« Reply #14 on: June 06, 2020, 12:15:21 AM »
if you're using AES, encrypt/decrypt can take up to 28uS for max supported packet length so even if you're using 8 bit avr internally clocked at 8MHz, and neglecting the few instructions that might take up to 5 clock cycles, depending on how you want to look at it that's an average of 1 to 2 cycles per instruction that you're either polling for data on the FIFO you know can't be ready yet or clock cycles wasted polling for data that isn't even there if you aren't transmitting that often.

Edit. radio Tx burst doesn't reach even 100mA on the units I have and it averages out under 14mA if I'm sending once every tenth of a second last I checked.

Another Edit: happen to have an uno with the adafruit breakout board for the HCW so at 5V I'm seeing 17.05mA listening. it does have additional goodies on it for level shifting.  previous edit above was at 3.3v setup.

Yet Another Edit: chanced across this while going over the datasheet again and thought showing the radio's use of interrupts might be useful.
« Last Edit: June 06, 2020, 07:22:48 PM by lemonforest »