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

发布于 2025-01-17 01:53:01 字数 1140 浏览 2 评论 0 原文

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

// 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

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

我之前已经记录过如何对 AVR 微控制器/Arduino 执行此操作:如何在 Atmel AVR mcus/Arduino 中强制原子性?

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

请介绍以下技术:

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

这个答案是相关的,但不够: How can我禁用stm32f103的外部中断后重新启用它?

The standard technique to enforce atomic access to volatile variables shared with ISRs, via "atomic access guards" or "interrupt guards", in particular when running a bare metal, single-threaded cooperative multi-tasking application with no operating system, is as follows:

// 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

See also where I describe this in detail here, including best practices (keep interrupts off for short period of time) and how to do atomic reads withOUT disabling interrupts first, via my doAtomicRead() repeat-read-loop function: Reading a 64 bit variable that is updated by an ISR.

I have previously documented how to do this for AVR microcontrollers/Arduino: How do I force atomicity in Atmel AVR mcus/Arduino?.

But, how do I do this for STM32 microcontrollers? I know there are a lot of ways.

Please cover the following techniques:

  1. Via ARM-core CMSIS:
    1. for global interrupts
    2. for specific IRQs (Interrupt Requests)
  2. Via STM32 HAL (Hardware Abstraction Layer)
  3. Via FreeRTOS

This answer is related, but insufficient: How can I re-enable the stm32f103's external interrupt after I disable it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

初与友歌 2025-01-24 01:53:01

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


在 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()

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

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

保存和恢复中断状态,请使用__get_PRIMASK(),例如this:

// 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();
}

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

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

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

他的例子:

void importantFunction1(void) {
    /* 重要函数1 */
    uint32_t prim;
    
    /* 在这里做一些可以被中断的事情 */
    
    /* 读取 PRIMASK 寄存器,在禁用中断之前检查中断状态 */
    /* 如果启用则返回 0,如果禁用则返回非零 */
    prim = __get_PRIMASK();
    
    /* 禁止中断 */
    __disable_irq();
    
    /* 在这里做一些不能被打断的事情 */
    
    /* 调用子函数 */
    重要函数2();
    
    /* 在这里做一些不能被打断的事情 */
    /*这部分仍然是中断安全的,因为ImportantFunction2不会启用中断*/
    
    /* 使能中断返回 */
    如果(!prim){
        __enable_irq();
    }
    
    /* 在这里做一些可以被中断的事情 */
}

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

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

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

void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);

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

将此与 AVR 微控制器(例如:ATMega328 / Arduino Uno)进行对比,后者没有基于优先级的中断,因此默认情况下,当任何 ISR 正在处理时,当程序进入 ISR 时,所有中断(即全局中断)都会自动禁用。请注意,即使在 AVR MCU 上,如果您愿意,您仍然可以手动启用嵌套中断/ISR,通过手动重新启用 ISR 内的全局中断="https://stackoverflow.com/a/39693278/4561887">在 Arduino 上调用 interrupts() 或原始 AVR 上的 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);

请参阅,例如:“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 核心 CMSIS NVIC_EnableIRQ() 和 NVIC_DisableIRQ()<上面的 /code> 功能!:

