Building Board Support Package (BSP) for STM32F411-Nucleo64 Part7:External Interrupt

In this seventh part of Board Support Package on STM32F411, we shall develop external interrupt driver to interrupt the processor each time the push button is pressed.

In this guide, we shall cover the following:

  • Header file code.
  • Source file code.
  • Main code.
  • Results.

1.Header file code:

Before we head into developing the header file, please refer to this guide to have better understanding what is an interrupt and how to develop it.

We start off by creating new header file with name of exti_bsp.h.

Within the header file, include header guard:

#ifndef EXTI_BSP_H_
#define EXTI_BSP_H_



#endif /* EXTI_BSP_H_ */

Within the header file, include the following two header files:

#include "stdint.h"
#include "bsp.h"

The reason behind adding the bsp.h file to have access to pin number and no need to redeclare the enum.

Since STM32F411 supports three types of edge detection as following:

  • Rising Edge.
  • Falling Edge.
  • Rising and Falling Edge.

Hence, we shall declare those as enum as following:

typedef enum
{
	RisingEdge,
	FallingEdge,
	Rising_FallingEdge
}Edge_ConfigTypedef;

In order to tell the interrupt handler which port should be the source, we shall create enum to handle those ports:

typedef enum
{
	PortA,
	PortB,
	PortC,
	PortE,
	PortF,
	PortG
}Ports_Typedef;

Also, create data structure to hold the EXTI configuration with the following parameters:

  • Pin number.
  • Mode.
  • Port.
typedef struct
{
	uint8_t pinNumber;
	uint8_t port;
	uint8_t mode;
}EXTI_ConfigTypedef;

Declare the following function:

void bsp_exti_init(EXTI_ConfigTypedef *config);

The function shall initialize the external interrupt and takes the data structure EXTI_ConfigTypedef as pointer and return nothing.

Hence, the entire header file as following:

#ifndef EXTI_BSP_H_
#define EXTI_BSP_H_

#include "stdint.h"
#include "bsp.h"

typedef enum
{
	RisingEdge,
	FallingEdge,
	Rising_FallingEdge
}Edge_ConfigTypedef;

typedef enum
{
	PortA,
	PortB,
	PortC,
	PortE,
	PortF,
	PortG
}Ports_Typedef;

typedef struct
{
	uint8_t pinNumber;
	uint8_t port;
	uint8_t mode;
}EXTI_ConfigTypedef;


void bsp_exti_init(EXTI_ConfigTypedef *config);


#endif /* EXTI_BSP_H_ */

Thats all for the header file.

2. Source code file:

We start off by creating new source file with name of exti_bsp.c.

Within the source code, include the following header files:

#include "exti_bsp.h"
#include "stm32f4xx.h"

Now we shall populate the initializing function:

void bsp_exti_init(EXTI_ConfigTypedef *config)

within the function:

We start with disabling the global interrupt:

__disable_irq();

Enable clock access to syscnfg as following:

RCC->APB2ENR|=RCC_APB2ENR_SYSCFGEN;

We shall configure the port needed (i.e. port a port b etc).

Since there are 4 registers to be configure:

  • SYSCFG_EXTICR1
  • SYSCFG_EXTICR2
  • SYSCFG_EXTICR3
  • SYSCFG_EXTICR4

Those registers are declare as an array of 4 and each array handle four pins as following:

By using if condition, we could build configure those register with ease as following:

	if (config->pinNumber>=0 &&config->pinNumber<=3)
	{
		SYSCFG->EXTICR[0] |= (config->port <<(config->pinNumber)*4);
	}

	if (config->pinNumber>=4 &&config->pinNumber<=7)
	{
		SYSCFG->EXTICR[1] |= (config->port <<(config->pinNumber-4)*4);
	}

	if (config->pinNumber>=8 &&config->pinNumber<=11)
	{
		SYSCFG->EXTICR[2] |= (config->port <<(config->pinNumber-8)*4);
	}

	if (config->pinNumber>=12 &&config->pinNumber<=15)
	{
		SYSCFG->EXTICR[3] |= (config->port <<(config->pinNumber-12)*4);
	}

