embeddedinterruptkeilbare-metalbluepill

STM32F103 blue pill interrupts from scratch


How could I create an interrupt for a blue pill from scratch?

I do not want to use any sort of special library. Also, I use Keil IDE, thus, by "building from scratch" I refer rather not to use any extra library than to assemble the project without the help of an IDE.

I tried to find resources, but no success. Could anybody help me and at least provide some information/bibliography for me? I would be grateful.

Moreover, by "strange library" I mean any other library than the stmf32f1xx.h header. I would like to fire an interrupt when one of the pins' input value toggles. In order to do this, on AVR MCUs it was very simple as long as only a few register values should be changed. Unfortunately, I don't know how an interrupt within an ARM MCU functions and in which registers should I write what values.

Also, a better understanding of the ARM MCU's interrupt mechanism would make me more prepared for tackling debouncing issues.


Solution

  • I am not going to take you entirely literally when you mandate "no libraries", because no one who wants to get work done and knows what they are doing on Cortex-M would do that - and I will assume at least that you will use the CMSIS - a common API provided for all ARM Cortex-M devices, and which makes your code more, not less portable.

    All the CMSIS code is provided as source, rather than static library, so there is nothing hidden and if you chose not to use it, you can see how it works and replicate that functionality (needlessly) if you wish.

    In the CMSIS default implementations are provided as "weak-links" that can be overridden by user code simply by defining a function of the pre-defined name to override the default. The default implementation is generally an infinite loop - so that unhandled interrupts are "trapped" so you can intervene with your debugger or wait for a watchdog reset for example.

    The Cortex-M core interrupt handlers and exception handlers have common names across all Cortex-M parts:

    Reset_Handler      
    NMI_Handler        
    HardFault_Handler  
    MemManage_Handler  
    BusFault_Handler   
    UsageFault_Handler 
    SVC_Handler        
    DebugMon_Handler   
    PendSV_Handler     
    SysTick_Handler    
    

    Peripheral interrupt handlers have names defined by the vendor, but the naming convention is <interrupt_source>_IRQHandler. For example on STM32F1xx EXTI0_IRQHandler is the shared external interrupt assigned to bit zero of GPIO ports.

    To implement an CMSIS interrupt handler, all you need do is:

    1. Implement the interrupt handler function using the CMSIS handler function name
    2. Enable the interrupt in the NVIC (interrupt controller).

    There other are things you might do such as assign the interrupt priority scheme (the split between preempt priorities and subpriorities), but lets keep it simple for the time being.

    Because it is ubiquitous to all Cortex-M parts, and because it is useful in almost any non-trivial application an illustration using the SYSTICK interrupt is useful as a starting point.

    #include "stm32f1xx.h"
      
    volatile uint32_t msTicks = 0 ;
      
    void SysTick_Handler(void)  
    {
        msTicks++ ;
    }
      
    int main (void)  
    {
        if( SysTick_Config( SystemCoreClock / 1000 ) != 0 ) // 1ms tick
        {
            // Error Handling 
        }
      
        ...
    
    }
    

    SysTick_Config() is another CMSIS function. In core_cm3.h it looks like this:

    __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
    {
      if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
      {
        return (1UL);                                                   /* Reload value impossible */
      }
    
      SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
      NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
      SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
      SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                       SysTick_CTRL_TICKINT_Msk   |
                       SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
      return (0UL);                                                     /* Function successful */
    }
    

    So let's say you have a external interrupt source on the falling edge of GPIOA pin 0, then you would use the STM32 EXTI0 interrupt. The minimal handler would look like:

    void EXTI0_IRQHandler(void)
    {
        EXTI->PR |= (1<<0);                           // clear pending interrupt
    
        // Handle interrupt...
    }
    

    Setting up the EXTI requires enabling the GPIO and the EXTI itself as well as the NVIC:

    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN ;           // enable clock for GPIOA
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN ;           // enable clock for Alternate Function
    AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0 ;        // set pin to use
    
    EXTI->IMR = EXTI_IMR_MR0 ;             // unmask interrupt
    EXTI->EMR = EXTI_EMR_MR0 ;             // unmask event
    EXTI->FTSR = EXTI_FTSR_TR0 ;           // set falling edge
    
    NVIC->ISER[0] |= (1 << (EXTI0_IRQChannel & 0x1F));    // enable interrupt EXTI 0
    

    The peripheral registers and structures are defined in stm32f10weakx.h, and the "weak" default peripheral handlers to be overridden are in startup_stm32f10x_cl.s for your specific part. Any handlers you override must match these symbol names exactly.

    All the peripheral interrupt sources and how to configure them is defined un the ST Reference Manual RM0008.

    All the Cortex-M core specific stuff - systtick, NVIC, exception handlers etc. is provided by ARM at https://developer.arm.com/ip-products/processors/cortex-m/cortex-m3

    CMSIS for CM3 is documented at https://developer.arm.com/documentation/dui0552/a/