Page 1 of 1

STM32 Programming. Part 15: External EXTI Interrupts

Posted: 17 Oct 2023, 06:48
by Oleg
image.png
image.png (19.12 KiB)
Viewed 3640 times
External interrupts are needed to react the MC firmware to some fast external events, which are problematic to register by polling the GPIO pin state. The stm32f103c8 has a special EXTI block for this purpose, which we will consider in this article.

EXTI external event/interruption controller capabilities
  • Sets the edge of the pulse at the EXTI channel input at which the interrupt will be generated. You can set a rising edge trigger, a falling edge trigger, or both triggers at once.
Each channel has its own interrupt enable bit
Each channel has its own interrupt request wait bit, which must be cleared in the corresponding interrupt handler
The EXTI controller is guaranteed to detect pulses whose duration is longer than the APB2 bus clock period. I.e. if the APB2 bus frequency is equal to 72 MHz, EXTI will correctly detect edges of signals with frequencies lower than 72 MHz.

EXTI channels

EXTI channels have the following names: EXTI0, EXTI1, EXTI2 . EXTI19. In total, we have 20 channels at our disposal. And EXTI0 - EXTI15 can be connected to one of the GPIO ports. EXTI16 is connected inside the MCU to the output of the programmable voltage detector PVD, EXTI17 to the RTC Alarm event, EXTI18 to the USB of the microcontroller, and EXTI19 to the Ethernet controller, if there is one.

At the moment we are interested in those EXTI channels that can be connected to GPIO ports. And there is one nuance here. At the input of each EXTI channel there is a multiplexer, which allows selecting the GPIO pin as follows:
image.png
image.png (37.73 KiB)
Viewed 3640 times
I.e. EXTI0 can be connected to one of the port 0 pins, EXTI1 to one of the port 1 pins, and so on. For each line the multiplexer value can be selected independently, i.e. EXTI0 can be connected to PA0, EXTI1 to PB1, and so on. However, this organization of connections has some limitation that must be taken into account: we cannot simultaneously register events from, for example, lines PA0 and PB0, as they are connected to the same multiplexer.

Internal Device

The block diagram of the EXTI controller is shown below:
image.png
image.png (48.1 KiB)
Viewed 3640 times
The input signal to the EXIT channel is supplied from the Input Line. It then goes to the rising and falling edge detectors. Next, the signal from the detectors goes to the OR logic element, which will generate an interrupt request signal either when the edge detectors are triggered or when the Software interrupt event bit is set. Next, the interrupt request signal goes to 2 logic AND elements that will pass this signal to event generation (lower element) and interrupt generation (upper element) if the appropriate bits are set. Next, the interrupt generation signal sets a bit in the Pending request register which results in a request to the NVIC interrupt controller. Once the EXTI registers have been described, this block diagram will become clearer.

GPIO configuration
According to the Reference manual, in order for a port pin to work in conjunction with EXTI, the pin must be set to input. No additional GPIO settings are required.

Configuration registers
There are registers located in the address space of the EXTI controller for configuring interrupts, rising edges, and so on. However, the settings of the multiplexer select GPIO pin that is connected to the corresponding EXTI channel are done in the AFIO (Alternate function I/O) registers. We will look at all EXTI registers and AFIO registers that relate to EXTI multiplexer settings.

Interrupt mask register (EXTI_IMR) - Interrupt mask register
image.png
image.png (13.52 KiB)
Viewed 3640 times
MRx: Channel interrupt permissions x
  • 0 - interrupt disabled
  • 1 - interrupt enabled
Event mask register (EXTI_EMR) - Event maxi register
image.png
image.png (13.44 KiB)
Viewed 3640 times
We haven't dealt with events yet, this register can be simply ignored

Rising trigger selection register (EXTI_RTSR) - Rising edge detector enable register
image.png
image.png (12.22 KiB)
Viewed 3640 times
TRx: Enable the rising edge trigger of channel x
  • 0 - trigger disabled
  • 1 - trigger enabled
Falling trigger selection register (EXTI_FTSR) - Falling edge detection trigger register
image.png
image.png (12.16 KiB)
Viewed 3640 times
TRx: Enable the falling edge trigger of channel x
  • 0 - trigger disabled
  • 1 - trigger enabled
Software interrupt event register (EXTI_SWIER) - Software interrupt register
image.png
image.png (15.38 KiB)
Viewed 3640 times
SWIERx: Software interrupt line x

