RTOS之UCOS(四)---中断管理与定时器

本文详细探讨了嵌入式系统中中断的原理和管理,包括中断源分类、中断控制器NVIC、中断优先级以及中断服务程序。重点讲解了定时器中断的配置,如定时器中断配置、SYSTICK的使用及其在时间管理与软件定时器中的应用。通过对STM32中断系统和SYSTICK的实例解析,深入理解中断系统在RTOS中的关键作用。
摘要由CSDN通过智能技术生成

一、什么是中断

前面谈到任务切换时,不管是任务级上下文切换还是中断级任务切换,都是通过悬起PendSV异常,引发一个异常中断实现任务切换的。谈到状态机时也提到状态机是事件驱动的,而这里的事件一般指的是中断触发信号,实际要执行的动作和状态迁移是在事件中断处理函数中进行的。不管是操作系统的任务切换,还是处理器与外设的I/O数据通信,甚至操作系统与用户操作的交互,都离不开中断系统的支持,那么什么是中断呢?

中断从表面意思看就是从中间断开,但程序运行是要可持续且确定的,自然不能出现程序断开后就回不去的情况,所以中断实际指的是一个中断信号打断原来执行的任务A转而去执行与中断信号对应的任务B,但原来的任务A可能还没执行完,所以打断A之前需要保存任务A的上下文信息,执行完任务B后需要借助任务A的上下文信息返回到任务A的折断处继续执行任务A。中断流程的示意图如下:
中断流程
从上图看,中断处理流程和调子程序有点类似,但二者有个显著的区别:调子程序是主程序主动调用,何时调用对主程序是确定的;中断处理则是中断请求信号触发的被动调用,中断请求何时到来取决于中断源何时发出中断请求信号,不是由主程序决定的。是不是想起了任务调度器的程序流反向控制机制,任务何时切换,主程序不再关心,而是交由子程序任务调度器决定。与此类似,中断何时触发,主程序也控制不了,而是交由中断控制器给出的中断请求信号决定的。

中断系统这种”请求—响应“机制在两个设备进行交互时尤其便捷高效,不管是处理器还是计算机,本身作为一个数据处理设备,少不了跟外界设备进行数据输入输出的交互,比如处理器要及时处理某外设比如串口或网口到来的数据,或者处理器要及时处理人机交互界面被点击的按钮等,处理器当然可以隔一段时间查看一遍是否有数据到来或者按钮被点击,但这种方式浪费了不少处理器的运算资源,而中断这种”请求—响应“的反向控制机制可以很好的解决这个问题。有中断系统辅助,处理器不需要再轮流检查外设是否有数据到达或者点击事件发生,转而专心去处理其他的任务(当然还是要耗费点运算资源去检查是否有中断请求信号到来,但这点耗费显然比轮询方式的耗费小多了),当外设有数据到达或点击事件发生时,通过中断控制器给处理器发出一个中断请求信号,处理器根据优先级再去处理该中断请求信号对应的任务就可以了。

由此可以看出,中断是处理两个系统间涉及到信号输入/输出等不确定性交互的绝佳工具,比如处理器与外设间的交互,系统与使用人间的交互等,外界的信号何时到来具有不确定性,使用中断作为通知机制可以大大提高交互效率。当然中断也可以用于协调系统内的数据处理,最常见的就是定时器/计数器这类时间确定性中断信号,特别是对于操作系统这类比较复杂的系统而言,内部需要有一个确定性的协调信号作为内部资源交互的基准,而定时器可以按照设定的时间间隔,周期性的发出确定性的中断信号可以协调这个系统的有序运行。如果把与外设交互的不确定性中断称为事件中断,则可以把协调内部资源交互的确定性中断(比如周期触发的定时器)称为时间中断,两者共同构成了处理器或计算机的中断系统。

二、中断系统的构成与管理

2.1 中断源

2.1.1 中断源分类

中断系统是根据输入的中断请求信号做出反应的,中断请求信号又是来自于哪里呢?前面谈到中断常用于处理器与外设间的通讯,中断请求信号自然是来自于外设,而外设又分为片内外设与片外外设,片内外设是集成在MCU上并与总线连接的外设(比如定时器、DMA、ADC/DAC、UART等),片外外设则是通过外界I/O引脚输入的中断触发信号(通常由外部某引脚的电平变化引起中断触发)。我们经常把确定性的时间中断(比如定时器)与不确定的事件中断(比如数据通信的外设)信号分开管理,把片外外设称为外部中断,所以中断源可以简单分为下面三大类:

  • 定时器:由内部的时钟计数器计数达到标准值而触发中断;
  • 外设中断:片上外设满足某条件时触发中断,比如串口当发送数据成功时可触发中断,其余的外设中断还有DMA、ADC/DAC、SPI、IIC等;
  • 外部中断:通过外部引脚电平变化(高电平、低电平、上升沿、下降沿)触发的中断。