Unmask the interrupt line as following:

	/*Unmask the interrupt line*/

	EXTI->IMR |=(1U<<config->pinNumber);

Configuring the mode :

switch (config->mode)
	{
		case RisingEdge:
			EXTI->RTSR|= (1U<<config->pinNumber);

			EXTI->FTSR&= ~(1U<<config->pinNumber);

			break;

		case FallingEdge:
			EXTI->RTSR&=~ (1U<<config->pinNumber);

			EXTI->FTSR|= (1U<<config->pinNumber);

			break;

		case Rising_FallingEdge:
			EXTI->RTSR|= (1U<<config->pinNumber);

			EXTI->FTSR|= (1U<<config->pinNumber);

			break;
	}

Enabling the interrupt in NVIC:

/*Enable the interrupt in NVIC*/
	if(config->pinNumber==0)
	{
		NVIC_EnableIRQ(EXTI0_IRQn);
	}

	if(config->pinNumber==1)
	{
		NVIC_EnableIRQ(EXTI1_IRQn);
	}

	if(config->pinNumber==2)
	{
		NVIC_EnableIRQ(EXTI2_IRQn);
	}

	if(config->pinNumber==3)
	{
		NVIC_EnableIRQ(EXTI3_IRQn);
	}

	if(config->pinNumber==4)
	{
		NVIC_EnableIRQ(EXTI4_IRQn);
	}

	if(config->pinNumber>=5 && config->pinNumber<=9)
	{
		NVIC_EnableIRQ(EXTI9_5_IRQn);
	}

	if(config->pinNumber>=10 && config->pinNumber<=15)
	{
		NVIC_EnableIRQ(EXTI15_10_IRQn);
	}

Finally enabling the global interrupt:

	__enable_irq();

For the interrupt handler:

/*Interrupt handler*/

void EXTI0_IRQHandler (void)
{
	EXTI->PR |=(1<<pin0);
	pin0_Callback();
}


void EXTI1_IRQHandler (void)
{
	EXTI->PR |=(1<<pin1);
	pin1_Callback();
}

void EXTI2_IRQHandler (void)
{
	EXTI->PR |=(1<<pin2);
	pin2_Callback();
}

void EXTI3_IRQHandler (void)
{
	EXTI->PR |=(1<<pin3);
	pin3_Callback();
}

void EXTI4_IRQHandler (void)
{
	EXTI->PR |=(1<<pin4);
	pin4_Callback();
}


void EXTI9_5_IRQHandler(void)
{
	if (EXTI->PR & (1<<pin5))
	{
		EXTI->PR |=(1<<pin5);
		pin5_Callback();
	}

	if (EXTI->PR & (1<<pin6))
	{
		EXTI->PR |=(1<<pin6);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin7))
	{
		EXTI->PR |=(1<<pin7);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin8))
	{
		EXTI->PR |=(1<<pin8);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin9))
	{
		EXTI->PR |=(1<<pin9);
		pin5_Callback();

	}

}

void EXTI15_10_IRQHandler(void)
{
	if (EXTI->PR & (1<<pin10))
	{
		EXTI->PR |=(1<<pin10);
		pin10_Callback();
	}

	if (EXTI->PR & (1<<pin11))
	{
		EXTI->PR |=(1<<pin11);
		pin11_Callback();
	}

	if (EXTI->PR & (1<<pin12))
	{
		EXTI->PR |=(1<<pin12);
		pin12_Callback();

	}

	if (EXTI->PR & (1<<pin13))
	{
		EXTI->PR |=(1<<pin13);
		pin13_Callback();

	}

	if (EXTI->PR & (1<<pin14))
	{
		EXTI->PR |=(1<<pin14);
		pin14_Callback();
	}

	if (EXTI->PR & (1<<pin15))
	{
		EXTI->PR |=(1<<pin15);
		pin15_Callback();
	}
}