If the interrupt of a given EXTI channel is enabled in the EXTI_IMR register, writing a '1' to the corresponding bit, if it was previously '0', sets the corresponding bit in the EXTI_PR register and generates an interrupt request for that EXTI channel.

This bit is cleared when the corresponding bit in the EXTI_PR register is cleared.

Pending register (EXTI_PR) - Pending register
image.png
image.png (13.33 KiB)
Viewed 3640 times
PRx: Waiting bit for channel x interrupt processing
  • 0 - no interrupt request trigger events occurred
  • 1 - events of interrupt request triggers have occurred
If the corresponding interrupt is enabled in the NVIC interrupt controller, setting a bit in the EXTI_PR register will cause the corresponding interrupt handler to be called. Once the handler is called, the corresponding bit is not reset automatically, it must be done manually, otherwise immediately after exiting the interrupt handler it will be called again, and so on indefinitely.

A bit in the EXTI_PR register is reset by writing a '1' value to the corresponding bit. Writing a '0' has no effect.

AFIO registers
Now let's look at a few registers in AFIO that are also involved in setting up the EXTI external interrupt controller.

External interrupt configuration register 1 (AFIO_EXTICR1) - External interrupt configuration register 1
image.png
image.png (10.75 KiB)
Viewed 3640 times
EXTIx[3:0]: Configuration of EXTI channel x multiplexer
  • 0000: PA[x] pin selection
  • 0001: Pin selection PB[x]
  • 0010: Pin selection PC[x]
  • 0011: Pin selection PD[x]
  • 0100: Pin selection PE[x]
  • 0101: Pin selection PF[x]
  • 0110: Pin selection PG[x]
External interrupt configuration register 2 (AFIO_EXTICR2) - External interrupt configuration register 2
image.png
image.png (10.78 KiB)
Viewed 3640 times
Here everything is the same as for AFIO_EXTICR1, only for EXTI4..7 channels.

External interrupt configuration register 3 (AFIO_EXTICR3) - External interrupt configuration register 3
image.png
image.png (10.58 KiB)
Viewed 3640 times
Configuration of EXTI8..11 channels

External interrupt configuration register 4 (AFIO_EXTICR4) - External interrupt configuration register 4
image.png
image.png (11.08 KiB)
Viewed 3640 times
Configuration of EXTI12..15 channels

Interrupt handlers

An interrupt can be enabled or disabled for each EXTI channel independently of the other channels. However, interrupt requests from multiple EXTI channels can be tied to the same handler. Here is a list of all EXTI related handlers:
  • EXTI0_IRQHandler
  • EXTI1_IRQHandler
  • EXTI2_IRQHandler
  • EXTI3_IRQHandler
  • EXTI4_IRQHandler
  • EXTI9_5_IRQHandler
  • EXTI15_10_IRQHandler
  • PVD_IRQHandler
  • RTC_Alarm_IRQHandler
  • USBWakeUp_IRQHandler
EXTI0..4 do not share anything with anyone and have their own interrupt handler. EXTI5..9 have the same interrupt handler EXTI9_5_IRQHandler, EXTI10..15 channels also have a common handler EXTI15_10_IRQHandler. If several EXTI channels correspond to the same handler, you can distinguish requests from different channels within the handler using the EXTI_PR register.

The last 3 handlers belong to EXTI16, EXTI17, EXTI18 lines and we are not interested in them now.

Example program

As a demonstration, let's set up an external interrupt on the rising and falling edge of the pulse on the PA0 pin of the microcontroller. Create the void EXTI_Init(void) function, in which we will make all the necessary settings:

Code: Select all

void EXTI_Init(void)
{
  
}
Let's proceed to customization. The first thing we do is to enable GPIOA and AFIO clocking. It is not necessary to enable EXTI clocking in a special way:

Code: Select all

  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Tacting GPIOA
  RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Tacting AFIO
Next, let's set PA0 to input with pull-up:

Code: Select all

/*
    GPIO setting
      Pin: PA0
      Mode: Input Pull Up
  */
  GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
  GPIOA->CRL |= (0x02 << GPIO_CRL_CNF0_Pos); //Pull Up/Pull Down Input
  GPIOA->ODR |= (1 << 0); //Pull Up.
You can read about GPIO setup in this article.

Now we are going to configure EXTI. Since we have chosen pin PA0, the interrupt will hang on the EXTI0 channel. We need to select PA0 as a signal source in the AFIO_EXTICR1 register using the EXTI0[3:0] bit group. This is done like this:

