3、中断服务
中断服务历程
中断处理是计算机系统中的一种重要机制,用于处理异步事件或请求,如硬件中断、软件异常等。中断处理的基本原理是通过一个中断向量表来确定中断源,并执行相应的中断处理程序。在操作系统层面,这通常是通过中断服务例程(ISR)来实现的。
基本原理如下:
-
中断源:计算机系统中的各个硬件组件(如外设、时钟、网络适配器等)都可以产生中断信号。当某个硬件组件需要 CPU 处理时,它会发出一个中断请求。
-
中断控制器:中断信号通常由中断控制器收集和管理。中断控制器是一个硬件组件,负责协调各个中断源,并向 CPU 发送中断请求。
-
中断向量表:操作系统维护了一个中断向量表,其中包含了一系列中断向量(或中断号),每个中断向量对应一个中断处理程序的地址。
-
中断处理程序:每个中断向量都关联一个中断处理程序,也称为中断服务例程 (ISR)。中断处理程序是一段特定的代码,它处理特定类型的中断。当中断请求被接受后,CPU会根据中断向量找到对应的中 断处理程序,并执行它。
-
中断服务例程注册:操作系统或应用程序可以注册中断服务例程,告诉系统在特定类型的中断发生时应该执行哪个处理程序。
-
中断处理过程:当中断请求到达 CPU 时,CPU会检查中断向量,并执行与之相关联的中断处理程序。中断处理程序负责处理中断,可能会采取一些操作,然后恢复正常的执行。
-
中断完成:一旦中断处理程序执行完毕,系统会继续执行之前的任务。在处理硬件中断时,通常还需要向中断控制器发送中断完成信号,以允许中断控制器继续处理其他中断请求。
中断控制器的结构体定义
在 STM32 处理器中,中断服务函数 (Interrupt Service Routine, ISR) 是用来处理特定中断事件的函数。这些函数是用户定义的,用于响应不同类型的中断请求,如外部硬件中断、定时器中断等。当中断事件发生时,处理器会自动跳转到相应的中断服务函数来执行相应的操作。为了让处理器知道哪个中断与哪个函数相关联,需要进行以下配置:
-
中断向量表 (Interrupt Vector Table): 在嵌入式系统中,有一个中断向量表,它是一个存储着中断服务函数地址的表格。不同的中断号(或中断优先级)会映射到不同的表项。当一个中断事件发生时,处理器会查找中断号对应的中断向量表项,然后跳转到相应的中断服务函数地址开始执行。
-
中断优先级和中断控制器 (NVIC, Nested Vectored Interrupt Controller): STM32 处理器使用 NVIC 控制器来管理中断。在 NVIC 中,你可以配置中断的优先级,使得一些中断比其他中断更具优先级。这是为了确保在多个中断同时发生时,处理器能够正确响应最紧急的中断。中断服务函数的执行顺序是由中断优先级决定的。
-
中断服务函数的命名和关联: 为了关联一个中断服务函数和特定的中断号或优先级,需要按照一定的规则给中断服务函数命名。在 STM32 HAL 库中,这些规则通常是固定的,比如命名为
void EXTI0_IRQHandler(void)
的函数,其中EXTI0
表示外部中断线 0 的中断服务函数。处理器会根据这些规则找到正确的中断服务函数。
综合上述,当你在 isr.c
当特定中断发生时,系统会查找中断向量表,找到对应的入口,然后跳转到该入口,以执行相应的中断处理程序。
因此,确保 ISR 函数的名称与中断向量表中的入口名称相匹配是关键的,这样系统知道应该执行哪个处理程序来处理特定的中断。如果 ISR 函数的名称不匹配,系统将无法正确地关联中断源和处理程序,从而导致中断处理错误或无法正常工作。
从c语言角度来理解这种模式,及中断向量表中定义了一个label,而函数名本身其实是一个地址,我们将这个label作为函数名就可完成只编写一个函数,系统自动处理的操作。
在STM32系列微控制器中使用中断的过程如下:
1. 配置NVIC:首先,需要配置NVIC(Nested Vector Interrupt Controller),使能中断。可以使用`NVIC_SetPriority()`函数设置中断优先级,使用`NVIC_EnableIRQ()`函数使能中断。
2. 配置外部中断线:如果使用的是外部中断(例如GPIO中断),就需要设置外部中断线的触发方式和使能中断。可以使用`EXTI_Init()`函数进行配置。
3. 编写中断处理函数:编写中断处理函数来处理中断事件。中断处理函数需要依据中断类型进行相应的处理。
4. 注册中断处理函数:使用`NVIC_SetVector()`函数将中断处理函数注册到相应的中断向量表中。
5. 启动中断:使用`NVIC_EnableIRQ()`函数启动中断。
6. 等待中断:等待中断事件的触发。
需要注意的是,使用中断时需要配置中断优先级。较高优先级的中断可以打断较低优先级的中断。还需要注意对共享资源的访问控制,以避免竞争条件的产生。
总结起来,使用中断的过程包括配置NVIC、配置外部中断线、编写中断处理函数、注册中断处理函数、启动中断和等待中断。具体的配置和编写会根据不同的中断类型和应用场景有所不同。详细的操作可以参考相应的STM32系列微控制器的参考手册和库文件的文档。
中断使能
table_irq_uart其实是一个数组,采用查表的方式去找中断号。
IRQn_Type table_irq_uart[3] = {USART1_IRQn, USART2_IRQn, USART3_IRQn};
#define NVIC_EnableIRQ __NVIC_EnableIRQ
参数IRQn
是中断号(IRQn_Type类型),用于指定要使能的中断。
函数的作用是使能指定的中断。具体的实现如下:
-
首先,通过判断
IRQn
是否大于等于0来检查传入的中断号是否是有效的。 -
如果中断号有效,则计算中断号对应的寄存器索引:
(((uint32_t)IRQn) >> 5UL)
。-
>> 5UL
表示右移5位,相当于除以32,用于确定该中断号在NVIC->ISER寄存器数组中的索引。
-
-
然后,设置中断使能寄存器对应位置的位,通过
NVIC->ISER[索引] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
实现。-
1UL << (((uint32_t)IRQn) & 0x1FUL)
表示将1左移对应的位数,通过按位与操作(& 0x1FUL
)确保位数不超过31,用于设置中断使能位。
-
ISER(Interrupt Set Enable Register)寄存器是嵌入式系统中的一种特殊寄存器,用于控制中断的使能状态。
在ARM Cortex-M微控制器中,ISER寄存器是Nested Vector Interrupt Controller(NVIC)的一部分。NVIC是用于管理中断的模块,负责管理中断的优先级、中断向量表和中断使能等功能。
ISER寄存器是一个32位的寄存器,每一位对应一个中断号,用于表示对应中断是否被使能。当某个中断的对应位被设置为1时,表示该中断被使能。当对应位被清零时,表示该中断被禁止。
ISER寄存器按照中断号的范围进行分组,每组32个中断号,共有8个ISER寄存器。以STM32为例,ISER寄存器数组为`NVIC->ISER[8]`,索引从0到7,分别对应ISER0到ISER7寄存器。
通过写入1到ISER寄存器的对应位,可以使能特定的中断。使能中断后,当中断条件满足时,对应的中断处理程序将被触发执行。
/** * @brief STM32L4XX Interrupt Number Definition, according to the selected device * in @ref Library_configuration_section */ typedef enum { /****** Cortex-M4 Processor Exceptions Numbers ****************************************************************/ NonMaskableInt_IRQn = -14, /*!< 2 Cortex-M4 Non Maskable Interrupt */ HardFault_IRQn = -13, /*!< 3 Cortex-M4 Hard Fault Interrupt */ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M4 Memory Management Interrupt */ BusFault_IRQn = -11, /*!< 5 Cortex-M4 Bus Fault Interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M4 Usage Fault Interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M4 SV Call Interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M4 Debug Monitor Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M4 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M4 System Tick Interrupt */ /****** STM32 specific Interrupt Numbers **********************************************************************/ WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */ PVD_PVM_IRQn = 1, /*!< PVD/PVM3/PVM4 through EXTI Line detection Interrupts */ 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_Channel1_IRQn = 11, /*!< DMA1 Channel 1 global Interrupt */ DMA1_Channel2_IRQn = 12, /*!< DMA1 Channel 2 global Interrupt */ DMA1_Channel3_IRQn = 13, /*!< DMA1 Channel 3 global Interrupt */ DMA1_Channel4_IRQn = 14, /*!< DMA1 Channel 4 global Interrupt */ DMA1_Channel5_IRQn = 15, /*!< DMA1 Channel 5 global Interrupt */ DMA1_Channel6_IRQn = 16, /*!< DMA1 Channel 6 global Interrupt */ DMA1_Channel7_IRQn = 17, /*!< DMA1 Channel 7 global Interrupt */ ADC1_IRQn = 18, /*!< ADC1 global Interrupt */ 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_TIM15_IRQn = 24, /*!< TIM1 Break interrupt and TIM15 global interrupt */ TIM1_UP_TIM16_IRQn = 25, /*!< TIM1 Update Interrupt and TIM16 global interrupt */ TIM1_TRG_COM_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt */ TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */ TIM2_IRQn = 28, /*!< TIM2 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 */ SDMMC1_IRQn = 49, /*!< SDMMC1 global Interrupt */ SPI3_IRQn = 51, /*!< SPI3 global Interrupt */ TIM6_DAC_IRQn = 54, /*!< TIM6 global and DAC1&2 underrun error interrupts */ TIM7_IRQn = 55, /*!< TIM7 global interrupt */ DMA2_Channel1_IRQn = 56, /*!< DMA2 Channel 1 global Interrupt */ DMA2_Channel2_IRQn = 57, /*!< DMA2 Channel 2 global Interrupt */ DMA2_Channel3_IRQn = 58, /*!< DMA2 Channel 3 global Interrupt */ DMA2_Channel4_IRQn = 59, /*!< DMA2 Channel 4 global Interrupt */ DMA2_Channel5_IRQn = 60, /*!< DMA2 Channel 5 global Interrupt */ COMP_IRQn = 64, /*!< COMP1 and COMP2 Interrupts */ LPTIM1_IRQn = 65, /*!< LP TIM1 interrupt */ LPTIM2_IRQn = 66, /*!< LP TIM2 interrupt */ DMA2_Channel6_IRQn = 68, /*!< DMA2 Channel 6 global interrupt */ DMA2_Channel7_IRQn = 69, /*!< DMA2 Channel 7 global interrupt */ LPUART1_IRQn = 70, /*!< LP UART1 interrupt */ QUADSPI_IRQn = 71, /*!< Quad SPI global interrupt */ I2C3_EV_IRQn = 72, /*!< I2C3 event interrupt */ I2C3_ER_IRQn = 73, /*!< I2C3 error interrupt */ SAI1_IRQn = 74, /*!< Serial Audio Interface 1 global interrupt */ SWPMI1_IRQn = 76, /*!< Serial Wire Interface 1 global interrupt */ TSC_IRQn = 77, /*!< Touch Sense Controller global interrupt */ RNG_IRQn = 80, /*!< RNG global interrupt */ FPU_IRQn = 81, /*!< FPU global interrupt */ CRS_IRQn = 82 /*!< CRS global interrupt */ } IRQn_Type;
在代码中关闭总中断的操作(DISABLE_INTERRUPTS)通常是为了确保某个操作的原子性和可靠性。
当需要在特定的代码片段中执行一些关键操作时,有时需要禁用所有中断。这样可以防止在该关键代码执行期间发生中断处理程序的干扰,确保该关键操作的执行不被中断打断。
禁用总中断可以避免以下情况的发生:
1. 竞态条件(Race Condition):当多个中断和主循环代码同时访问共享资源时,可能会导致竞态条件,即数据被非预期地修改。通过禁用中断,可以避免中断处理程序和主循环代码同时对共享资源进行访问,从而消除竞态条件的风险。
2. 实时性要求:某些操作需要在严格的时间限制下完成,不能被其他中断或任务打断。通过禁用中断,可以确保关键操作不会被其他中断的触发或任务的调度打断,从而满足实时性的要求。
禁用总中断可能会带来一些不便,例如无法及时响应其他中断事件或任务的调度等。因此,在禁用总中断时,需要谨慎考虑代码的执行时间和对实时性的要求。一般来说,应尽量将关键操作的执行时间控制在较短的范围内,以减少对系统其他部分的影响。
综上所述,关闭总中断的操作常用于确保关键操作的原子性和可靠性,以避免竞态条件和满足实时性要求。
关于以下代码中的初始化时关总中断
在一般情况下,初始化操作并不需要保证原子性。初始化操作的目标是对系统的各个部分进行初始化,通常在系统启动阶段或特定的初始化函数中执行。
初始化操作一般在单线程环境中进行,并且不涉及对共享资源的访问。因此,不需要禁用总中断或使用其他机制来保证初始化操作的原子性。
但是,在某些特殊的情况下,如果初始化操作涉及多个线程或任务的并发执行,或者涉及对共享资源的初始化,那么可能需要考虑保证初始化操作的原子性。
在这种情况下,可以采用以下方法来保证初始化操作的原子性:
1. 使用互斥锁(Mutex):通过在初始化操作之前获取互斥锁,并在初始化操作完成后释放互斥锁,确保只有一个线程或任务可以执行初始化操作。
2. 使用原子操作:某些处理器提供原子操作指令,可以保证对特定内存位置的读写操作是原子的,从而避免竞态条件的发生。
3. 禁用中断:在某些特殊的场景下,如果初始化操作涉及对共享资源的访问,并且中断可能会干扰初始化操作的执行,可以考虑在初始化操作期间禁用中断,以确保初始化操作的原子性。
需要根据具体情况来决定是否需要保证初始化操作的原子性。一般来说,对于单线程环境下的简单初始化操作,不需要额外的措施来保证原子性。但是,在多线程、多任务环境或对共享资源的初始化操作中,需要谨慎考虑原子性的问题,并采取相应的措施来保证初始化操作的正确执行。
//【不变】关总中断 DISABLE_INTERRUPTS; // 用户外设模块初始化 gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON); //初始化蓝灯 uart_init(UART_User,115200); //初始化串口模块 // 使能模块中断 uart_enable_re_int(UART_User); //使能UART_USER模块接收中断功能 //【不变】开总中断 ENABLE_INTERRUPTS;
总结:
当我们要使用某个特定中断的时候,首先要在main.c里面进行初始化,初始化内容包括初始化引脚和使能中断, 然后我们要按照规范和约定编写特定的中断处理函数。
在程序运行时,当处理器收到了中断信号,首先会确定中断源,然后根据我们对中断控制器的配置和中断向量表执行中断处理程序(也就是我们根据特定的命名规则编写在isr.c中的程序)。中断处理结束后,程序返回main函数中继续执行。