为更方便理解片上外设,下面给出一个STM32 MCU的系统结构图,读者可以看到连接到总线上的外设都有哪些:
STM32系统结构图
跟总线矩阵连接的连接的比如DMA、Ethernet MAC、APB1、APB2等都属于片上外设,可以看出STM32支持的外设总类数量还算丰富。下面给出外部中断信号进入中断控制器的过程框图:
外部中断框图
上图中的20指的是该MCU支持20个外部中断/事件请求,这里外部中断电平信号使用边沿触发方式,至于想使用上升沿还是下降沿触发则由开发者配置寄存器确定。上图显示外部中断信号分为了蓝色与红色两条路径:蓝色路径是输入到NVIC(Nested Vectored Interrupt Controller)中断控制器的,最后由CPU处理该中断信号;红色路径通过脉冲发生器最后是输入到特定片内外设的(比如DMA(Direct Memory Access)、DAC(Digital to analog converter)/ADC(Analog-to-Digital Converter)等),并不需要经过CPU处理,直接通过外部中断信号实现外部设备与片内外设的直接通信,减轻了CPU的处理负担提高了MCU的处理效率,但这需要提前配置好相应的寄存器。

既然外部中断是靠外部输入的电平变化实现的,片内外设和定时器也是靠某引脚电平变化来实现中断触发的吗?答案是肯定的,但MCU靠什么获得电平变化的脉冲信号呢?中断需要电平变化触发,MCU内的通信与运算自然也少不了高低电平变化的驱动,所以MCU的运行需要一套提供不同频率脉冲信号的系统支持,这套系统就是时钟树。下面给出STM32时钟树的结构图供参考:
STM32时钟树
从上图可以看出,不管是前面提到的操作系统心跳SYSTICK、处理器基频SYSCLK,还是DMA、APB1、APB2等通信外设,时钟树都能根据它们各自合适的需求提供相应频率的脉冲信号作为驱动力。时钟树的时钟源可以选择内部时钟也可以选择外部时钟:内部时钟电路简单成本低但时钟精确度也较低,外部时钟需要额外的电路和成本但时钟精度较高。为简化时钟树复杂度,又分别提供了高速时钟与低速时钟,高速时钟主要驱动处理器运算与外设通信,低速时钟主要驱动看门狗与RTC。片内外设的通信和定时器的计数有了时钟树提供的周期脉冲驱动自然可以正常工作了,至于中断信号的触发,则需要开发者配置相应外设或定时器的寄存器,设定好中断触发条件,待条件满足时自然会引起中断触发信号。

2.1.2 外部中断配置

从外部中断进入中断控制器的流程框图可以看出,跟外部中断管理相关的寄存器(以STM32为例)主要如下:

  • 中断屏蔽寄存器(EXTI_IMR)
  • 事件屏蔽寄存器(EXTI_EMR)
  • 上升沿触发选择寄存器(EXTI_RTSR)
  • 下降沿触发选择寄存器(EXTI_FTSR)
  • 软件中断事件寄存器(EXTI_SWIER)
  • 请求挂起寄存器(EXTI_PR)

直接管理这些寄存器的数据结构及相应寄存器的地址如下:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
#define EXTI                ((EXTI_TypeDef *) EXTI_BASE)

typedef struct
{
   
  __IO uint32_t IMR;
  __IO uint32_t EMR;
  __IO uint32_t RTSR;
  __IO uint32_t FTSR;
  __IO uint32_t SWIER;
  __IO uint32_t PR;
} EXTI_TypeDef;

ST为了便于开发,将跟中断相关的寄存器封装为相应的结构体,以结构体的形式管理这些寄存器配置能简化程序开发,当然结构体只提供了最重要的配置选项,还有一部分配置可以通过函数实现,管理外部中断寄存器配置的数据结构及操作函数如下:

// Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_exti.h

