启用或禁用特定类型的中断:
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(硬件抽象层)库
启用或禁用特定类型的中断:
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
例如: "stm/stm32f2xx/st_hal_v1.1.3/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c/.h" - 上述函数的定义在这些文件中。在线查看它们:
- https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Inc/stm32f2xx_hal_cortex.h#L264-L265
- 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()
函数!
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
NVIC_EnableIRQ(IRQn);
}
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
NVIC_DisableIRQ(IRQn);
}
对于IRQn_Type
:请查看适当的定义文件,针对您特定的板子!这些是特定于板子的定义,来自您制造商的板子。例如,以下是STM32 F2xx系列中的所有板子:https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include。让我们具体看一下stm32f217xx.h
文件:
从这个文件中,我们可以看到IRQn_Type
的typedef enum
定义,它是"STM32F2XX中断号定义"。它看起来像这样:
- https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
- 原始视图(由于文件太大,在GitHub上无法查看): https://raw.githubusercontent.com/STMicroelectronics/STM32CubeF2/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
typedef enum
{
NonMaskableInt_IRQn = -14,
HardFault_IRQn = -13,
MemoryManagement_IRQn = -12,
BusFault_IRQn = -11,
UsageFault_IRQn = -10,
SVCall_IRQn = -5,
DebugMonitor_IRQn = -4,
PendSV_IRQn = -2,
SysTick_IRQn = -1,
WWDG_IRQn = 0,
PVD_IRQn = 1,
TAMP_STAMP_IRQn = 2,
RTC_WKUP_IRQn = 3,
FLASH_IRQn = 4,
RCC_IRQn = 5,
EXTI0_IRQn = 6,
EXTI1_IRQn = 7,
EXTI2_IRQn = 8,
EXTI3_IRQn = 9,
EXTI4_IRQn = 10,
DMA1_Stream0_IRQn = 11,
DMA1_Stream1_IRQn = 12,
DMA1_Stream2_IRQn = 13,
DMA1_Stream3_IRQn = 14,
DMA1_Stream4_IRQn = 15,
DMA1_Stream5_IRQn = 16,
DMA1_Stream6_IRQn = 17,
ADC_IRQn = 18,
CAN1_TX_IRQn = 19,
CAN1_RX0_IRQn = 20,
CAN1_RX1_IRQn = 21,
CAN1_SCE_IRQn = 22,
EXTI9_5_IRQn = 23,
TIM1_BRK_TIM9_IRQn = 24,
TIM1_UP_TIM10_IRQn = 25,
TIM1_TRG_COM_TIM11_IRQn = 26,
TIM1_CC_IRQn = 27,
TIM2_IRQn = 28,
TIM3_IRQn = 29,
TIM4_IRQn = 30,
I2C1_EV_IRQn = 31,
I2C1_ER_IRQn = 32,
I2C2_EV_IRQn = 33,
I2C2_ER_IRQn = 34,
SPI1_IRQn = 35,
SPI2_IRQn = 36,
USART1_IRQn = 37,
USART2_IRQn = 38,
USART3_IRQn = 39,
EXTI15_10_IRQn = 40,
RTC_Alarm_IRQn = 41,
OTG_FS_WKUP_IRQn = 42,
TIM8_BRK_TIM12_IRQn = 43,
TIM8_UP_TIM13_IRQn = 44,
TIM8_TRG_COM_TIM14_IRQn = 45,
TIM8_CC_IRQn = 46,
DMA1_Stream7_IRQn = 47,
FSMC_IRQn = 48,
SDIO_IRQn = 49,
TIM5_IRQn = 50,
SPI3_IRQn = 51,
UART4_IRQn = 52,
UART5_IRQn = 53,
TIM6_DAC_IRQn = 54,
TIM7_IRQn = 55,
DMA2_Stream0_IRQn = 56,
DMA2_Stream1_IRQn = 57,
DMA2_Stream2_IRQn = 58,
DMA2_Stream3_IRQn = 59,
DMA2_Stream4_IRQn = 60,
ETH_IRQn = 61,
ETH_WKUP_IRQn = 62,
CAN2_TX_IRQn = 63,
CAN2_RX0_IRQn = 64,
CAN2_RX1_IRQn = 65,
CAN2_SCE_IRQn = 66,
OTG_FS_IRQn = 67,
DMA2_Stream5_IRQn = 68,
DMA2_Stream6_IRQn = 69,
DMA2_Stream7_IRQn = 70,
USART6_IRQn = 71,
I2C3_EV_IRQn = 72,
I2C3_ER_IRQn = 73,
OTG_HS_EP1_OUT_IRQn = 74,
OTG_HS_EP1_IN_IRQn = 75,
OTG_HS_WKUP_IRQn = 76,
OTG_HS_IRQn = 77,
DCMI_IRQn = 78,
CRYP_IRQn = 79,
HASH_RNG_IRQn = 80
} IRQn_Type;
2.A. 使用示例使用STM32 HAL:
要通过基于HAL的阻止(轮询)模式(即通过HAL_UART_Transmit()
)获得独占访问(例如,确保字符串被原子化打印),以便通过USART1
打印调试字符,您需要执行以下操作来禁用所有中断USART1_IRQn
。 (这确保您可以原子地访问此设备):
HAL_NVIC_DisableIRQ(USART1_IRQn);
HAL_UART_Transmit(&huart1, (uint8_t *)my_str, strlen(my_str), HAL_MAX_DELAY);
HAL_NVIC_EnableIRQ(USART1_IRQn);
3. 通过FreeRTOS:
FreeRTOS原子访问保护/中断相关函数在此处的内核控制API的“模块”部分中列出:Kernel Control:
taskYIELD()
taskENTER_CRITICAL()
taskEXIT_CRITICAL()
taskENTER_CRITICAL_FROM_ISR()
taskEXIT_CRITICAL_FROM_ISR()
taskDISABLE_INTERRUPTS()
taskENABLE_INTERRUPTS()
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. 低级宏:
这些不支持嵌套调用!
官方文档在主"内核控制"页面上:
taskDISABLE_INTERRUPTS()
taskENABLE_INTERRUPTS()
注意事项和限制:
taskDISABLE_INTERRUPTS()
在上面的链接中说明:
通常不直接调用此宏,应使用 taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
代替。
taskENABLE_INTERRUPTS()
在上面的链接中说明:
通常不直接调用此宏,应使用 taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
代替。
- 还要注意,
taskDISABLE_INTERRUPTS()
的使用被演示为在 configASSERT()
的示例宏定义中引发紧急情况的技术。
- 从这里:https://www.freertos.org/a00110.html#configASSERT,当与调试器一起使用时,它被定义为:
#define configASSERT( ( x ) ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
- 我的想法是:也许在这种情况下(即:硬断言或紧急情况),
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()
(读取、修改、写入)指令
添加一个原子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(&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. 参见:
- 我在AVR mcus/Arduino上的回答
- 我关于使用原子访问保护的一般实践和演示以及我的
doAtomicRead()
函数,该函数确保在不关闭中断的情况下进行原子访问
- [我的问答] 哪些变量类型/大小在STM32微控制器上是原子性的?
- [我的回答] 像STM8一样编程STM32(寄存器级GPIO)