/**
  * @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);
}

对于 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 IRQn_Type 的 enum 定义,即“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;

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

获得 USART1 的独占访问权限(例如,确保以原子方式打印字符串),以通过以下方式打印调试字符基于 HAL 的阻塞(轮询)模式(即:通过 HAL_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/stm32f7xx/st_hal_v1.1.2/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_uart.c": 
// - `HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, 
//        uint8_t *pData, uint16_t Size, uint32_t Timeout)`
// Note: `huart` is defined by STM32CubeMX as `UART_HandleTypeDef huart1;` 
// in "hal_source/Src/usart.c"
HAL_UART_Transmit(&huart1, (uint8_t *)my_str, strlen(my_str), HAL_MAX_DELAY);

// 3. Enable the UART_IRQ
// FUTURE WORK: make this nestable/more robust by only enabling the 
// IRQ here if it was previously enabled before disabling it!
HAL_NVIC_EnableIRQ(USART1_IRQn);

3. 通过 FreeRTOS:

FreeRTOS 原子访问保护/中断相关函数列在内核控制 API 的“模块”部分下:内核控制

taskYIELD()
taskENTER_CRITICAL()            // interrupts off
taskEXIT_CRITICAL()             // restore interrupts
taskENTER_CRITICAL_FROM_ISR()   // interrupts off
taskEXIT_CRITICAL_FROM_ISR()    // restore interrupts
taskDISABLE_INTERRUPTS()        // interrupts off
taskENABLE_INTERRUPTS()         // interrupts on

重要!:

不得从关键部分调用 FreeRTOS API 函数。

来源: https://www.freertos.org/Documentation/02-Kernel/04-API-references/04-RTOS-kernel-control/01-taskENTER_CRITICAL_taskEXIT_CRITICAL

另请参阅我的自述文件,其中包含潜在列表FreeRTOS 关键部分中允许不允许其中的调用:https://github.com/ElectricRCAaircraftGuy/eRCaGuy_Engineering/tree/ main/FreeRTOS#freertos-ritic-section-calls

3.A.高级宏:

  1. 这些是首选使用的宏,也是 freertos 推荐的宏!

  2. 这些都支持嵌套调用,并且最终都会调用 portDISABLE_INTERRUPTS() ,这是较低级别 taskDISABLE_INTERRUPTS() 的端口实现,如下所示。

    关于嵌套:来自:https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html

    <块引用>

    taskENTER_CRITICAL()taskEXIT_CRITICAL() 的调用旨在嵌套。因此,只有当之前每次调用 taskENTER_CRITICAL() 都执行了一次 taskEXIT_CRITICAL() 调用时,才会退出临界区。

    其他限制:

    <块引用>

    关键部分必须保持非常短,否则将对中断响应时间产生不利影响。对 taskENTER_CRITICAL() 的每次调用都必须与对 taskEXIT_CRITICAL() 的调用紧密配对。

    不得从关键部分调用 FreeRTOS API 函数。

  3. 来自:https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html

    taskENTER_CRITICAL() // 中断关闭
    taskEXIT_CRITICAL() // 恢复中断
    
  4. 来自: <一href="https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html" rel="nofollow noreferrer">https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html

    taskENTER_CRITICAL_FROM_ISR()
    任务EXIT_CRITICAL_FROM_ISR()
    

3.B.较低级别的宏:

  1. 这些不支持嵌套调用!

  2. 有关它们的官方文档位于“内核控制”主页上:

    taskDISABLE_INTERRUPTS()
    任务ENABLE_INTERRUPTS()
    
  3. 注释和限制:

    1. taskDISABLE_INTERRUPTS() 在上面的链接中指出:

    <块引用>

    通常不会直接调用此宏,应使用 taskENTER_CRITICAL()taskEXIT_CRITICAL() 代替它。

    1. taskENABLE_INTERRUPTS() 在上面的链接中指出:

    <块引用>

    通常不会直接调用此宏,应使用 taskENTER_CRITICAL()taskEXIT_CRITICAL() 代替它。

    1. 另请注意,taskDISABLE_INTERRUPTS() 的使用被演示为用于在 configASSERT() 的示例宏定义中发生恐慌的技术。
      1. 从这里:https://www.freertos.org/a00110.html#configASSERT< /a>,当与调试器一起使用时,它定义为:
        /* 定义 configASSERT() 以禁用中断并处于循环中。 */
        #define configASSERT( ( x ) ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS();为了( ;; ); }
        

      2. 我的想法:也许在这种情况下(即:硬断言或恐慌),taskDISABLE_INTERRUPTS() 可能比 taskENTER_CRITICAL() 更好,因为没有大量的调用一旦调用 taskDISABLE_INTERRUPTS(),来自另一个线程的 taskEXIT_CRITICAL() 将重新启用中断 [I想想!?]——相反,我们必须显式地(并且意外地)调用taskENABLE_INTERRUPTS()(例如:从另一个线程)来重新启用中断一次taskDISABLE_INTERRUPTS() > 已被调用。换句话说,这里使用低级 taskDISABLE_INTERRUPTS() 调用是合适的,因为它确实会根据需要导致系统陷入循环,而 taskENTER_CRITICAL()不会。




3.C.互斥体和其他支持 OS(操作系统)的同步原语

除了上面的示例之外,您还可以使用 FreeRTOS 队列(它们是线程安全的, C++ 中的所有容器不同) std 库)、互斥体信号量任务通知和其他同步原语(在可能且适当的情况下),以保护共享的某些数据之间FreeRTOS 任务(线程),假设您正在运行 FreeRTOS。

请在此处查看这些工具的列表:https://www.freertos.org/a00106.html,并在单击该链接后出现在左侧导航菜单中。