For each interrupt handler, it has own weak defined callback function as following:

__WEAK void pin0_Callback(void)
{

}

__WEAK void pin1_Callback(void)
{

}

__WEAK void pin2_Callback(void)
{

}

__WEAK void pin3_Callback(void)
{

}

__WEAK void pin4_Callback(void)
{

}


__WEAK void pin5_Callback(void)
{

}

__WEAK void pin6_Callback(void)
{

}
__WEAK void pin7_Callback(void)
{

}

__WEAK void pin8_Callback(void)
{

}

__WEAK void pin9_Callback(void)
{

}
__WEAK void pin10_Callback(void)
{

}
__WEAK void pin11_Callback(void)
{

}
__WEAK void pin12_Callback(void)
{

}

__WEAK void pin13_Callback(void)
{

}
__WEAK void pin14_Callback(void)
{

}

__WEAK void pin15_Callback(void)
{

}

Defining the function as weak, it will allow the user to override the function in any other source and implement his own function.

Hence the entire source code as following:

#include "exti_bsp.h"
#include "stm32f4xx.h"


void bsp_exti_init(EXTI_ConfigTypedef *config)
{
	/*Disable global interrupts*/
	__disable_irq();

	/*Enable Clock Access to SYSCFG*/

	RCC->APB2ENR|=RCC_APB2ENR_SYSCFGEN;


	if (config->pinNumber>=0 &&config->pinNumber<=3)
	{
		SYSCFG->EXTICR[0] |= (config->port <<(config->pinNumber)*4);
	}

	if (config->pinNumber>=4 &&config->pinNumber<=7)
	{
		SYSCFG->EXTICR[1] |= (config->port <<(config->pinNumber-4)*4);
	}

	if (config->pinNumber>=8 &&config->pinNumber<=11)
	{
		SYSCFG->EXTICR[2] |= (config->port <<(config->pinNumber-8)*4);
	}

	if (config->pinNumber>=12 &&config->pinNumber<=15)
	{
		SYSCFG->EXTICR[3] |= (config->port <<(config->pinNumber-12)*4);
	}


	/*Unmask the interrupt line*/

	EXTI->IMR |=(1U<<config->pinNumber);

	switch (config->mode)
	{
		case RisingEdge:
			EXTI->RTSR|= (1U<<config->pinNumber);

			EXTI->FTSR&= ~(1U<<config->pinNumber);

			break;

		case FallingEdge:
			EXTI->RTSR&=~ (1U<<config->pinNumber);

			EXTI->FTSR|= (1U<<config->pinNumber);

			break;

		case Rising_FallingEdge:
			EXTI->RTSR|= (1U<<config->pinNumber);

			EXTI->FTSR|= (1U<<config->pinNumber);

			break;
	}

	/*Enable the interrupt in NVIC*/
	if(config->pinNumber==0)
	{
		NVIC_EnableIRQ(EXTI0_IRQn);
	}

	if(config->pinNumber==1)
	{
		NVIC_EnableIRQ(EXTI1_IRQn);
	}

	if(config->pinNumber==2)
	{
		NVIC_EnableIRQ(EXTI2_IRQn);
	}

	if(config->pinNumber==3)
	{
		NVIC_EnableIRQ(EXTI3_IRQn);
	}

	if(config->pinNumber==4)
	{
		NVIC_EnableIRQ(EXTI4_IRQn);
	}

	if(config->pinNumber>=5 && config->pinNumber<=9)
	{
		NVIC_EnableIRQ(EXTI9_5_IRQn);
	}

	if(config->pinNumber>=10 && config->pinNumber<=15)
	{
		NVIC_EnableIRQ(EXTI15_10_IRQn);
	}


	__enable_irq();

}

__WEAK void pin0_Callback(void)
{

}

