在 STM32 微控制器中禁用和重新启用中断以实现原子访问防护的各种方法有哪些?

Gab*_*les 9 c atomic interrupt stm32 isr

通过“原子访问防护”或“中断防护”强制对与 ISR 共享的易失性变量进行原子访问的标准技术,特别是在没有操作系统的情况下运行裸机、单线程协作多任务应用程序时,如下所示:

// 1. save interrupt state
// 2. disable only the interrupts necessary

// You get atomic access to volatile variables shared with ISRs here,
// since ISRs are the only other "context" or running "thread" which
// might attempt to modify a shared memory block or variable.

// 3. restore interrupt state
Run Code Online (Sandbox Code Playgroud)

另请参阅我在这里详细描述的地方,包括最佳实践(在短时间内保持中断关闭)以及如何通过我的doAtomicRead()重复读取循环函数进行原子读取而不首先禁用中断:读取 64 位变量,即由 ISR 更新

我之前已经记录过如何对 AVR 微控制器/Arduino 执行此操作:How do I Forceatomity in Atmel AVR mcus/Arduino?

但是,我该如何为 STM32 微控制器做到这一点呢?我知道有很多方法。

请涵盖以下技术:

  1. 通过 ARM 核 CMSIS:
    1. 对于全局中断
    2. 针对特定 IRQ(中断请求)
  2. 通过STM32 HAL(硬件抽象层)
  3. 通过FreeRTOS

这个答案是相关的,但还不够:How can I re-enable the stm32f103's external中断后我禁用它?

Gab*_*les 23

2023 年 5 月 10 日更新:我学习这些东西的主要动机之一与我在 7 年前的 2016 年编写的第一个环形缓冲区实现有关,导致了这个调试问题,我在 2 天内损失了 25 个小时的调试工作。我最终编写了一个非常好的环形缓冲区实现,当在任何支持 C11 或 C++11 原子类型的系统上使用时,它是无锁的。这是我写过的最好的实现,也是我见过的最好的实现。它解决了其他实现的许多问题。完整的详细信息位于文件顶部。它可以在 CC++ 中运行。您可以在此处查看完整的实现:我的eRCaGuy_hello_world存储库中的Containers_ring_buffer_FIFO_GREAT.c


STM32 MCU 中启用/禁用中断的多种方法

...启用原子访问(关键部分)防护:

1.通过 ARM 核 CMSIS:

1.A. 对于全局中断

__enable_irq()   // enable all interrupts
__disable_irq()  // disable all interrupts

// Returns the current state of the priority mask bit from the Priority Mask
// Register. [0 if global interrupts are **enabled** and non-zero if they
// are **disabled**]
__get_PRIMASK()
Run Code Online (Sandbox Code Playgroud)

关于这些函数的定义,请参见:

  1. https://github.com/ARM-software/CMSIS/blob/master/CMSIS/Include/cmsis_gcc.h
    1. 至少包含:
      __enable_irq() 
      __disable_irq()
      __get_PRIMASK()
      __set_PRIMASK()
      
      Run Code Online (Sandbox Code Playgroud)
  2. STM32示例位置:“stm/stm32f2xx/st_hal_v1.1.3/CMSIS/Include/cmsis_gcc.h”:

保存和恢复中断状态,请使用__get_PRIMASK(),如下所示:

// 1. back up interrupt state; `__get_PRIMASK()` returns 0 if interrupts
// are **enabled**, and non-zero if they are **disabled**.
bool interrupts_enabled = (__get_PRIMASK() == 0);

// do stuff

// 2. Disable interrupts
__disable_irq();
// 3. Restore backed-up-state
if (interrupts_enabled) {
    __enable_irq();
}
Run Code Online (Sandbox Code Playgroud)

处理全局中断时,这是裸机、非 FreeRTOS 代码的最佳方法!

认为这项技术还与所有 ARM 核 MCU 交叉兼容,而不仅仅是 STM32。

我首先从 Tilen Majerle 那里学到了这项技术: https: //stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/。他为澄清这些超级混乱的东西所做的工作和贡献是无限有价值和值得赞赏的!

他的例子:

void ImportantFunction1(void) {
    /* Important function 1 */
    uint32_t prim;
    
    /* Do some stuff here which can be interrupted */
    
    /* Read PRIMASK register, check interrupt status before you disable them */
    /* Returns 0 if they are enabled, or non-zero if disabled */
    prim = __get_PRIMASK();
    
    /* Disable interrupts */
    __disable_irq();
    
    /* Do some stuff here which can not be interrupted */
    
    /* Call subfunction */
    ImportantFunction2();
    
    /* Do some stuff here which can not be interrupted */
    /* This part is still interrupt safe because ImportantFunction2 will not enable interrupts */
    
    /* Enable interrupts back */
    if (!prim) {
        __enable_irq();
    }
    
    /* Do some stuff here which can be interrupted */
}
Run Code Online (Sandbox Code Playgroud)

