External interrupts on STM32 bluepill

 Author:   Posted on:   Updated on:  2019-02-23T08:20:10Z

Learn to handle GPIO interrupts on the STM32 bluepill with HAL library and System Workbench.

The bluepill is a cheap STM32F103 development board. It can be programmed even from Arduino IDE with an additional boards package. But to get the most out of it, you should develop software using the development kit from ST. This is because STM32 is much more complex than ATmega microcontrollers used by some Arduino boards. While the latter are use 8 bit CPUs, STM32F103 contains a 32 bit ARM Cortex CPU.

It's not my first attempt to program the STM32 bluepill using HAL library from ST. This post contains information about the tools you need to install in order to program the board. I'm going to make a new project in System Workbench and I will use the same clock configuration routine from the mentioned post (the SystemClock_Config() function). Then I will write code that toggles an LED when you push a button using interrupts.

STM32 bluepill on the breadboard

STM32 bluepill on the breadboard

In the above photo you see my hardware. The bluepill is on the breadboard. An LED is wired in series with a 330 ohms resistor between pin 14 of port B and ground. A pushbutton is wired from pin 13 of port B to ground. For demonstration purposes, the pushbutton will be able to generate the interrupt. But, in real world, the pull generated when pressed is not steady and you have to debounce the button with a hardware RC filter or in software. When doing it in software it's easier to use polling instead of interrupts.

Start Project

Launch SW4STM32, go to File menu and choose New - C Project. Give your project a name and select Ac6 STM32 MCU Project type, then click Next. On this screen leave both build configurations selected and go to the next one. Select Mcu tab and choose STM32F103CBTx. Next you must select (and maybe download, if the first time) the Cube HAL firmware. Let Eclipse create the initial files and once it is done open main.c.

Steps to generate STM32 project

Steps to generate STM32 project

Before int main() add your own clock configuration function. For the bluepill this is what gives you the maximum frequencies:

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    while (1);
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
    while (1);
  }

  HAL_RCC_EnableCSS();
}

In main(), initialise HAL, then call this clock configuration function like this:

 HAL_Init();
 SystemClock_Config();

Now you have the clock configured and can proceed to further programming.

GPIO Configuration

On bluepill you have available ports A, B and C. The STM32F103 has also port D, with only two pins used for XTAL oscillator. I wired my button and LED to pins of port B. That is important, because before any pin setting, you must enable the clock for the port (or peripheral) you intend to use. This is done with a macro, in a single line. You need to call the specific macro for each port (or peripheral) you will enable.

 __HAL_RCC_GPIOB_CLK_ENABLE();

Then we can create a GPIO configuration structure. This should be regarded as a placeholder for pin setting. Once you pass it to the GPIO driver, its parameters can be changed and it can be reused for another pin. Therefore, I'll set the button pin for external interrupt on falling edge. I will also activate the internal pull up on this pin. When the button is pressed, it will pull low the pin. Here is the code:

 GPIO_InitTypeDef gpio;
 gpio.Mode = GPIO_MODE_IT_FALLING; // interrupt on falling edge
 gpio.Pin = GPIO_PIN_13; // pin 13
 gpio.Pull = GPIO_PULLUP;

 HAL_GPIO_Init(GPIOB, &gpio);

On the last line is the driver initialisation routine which takes the GPIO and pin configuration as input parameters. With the same gpio variable I will configure the LED pin too.

 gpio.Mode = GPIO_MODE_OUTPUT_PP;
 gpio.Pin = GPIO_PIN_14; // pin 14
 gpio.Speed = GPIO_SPEED_FREQ_MEDIUM;

 HAL_GPIO_Init(GPIOB, &gpio);

For input pins you don't have to set speed according to UM1850 manual. For push-pull output pins there is no need to set pull type. I added a small test to see if LED is working. After that there should be an endless loop (main() must not return for the program to run).

 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
 HAL_Delay(500);
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
 HAL_Delay(500);

 while (1);

Let's configure the button interrupt.

External interrupt

First of all, you must enable it and give it a priority. This must be done in main(), somewhere before the endless loop. Just after the GPIO configuration would be a good place.

 HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

The first routine sets the highest priority for this IRQ. Second parameter is the preemption priority for the IRQn channel and the third is the subpriority level. They can have values from 0 to 15, the lower means higher priority. The problem is what IRQn channel you enable for a pin. The reference manual RM0008 lists all interrupts.

IRQ channels and handlers for each pin

IRQ channels and handlers for each pin

It's not enough to enable interrupt channel. You must also implement a handler. This is how it looks for my example:

void EXTI15_10_IRQHandler(void) {
 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}

You pass the pin name to the GPIO driver interrupt handler. Since EXTI15_10_IRQHandler handles interrupts from more than one pin, if the case you can call multiple GPIO handlers inside. If I would also have an interrupt on pin 11, I should have called HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);. For pin 0 to 4 interrupts, there is only one GPIO handler to call.

The function HAL_GPIO_EXTI_IRQHandler() clears an internal interrupt flag and calls back a function. This function must be implemented by user and this is the place where you will write your code.

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
 if (GPIO_Pin == GPIO_PIN_13) {
  HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_14);
 }
}

Only pin number is known to the callback. Not the GPIO of the pin. This means that you can set interrupts on 16 different pins, no matter from which GPIO. You can configure an interrupt on PA3 and PB4, but you can't have an interrupt at the same time on PA3 and PB3.

Here is the complete code of main.c:

#include "stm32f1xx.h"

void SystemClock_Config(void) {
 RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
 RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
 RCC_OscInitStruct.HSEState = RCC_HSE_ON;
 RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
 RCC_OscInitStruct.HSIState = RCC_HSI_ON;
 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
 RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
  while (1);
 }

 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
   | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
  while (1);
 }

 HAL_RCC_EnableCSS();
}

void EXTI15_10_IRQHandler(void) {
 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
 if (GPIO_Pin == GPIO_PIN_13) {
  HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_14);
 }
}

int main(void) {
 HAL_Init();
 SystemClock_Config();

 __HAL_RCC_GPIOB_CLK_ENABLE();

 // button is on PB13; LED is on PB14

 GPIO_InitTypeDef gpio;
 gpio.Mode = GPIO_MODE_IT_FALLING; // interrupt on falling edge
 gpio.Pin = GPIO_PIN_13; // pin 13
 gpio.Pull = GPIO_PULLUP;
 HAL_GPIO_Init(GPIOB, &gpio);

 HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

 gpio.Mode = GPIO_MODE_OUTPUT_PP;
 gpio.Pin = GPIO_PIN_14;
 gpio.Speed = GPIO_SPEED_FREQ_MEDIUM;
 HAL_GPIO_Init(GPIOB, &gpio);

 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
 HAL_Delay(500);
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
 HAL_Delay(500);

 while (1);
}

You can find more information in ST manuals RM0008 and UM1850. To upload binary to bluepill I'm using ST-Link and I have configured SW4STM32 for it.

No comments :

Post a Comment

Please read the comments policy before publishing your comment.