cancel
Showing results for 
Search instead for 
Did you mean: 

How do I get an interrupt to work using STM32CubeIDE on my STM32F446RE board?

GProp.1999
Associate

Oh there are so many more details... The short story is that I recently got a freelance job converting an old 6800 based controller box to use the STM32 boards. The basic idea is that we will have various things happening in the real world that need to trigger some action from the control software. The obvious solution is interrupts. I must confess that I find the STM32 documentation rather baffling and it is difficult to see exactly what I'm supposed to do in STM32CubeIDE to just connect an interrupt coming in on a pin to an interrupt handler. There's all kinds of stuff online about clocks but very few simple tutorials that show how to do this one simple thing. I've done this kind of work before using Arduinos and that world seems like the opposite extreme of simplicity. Can anyone point me to a good, well written set of tutorials on how to get basic things like interrupts and peripherals running? So far I've managed to make a simple "blink" app and can read the state of the blue button (just polling it, not using interrupts). I'm willing to abandon STM32CubeIDE if there's a better way to get this going but I need a way to use the integrated stepping ST-Link debugging while we test the code.

4 REPLIES 4

Let's start with that you change your username to a normal nick.

> Can anyone point me to a good, well written set of tutorials

No. There are simply none.

The vast majority of material available evolves around some ST "library" - the older ones around so called Standard Peripheral Library (SPL) (which is mostly a "wrapper", basically just renaming the peripheral registers) and the newer ones around Cube/HAL. Then there are third party ones, too, with even lesser tutorials and documentation but supposedly more mojo, like mbed and some form of arduino. These days you are mostly supposed to point and click (that's probably what you see in CubeIDE, or CubeMX) - which may be nice, if your job is a simple-minded one, arduino style, i.e. if you won't ever depart from the inevitably limited usage modes envisaged by the "libraries" authors.

So the landscape is pretty much fragmented and you have to hand-pick the wisdom yourself.

Your basic literature is mostly to be found in the webfolder for the given STM32 model (yes I know those folders are a mess) - the Datasheet (DS), the Reference Manual (RM), the Errata (ES) for your STM32 model. And then, for the foundations, ARM's material for the processor core, digested into so called Programming Manual, but you may want to reach out for Joseph Yiu's books, which are basically the ARM manuals (which you can get at ARM's site) but rewritten from ARM's signature mess into the form they were supposed to be at the first place.

I don't know how to use the CubeIDE for normal programming. I don't use it, I loathe the Eclipsoids.

The RM may be overwhelming, but at least skim over the few dozen or so chapters (before it gets deep into specific peripherals). The "registers" subchapters are often more enlightening than the narrative, which is written by non-english and often obviously unexperienced writers.

A good programming practice is to stick to the symbols for registers given in the NVIC-mandated device headers, to be found in [Cube for given STM32 family]\Drivers\CMSIS\Device\ST\[family]\Include\. Unfortunately, ST does not provide concise set symbols for values except the single-bit ones (mostly); they do it haphazardly in the individual "libraries", I started to write my own augmented headers for this.

The basic workflow is:

  • set up the primary clocks - FLASH latency, regulator range (for this you'll need to enable PWR clock in RCC's clock enable registers), AHB/APB dividers, HSI/HSE, PLL. Most ST "libraries" want to do this for you in the startup code (a .s file, something like startup_stm32?????xx.s - we will get to this later again); I simply throw out any function calls from the startup code and set up clock to my liking in main(); it may result in slower startup (the C standard mandated clear/initialization of global/static variables) but for my applications I don't care, IMMV.
  • enable clocks for peripherals you intend to use
  • set up pins in GPIO as you intend to use
  • set up individual peripherals' registers as outlined in their individual chapters in RM
  • while(1) {}

For interrupts, you

  • enable the interrupt source in the given peripheral, eg. in TIMx_DIER for timers. For external pin-interrupts, see EXTI (note that you need to set the pin mux in SYSCFG, and for that you need to have SYSCFG's clock enabled in RCC)
  • enable particular interrupt in NVIC using  NVIC_EnableIRQ(). For list of available interrupts see the interrupts chapter in RM (note that they usually merge (OR) several sources which you then have to discriminate in the interrupt service routine (ISR). For symbols defined for individual interrupts, to be used as parameter in NVIC functions calls, see the CMSIS-mandated device header.
  • optionally set priority using NVIC_SetPriority() (not much reason to do this if you haven't set  NVIC_SetPriorityGrouping() previously - read the NVIC stuff in ARM's material/PM)
  • write the ISR as a normal function, but beware, its name has to match the handler name defined in the vector table in startup code (remember I told you we will get to this later), eg. void EXTI1_IRQHandler(void) {}
  • in ISR, don't forget to clear the signal which caused the interrupt (usually a bit in some status register; these bits usually MUST be cleared by direct writing 0 or 1 (mask) into the register, not by read-modify-write of the whole register). Do this as the first thing in the ISR.

Examples of my approach here - this is not tutorial-grade stuff, though. Timer interrupt example in "bitband dangerous".

A useless example of EXTI usage below (it's been written for https://community.st.com/s/question/0D50X00009XkfHrSAJ/software-interrupt-on-stm32l4 but that lost its formatting courtesy of dilettantish forum migration)

HTH,

JW

// (C)2018 wek at efton dot sk
 
// simple extint example
// https://community.st.com/message/201572-re-software-interrupt-on-stm32l4
// 'L476 DISCO
 
 
// RED LED on DISCOL4 is PB2
 
// PA5 - JOY_DOWN
 
 
// pressing JOY_DOWN triggers EXTI1, which in ISR toggles the red LED
 
 
// default 4MHz MSI clock
 
 
 
 
 
#include <stdint.h>
#include "stm32l476xx.h"
#include "stm32l476xxx_augment.h"
#include "common.h"
 
 
 
// --------  trivial loopdelay
 
volatile uint32_t __attribute__((section(".ram2"))) delayN;
 
void LoopDelay(uint32_t n) {
  delayN = n;
  while(delayN > 0) delayN--;
}
 
#define DELAY_CONSTANT 50000
 
 
// ISR
 
void EXTI1_IRQHandler(void) __attribute__((interrupt));
void EXTI1_IRQHandler(void) {
  uint32_t pr;
 
  pr = EXTI->PR1 & (1 << 1);
  EXTI->PR1 = pr;  // clear pending interrupts by writing 1
  if (pr & (1 << 1)) {
    // toggle LED
    if (GPIOB->ODR AND (1 << 2)) {
      GPIOB->BSRR = (1 << 2) << 16;
    } else {
      GPIOB->BSRR = (1 << 2);
    }
  }
}
 
 
 
// --------  main
 
int main(void) {
 
  RCC->AHB2ENR |= 0
    | RCC_AHB2ENR_GPIOAEN
    | RCC_AHB2ENR_GPIOBEN
  ;
 
 
 
  GPIOA->PUPDR = (GPIOA->PUPDR
    & (~GPIO_PUPDR_PUPD5)
  ) | (0
    | (GPIO_PullDown * GPIO_PUPDR_PUPD5_0)  // JOY surprisingly switches to +3V!
  );
  ;
  GPIOA->MODER = (GPIOA->MODER
    & (~GPIO_MODER_MODER5)
  ) | (0
    | (GPIO_Mode_In * GPIO_MODER_MODER5_0)   // PA5 - JOY_DOWN - TIM2_CH1
  );
 
  GPIOB->MODER = (GPIOB->MODER
    & (~GPIO_MODER_MODER2)
  ) | (0
    | (GPIO_Mode_Out * GPIO_MODER_MODER2_0)   // PB3 (jumper to PB2-RED LED)
  );
 
 
  NVIC_SetPriority(EXTI1_IRQn, 3);   // Set priority
  NVIC_EnableIRQ(EXTI1_IRQn);
  EXTI->IMR1 = 1 << 1;
  EXTI->EMR1 = 1 << 1; // this is not needed, but we add it to mimic the code in post
 
 
  while(1) {
    static _Bool down;
    unsigned i;
    #define DEBOUNCE_CNT 4000
 
    if (down) {
      if ((GPIOA->IDR & GPIO_IDR_ID5_Msk) == 0) down = 0;
    } else {
      for (i = 0; i < DEBOUNCE_CNT; i++) {
        if ((GPIOA->IDR & GPIO_IDR_ID5_Msk) == 0) break;
      }
      if (i == DEBOUNCE_CNT) {
        down = 1;
        EXTI->SWIER1 = 1 << 1;
      }
    }
 
 
  }
}
 
 

Pavel A.
Evangelist III

ST has online training on several MCU families. Unfortunately, F4 is not covered (yet?)

https://www.st.com/content/st_com/en/support/learning/stm32-education/stm32-online-training/stm32h7-online-training.html

-- pa

Hello, I'm trying to find out if you ever got around to this: "a .s file, something like startup_stm32?????xx.s - we will get to this later again"

I'm trying to setup a minimal possibly barebones template in STM32CubeIDE for a Nucleo STM32H755ZI-Q. I'd like to ditch the .ico and write my own code at the HAL level or lower. The startup file (.s) documentation says that it does the following:

  • Set the initial SP
  • Set the initial PC == Reset_Handler,
  • Set the vector table entries with the exceptions ISR address
  • Branches to main in the C library (which eventually
  • calls main()).
  • After Reset the Cortex-M processor is in Thread mode, priority is Privileged, and the Stack is set to Main.

I was curious what you had to say about this file. I'm guessing I should keep it since I see it's present in your basic Blinky example. Are there any changes to it that you would recommend?

edit: Also, I don't see a void SystemClock_Config(void) type function in your examples. Is that not required for a minimal project? Looks like I have a lot to learn

'H7 is a complex beast, I'm not sure it's the best option to start. I don't use any Cortex-M7, much of work is on the control side.

I use startup file coming from the "libraries". SPL tended to call click so from there, I removed that. I also removed any C-library (newlib) related calls, that might perhaps backfire at me later, but I generally avoid <stdio.h> facilities in embedded environment. I do enable the FPU/coprocessor in the startup asm code where fpu is present and enabled for compiler, as the prologue to main () generated by compiler then may contain code using FPU registers.

I don't enable and clear external memories (nor the non-continuous portions of internal RAM) in the startup, I do it at the beginning of main (). Maybe that will backfire at me one day, too, but I do that knowingly.

In most of the examples there was no reason to use anything else than the default clock (HSI/MSI depending on STM32 model), there may be examples where HSE/PLL is used, but I then switch it using a few lines at the beginning of main(), I don't see any reason to write a separate function for such a simple task.

YMMV

JW