Hardware configuration: ESP32-S2 and ESP32-S3 Dev Modules - (both do the same thing)
I created a very basic high level interrupt test for a GPIO input, with the required assembly code (.S) handler. This works fine, but there is an issue that was also reported by another person in this thread: viewtopic.php?f=13&t=8826&start=10#p37384
The issue is that the interrupt occurs twice, and there was no resolution given.
I initially had just a momentary switch on GPIO6 (the GPIO I am using to trigger the interrupt). I thought I might be getting a lot of switch bounce, so I added a few different caps and I still got a double count every time the switch was pressed (grounding the GPIO pin). So, I setup a function generator with a 10 second period (5 seconds of low pulse, and 5 seconds of high pulse). The interrupt is setup so the GPIO interrupt triggers on the negative edge, and when the pulse goes from high to low the interrupt fires - apparently twice. When the pulse goes back to high, nothing happens (expected)... until 5 seconds later when the interrupt fires again (twice) on the next negative edge.
I am doing this under v2.0.17 of the Arduino core, but I did switch to v3.0.4 just to see if that made any difference, and it didn't. I currently have some libraries that are broken under 3.x so i am stuck using the older core until those issues are resolved. Here is the C++ code I am using for the test:
- #include "soc/periph_defs.h"
- #define GPIO_INPUT_IO_0 6 // GPIO 6 as the input
- #define GPIO_INPUT_PIN_SEL (1ULL<<GPIO_INPUT_IO_0)
- //#define INTR_NUM 14 // NMI
- #define INTR_NUM 31 // LEVEL 5
- volatile uint32_t intCounter = 0;
- void setup() {
- Serial.begin(115200);
- gpio_config_t io_conf;
- io_conf.intr_type = GPIO_INTR_NEGEDGE; // interrupt of falling edge
- io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; // bit mask of the pin(s)
- io_conf.mode = GPIO_MODE_INPUT; // set as input mode
- io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // disable pull-down mode
- io_conf.pull_up_en = GPIO_PULLUP_ENABLE; // enable pull-up mode
- gpio_config(&io_conf);
- ESP_INTR_DISABLE(INTR_NUM); // disable interrupt 31
- esp_intr_alloc(ETS_GPIO_INTR_SOURCE, ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_IRAM, NULL, NULL, NULL);
- ESP_INTR_ENABLE(INTR_NUM); // enable interrupt 31
- }
- void loop() {
- Serial.println(intCounter);
- delay(100);
- }
You can see I used the ESP-IDF method of setting up GPIO06 as an input with pull-up, and using the negative edge for the trigger. Then the interrupt handler is installed. Once the setup() section is complete the main loop just outputs the value of a variable every 100ms. The interrupt handler updates that variable every time the interrupt is fired. This code should be easy to compile under the ESP-IDF when you add the necessary includes.
The assembly interrupt handler code:
- #include <xtensa/coreasm.h>
- #include <xtensa/corebits.h>
- #include <xtensa/config/system.h>
- #include "freertos/xtensa_context.h"
- #include "esp_private/panic_reason.h"
- #include "sdkconfig.h"
- #include "soc/soc.h"
- #include "soc/gpio_reg.h"
- #include "soc/dport_reg.h"
- #define L5_INTR_STACK_SIZE 8
- #define LX_INTR_A15_OFFSET 0
- #define LX_INTR_A14_OFFSET 4
- .data
- _l5_intr_stack:
- .space L5_INTR_STACK_SIZE // define stack size
- .section .iram1,"ax" // place ISR into IRAM
- .global xt_highint5 // re-define highint5 pointer
- .type xt_highint5,@function // set type to interrupt
- .align 4
- /* start of ISR */
- xt_highint5:
- /* Save registers - a0 saved in ISR entry */
- movi a0, _l5_intr_stack // get stack pointer
- s32i a15, a0, LX_INTR_A15_OFFSET // save a15
- s32i a14, a0, LX_INTR_A14_OFFSET // save a14
- /* Increment intr_cnt */
- movi a14, intCounter // get address of intCounter variable from main program
- l32i a15, a14, 0 // get value from variable
- addi a15, a15, 1 // add 1 to value
- s32i a15, a14, 0 // put value back into variable
- memw // write through
- /* Clear interrupt flag for input */
- movi a14, GPIO_STATUS_W1TC_REG // address of interrupt flags register
- movi a15, (1 << 6) // mask for GPIO 6 (our input)
- s32i a15, a14, 0 // store mask into interrupt flags (clear interrupt)
- memw // write through
- /* Restore registers */
- l32i a14, a0, LX_INTR_A14_OFFSET // restore a14
- l32i a15, a0, LX_INTR_A15_OFFSET // restore a15
- memw // write through
- rsr a0, EXCSAVE_5 // restore a0
- rfi 5 // exist ISR
- .global ld_include_xt_highint5 // force compiler to override weak inclusion
- ld_include_xt_highint5:
Here you can see the registers are saved, the variable (intCounter) is incremented, the registers are restored, and the routine exits. The weak override is clearly working because I do get an incrementing value - just by 2 instead of 1 that I expect!
I am hoping someone can explain why the interrupt is firing twice.
PROBLEM #2 - NMI interrupt crashes and reports BRK instruction wasn't handled
I have a situation where I need two high level interrupts, neither of which I want interrupted by anything in the ESP32 OS. One interrupt has to take priority over the other so I thought the simple solution would be to have xt_highint5 and a xt_nmi interrupts.
I took the above code and modified it so that the interrupt number (INTR_NUM) for the interrupt installation is 14 (NMI) instead of 31 (INT5), and then modified the assembly code so that it overrides the NMI vector using the same method I used for the high level interrupt (5). This compiles correctly but when the interrupt is triggered I get a core panic with a message that a BRK instruction was not handled... almost like the wrong INTR_NUM is being used. Is there any known issues with taking over the NMI? Does something else need to be cleared or set because it's an NMI interrupt?
This is probably above most user's requirements, but if you know something please chime in! Thank you!