(resolved) handling GPIO interrupts
(resolved) handling GPIO interrupts
Hi all -
I've been reading the examples, and Neil's book, on the topic of handling GPIO signals. I'm familiar with the notion of registering callbacks and/or ISRs to be invoked when something happens, but the concept of queues for this purpose is new to me, and I'm not sure I understand the value of using them. Is the purpose of the queue to hold multiple interrupts if the application can't finish servicing one before another comes in?
My application needs to be told when a hardware momentary button (there will be only one button) is pressed. When this happens, my app will compose a small XML message and send it via a socket on a WiFi network. I'd be perfectly happy throwing away any additional button presses until I'm ready for another one. With this in mind, can I skip the queue mechanism (observing the "less is more" philosophy), or is it considered "best practices" to use queues?
Thanks...
I've been reading the examples, and Neil's book, on the topic of handling GPIO signals. I'm familiar with the notion of registering callbacks and/or ISRs to be invoked when something happens, but the concept of queues for this purpose is new to me, and I'm not sure I understand the value of using them. Is the purpose of the queue to hold multiple interrupts if the application can't finish servicing one before another comes in?
My application needs to be told when a hardware momentary button (there will be only one button) is pressed. When this happens, my app will compose a small XML message and send it via a socket on a WiFi network. I'd be perfectly happy throwing away any additional button presses until I'm ready for another one. With this in mind, can I skip the queue mechanism (observing the "less is more" philosophy), or is it considered "best practices" to use queues?
Thanks...
Last edited by mzimmers on Thu Dec 06, 2018 9:45 pm, edited 1 time in total.
Re: handling GPIO interrupts
Howdy,
The notion of an interrupt service routine is to "literally" get out of it as soon as possible. When an interrupt occurs, you will suspend what you were previously doing, process the interrupt request and then get back to what you were previously doing.
Imagine two scenarios ...
The first is that you process the interrupt in its entirety when it occurs. Imagine the door bell rings. (I kid you not ... the moment I typed ... "door bell rings" ... my door bell rang!!! WOW!!! Now THAT was spooky! ... however I can use this). At the door is a neighbor who wants to borrow your lawn mower ... which you are happy to do. You walk with into your garden, you unlock your shed, you pull out the lawnmower and hand it over and chat for a bit. All that takes 5-10 minutes. Now you go back to what you were doing which might be:
1. Cooking ... and now your food is burned (or your house on fire).
2. Filling the bath tub with the baby in it (lets not talk about that any more)
3. Watching live TV and now you missed the outcome of the sporting event that was in its last few minutes.
The second scenario is that ... again ... the door bell rings. You answer it and the neighbor wants to borrow your lawn mower. This time, you tell him you are busy and will bring it over shortly. You write a note to yourself on a sticky and go back to exactly what you were doing. 30 seconds has passed from the ring of the door bell to what you were previously doing.
When you finish what you were doing, you look at your to-do list and see a sticky note to take the lawn mower to the neighbor which you then do.
And THAT is the difference between handling an interrupt when it occurs or placing a "message" in a queue. You sometimes can't handle an interrupt when it occurs because if you are interrupt processing, then there is something else that you are otherwise not doing.
The pattern of using a queue to hold work to be done when you are free is pretty solid. The down-side (of course) is that there is now a latency between when the interrupt occurs and when the interrupt is actually processed. For some patterns, this latency has to be as short as possible and these are situations where it might be possible to process the request in the interrupt but those should be very, very rare. In your story (compose an XML document, send it via sockets over WiFi) ... I don't believe that should ever be done in an interrupt handler.
The notion of an interrupt service routine is to "literally" get out of it as soon as possible. When an interrupt occurs, you will suspend what you were previously doing, process the interrupt request and then get back to what you were previously doing.
Imagine two scenarios ...
The first is that you process the interrupt in its entirety when it occurs. Imagine the door bell rings. (I kid you not ... the moment I typed ... "door bell rings" ... my door bell rang!!! WOW!!! Now THAT was spooky! ... however I can use this). At the door is a neighbor who wants to borrow your lawn mower ... which you are happy to do. You walk with into your garden, you unlock your shed, you pull out the lawnmower and hand it over and chat for a bit. All that takes 5-10 minutes. Now you go back to what you were doing which might be:
1. Cooking ... and now your food is burned (or your house on fire).
2. Filling the bath tub with the baby in it (lets not talk about that any more)
3. Watching live TV and now you missed the outcome of the sporting event that was in its last few minutes.
The second scenario is that ... again ... the door bell rings. You answer it and the neighbor wants to borrow your lawn mower. This time, you tell him you are busy and will bring it over shortly. You write a note to yourself on a sticky and go back to exactly what you were doing. 30 seconds has passed from the ring of the door bell to what you were previously doing.
When you finish what you were doing, you look at your to-do list and see a sticky note to take the lawn mower to the neighbor which you then do.
And THAT is the difference between handling an interrupt when it occurs or placing a "message" in a queue. You sometimes can't handle an interrupt when it occurs because if you are interrupt processing, then there is something else that you are otherwise not doing.
The pattern of using a queue to hold work to be done when you are free is pretty solid. The down-side (of course) is that there is now a latency between when the interrupt occurs and when the interrupt is actually processed. For some patterns, this latency has to be as short as possible and these are situations where it might be possible to process the request in the interrupt but those should be very, very rare. In your story (compose an XML document, send it via sockets over WiFi) ... I don't believe that should ever be done in an interrupt handler.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
-
- Posts: 9739
- Joined: Thu Nov 26, 2015 4:08 am
Re: handling GPIO interrupts
Additionally, note that you cannot do a fair few things in an ISR context; you're supposed to only call functions specifically marked as ISR-compatible (like the Freertos ...FromISR functions) in it. This means that creating an XML-message in an interrupt would probably work, but sending that message would need to be done outside of the interrupt.
Re: handling GPIO interrupts
EDIT: I posted this before I saw the replies to my OP...thanks guys for the information.
Update to my question...after doing a bit more reading in The Bible, it appears that one reason for using queues is to minimize the amount of work performed in the ISR. The ISR merely adds an entry to the queue, then exits. A looping task receives the entry from the queue and takes whatever action that is desired. Is this about right?
I do have a followup question, though -- in The Bible, there's a section on GPIO interrupt handling (around page 236 in my version). Neil mentions the routine gpio_isr_register(). I tried using this by itself, and it produced no visible results. I then tried using it in combination with gpio_install_isr_service() and gpio_isr_handler_add() (from the example project) and I got a kernel panic. Finally I just used gpio_install_isr_service() and gpio_isr_handler_add(), and it works. Can anyone point me to some information on the correct usage of gpio_isr_register()? Thanks.
Update to my question...after doing a bit more reading in The Bible, it appears that one reason for using queues is to minimize the amount of work performed in the ISR. The ISR merely adds an entry to the queue, then exits. A looping task receives the entry from the queue and takes whatever action that is desired. Is this about right?
I do have a followup question, though -- in The Bible, there's a section on GPIO interrupt handling (around page 236 in my version). Neil mentions the routine gpio_isr_register(). I tried using this by itself, and it produced no visible results. I then tried using it in combination with gpio_install_isr_service() and gpio_isr_handler_add() (from the example project) and I got a kernel panic. Finally I just used gpio_install_isr_service() and gpio_isr_handler_add(), and it works. Can anyone point me to some information on the correct usage of gpio_isr_register()? Thanks.
Re: handling GPIO interrupts
@ESP_Sprite: I totally understand you...I was just wondering why one would need the queue, as opposed to simply setting a semaphore bit somewhere in shared memory. But Neil gives a good explanation both for the need for, and the value of, using queues.
As a side note, I personally wouldn't even compose an XML string from within an ISR, unless I was prepared to bypass all string handling functions and just do everything myself. Just my opinion.
As a side note, I personally wouldn't even compose an XML string from within an ISR, unless I was prepared to bypass all string handling functions and just do everything myself. Just my opinion.
Re: handling GPIO interrupts
Howdy,
On the topic of a queue vs a semaphore ... I "loosely" consider both of those in the class of "inter task/thread/process communication". When you read a queue, I am going to assume that internally if no messages are on the queue, then the request to read from the queue blocks on a semaphore that is signaled when a new message is placed on the queue causing the original caller to wake up.
If all you ever need is to be block waiting for something external to happen, there is no data associated with that external thing happening and you don't care if it happens again before you handle the first occurrence, then a semaphore by itself will just be fine.
However, flipping it around ... if there is data associated with the external event, a queue message is a good place to store it and if you do care about handling each occurrence of the event, then a queue grows ... 1 per event.
On the topic of a queue vs a semaphore ... I "loosely" consider both of those in the class of "inter task/thread/process communication". When you read a queue, I am going to assume that internally if no messages are on the queue, then the request to read from the queue blocks on a semaphore that is signaled when a new message is placed on the queue causing the original caller to wake up.
If all you ever need is to be block waiting for something external to happen, there is no data associated with that external thing happening and you don't care if it happens again before you handle the first occurrence, then a semaphore by itself will just be fine.
However, flipping it around ... if there is data associated with the external event, a queue message is a good place to store it and if you do care about handling each occurrence of the event, then a queue grows ... 1 per event.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
Re: handling GPIO interrupts
Hi Neil - yes, that's a good distinction. In this case, semaphores probably would have been adequate, but since the queue seems to be working, I think I'll leave it as it is and move on.
So, in this forum, do we do anything to mark threads as solved or answered or anything?
So, in this forum, do we do anything to mark threads as solved or answered or anything?
Re: handling GPIO interrupts
So, I borrowed (heavily) from the debounce handler in The Bible for my GPIO processing. Here's the routine:
I expected the returned value from the gpio_get_level() call to be the same every time, but roughly 1 time in 10, it's not. Is my expectation unreasonable, or do I perhaps have a faulty circuit, or maybe something else is wrong?
Code: Select all
for (;;)
{
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY))
{
gpio_level = gpio_get_level(io_num);
gettimeofday(&now, nullptr);
if (timeval_durationBeforeNow(&lastPress) > INTERVAL)
{
printf("GPIO[%d] interrupt received. Count: %d, value: %d\n", io_num, count++, gpio_level);
}
lastPress = now;
}
}
Re: handling GPIO interrupts
Howdy,
I'm not sure I'm following. I think the gpio_get_level() will return the instantaneously read current value of the given GPIO pin. Lets assume that the value returned by the gpio_get_level() accurately reflects the signal on the pin ...
Without more of an explanation of your circuit and intent, there is likely not much more to add.
I'm not sure I'm following. I think the gpio_get_level() will return the instantaneously read current value of the given GPIO pin. Lets assume that the value returned by the gpio_get_level() accurately reflects the signal on the pin ...
Without more of an explanation of your circuit and intent, there is likely not much more to add.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
Who is online
Users browsing this forum: Google [Bot], Majestic-12 [Bot] and 71 guests