typedef struct
{
   
  uint32_t EXTI_Line;               /*!< Specifies the EXTI lines to be enabled or disabled.
                                         This parameter can be any combination of @ref EXTI_Lines */
   
  EXTIMode_TypeDef EXTI_Mode;       /*!< Specifies the mode for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  FunctionalState EXTI_LineCmd;     /*!< Specifies the new state of the selected EXTI lines.
                                         This parameter can be set either to ENABLE or DISABLE */ 
}EXTI_InitTypeDef;

typedef enum
{
   
  EXTI_Mode_Interrupt = 0x00,
  EXTI_Mode_Event = 0x04
}EXTIMode_TypeDef;

typedef enum
{
   
  EXTI_Trigger_Rising = 0x08,
  EXTI_Trigger_Falling = 0x0C,  
  EXTI_Trigger_Rising_Falling = 0x10
}EXTITrigger_TypeDef;

typedef enum {
   DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
typedef enum {
   RESET = 0, SET = !RESET} FlagStatus, ITStatus;

void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

从上面的代码可以看出,管理外部中断最主要的结构体EXTI_InitTypeDef可以配置中断线引脚号、中断信号的边沿触发类型、中断/事件模式、中断使能/屏蔽等寄存器,在使用外部中断前需要先对该结构体进行初始化。除了该结构体,还有几个操作函数(主要是获取/清除标志位和中断悬起状态位操作)用于外部中断的配置,这些操作函数是联结EXTI_InitTypeDef与EXTI_TypeDef的纽带,把面向开发者配置项的EXTI_InitTypeDef结构体的初始值配置到面向底层寄存器的EXTI_TypeDef结构体成员中。

2.1.3 定时器中断配置

中断源除了外部中断,还有片上外设中断,外设中断可以简单分为两大类:一类是用于数据通信的,中断请求信号何时触发是不确定的,取决于数据什么时候发出或到达,比如USART中断;另一类是用于发出周期性信号的,中断请求信号何时触发是确定的,触发周期是可以配置的,比如Timer定时器中断。下面给出跟高级定时器相关的寄存器(以STM32为例)如下:

  • 控制寄存器1/2(TIMx_CR1/CR2)
  • 从模式控制寄存器(TIMx_SMCR)
  • DMA/中断使能寄存器(TIMx_DIER)
  • 状态寄存器(TIMx_SR)
  • 事件产生寄存器(TIMx_EGR)
  • 捕获/比较模式寄存器1/2(TIMx_CCMR1/CCMR2)
  • 捕获/比较使能寄存器(TIMx_CCER)
  • 计数器(TIMx_CNT)
  • 预分频器(TIMx_PSC)
  • 自动重装载寄存器(TIMx_ARR)
  • 重复计数寄存器(TIMx_RCR)
  • 捕获/比较寄存器1/2/3/4(TIMx_CCR1/CCR2/CCR3/CCR4)
  • 刹车和死区寄存器(TIMx_BDTR)
  • DMA控制寄存器(TIMx_DCR)
  • 连续模式的DMA地址(TIMx_DMAR)

直接管理这些寄存器的数据结构及相关寄存器的地址如下:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define TIM1_BASE             (APB2PERIPH_BASE + 0x2C00)
#define TIM2_BASE             (APB1PERIPH_BASE + 0x0000)
#define TIM1                ((TIM_TypeDef *) TIM1_BASE)
#define TIM2                ((TIM_TypeDef *) TIM2_BASE)

typedef struct
{
   
  __IO uint16_t CR1;
  uint16_t  RESERVED0;
  __IO uint16_t CR2;
  uint16_t  RESERVED1;
  __IO uint16_t SMCR;
  uint16_t  RESERVED2;
  __IO uint16_t DIER;
  uint16_t  RESERVED3;
  __IO uint16_t SR;
  uint16_t  RESERVED4;
  __IO uint16_t EGR;
  uint16_t  RESERVED5;
  __IO uint16_t CCMR1;
  uint16_t  RESERVED6;
  __IO uint16_t CCMR2;
  uint16_t  RESERVED7;
  __IO uint16_t CCER;
  uint16_t  RESERVED8;
  __IO uint16_t CNT;
  uint16_t  RESERVED9;
  __IO uint16_t PSC;
  uint16_t  RESERVED10;
  __IO uint16_t ARR;
  uint16_t  RESERVED11;
  __IO uint16_t RCR;
  uint16_t  RESERVED12;
  __IO uint16_t CCR1;
  uint16_t  RESERVED13;
  __IO uint16_t CCR2;
  uint16_t  RESERVED14;
  __IO uint16_t CCR3;
  uint16_t  RESERVED15;
  __IO uint16_t CCR4;
  uint16_t  RESERVED16;
  __IO uint16_t BDTR;
  uint16_t  RESERVED17;
  __IO uint16_t DCR;
  uint16_t  RESERVED18;
  __IO uint16_t DMAR;
  uint16_t  RESERVED19;
} TIM_TypeDef;

ST封装后跟中断配置相关的数据结构和操作函数如下:

// Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_tim.h

/** 
  * @brief  TIM Time Base Init structure definition
  * @note   This structure is used with all TIMx except for TIM6 and TIM7.    
  */

typedef struct
{
   
  uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                       This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                       This parameter can be a value of @ref TIM_Counter_Mode */

  uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                       Auto-Reload Register at the next update event.
                                       This parameter must be a number between 0x0000 and 0xFFFF.  */ 

  uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */

  uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                       reaches zero, an update event is generated and counting restarts
                                       from the RCR value (N).
                                       This means in PWM mode that (N+1) corresponds to:
                                          - the number of PWM periods in edge-aligned mode
                                          - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                       @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;   

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
void TIM_GenerateEvent(TIM_TypeDef* TIMx, uint16_t TIM_EventSource); 
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

定时器功能相当丰富,包括输入捕获、输出比较等,所以与定时器相关的数据结构和操作函数也挺多,远不止上面列出的这一部分,由于本文的重点是谈论中断系统的,所以上面只列出了跟中断相关的数据结构和操作函数,跟上面外部中断的操作函数对比还是有点类似性的。这些操作函数是联结TIM_TimeBaseInitTypeDef与TIM_TypeDef的纽带,把面向开发者配置项的TIM_TimeBaseInitTypeDef结构体的初始值配置到面向底层寄存器的TIM_TypeDef结构体成员中。

2.1.4 片上外设中断配置

以STM32为例,支持的片上外设还是挺丰富的,这里不能一一列举,实际上定时器也是片上外设的一种,只不过定时器是发出周期性确定的触发信号,其余的大部分都跟通信接口协议相关,属于非周期不确定性触发信号,所以下面再选择一个外设简单介绍下。这里就选择最常用的USART外设作为示例了,跟USART相关的主要寄存器如下:

  • 状态寄存器(USART_SR)
  • 数据寄存器(USART_DR)
  • 波特比率寄存器(USART_BRR)
  • 控制寄存器1(USART_CR1)
  • 控制寄存器2(USART_CR2)
  • 控制寄存器3(USART_CR3)
  • 保护时间和预分频寄存器(USART_GTPR)

直接管理这些寄存器的数据结构及相关寄存器地址如下:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)

typedef struct
{
   
  __IO uint16_t SR;
  uint16_t  RESERVED0;
  __IO uint16_t DR;
  uint16_t  RESERVED1;
  __IO uint16_t BRR;
  uint16_t  RESERVED2;
  __IO uint16_t CR1;
  uint16_t  RESERVED3;
  __IO uint16_t CR2;
  uint16_t  RESERVED4;
  __IO uint16_t CR3;
  uint16_t  RESERVED5;
  __IO uint16_t GTPR;
  uint16_t  RESERVED6;
} USART_TypeDef;

ST封装后跟中断配置相关的数据结构和操作函数如下:

// Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_usart.h

typedef struct
{
   
  uint32_t USART_BaudRate;            /*!< This member configures the USART communication baud rate.
                                           The baud rate is computed using the following formula:
                                            - IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
                                            - FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */

  uint16_t USART_WordLength;          /*!< Specifies the number of data bits transmitted or received in a frame.
                                           This parameter can be a value of @ref USART_Word_Length */

  uint16_t USART_StopBits;            /*!< Specifies the number of stop bits transmitted.
                                           This parameter can be a value of @ref USART_Stop_Bits */

  uint16_t USART_Parity;              /*!< Specifies the parity mode.
                                           This parameter can be a value of @ref USART_Parity
                                           @note When parity is enabled, the computed parity is inserted
                                                 at the MSB position of the transmitted data (9th bit when
                                                 the word length is set to 9 data bits; 8th bit when the
                                                 word length is set to 8 data bits). */
 
  uint16_t USART_Mode;                /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
                                           This parameter can be a value of @ref USART_Mode */

  uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
                                           or disabled.
                                           This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;

void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef
  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值