Code: Select all

AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //The EXTI channel zero is connected to the PA0 port
Next, we choose which edges we want the interrupt to occur on. In our case, both edges:

Code: Select all

  EXTI->RTSR |= EXTI_RTSR_TR0; //Interrupt on pulse rise
  EXTI->FTSR |= EXTI_FTSR_TR0; //Break on the pulse fall.
And a few final touches: just in case we reset the interrupt request flag, enable the corresponding interrupt in the EXTI_IMR register and enable the EXTI0_IRQn interrupt in the NVIC:

Code: Select all

 EXTI->PR = EXTI_PR_PR0; //Reset the interrupt flag
                               //before enabling the interrupt itself
  EXTI->IMR |= EXTI_IMR_MR0; //Enable the interrupt of EXTI channel 0
  
  NVIC_EnableIRQ(EXTI0_IRQn); // Enable interrupt in the interrupt controller.
It's done

Here is the full code for the function:

Code: Select all

void EXTI_Init(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Tacting GPIOA
  RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Acting AFIO
  
  
  /*
    GPIO setting
      Pin: PA0
      Mode: Input Pull Up
  */
  GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
  GPIOA->CRL |= (0x02 << GPIO_CRL_CNF0_Pos); //Pull Up/Pull Down Input
  GPIOA->ODR |= (1 << 0); //Pull Up/Pull Down
  
  /*
    EXTI setting
  */
  AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //Zero EXTI channel is connected to PA0 port
  
  EXTI->RTSR |= EXTI_RTSR_TR0; //Break on pulse rise
  EXTI->FTSR |= EXTI_FTSR_TR0; //Break on pulse fall
  
  EXTI->PR = EXTI_PR_PR0; //Reset the interrupt flag
                               //before enabling the interrupt itself
  EXTI->IMR |= EXTI_IMR_MR0; //Enable interrupt of EXTI channel 0
  
  NVIC_EnableIRQ(EXTI0_IRQn); // Enable interrupt in interrupt controller
}
Don't forget about the interrupt handler:

Code: Select all

void EXTI0_IRQHandler(void)
{
  EXTI->PR = EXTI_PR_PR0; //Reset interrupt flag
  asm("nop");
  asm("nop");
  asm("nop");
  asm("nop");
  asm("nop");
}
And here is a simple main() like this

Code: Select all

void main(void)
{
  EXTI_Init();
  for(;;)
  {
  }
}
An EXTI0_IRQHandler() interrupt will occur when the logic level on PA0 changes.

Here you should pay attention to a bunch of asm("nop")-s in the interrupt handler. The point is that right after calling EXTI->PR = EXTI_PR_PR0; at least 2 processor cycles are needed before exiting the interrupt handling function, so that the interrupt flag has time to reset. Otherwise, the interrupt handler will be called again. Therefore, it is advisable to reset the interrupt flag in the EXTI_PR register at the beginning of the handler.

And lastly, a little experiment. The Reference manual says that when a microcontroller pin works as a signal source for EXTI, it should be configured as an input. What will happen if this pin is configured as an output? How will the system behave?

Code: Select all

void EXTI_Init(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Tactivating GPIOA
  RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Acting AFIO
  
  
  /*
    GPIO setting
      Pin: PA0
      Mode: Output
  */
  GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
  GPIOA->CRL |= (0x00 << GPIO_CRL_CNF0_Pos) | (0x01 << GPIO_CRL_MODE0_Pos);
  
  /*
    EXTI setting
  */
  AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //Zero EXTI channel is connected to PA0 port
  
  EXTI->RTSR |= EXTI_RTSR_TR0; //Break on pulse rise
  EXTI->FTSR |= EXTI_FTSR_TR0; //Break on pulse fall
  
  EXTI->PR = EXTI_PR_PR0; //Reset the interrupt flag
                               //before enabling the interrupt itself
  EXTI->IMR |= EXTI_IMR_MR0; //Enable interrupt of EXTI channel 0
  
  NVIC_EnableIRQ(EXTI0_IRQn); // Enable interrupt in interrupt controller
}
In this case, when PA0 state is changed programmatically, the system behaves exactly the same way as in the previous case, an interrupt occurs on the change of the logic level on PA0. Perhaps, if you set the microcontroller output to the Open-drain output (open collector), this can be used somehow in some exotic cases.