__WEAK void pin1_Callback(void)
{

}

__WEAK void pin2_Callback(void)
{

}

__WEAK void pin3_Callback(void)
{

}

__WEAK void pin4_Callback(void)
{

}


__WEAK void pin5_Callback(void)
{

}

__WEAK void pin6_Callback(void)
{

}
__WEAK void pin7_Callback(void)
{

}

__WEAK void pin8_Callback(void)
{

}

__WEAK void pin9_Callback(void)
{

}
__WEAK void pin10_Callback(void)
{

}
__WEAK void pin11_Callback(void)
{

}
__WEAK void pin12_Callback(void)
{

}

__WEAK void pin13_Callback(void)
{

}
__WEAK void pin14_Callback(void)
{

}

__WEAK void pin15_Callback(void)
{

}




/*Interrupt handler*/

void EXTI0_IRQHandler (void)
{
	EXTI->PR |=(1<<pin0);
	pin0_Callback();
}


void EXTI1_IRQHandler (void)
{
	EXTI->PR |=(1<<pin1);
	pin1_Callback();
}

void EXTI2_IRQHandler (void)
{
	EXTI->PR |=(1<<pin2);
	pin2_Callback();
}

void EXTI3_IRQHandler (void)
{
	EXTI->PR |=(1<<pin3);
	pin3_Callback();
}

void EXTI4_IRQHandler (void)
{
	EXTI->PR |=(1<<pin4);
	pin4_Callback();
}


void EXTI9_5_IRQHandler(void)
{
	if (EXTI->PR & (1<<pin5))
	{
		EXTI->PR |=(1<<pin5);
		pin5_Callback();
	}

	if (EXTI->PR & (1<<pin6))
	{
		EXTI->PR |=(1<<pin6);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin7))
	{
		EXTI->PR |=(1<<pin7);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin8))
	{
		EXTI->PR |=(1<<pin8);
		pin5_Callback();

	}

	if (EXTI->PR & (1<<pin9))
	{
		EXTI->PR |=(1<<pin9);
		pin5_Callback();

	}

}

void EXTI15_10_IRQHandler(void)
{
	if (EXTI->PR & (1<<pin10))
	{
		EXTI->PR |=(1<<pin10);
		pin10_Callback();
	}

	if (EXTI->PR & (1<<pin11))
	{
		EXTI->PR |=(1<<pin11);
		pin11_Callback();
	}

	if (EXTI->PR & (1<<pin12))
	{
		EXTI->PR |=(1<<pin12);
		pin12_Callback();

	}

	if (EXTI->PR & (1<<pin13))
	{
		EXTI->PR |=(1<<pin13);
		pin13_Callback();

	}

	if (EXTI->PR & (1<<pin14))
	{
		EXTI->PR |=(1<<pin14);
		pin14_Callback();
	}

	if (EXTI->PR & (1<<pin15))
	{
		EXTI->PR |=(1<<pin15);
		pin15_Callback();
	}
}

3. Main code:

With main.c include the following header file:

#include "bsp.h"
#include "uart_bsp.h"
#include "exti_bsp.h"

Declare the following data structures:

GPIO_Output_Typedef LED;
GPIO_Input_Typedef Button;
EXTI_ConfigTypedef PC13_EXTI;

Within main function:

Enable clock access to GPIOC and GPIOA:

GPIOA_CLOCK_ENABLE();
GPIOC_CLOCK_ENABLE();

LED and push button configuration:

	GPIO_Configure_Typedef LED_Config;
	LED_Config.PinNumber=pin5;
	LED_Config.Mode=OUTPUT;
	LED.pinNumber=pin5;
	GPIO_Initialization(GPIOA,&LED_Config);

	GPIO_Configure_Typedef Button_Config;
	Button.pinNumber=pin13;
	Button_Config.PinNumber=pin13;
	Button_Config.Mode=INPUT;
	GPIO_Initialization(GPIOC,&Button_Config);