4. TODO:互斥原语:通过原子 set_and_test()(读取、修改、写入)指令实现原始、与操作系统无关的自旋锁

  1. 添加原子 test_and_set() (我认为,set_and_test()read_modify_write() 作为函数名称确实更有意义)使用 ARM 核心的演示CMSIS 函数、汇编或任何必要的方式,以演示在 STM32 中编写自旋锁。我还不知道如何做到这一点,因此需要找到正确的函数或操作来使用。请参阅此处: https://en.wikipedia.org/wiki/Test -and-set#Pseudo-C_implementation_of_a_spin_lock

    易失性 int 锁 = 0;
    
    无效关键(){
        // 自旋锁:永远循环直到获得锁;我们知道锁是
        // 退出这个while循环后成功获取,因为 
        // test_and_set()函数锁定该锁并返回之前的锁 
        // 价值。如果之前的锁值为 1,那么锁**已经**
        // 被另一个线程或进程锁定。一旦先前的锁定值
        // 但是,如果是 0,那么它表示在我们之前该锁**未**锁定
        // 锁定了它,但现在它**被**锁定了,因为我们锁定了它,表明
        // 我们拥有锁。
        while (test_and_set(&lock) == 1);  
        临界区 // 一次只能有一个进程位于该部分
        锁=0; // 完成临界区后释放锁
    }
    

    这是我在 C11 中使用 _Atomic 类型实现的自旋锁。它也应该适用于 STM32,并且可能编译为使用底层专有的 STREX / LDREX 操作来存储(写入)和读取(加载),但我' d 必须通过查看装配来检查这一点。此外,还需要修改此实现以添加安全防死锁机制,例如自动延迟、超时和重试,以防止死锁。请参阅我的注释:在 C11、C++11 中添加基本互斥锁(锁)和自旋锁实现、AVR和STM32

5. 另请参阅:

  1. 我对 AVR mcus/Arduino 的回答
  2. 我对使用原子访问防护的一般实践和演示的回答,以及我的doAtomicRead()函数,它确保原子访问不带OUT关闭中断
  3. [我的问答] 哪些变量类型/大小在 STM32 微控制器上是原子的?
  4. [我的答]像STM8一样编程STM32(寄存器级GPIO)

对于 Microchip PIC32 微控制器

请参阅我的问题的参考部分:如何正确计数定时器溢出以转换 32-位高分辨率定时器转换为 64 位高分辨率定时器

unsigned int __builtin_get_isr_state(void)
void __builtin_set_isr_state(unsigned int)
unsigned int __builtin_disable_interrupts(void)
void __builtin_enable_interrupts(void)

Update 10 May 2023: one of my primary motivating factors in learning this stuff was related to my first ever ring buffer implementation I wrote 7 years ago in 2016, leading to this debugging problem where I lost 25 hours of debugging work in 2 days. I finally wrote a really good ring buffer implementation that is lock-free when used on any system which supports C11 or C++11 atomic types. It is the best implementation I've ever written, and also the best I've ever seen. It solves a lot of the problems of other implementations. Full details are in the top of the file. It runs in both C and C++. You can see the full implementation here: containers_ring_buffer_FIFO_GREAT.c in my eRCaGuy_hello_world repo.


Multiple ways to enable/disable interrupts in STM32 mcus

...to enable atomic access (critical section) guards:

1. Via ARM-core CMSIS:

1.A. For global interrupts

__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()

For the definition of these functions, see:

  1. https://github.com/ARM-software/CMSIS/blob/master/CMSIS/Include/cmsis_gcc.h
    1. Contains at least:
      __enable_irq() 
      __disable_irq()
      __get_PRIMASK()
      __set_PRIMASK()
      
  2. STM32 example location: "stm/stm32f2xx/st_hal_v1.1.3/CMSIS/Include/cmsis_gcc.h":

To save and restore the interrupt state, use __get_PRIMASK(), like this:

// 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();
}

When dealing with global interrupts, this is the best way for bare-metal, non-FreeRTOS code!

I think this technique is also cross-compatible with ALL ARM-core mcus, not just STM32.

I first learned this technique from Tilen Majerle, here: https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/. His work and contributions to clear up this super-obfuscated stuff are infinitely valuable and appreciated!

His example:

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 */
}

1.B. For specific IRQs (Interrupt Requests)

It is best to avoid disabling global interrupts, if possible, and disable only the fewest number of specific interrupts possible to achieve atomicity for your specific code. So, using these functions allows you to enable or disable only the specific interrupts you need to!

Enable or disable specific types of interrupts:

