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

8

当在没有操作系统的裸机单线程协作多任务应用程序中与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

请参阅我在这里详细描述的内容,包括最佳实践(短时间内保持中断关闭)和如何在不先禁用中断的情况下进行原子读取,通过我的 doAtomicRead() 重复读取循环函数:Reading a 64 bit variable that is updated by an ISR
我之前已经记录了如何在 AVR 微控制器 / Arduino 中执行此操作:How do I force atomicity 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 interrupt after I disable it?
2个回答

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

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

1. 通过ARM-core 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(),如下所示:

// 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) {
    /* 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. 针对特定的 IRQ(中断请求)

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

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

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

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

与AVR微控制器(例如ATMega328 / Arduino Uno)相比,ARM-core微控制器具有基于优先级的中断。默认情况下,在处理任何ISR时,所有中断(即全局中断)都会自动禁用,而AVR mcus上甚至可以通过在ISR内手动重新启用全局中断(通过Arduino上的interrupts()或原始AVR上的sei()调用)来手动启用嵌套中断/ ISR。
我认为,每个ARM-core微控制器制造商,包括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-core CMSIS NVIC_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);
}

对于IRQn_Type:请查看适当的定义文件,针对您特定的板子!这些是特定于板子的定义,来自您制造商的板子。例如,以下是STM32 F2xx系列中的所有板子:https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include。让我们具体看一下stm32f217xx.h文件:

从这个文件中,我们可以看到IRQn_Typetypedef enum定义,它是"STM32F2XX中断号定义"。它看起来像这样:
  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
/**
 * @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:

要通过基于HAL的阻止(轮询)模式(即通过HAL_UART_Transmit())获得独占访问(例如,确保字符串被原子化打印),以便通过USART1打印调试字符,您需要执行以下操作来禁用所有中断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的“模块”部分中列出: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

3.A. 高级宏:

这些是首选的宏,也是 FreeRTOS 推荐使用的宏!所有这些宏都支持嵌套调用,并最终调用 portDISABLE_INTERRUPTS(),这是较低级别的 taskDISABLE_INTERRUPTS() 的端口实现,如下所示:
From: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
taskENTER_CRITICAL()        // 中断关闭
taskEXIT_CRITICAL()         // 恢复中断

From: https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html
taskENTER_CRITICAL_FROM_ISR()
taskEXIT_CRITICAL_FROM_ISR()

3.B. 低级宏:

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

  2. 官方文档在主"内核控制"页面上:

    taskDISABLE_INTERRUPTS()
    taskENABLE_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,当与调试器一起使用时,它被定义为:
        /* Define configASSERT() to disable interrupts and sit in a loop. */
        #define configASSERT( ( x ) )     if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
        
      2. 我的想法是:也许在这种情况下(即:硬断言或紧急情况),taskDISABLE_INTERRUPTS() 可能比 taskENTER_CRITICAL() 更可取,因为从另一个线程调用任意次数的 taskEXIT_CRITICAL() 都不会重新启用一旦调用了 taskDISABLE_INTERRUPTS() 中断--[我认为!]--相反,必须显式地(并且意外地)调用 taskENABLE_INTERRUPTS()(例如,从另一个线程)才能在调用了 taskDISABLE_INTERRUPTS() 后重新启用中断。换句话说,在此处使用低级别的 taskDISABLE_INTERRUPTS() 调用是适当的,因为它将真正使系统进入循环,如所需,而 taskENTER_CRITICAL() 不会。

3.C. 互斥锁和其他操作系统(OS)启用的同步原语

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

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

4. TODO:互斥原语:原始的、无需OS支持的自旋锁通过原子set_and_test()(读取、修改、写入)指令

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

    volatile int lock = 0;
    
    void critical() {
        // 旋转锁:循环直到获得锁;我们知道在退出这个while循环后成功获取了锁,因为test_and_set()函数锁定了锁并返回先前的锁值。
        // 如果前一个锁值为1,则锁已经被另一个线程或进程**锁定**。但是一旦前一个锁值为0,则表示锁在我们锁定之前**没有**被锁定,
        // 但现在它被我们锁定了,表明我们拥有该锁。
        while (test_and_set(&lock) == 1);  
        // 临界区 - 同一时间只能有一个进程在此区域内
        lock = 0;  // 释放锁,完成对关键区的访问
    }
    

    这是我使用_Atomic类型在C11中实现的旋转锁。它也应该适用于STM32,并且可能编译为使用底层独占的STREX/LDREX操作来存储(写入)和读取(加载),但我需要通过查看汇编来检查这一点。此外,为了防止死锁,还需要修改此实现以添加安全的反死锁机制,例如自动推迟、超时和重试。请参见我的笔记:Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32

5. 参见:

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

4

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

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

在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()来关闭所有中断。


你能演示一下吗?它可以在AVR微控制器上运行吗?STM32微控制器呢? - Gabriel Staples
在裸机STM32上展示它的代码。 - Gabriel Staples
2
据我所知,Cortex M0内核上不支持LDREX/STREX指令。尽管如此,std::atomic可用于读取或写入访问,但不能用于++或--操作。我不记得它在M0上使用哪些指令。我没有尝试过,但我猜想C11 _Atomic的工作方式类似。 - Tagli
@Tagli,你说得很对,我已经大幅修改了我的答案。 - Tom V

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接