1

Summary: I've configured a GPIO as an interrupt. I can see from all of the registers that it appears to be triggering, but my interrupt routine is never called.

Details: I'm using a Nucleo F446 board, and the documentation specifies that PC13 should be the input for the push button. I'd like to trigger an interrupt when this happens (I know that this isn't the best way to handle a button; I was having trouble with a more complex system and reduced it to this simplified example). I'm doing this on bare metal and not using any existing libraries.

push button GPIO description

PC13 comes into EXTI13:

EXTI muxes

I see that this is interrupt #40 from the microcontroller reference manual:

interrupt vector table

I'm configuring the microcontroller as follows (using pseudo-c here for simplicity):

  1. Enable clocks for GPIOC block, SYSCFG

      RCC_AHB1ENR |= GPIOC_EN
      RCC_APB2EN |= SYSCFG_EN
    
  2. Enable external interrupts for GPIO C13 (it is by default an input)

external interrupt enable select

    SYSCFG_EXTICR4 |= (PCx << 4)
  1. Set pin 13 of the interrupt mask, event mask, and rising trigger selection registers:

     EXTI_IMR |= 1 << 13
     EXTI_EMR |= 1 << 13
     EXTI_RTSR |= 1 << 13
    
  2. Enable IRQ 40

     NVIC_ISER1 |= 1 << 8
    
  3. Set up interrupt vector (here is a disassembly)

     08000000 <_reset-0x124>:
     ...
     80000e0:       08000621        .word   0x08000621
    
    
     08000620 <exti15_10_handler>:
      8000620:       4906            ldr     r1, [pc, #24]   ; (800063c <exti15_10_handler+0x1c>)
    

I have the main code in a loop printing a number of register values, which I will describe in a moment, to the serial port. I've implemented exti15_10_handler to turn on an LED and go into an infinite loop, so I should know when it is called, because it will also stop the printing. When I press and release the button, I see the following:

  1. In GPIOC_IDR (the GPIO input register), I can see bit 13 change, which tells me the GPIO block is working.
  2. In EXTI_PR (external interrupt pending), I see the value of bit 13 switch from 0 to 1 and stays there.
  3. In NVIC_ISPR1 (interrupt set pending), bit 8 (corresponding to interrupt 40) switches from 0 to 1 and stays there.
  4. However, NVIC_IABR0 (interrupt active bit register) does not change.
  5. Interrupt is not called, as I see no change in the LED and the board does not hang.

I'm sure I'm forgetting to enable something, but after dredging through the reference manuals and a bunch of code examples, I'm just not seeing it. I did try the following:

asm volatile ("cpsie i" : : : "memory");

To set the interrupt flag (which I think should have been on already). I'm curious if this looks familiar to anyone.

JeffB
  • 407
  • 3
  • 13
  • 1
    Perhaps you function isn't connected up to your vector table correctly? It looks like you aren't using the standard startup routines or CMSIS headers. Try to get it working with them first, then gradually switch your custom code back in gradually if you must. Also PC13 is one of the funny pins that is used by the RTC and so is powered differently to everything else. Maybe try a normal pin first? – Tom V Jan 08 '22 at 12:35
  • I have created an app to do the same thing using the STM32CubeIDE. I've compared the code to mine and haven't found any substantive differences. I also tried enabling the systick interrupt and see similar behavior: I can see the systick counter changing, but no interrupts. One interesting point though: after some experimentation, I've found that if I keep resetting, it works maybe 1 out of every 8 times. I'm using the open source stlink tools, so perhaps this is related: https://github.com/stlink-org/stlink. – JeffB Jan 09 '22 at 21:20
  • Looking at the ICSR register before pressing the button, I can see that the VECTACTIVE field is set to 3 (HardFault). When I press the button, I can see the VECTPENDING go from 0 to the vector in question. So, somehow during the boot process, it thinks it is in a high priority active ISR, and so won't dispatch the new one. The ARM documentation is vague on what it means specifically to have an exception be "active" (an OS can do lots of things in an exception handler) and what operations clear that state. I'm debating whether to rework this question to remove some now-superfluous info. – JeffB Jan 10 '22 at 02:00
  • The states of exceptions on a ARM Cortex are explained clearly in ARM documentation. "Active" means that the exception is besing served (an exception handler is being executed. – Guillaume Petitjean Jan 10 '22 at 09:54
  • Apparently your code is crashing and goes to a hard fault . Hard fault default handler is frequently an infinite loop on sample source code. Your IRQ will never be served as long as you don't exist from Hard Fault. – Guillaume Petitjean Jan 10 '22 at 09:56
  • You didn't try to debug your code ? You should see it breaking in the Hard Fault handler – Guillaume Petitjean Jan 10 '22 at 09:56
  • What was initially unclear from the documentation is what exactly causes the processor switch from active back to thread mode, but after further reading, it seems to be a magic value (EXC_RETURN) being transferred into the LR register. I have a Hard Fault handler that goes into an infinite loop, but, if it were hitting that, I would expect to see it hang rather than the code continuing to execute normally as it does. My theory is that it is somehow getting into this state as a side effect of the flashing process (via SWD/st-link), and it is in active mode when my code starts executing. – JeffB Jan 10 '22 at 20:33

1 Answers1

0

This is a pretty unsatisfying result. While looking at the disassembly of the interrupt vector table, I noticed:

 8000000:       20020000        .word   0x20020000
 8000004:       08000124        .word   0x08000124
 8000008:       08000595        .word   0x08000595
 800000c:       08000595        .word   0x08000595

The second entry is supposed to be my reset vector

            .section .interrupt_vector
            .word _estack    // Stack pointer
            .word _reset

Although _reset is a thumb function, it is not encoded with the LSB set to indicate that. If I change the line to:

   .word _reset + 1

...or put .thumb_func immediate before my reset handler in my startup code, it works correctly every time.

JeffB
  • 407
  • 3
  • 13