void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);

NVIC stands for "Nested Vector Interrupt Controller". Nested interrupts (meaning: a higher-priority interrupt can still fire within an ISR) are enabled by default on STM32 microcontrollers. Each interrupt type has a priority assigned to it, with lower numbers being higher priority, and higher-priority interrupts are able to fire while an ISR is being processed for a lower-priority interrupt. See here for a little more information on the STM32 NVIC: https://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-nvic-or-nested-vector-interrupt-controller/.

Contrast this to AVR microcontrollers (ex: ATMega328 / Arduino Uno), which do not have priority-based interrupts, so by default, when any ISR is being processed, all interrupts (ie: global interrupts) are automatically disabled as the program enters the ISR. Note that even on AVR mcus, however, you can still manually enable nested interrupts / ISRs if you like by manually re-enabling global interrupts inside your ISR, via a call to interrupts() on Arduino or sei() (set interrupts) on raw AVR.

Each ARM-core microcontroller manufacturer, I believe, including STM32 types, must define and create its own list of IRQn_Type interrupt request types, so see below for the STM32 details on their specific interrupt types defined for each mcu.

2. Via STM32 HAL (Hardware Abstraction Layer) libraries

Enable or disable specific types of interrupts:

// enable interrupts
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
// disable interrupts 
HAL_NVIC_DisableIRQ(IRQn_Type IRQn);

See, for example: "stm/stm32f2xx/st_hal_v1.1.3/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c/.h" - definitions for those functions above are in those files. See them online:

  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

Here are the definitions of HAL_NVIC_EnableIRQ() and HAL_NVIC_DisableIRQ(). Notice that they just check to ensure your IRQn is valid, then they pass the input argument on to the ARM-core CMSIS NVIC_EnableIRQ() and NVIC_DisableIRQ() functions above!:

/**
  * @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);
}

For IRQn_Types: see the appropriate definition file for your specific board! These are board-specific definitions, for your board from your manufacturer. Here are all of the boards in the STM32 F2xx line, for instance: https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include. Let's look at the stm32f217xx.h file specifically:

  1. https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
  2. Raw view (since file is too big to view on GitHub otherwise): https://raw.githubusercontent.com/STMicroelectronics/STM32CubeF2/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h

From this file, we can see the typedef enum definition for the IRQn_Type, which is the "STM32F2XX Interrupt Number Definition". Here is what it looks like:

/**
 * @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;

2.A. Example usage using STM32 HAL:

To get exclusive access (to ensure strings are atomically printed, for instance) to the USART1 for printing debug chars via a HAL-based blocking (polled) mode (ie: via HAL_UART_Transmit()), you need to disable all interrupts for USART1_IRQn by doing the following. (This guarantees you get atomic access to this device):

// 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/stm32f7xx/st_hal_v1.1.2/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_uart.c": 
// - `HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, 
//        uint8_t *pData, uint16_t Size, uint32_t Timeout)`
// Note: `huart` is defined by STM32CubeMX as `UART_HandleTypeDef huart1;` 
// in "hal_source/Src/usart.c"
HAL_UART_Transmit(&huart1, (uint8_t *)my_str, strlen(my_str), HAL_MAX_DELAY);

// 3. Enable the UART_IRQ
// FUTURE WORK: make this nestable/more robust by only enabling the 
// IRQ here if it was previously enabled before disabling it!
HAL_NVIC_EnableIRQ(USART1_IRQn);

3. Via FreeRTOS:

The FreeRTOS atomic-access-guard / interrupt-related functions are listed under the "Modules" section of the Kernel Control API here: Kernel Control:

taskYIELD()
taskENTER_CRITICAL()            // interrupts off
taskEXIT_CRITICAL()             // restore interrupts
taskENTER_CRITICAL_FROM_ISR()   // interrupts off
taskEXIT_CRITICAL_FROM_ISR()    // restore interrupts
taskDISABLE_INTERRUPTS()        // interrupts off
taskENABLE_INTERRUPTS()         // interrupts on

Important!:

FreeRTOS API functions must not be called from within a critical section.

Source: https://www.freertos.org/Documentation/02-Kernel/04-API-references/04-RTOS-kernel-control/01-taskENTER_CRITICAL_taskEXIT_CRITICAL

See also my README here, with a potential list of which calls are and are not allowed within FreeRTOS critical sections: https://github.com/ElectricRCAircraftGuy/eRCaGuy_Engineering/tree/main/FreeRTOS#freertos-critical-section-calls

3.A. Higher-level macros:

  1. These are the preferred macros to use, and are the freertos-recommended ones!

  2. These all support nested calls, and end up calling portDISABLE_INTERRUPTS() anyway, which is the port implementation of the lower-level taskDISABLE_INTERRUPTS(), shown below.

    About nesting: from: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html:

    Calls to taskENTER_CRITICAL() and taskEXIT_CRITICAL() are designed to nest. Therefore, a critical section will only be exited when one call to taskEXIT_CRITICAL() has been executed for every preceding call to taskENTER_CRITICAL().

    Other limitations:

    Critical sections must be kept very short, otherwise they will adversely affect interrupt response times. Every call to taskENTER_CRITICAL() must be closely paired with a call to taskEXIT_CRITICAL().

    FreeRTOS API functions must not be called from within a critical section.

  3. From: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html

    taskENTER_CRITICAL()        // interrupts off
    taskEXIT_CRITICAL()         // restore interrupts
    
  4. From: https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html

    taskENTER_CRITICAL_FROM_ISR()
    taskEXIT_CRITICAL_FROM_ISR()
    

3.B. Lower-level macros:

  1. These do NOT support nested calls!

  2. Official documentation on them is on the main "Kernel Control" page:

    taskDISABLE_INTERRUPTS()
    taskENABLE_INTERRUPTS()
    
  3. Notes and limiatations:

    1. taskDISABLE_INTERRUPTS() at the link above states:

    Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.

    1. taskENABLE_INTERRUPTS() at the link above states:

    Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.

    1. Note also that the use of taskDISABLE_INTERRUPTS() is demonstrated as the technique used to panic inside an example macro definition for configASSERT().
      1. From here: https://www.freertos.org/a00110.html#configASSERT, when used with a debugger, it is defined as:
        /* Define configASSERT() to disable interrupts and sit in a loop. */
        #define configASSERT( ( x ) )     if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
        
      2. My thoughts: perhaps in this one case (ie: hard asserting, or panicking), taskDISABLE_INTERRUPTS() might be preferred over taskENTER_CRITICAL() because no amount of calling taskEXIT_CRITICAL() from another thread will re-enable interrupts once taskDISABLE_INTERRUPTS() has been called [I think!?]--rather, one would have to explicitly (and accidentally) call taskENABLE_INTERRUPTS() (ex: from another thread) to re-enable interrupts once taskDISABLE_INTERRUPTS() has been called. In other words, using the low-level taskDISABLE_INTERRUPTS() call is appropriate here because it will truly cause the system to sit in a loop, as desired, whereas taskENTER_CRITICAL() would not.