External interrupt configuration:

	PC13_EXTI.pinNumber=pin13;
	PC13_EXTI.port=PortC;
	PC13_EXTI.mode=FallingEdge;

Initialize the external interrupt:

bsp_exti_init(&PC13_EXTI);

while loop shall be empty.

For pin 13 callback function:

We shall toggle the LED:

void pin13_Callback(void)
{
	GPIO_TogglePin(GPIOA,&LED);
}

Hence, the entire main.c file as following:

#include "bsp.h"
#include "uart_bsp.h"
#include "exti_bsp.h"

GPIO_Output_Typedef LED;
GPIO_Input_Typedef Button;
EXTI_ConfigTypedef PC13_EXTI;

void clock_config(void);



int main()
{

	clock_config();

	GPIOA_CLOCK_ENABLE();
	GPIOC_CLOCK_ENABLE();

	GPIO_Configure_Typedef LED_Config;
	LED_Config.PinNumber=pin5;
	LED_Config.Mode=OUTPUT;
	LED.pinNumber=pin5;
	GPIO_Initialization(GPIOA,&LED_Config);

	GPIO_Configure_Typedef Button_Config;
	Button.pinNumber=pin13;
	Button_Config.PinNumber=pin13;
	Button_Config.Mode=INPUT;
	GPIO_Initialization(GPIOC,&Button_Config);

	PC13_EXTI.pinNumber=pin13;
	PC13_EXTI.port=PortC;
	PC13_EXTI.mode=FallingEdge;

	bsp_exti_init(&PC13_EXTI);

	BSP_Ticks_Init(100000000);


	while(1)
	{


	}

}

void clock_config(void)
{
	Clock_Config_Typedef clockConfig;

	clockConfig.PLL_M= 4;
	clockConfig.PLL_N= 200;
	clockConfig.PLL_P= 4;

	clockConfig.AHB1Prescaler=AHB1_Prescaler1;
	clockConfig.APB1Prescaler=APB1_Prescaler2;
	clockConfig.APB2Prescaler=APB2_Prescaler1;

	clockConfig.clockSourc=External_Oscillator;
	clockConfig.flash_latency= Three_wait_state;

	Clock_Configuration(&clockConfig);
}

void pin13_Callback(void)
{
	GPIO_TogglePin(GPIOA,&LED);
}

4. Results:

By downloading the code to your board and pressing the user button you should see the LED being toggled each time the button is pressed as shown in the video:

Note: Not same board, but same results.

Happy coding 🙂

6 Comments

  • Andrey Poletimov Posted April 18, 2024 7:55 am

    good afternoon. Thank you for the article. Please clarify why in void EXTI15_10_IRQHandler(void) different callbacks are called: pin10_Callback(); pin11_Callback() etc., and in void EXTI9_5_IRQHandler(void) only one pin5_Callback()?

    • Husamuldeen Posted April 20, 2024 3:42 am

      Hi,
      pin 10 to 15 share same NVIC controller.
      Same thing for 5 to 9.

      • Andrey Poletimov Posted April 20, 2024 9:25 am

        Good afternoon. In the above code in the function EXTI15_10_IRQHandler(void) the callbacks change depending on the pin, while in void EXTI9_5_IRQHandler(void), pin5_Callback() is always called. That’s what my question is. It is strange, because from a physical point of view they are not different from each other.

        • Husamuldeen Posted April 22, 2024 5:08 am

          Well, these pins are sharing same controller.
          You need to do it in software using if statements.

  • Andrey Poletimov Posted April 18, 2024 8:19 am

    I wanted to clarify whether /*Clear the pending flag*/ should be done in the callback:
    EXTI->PR|=EXTI_PR_PR_PR11;

    • Husamuldeen Posted April 20, 2024 3:43 am

      Hi,
      it is being cleared in the interrupt handler.
      Callback is just for user implementation.

Add Comment

Your email address will not be published. Required fields are marked *