作者:junziyang
<hr>(注:如非特別声明,以下笔记内容均针对STM32F103ZET6而言。不同型号,细节可能存在差别)
单片机中程序的执行方式主要有轮询、中断和DMA三种。轮询模式中CPU一直全速运转,在一个循环中周期性的查询各程序模块是否有要处理的任务,这种模式适合处理连续的、周期性的、短周期性的任务。中断模式则适于处理临时性的、不定期的、需要快速响应的任务,CPU处理完中断请求后可以休眠,有新的中断请求时再被唤醒。而DMA模式则主要用来传输大量的数据,因不占用CPU资源,可为CPU减负。轮询模式靠CPU来控制,中断模式靠中断控制器NVIC来控制,而DMA模式则主要受DMA控制器控制。当然,DMA模式中往往需要中断,中断模式中也离不开CPU,往往需要综合运用三大指挥中枢来优化程序的功耗,响应速度和执行效率。
10.1 Coretex-M3中断系统简介
所谓中断,就是当有优先级更高的任务需要执行时,CPU能够暂停当前正在处理的任务,转而去执行更高优先级的任务的处理方式。加塞儿的任务执行完毕后,再返回原任务继续执行。中断是提高处理器响应速度,执行效率和降低能耗的关键。点击电脑上的图标或按钮会执行相应的程序,这其实就是一种中断响应。
Coretex-M3微处理器支持多达256个中断,包括16个内核中断和240个外部中断,统一由嵌套向量中断控制器NVIC(Nested Vector Interrupt Controller)管理。NVIC属于M3内核外设之一,紧挨着CPU集成在内核中,这样有利于缩短中断迟滞,提高中断响应速度和执行效率。
图1. STM32中Coretex-M3的实现
Coretex内核及其外设是由ARM设计的一个架构,在core_cm3.h进行了定义,不同的芯片厂商可以根据产品需要实现其中的一部分或全部。以NVIC为例,core_cm3.h中相关寄存器别名及地址映射定义如下:
/** \brief Structure type to access the (NVIC). */typedef struct{ __IOM uint32_t ISER[8U]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */ uint32_t RESERVED0[24U]; __IOM uint32_t ICER[8U];/*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */ uint32_t RSERVED1[24U]; __IOM uint32_t ISPR[8U]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */ uint32_t RESERVED2[24U]; __IOM uint32_t ICPR[8U]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */ uint32_t RESERVED3[24U]; __IOM uint32_t IABR[8U]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */ uint32_t RESERVED4[56U]; __IOM uint8_t IP[240U]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */ uint32_t RESERVED5[644U]; __OM uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */} NVIC_Type;可以看出,ARM为NVIC预留了巨大的寄存器空间,已备日后扩充功能之用。目前NVIC相关的寄存器共有7个,常用的有如下3个:
- ISER(Interrupt Set Enable Register)
用来使能中断。向中断向量编号对应的位写入1即可使能。写入0无影响。ISER由8个32bit寄存器组成,可以支持256个中断向量(编号0-255)。stm32F103系列只用了其中的60个,具体中断向量的分配和对应的编号可以查阅芯片的《Reference Manual》或库函数中的头文件,例如HAL库的stm32f103xe.h或STD库的stm32f10x.h。以STM32F103ZET6的ADC1为例,对应的中断向量为ADC1_2_IRQn,编号18。使能设置方法:NVIC->ISER[0]=0x01<<18。ARM仅分配了寄存器空间,具体外设中断向量对应的寄存器位是由芯片厂商设定的。
- ICER(Interrupt Clear Enable Register)
用来清除中断。与ISER一一对应,也由8个32bit寄存器组成,向对应位写入1可以关闭对应的中断。例如,关闭ADC1的中断,设置方法:NVIC->ICER[0]=0x01<<18。
- IP(Interrupt Priority Register )
设置中断优先级。IP共有240个8bit的寄存器构成,每个中断向量对应1个寄存器(16个内核中断的优先级是固定的)。stm32只用了高4位,数值越小,优先级越高。例如:将ADC1的抢断优先级设为00,子优先级设为11,设置方法:NVIC->IP[18] = 0x03<<4。
10.2 NVIC工作原理
10.2.1 中断向量
如前所述,每个外设或功能的中断都被赋予了一个编号,以便与NVIC寄存器中的功能位对应。这样所有中断编号就构成了一个一维数组,这个数组就是中断向量。每个外设或功能的中断可能不止一个,例如ADC有JEOC、EOC、AWD 3个中断,而DMA每个通道都有TC、HT、TE 3个中断。各个外设或功能的多个中断都管理到各自的中断向量上,这样形成了一个嵌套结构。当有中断发生时,NVIC首先将中断分配到对应的中断向量,调用相应的IRQ(Interrupt Request)函数,然后IRQ再根据外设的寄存器标志位,判断具体是什么中断,并调用相应的中断回调函数进行处理。这类似于一种从上到下的分支结构,有利于提高决策效率。
10.2.2 NVIC相关寄存器
内核外设相关寄存器在《Reference Manual》中没有相关说明,需要查阅《STM32F10xx Cortex-M3 programing manual》,其中描述了ARM内核架构在STM32中的实现细节。
如前所述,ARM为ISER、ICER、ISPR、ICPR、IABR都预留了8个32bit的寄存器,为IP预留了240个8bit的寄存器。STM32由于中断数量较少,只用到了8个32bit的寄存器中的前3个和IP寄存器中的前68个。每个IP寄存器对应一个中断向量。其余寄存器中每个位对应一个中断向量。中断寄存器与中断向量编号的对应关系如图2所示。
图2. STM32的中断寄存器对应关系
1. 中断开关管理
ISER和ICER的功能位一一对应,从低位向高位顺序编号。向ISERx位写入1,开启编号x的中断向量(IRQx),而向ICERx位写入1,则关闭IRQx。写入0均无效。通过读取ISERx/ICERx可以查询中断向量IRQx的使能状态,1表示中断开启,0表示中断关闭(ICERx写入1,关闭中断后该位会被硬件复位为0)。
如果挂起状态的中断是使能的,NVIC会根据其优先级启动该中断;如果中断是未使能的,将其标志位置位只会使其中断状态变为挂起,无论其优先级高低,NVIC永远不会执行该中断。开启中断向量,使能外设的中断,然后NVIC才能响应该中断。
2. 中断挂起管理
ISPR和ICPR的功能位也是一一对应,从低位向高位顺序编号。向ISPRx位写入1,将中断向量IRQx强制挂起(Set Pending,暂停执行),而向ICPRx位写入1,则取消IRQx挂起(Clear Pending)。写入0均无效。通过读取ISPRx/ICPRx可以查询IRQx的挂起状态,1表示挂起,0表示未挂起。
3. 中断状态管理
IABR(Interrupt Active Bit Register)寄存器是个只读寄存器,用来管理中断向量的状态。如果IABR的第x位为1,表明IRQx是使能的或使能并处于挂起状态。
4. 中断优先级管理
IP是一组8bit的寄存器,用来管理中断优先级,每个寄存器对应一个中断向量。每个IP寄存器中只有[7:4]位(高4位)有效,低4位始终为0。抢断优先级和子优先级的位数可以通过程序设置,抢断优先级优先占据高位。
5. 中断的软件触发
除了事件触发,中断也可以通过软件触发(SGI,software generated interrupt)。触发的方法就是将中断的编号写入NVIC_STIR寄存器(Software TIgger Register)。这是一个只写寄存器,只有低9位有效,可以写入范围在0-239的值。例如写入0b0001 0010则产生IRQ18,即ADC1的中断请求。
软件触发受SCB控制,只有SCB_SCR寄存器的USERSETMPEND=1,无特权的软件才可访问STIR。而对USERSETMPEND的设置只能由有特权的软件来控制。
10.2.3 中断的运作机制
STM32的中断可分为电平敏感中断和脉冲(边沿)敏感中断。电平敏感中断在外设撤掉中断信号之前一直有效。通常需要通过ISR(Interrupt Service Routine)访问外设来清除其中断请求。而脉冲中断是一种需要通过处理器时钟上升沿同步取样的中断信号。为确保能被NVIC捕获到,外设发出的中断有效信号必须持续至少一个时钟周期。
当处理器进入ISR以后,会自动清除中断的挂起状态。对电平敏感中断,如果处理器从IRQ返回后中断信号仍未撤销,中断重新挂起进入等待,处理器必须再次执行IRQ。对这种中断,外设可以一直保持中断请求信号有效状态,让中断反复被执行,直至不再需要中断服务时再撤掉。
1. 中断的软件和硬件控制
Cortex-M3会执行所有的中断。如果一个外设的中断处于挂起状态,可能的原因如下:
- NVIC检测到中断信号是高电平,而中断没有激活。
- NVIC检测到中断信号的上升沿。
- 软件向中断对应的ISPR位写入了1,或者向STIR写入产生了SGI挂起。
解除中断挂起的方法:
- 处理器进入了该中断的ISR(受理了中断),中断的状态由pending变为active。
- 对电平敏感的中断,处理器从ISR返回后,NVIC会取样中断信号,如果信号仍有效,中断状态变为挂起,按优先级排队,等待再次被执行。反之,如果从ISR返回是中断信号无效,中断状态变为inactive。
- 对脉冲敏感的中断,NVIC持续监测中断信号,只要脉冲出现,中断状态即变为pending and active。处理器在ISR中时,如果有中断脉冲,当处理器返回中断状态变为pending,等待再次执行。而如果处理器在ISR中时,无中断脉冲,处理器从ISR返回后,中断状态变为inactive。
- 当软件写入中断的ISPR位时:对电平敏感中断,如果中断信号仍有效,中断状态保持不变。反之,变为inactive;对脉冲敏感中断,如果当时中断状态为pending,软件写入会将其清除,即变为inactive。而如果当时的状态是active and pending,软件写入清除无效,仍为active。
2. NVIC设计建议和提示
- 确保软件访问寄存器时正确对齐。处理器不支持非对齐的NVIC寄存器访问。
- 在编程VTOR重新分配向量表之前,确保新向量表中除了中断,也设置故障处理、NMI和所有开启的异常中断。
- 为了便于中断管理,CMSIS提供了两个内置函数,
__disable_irq() // Disable Interrupts
__enable_irq() // Enable Interrupts
另外,CMSIS还定义了专门的中断管理函数(core_cm3.h),如表1所示。
表1. core_cm3.h中定义的NVIC函数
CMSIS 中断控制函数
| 说明
| NVIC_SetPriorityGrouping()
| 设置优先级分组
| NVIC_GetPriorityGrouping()
| 查询优先级分组
| NVIC_EnableIRQ()
| 使能中断
| NVIC_GetEnableIRQ()
| 查询中断使能状态
| NVIC_DisableIRQ()
| 禁用中断
| NVIC_SetPendingIRQ()
| 设置中断挂起
| NVIC_GetPendingIRQ()
| 查询中断是否挂起
| NVIC_ClearPendingIRQ()
| 清除中断管钱标志
| NVIC_GetActive()
| 查询活动中的中断的IRQ编号
| NVIC_SetPriority()
| 设置中断优先级
| NVIC_GetPriority()
| 查询中断优先级
| NVIC_EncodePriority()
| 将2位抢断优先级和2位子优先级合成4位以便写入对应的IP[n]寄存器
| NVIC_DecodePriority()
| IP[n]寄存器中的优先级解析为抢断优先级和子优先级
| NVIC_SetVector()
| 设置中断向量表(SCB->VTOR)设置中断向量对应的处理函数地址
| NVIC_GetVector()
| 查询中断向量表,返回中断处理函数地址
| NVIC_SystemReset()
| 系统复位
|
注:STD库在misc.c中重新定义了NVIC管理函数(设置NVIC和SCB寄存器),而HAL库中则是在stm32f1xx_hal_cortex.c中用库函数对core_m3.h中的NVIC函数进行了封装。
10.3 HAL库NVIC函数
NVIC相关的HAL库函数在stm32f1xx_hal_cortexex.h中声明,在stm32f1xx_hal_cortexex.c中定义。每个外设相关的中断函数在外设的API中定义。
10.3.1 初始化和复位函数
- HAL_NVIC_SetPriorityGrouping(); 设置中断优先级分组,即抢断优先级和子优先级的位数。只需设置1次,不要中间修改。在stm32f1xx_hal_msp.c中的HAL_MspInit函数中会调用该函数进行中断优先级分组。
- HAL_NVIC_SetPriority();设置外设IRQn的抢断和子优先级。
- HAL_NVIC_EnableIRQ();开启外设IRQn的中断。一般在外设的HAL_PPP_MspInit函数中调用HAL_NVIC_SetPriority设置优先级,然后调用HAL_NVIC_EnableIRQ开启中断。
- HAL_NVIC_DisableIRQ();关闭外设IRQn的中断。一般在外设的HAL_PPP_MspDeInit函数中调用HAL_NVIC_DisableIRQ关闭中断。
- HAL_NVIC_SystemReset(); 发出系统复位请求,复位MCU(软件复位)。
- HAL_SYSTICK_Config(); 初始化系统时钟及其中断,并启动SYSTIC计时器。计数器自由运转,产生周期性的中断。
10.3.2 外设控制函数
- HAL_NVIC_GetPriorityGrouping();
- HAL_NVIC_GetPriority();
- HAL_NVIC_GetPendingIRQ();
- HAL_NVIC_SetPendingIRQ();
- HAL_NVIC_ClearPendingIRQ();
- HAL_NVIC_GetActive();
- HAL_SYSTICK_CLKSourceConfig();
- HAL_SYSTICK_IRQHandler();
- HAL_SYSTICK_Callback();
10.4 外部中断/时间控制器(EXTI)
10.4.1 EXTI简介
NVIC的中断向量中,除了与内核异常和片内外设的中断请求相关的向量外,还有一类来自EXTI的中断向量。EXTI称为外部中断/事件控制器(External interrupt/event controller),故名思议,是用来处理外部中断和外部事件的控制器。STM32的外部中断线有19条(互联系列20条),每条都可以单独设置类型(事件/中断)、触发方式(上升沿、下降沿或二者均有效),而且可单独屏蔽。EXTI与内核中的NVIC相连,通过NVIC向内核提交中断/事件请求。EXTI控制器的原理框图如图3所示。
图3. EXTI控制器原理框图
NVIC为EXTI分配了6个中断向量:EXTI0-EXTI4分别占用一个中断向量(EXTIx_IRQn,编号6-10);EXTI5-9和EXTI10-15分别合用一个中断向量(EXTI 9_5_IRQn和EXTI15_9_IRQn,编号23,40);EXTI16连接的是PVD检测中断事件,独占PVD_RIQn(编号1);EXTI17连接的是RTC闹钟事件,独占RTC_Alarm_RIQn(编号41);EXTI18连接的是USB唤醒事件,独占USBWakeUp_IRQn(编号42)。
112个GPIO引脚分别连接到了16条外部中断线上,对应关系为:P*x连接到EXTIx,*代表A-G,x代表0-16。例如:PA1,PB1,.....,PG1均连接与EXTI1。即,每条外部中断线服务7个GPIO引脚。但这7个引脚不能同时发送中断/事件请求。EXTIx的输入源由AFIO_EXTICR1-4寄存器来配置。AFIO_EXTICR1-4均为32bit的寄存器,每个寄存器仅使用低16位,每个EXTI线占4bit,所以一个寄存器可以容纳4个寄存器的配置。AFIO_EXTICR1配置EXTI0-3,AFIO_EXTICR2配置EXTI4-7,依次类推。每个EXTIx[3:0]的逻辑配位为:0000-PAx;0001-PBx;......;0101-PFx;0111-PGx。1111保留。
10.4.2 EXTI相关寄存器
图4. EXTI寄存器地址映射与复位值表
图4所示为EXTI相关寄存器与复位值表。EXTI的寄存器非常简单,共有6个32bit的寄存器,均使用0-19位。位x对应EXTIx的设置。其中,EXTI_IMR管理中断屏蔽;EXTI_EMR管理事件屏蔽;EXTI_RTSR管理上升沿触发;EXTI_FTSR管理下降沿触发;EXTI_SWER管理软件中断事件;EXTI_PR管理中断挂起状态。
注意:
- 外部唤醒线是边缘触发的,这些线上不能出现毛刺信号;
- 在写EXTI_RTSR(或EXTI_FTSR)寄存器的过程中,发生在外部中断线上的上升沿(或下降沿)不会被识别,挂起位也不会被设置。
10.5.3 EXTI功能说明
EXTI线既可以配置为响应中断(将EXTI_IMR的对应位置1),也可以配置为响应事件(将EXTI_EMR的对应位置1)。可以配置为上升沿触发(将EXTI_RTSR的对应位置1)、下降沿触发(将EXTI_FTSR的对应位置1)或双边沿触发(同时将EXTI_RTSR和EXTI_FTSR的对应位置1),还可以通过软件触发(将EXTI_SWIER的对应位置1)。配置为中断的EXTI线称为中断线(Interrupt Line),配置为事件的则称为事件线(Event Line)。
当中断线上发生所设置的边沿变化时,会产生相应的中断请求,EXTI_PR中对应的挂起位被同时置1。向该位再次写1可以清除挂起中的中断请求。
当事件线上发生所设置的边沿变化时,会产生一个事件脉冲,EXTI_PR中对应的挂起位不会被设置。也就是说,EXTI_PR是管理中断挂起的,事件不会被挂起,因为事件没有优先级。
设置边沿触发属于硬件事件触发。向EXTI_SWIER的对应写入1,也可以产生中断/事件请求,这种触发称为软件触发。软件触发情况下不要设置EXTI_EMR和EXTI_FTSR。
10.5 HAL库EXTI函数
10.5.1 EXIT配置步骤
在HAL库中,EXTI的API在stm32f1xx_hal_exti.h中声明,在stm32f1xx_hal_exti.c中定义。配置EXTI中断或事件线的基本步骤如下:
- 选择EXTI线:0-15对应GPIO引脚;16-PVD中断事件;17-RTC闹钟事件;18-USB唤醒事件。
- 设置EXTI线的模式:中断EXTI_IMR/事件EXTI_EMR/Both;
- 确定EXTI线的触发方式:上升沿EXTI_RTSR/下降沿EXTI_RTSR/Both;
- 如果EXTI线为GPIO引脚,设置AFIO_EXTICR,选择GPIO端口。
10.5.2 配置函数
- HAL_EXTI_SetConfigLine(); 根据两个结构体输入的参数配置EXTI
- HAL_EXTI_GetConfigLine(); 查询hexti的设置。hexti为EXTI线句柄。
- HAL_EXTI_ClearConfigLine(); 清除EXTI配置。复位IMR/EMR/RTSR/FTSR...
- HAL_EXTI_RegisterCallback(); 注册回调函数
- HAL_EXTI_GetHandle(); 将线号存入句柄hexti->Line = ExtiLine;
10.5.3 I/O函数
- HAL_EXTI_IRQHandler(); 中断请求处理函数
- HAL_EXTI_GetPending(); 获取指定线的挂起位
- HAL_EXTI_ClearPending(); 清除指定线的挂起位
- HAL_EXTI_GenerateSWI(); 设置SWIER,在hexti上产生软件中断SWI
版权声明:本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。 |