3.C. Mutexes and other OS (Operating System)-enabled synchronization primitives

Beyond the examples above, you can also use FreeRTOS queues (which are thread-safe, unlike all containers in the C++ std library), mutexes, semaphores, task notifications, and other synchronization primitives, where able and where appropriate, to protect certain data which is shared between FreeRTOS tasks (threads), assuming you are running FreeRTOS.

See the list of these tools here: https://www.freertos.org/a00106.html, and in the left-hand navigation menus once you click on that link.

4. TODO: mutex primitives: raw, OS-free spin locks via atomic set_and_test() (read, modify, write) instructions

  1. Add an atomic test_and_set() (set_and_test() or read_modify_write() really makes more sense as a function name for this, I think) demo using ARM-core CMSIS functions, or assembly, or whatever means necessary, to demonstrate writing a spin lock in STM32. I don't know how to do this yet so it will require finding the right function or operation to use. See here: https://en.wikipedia.org/wiki/Test-and-set#Pseudo-C_implementation_of_a_spin_lock:

    volatile int lock = 0;
    
    void critical() {
        // Spin lock: loop forever until we get the lock; we know the lock was
        // successfully obtained after exiting this while loop because the 
        // test_and_set() function locks the lock and returns the previous lock 
        // value. If the previous lock value was 1 then the lock was **already**
        // locked by another thread or process. Once the previous lock value
        // was 0, however, then it indicates the lock was **not** locked before we
        // locked it, but now it **is** locked because we locked it, indicating
        // we own the lock.
        while (test_and_set(&lock) == 1);  
        critical section  // only one process can be in this section at a time
        lock = 0;  // release lock when finished with the critical section
    }
    

    Here is a spin lock implementation I did in C11 using _Atomic types. It should work just fine for STM32 as well, and probably compiles to use the underlying exclusive STREX/LDREX operations to store (write) and read (load), but I'd have to check that by looking at the assembly. Additionally, this implementation would need to be modified to add safety anti-deadlock mechanisms such as automatic deferral, timeout, and retry, to prevent deadlock. See my notes here: Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32