1.B. 对于特定的 IRQ(中断请求)

如果可能的话,最好避免禁用全局中断,并仅禁用尽可能少的特定中断,以实现特定代码的原子性。因此,使用这些函数可以让您仅启用或禁用您需要的特定中断!

启用或禁用特定类型的中断:

void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);
Run Code Online (Sandbox Code Playgroud)

NVIC 代表“嵌套向量中断控制器”。STM32 微控制器默认启用嵌套中断(意味着:更高优先级的中断仍然可以在 ISR 内触发)每种中断类型都分配有一个优先级,数字越小,优先级越高,当 ISR 正在处理较低优先级中断时,可以触发较高优先级的中断。有关 STM32 NVIC 的更多信息,请参阅此处:https://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-nvic-or-nested-vector-interrupt-controller/

与 AVR 微控制器(例如:ATMega328 / Arduino Uno)相比,后者没有基于优先级的中断,因此默认情况下,当任何ISR 正在处理时,所有中断(即:全局中断)都会在程序进入情监侦。但请注意,即使在 AVR mcus 上,如果您愿意,您仍然可以通过调用Arduino 或原始 AVR 上的(设置中断)手动重新启用ISR 内的全局中断来手动启用嵌套中断/ISR 。interrupts()sei()

我相信,每个 ARM 核微控制器制造商,包括 STM32 类型,都必须定义和创建自己的IRQn_Type中断请求类型列表,因此请参阅下面的 STM32 详细信息,了解为每个 MCU 定义的特定中断类型。

2.通过STM32 HAL(硬件抽象层)库

启用或禁用特定类型的中断:

// enable interrupts
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
// disable interrupts 
HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
Run Code Online (Sandbox Code Playgroud)

例如,请参阅:“stm/stm32f2xx/st_hal_v1.1.3/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c/.h” - 上述函数的定义位于这些文件中。在线查看他们:

  1. https://github.com/STMicroElectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Inc/stm32f2xx_hal_cortex.h#L264-L265
  2. https://github.com/STMicroElectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c#L178-L210

HAL_NVIC_EnableIRQ()以下是和的定义HAL_NVIC_DisableIRQ()。请注意,他们只是检查以确保您的IRQn有效性,然后将输入参数传递给 ARM 核心 CMSISNVIC_EnableIRQ()NVIC_DisableIRQ()上面的函数!:

/**
  * @brief  Enables a device specific interrupt in the NVIC interrupt controller.
  * @note   To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
  *         function should be called before. 
  * @param  IRQn External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
  * @retval None
  */
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
  
  /* Enable interrupt */
  NVIC_EnableIRQ(IRQn);
}

/**
  * @brief  Disables a device specific interrupt in the NVIC interrupt controller.
  * @param  IRQn External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
  * @retval None
  */
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
  
  /* Disable interrupt */
  NVIC_DisableIRQ(IRQn);
}
Run Code Online (Sandbox Code Playgroud)

对于s:请参阅适合您的特定板的IRQn_Type定义文件!这些是制造商为您的主板提供的特定于主板的定义。以下是 STM32 F2xx 系列中的所有板,例如: https: //github.com/STMicroElectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include。我们具体看一下该文件:stm32f217xx.h

  1. https://github.com/STMicroElectronics/STM32CubeF2/blob/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
  2. 原始视图(因为文件太大,无法在 GitHub 上查看):https://raw.githubusercontent.com/STMicroElectronics/STM32CubeF2/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h

从这个文件中,我们可以看到typedef enum的定义IRQn_Type,即“STM32F2XX中断号定义”。它看起来是这样的:

/**
 * @brief STM32F2XX Interrupt Number Definition, according to the selected device 
 *        in @ref Library_configuration_section 
 */