5. See also:

  1. My answer on this for AVR mcus/Arduino
  2. My answer on the general practice and demo of using atomic access guards, and my doAtomicRead() func which ensures atomic access withOUT turning interrupts off
  3. [my Q&A] Which variable types/sizes are atomic on STM32 microcontrollers?
  4. [my answer] Programing STM32 like STM8 (register-level GPIO)

For Microchip PIC32 microcontrollers

See the References section of my question here: How to properly count timer overflows to convert a 32-bit high-resolution timer into a 64-bit high-resolution timer:

unsigned int __builtin_get_isr_state(void)
void __builtin_set_isr_state(unsigned int)
unsigned int __builtin_disable_interrupts(void)
void __builtin_enable_interrupts(void)
下雨或天晴 2025-01-24 01:53:01

对共享变量的原子访问只能通过在没有更现代的替代方案可用的情况下关闭中断来实现,或者有时在性能和延迟不成问题的非常简单的项目中。

禁用中断会以难以预测的方式增加系统延迟,应尽可能避免。

在 ARMv7M 及更高版本的内核(包括所有 STM32F1xx、STM32F2xx、STM32F3xx、STM32F4xx、STM32F7xx、STM32H7xx、STM32G4xx、STM32L1xx、STM32L4xx、SRM32L5xx、STM32U5xx)上,应使用 LDREX/STREX 独占访问指令实现原子访问。复杂的消息队列和信号量系统可以基于这些原语构建,而无需禁用中断。有关示例,请查看 mbed-os 中的信号量实现

STM32 系列的其他成员(STM32F0xx、STM32G0xx 和 STM32L0xx)可以使用 NVIC_EnableIRQ/NVIC_EnableIRQ 关闭各个中断,或者作为最后的手段使用 关闭所有中断__disable_irq()/__enable_irq()

Atomic access to shared variables should only be achieved by turning interrupts off where more modern alternatives are not available, or sometimes in very simple projects where performance and latency are not an issue.

Disabling interrupts increases the latency of the system in ways which are difficult to predict, and should be avoided wherever possible.

On ARMv7M and higher cores (including all STM32F1xx, STM32F2xx, STM32F3xx, STM32F4xx, STM32F7xx, STM32H7xx, STM32G4xx, STM32L1xx, STM32L4xx, SRM32L5xx, STM32U5xx) atomic access should be achieved using LDREX/STREX exclusive access instructions. Complex message queues and semaphore systems can be built upon these primitives which do not ever require to disable interrupts. For an example look at the semaphore implementation in mbed-os.

The other members of the STM32 family (STM32F0xx, STM32G0xx and STM32L0xx) may turn individual interrupts off using NVIC_EnableIRQ/NVIC_EnableIRQ or as a last resort turn all interrupts off with __disable_irq()/__enable_irq().

兮颜 2025-01-24 01:53:01

另一种选择是使用 __get_PRIMASK() 和 __set_PRIMASK() ,如下所示:

uint32_t old_primask = __get_PRIMASK();
__disable_irq();
manipulation();
__set_PRIMASK(old_primask);

“这看起来可能有竞争条件,但实际上没有。如果启用了中断,并且有东西在 __get_PRIMASK() 和 __disable_irq() 之间打断了您并禁用IRQ 本身,它会在完成之前恢复它们,因此您的“old_primask”变量仍然有效。” 来源< /a>

“对于短操作(几个到数百条指令,具体取决于您的延迟要求),禁用中断很好,对于非常短的操作通常是最有效的方法。对于非常长的操作或任何其他操作甚至有可能阻塞 RTOS 操作,因此需要互斥锁。” 来源

Another option is to use __get_PRIMASK() and __set_PRIMASK() like so:

uint32_t old_primask = __get_PRIMASK();
__disable_irq();
manipulation();
__set_PRIMASK(old_primask);

"This looks like it might have a race condition but doesn't. If interrupts were enabled and something interrupts you between the __get_PRIMASK() and the __disable_irq() and disables IRQs itself, it will restore them before it finishes so your 'old_primask' variable will still be valid." Source

"For short operations (a few to hundreds of instructions depending on your latency requirements), disabling interrupts is fine and for very short operations usually the most efficient way to do it. For very long operations or anything that has even a chance of blocking on an RTOS operation, a mutex is required." Source

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文