typedef enum
{
/******  Cortex-M3 Processor Exceptions Numbers ****************************************************************/
  NonMaskableInt_IRQn         = -14,    /*!< 2 Non Maskable Interrupt                                          */
  HardFault_IRQn              = -13,    /*!< 3 Hard Fault Interrupt                                            */
  MemoryManagement_IRQn       = -12,    /*!< 4 Cortex-M3 Memory Management Interrupt                           */
  BusFault_IRQn               = -11,    /*!< 5 Cortex-M3 Bus Fault Interrupt                                   */
  UsageFault_IRQn             = -10,    /*!< 6 Cortex-M3 Usage Fault Interrupt                                 */
  SVCall_IRQn                 = -5,     /*!< 11 Cortex-M3 SV Call Interrupt                                    */
  DebugMonitor_IRQn           = -4,     /*!< 12 Cortex-M3 Debug Monitor Interrupt                              */
  PendSV_IRQn                 = -2,     /*!< 14 Cortex-M3 Pend SV Interrupt                                    */
  SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt                                */
/******  STM32 specific Interrupt Numbers **********************************************************************/
  WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt                                         */
  PVD_IRQn                    = 1,      /*!< PVD through EXTI Line detection Interrupt                         */
  TAMP_STAMP_IRQn             = 2,      /*!< Tamper and TimeStamp interrupts through the EXTI line             */
  RTC_WKUP_IRQn               = 3,      /*!< RTC Wakeup interrupt through the EXTI line                        */
  FLASH_IRQn                  = 4,      /*!< FLASH global Interrupt                                            */
  RCC_IRQn                    = 5,      /*!< RCC global Interrupt                                              */
  EXTI0_IRQn                  = 6,      /*!< EXTI Line0 Interrupt                                              */
  EXTI1_IRQn                  = 7,      /*!< EXTI Line1 Interrupt                                              */
  EXTI2_IRQn                  = 8,      /*!< EXTI Line2 Interrupt                                              */
  EXTI3_IRQn                  = 9,      /*!< EXTI Line3 Interrupt                                              */
  EXTI4_IRQn                  = 10,     /*!< EXTI Line4 Interrupt                                              */
  DMA1_Stream0_IRQn           = 11,     /*!< DMA1 Stream 0 global Interrupt                                    */
  DMA1_Stream1_IRQn           = 12,     /*!< DMA1 Stream 1 global Interrupt                                    */
  DMA1_Stream2_IRQn           = 13,     /*!< DMA1 Stream 2 global Interrupt                                    */
  DMA1_Stream3_IRQn           = 14,     /*!< DMA1 Stream 3 global Interrupt                                    */
  DMA1_Stream4_IRQn           = 15,     /*!< DMA1 Stream 4 global Interrupt                                    */
  DMA1_Stream5_IRQn           = 16,     /*!< DMA1 Stream 5 global Interrupt                                    */
  DMA1_Stream6_IRQn           = 17,     /*!< DMA1 Stream 6 global Interrupt                                    */
  ADC_IRQn                    = 18,     /*!< ADC1, ADC2 and ADC3 global Interrupts                             */
  CAN1_TX_IRQn                = 19,     /*!< CAN1 TX Interrupt                                                 */
  CAN1_RX0_IRQn               = 20,     /*!< CAN1 RX0 Interrupt                                                */
  CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt                                                */
  CAN1_SCE_IRQn               = 22,     /*!< CAN1 SCE Interrupt                                                */
  EXTI9_5_IRQn                = 23,     /*!< External Line[9:5] Interrupts                                     */
  TIM1_BRK_TIM9_IRQn          = 24,     /*!< TIM1 Break interrupt and TIM9 global interrupt                    */
  TIM1_UP_TIM10_IRQn          = 25,     /*!< TIM1 Update Interrupt and TIM10 global interrupt                  */
  TIM1_TRG_COM_TIM11_IRQn     = 26,     /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
  TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                                    */
  TIM2_IRQn                   = 28,     /*!< TIM2 global Interrupt                                             */
  TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                             */
  TIM4_IRQn                   = 30,     /*!< TIM4 global Interrupt                                             */
  I2C1_EV_IRQn                = 31,     /*!< I2C1 Event Interrupt                                              */
  I2C1_ER_IRQn                = 32,     /*!< I2C1 Error Interrupt                                              */
  I2C2_EV_IRQn                = 33,     /*!< I2C2 Event Interrupt                                              */
  I2C2_ER_IRQn                = 34,     /*!< I2C2 Error Interrupt                                              */  
  SPI1_IRQn                   = 35,     /*!< SPI1 global Interrupt                                             */
  SPI2_IRQn                   = 36,     /*!< SPI2 global Interrupt                                             */
  USART1_IRQn                 = 37,     /*!< USART1 global Interrupt                                           */
  USART2_IRQn                 = 38,     /*!< USART2 global Interrupt                                           */
  USART3_IRQn                 = 39,     /*!< USART3 global Interrupt                                           */
  EXTI15_10_IRQn              = 40,     /*!< External Line[15:10] Interrupts                                   */
  RTC_Alarm_IRQn              = 41,     /*!< RTC Alarm (A and B) through EXTI Line Interrupt                   */
  OTG_FS_WKUP_IRQn            = 42,     /*!< USB OTG FS Wakeup through EXTI line interrupt                     */    
  TIM8_BRK_TIM12_IRQn         = 43,     /*!< TIM8 Break Interrupt and TIM12 global interrupt                   */
  TIM8_UP_TIM13_IRQn          = 44,     /*!< TIM8 Update Interrupt and TIM13 global interrupt                  */
  TIM8_TRG_COM_TIM14_IRQn     = 45,     /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
  TIM8_CC_IRQn                = 46,     /*!< TIM8 Capture Compare Interrupt                                    */
  DMA1_Stream7_IRQn           = 47,     /*!< DMA1 Stream7 Interrupt                                            */
  FSMC_IRQn                   = 48,     /*!< FSMC global Interrupt                                             */
  SDIO_IRQn                   = 49,     /*!< SDIO global Interrupt                                             */
  TIM5_IRQn                   = 50,     /*!< TIM5 global Interrupt                                             */
  SPI3_IRQn                   = 51,     /*!< SPI3 global Interrupt                                             */
  UART4_IRQn                  = 52,     /*!< UART4 global Interrupt                                            */
  UART5_IRQn                  = 53,     /*!< UART5 global Interrupt                                            */
  TIM6_DAC_IRQn               = 54,     /*!< TIM6 global and DAC1&2 underrun error  interrupts                 */
  TIM7_IRQn                   = 55,     /*!< TIM7 global interrupt                                             */
  DMA2_Stream0_IRQn           = 56,     /*!< DMA2 Stream 0 global Interrupt                                    */
  DMA2_Stream1_IRQn           = 57,     /*!< DMA2 Stream 1 global Interrupt                                    */
  DMA2_Stream2_IRQn           = 58,     /*!< DMA2 Stream 2 global Interrupt                                    */
  DMA2_Stream3_IRQn           = 59,     /*!< DMA2 Stream 3 global Interrupt                                    */
  DMA2_Stream4_IRQn           = 60,     /*!< DMA2 Stream 4 global Interrupt                                    */
  ETH_IRQn                    = 61,     /*!< Ethernet global Interrupt                                         */
  ETH_WKUP_IRQn               = 62,     /*!< Ethernet Wakeup through EXTI line Interrupt                       */
  CAN2_TX_IRQn                = 63,     /*!< CAN2 TX Interrupt                                                 */
  CAN2_RX0_IRQn               = 64,     /*!< CAN2 RX0 Interrupt                                                */
  CAN2_RX1_IRQn               = 65,     /*!< CAN2 RX1 Interrupt                                                */
  CAN2_SCE_IRQn               = 66,     /*!< CAN2 SCE Interrupt                                                */
  OTG_FS_IRQn                 = 67,     /*!< USB OTG FS global Interrupt                                       */
  DMA2_Stream5_IRQn           = 68,     /*!< DMA2 Stream 5 global interrupt                                    */
  DMA2_Stream6_IRQn           = 69,     /*!< DMA2 Stream 6 global interrupt                                    */
  DMA2_Stream7_IRQn           = 70,     /*!< DMA2 Stream 7 global interrupt                                    */
  USART6_IRQn                 = 71,     /*!< USART6 global interrupt                                           */
  I2C3_EV_IRQn                = 72,     /*!< I2C3 event interrupt                                              */
  I2C3_ER_IRQn                = 73,     /*!< I2C3 error interrupt                                              */
  OTG_HS_EP1_OUT_IRQn         = 74,     /*!< USB OTG HS End Point 1 Out global interrupt                       */
  OTG_HS_EP1_IN_IRQn          = 75,     /*!< USB OTG HS End Point 1 In global interrupt                        */
  OTG_HS_WKUP_IRQn            = 76,     /*!< USB OTG HS Wakeup through EXTI interrupt                          */
  OTG_HS_IRQn                 = 77,     /*!< USB OTG HS global interrupt                                       */
  DCMI_IRQn                   = 78,     /*!< DCMI global interrupt                                             */
  CRYP_IRQn                   = 79,     /*!< CRYP crypto global interrupt                                      */
  HASH_RNG_IRQn               = 80      /*!< Hash and Rng global interrupt                                     */
} IRQn_Type;
Run Code Online (Sandbox Code Playgroud)

2.A. 使用STM32 HAL 的示例用法:

要通过基于 HAL 的阻塞(轮询)模式(即:via )获得对打印调试字符的独占访问(例如,确保以原子方式打印字符串),您需要通过执行以下操作来禁用所有中断。(这保证您能够原子访问该设备):USART1HAL_UART_Transmit()USART1_IRQn

// 1. Disable the UART IRQ
HAL_NVIC_DisableIRQ(USART1_IRQn);

// 2. Send your string (in HAL blocking/polled mode)
// Prototype for this function is from 
// "...stm/stm32f7