第23节 中断系统 (NVIC) Nesting Vector Interrupt Controller
小结 向量表定向-向量偏移寄存器 优先级分组 优先级寄存器 抢占优先级,响应优先级 Stm32 共支持59个外部中断源,即有59个中断通道 59个通道的开启或关闭有NVIC控制器决定(CM3内核)
NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 中断通道的使能与除能 CM3 中可以有240 对使能位/除能位,每个中断拥有一对。这240 对分布在8 对32 位寄存器中(最后一对没有用完)。 使能一个中断,需要写1 到对应SETENA 的位中; 除能一个中断,需要写1 到对应的CLRENA 位中; 如果往它们中写0,不会有任何效果
中断通道的使能与除能 具体芯片根据中断通道数不同,需要数目不同的相应使能和除能寄存器 对于stm32,59个外部中断通道,所以,需要两个32位使能寄存器和两个除能寄存器
另外,因为cm3支持中断嵌套,当中断发生时,正在处理高中断或同级中断,该中断会不会被错过? NVIC寄存器组 typedef struct { vu32 ISER[2]; //使能寄存器 u32 RESERVED0[30]; vu32 ICER[2]; //除能寄存器 u32 RSERVED1[30]; vu32 ISPR[2]; //中断挂起 u32 RESERVED2[30]; vu32 ICPR[2];//中断挂起清除 u32 RESERVED3[30]; vu32 IABR[2]; u32 RESERVED4[62]; vu32 IPR[15]; //优先级寄存器 } NVIC_TypeDef; 另外,因为cm3支持中断嵌套,当中断发生时,正在处理高中断或同级中断,该中断会不会被错过?
中断的挂起 中断请求状态被保存于中断挂起寄存器 当高级中断执行完毕,根据现有中断请求中的优先级别,选择高优先级的中断设为激活状态
中断挂起解除 当不需要在响应此中断,置1响应挂起清除寄存器,不在对其响应 同样,两个寄存器写1有效,写0无效
#define SCS_BASE ((u32)0xE000E000) #define SysTick_BASE (SCS_BASE + 0x0010) #define NVIC_BASE (SCS_BASE + 0x0100) #define SCB_BASE (SCS_BASE + 0x0D00)
NVIC寄存器组 typedef struct { vu32 ISER[2]; //使能寄存器 u32 RESERVED0[30]; vu32 ICER[2]; //除能寄存器 u32 RSERVED1[30]; vu32 ISPR[2]; //中断挂起 u32 RESERVED2[30]; vu32 ICPR[2];//中断挂起清除 u32 RESERVED3[30]; vu32 IABR[2]; u32 RESERVED4[62]; vu32 IPR[15]; //优先级寄存器 } NVIC_TypeDef; #define NVIC ((NVIC_TypeDef *) NVIC_BASE)
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel; NVIC->ISER[0]=0x00800000;//使能23号外部中断 NVIC->ICER[0]=0x00800000;//除能23号外部中断
外部中断共有59个,每个中断用一个字节表示,4个中断用一个字,15个32位寄存器即可 如何设置优先级 NVIC->IPR[15] 外部中断共有59个,每个中断用一个字节表示,4个中断用一个字,15个32位寄存器即可 中断号左移2位即为优先级寄存器位置 23号中断,23>>2=0101=5 ,即NVIC->IPR[5]中定义其优先级 优先级分组为1的情况下:1位抢占,3位响应,高位对齐 如设定为抢占1,响应1 则: NVIC->IPR[5]=10010000=x90;
外部中断/事件控制器由20个产生事件/中断要求的边沿检测器组成。 NVIC控制器的中断通道已经使能打开 问题:怎样将外部管脚或中断源与中断通道对应? 外部中断/事件控制器由20个产生事件/中断要求的边沿检测器组成。 PA~E 0 对应 EXTI 0 输入线 PA~E 1 对应 EXTI 1 输入线 依次类推共15个输入线
EXTI 线16 连接到PVD 输出 EXTI 线17 连接到RTC 闹钟事件 EXTI 线18 连接到USB 唤醒事件 EXTI 线19连接到以太网唤醒事件(只适 用于互联型产品)
每个输入线可以独立地配置输入类型和对应的触发事件(上升沿或下降沿或者双边沿都触发)。 每个输入线都可以被独立的屏蔽。 挂起寄存器可以保持着输入线的中断要求。
响应中断中的几个概念及问题: 1、输入线是否被配置及打开?输入线 2、是否设置触发方式?触发寄存器 3、当前中断优先级如何?是否被挂起? (优先级分组寄存器,优先级寄存器,挂起寄存器) 4、该中断是否被屏蔽?(中断屏蔽寄存器) 5、NVIC是否将该中断通道使能?(NVIC使能或除能寄存器)
因此,每个IO管脚都可设置为中断源 问题:如何选择? 既然是将IO口作为中断输入,首先需要对IO口进行设置。 1、输入线是否被配置及打开?输入线
配置GPIO针脚为输入模式 (1) 选择IO针脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; (2) 配置针脚为输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; (3) 初始化针脚 GPIO_Init(GPIOB,&GPIO_InitStructure); 接下来,将管脚的接受中断功能打开,用于接受中断,确定中断源。 AFIO寄存器
输入线被配置及打开 typedef struct { vu32 EVCR; vu32 MAPR; vu32 EXTICR[4]; } AFIO_TypeDef; 配置PB9作为中断源: AFIO->EXTICR[2]=0x0000 0010 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource9); AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp; AFIO->EXTICR[GPIO_PinSource >> 0x02] | = (((u32)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (u8)0x03))); 输入线被配置及打开
2、是否设置触发方式?触发寄存器 3、该中断是否被屏蔽?(中断屏蔽寄存器)
2、是否设置触发方式?触发寄存器 3、该中断是否被屏蔽?(中断屏蔽寄存器) typedef struct { vu32 IMR; //中断屏蔽寄存器 vu32 EMR; //事件屏蔽寄存器 vu32 RTSR; //上升沿触发 vu32 FTSR; //下降沿触发 vu32 SWIER; //软件中断事件寄存器 vu32 PR; //挂起寄存器 } EXTI_TypeDef; 硬件中断选择 通过下面的过程来配置20个线路做为中断源: ● 配置20个中断线的屏蔽位(EXTI_IMR) ● 配置所选中断线的触发选择位(EXTI_RTSR和EXTI_FTSR); ● 配置对应到外部中断控制器(EXTI)的NVIC中断通道的使能和屏蔽 位,使得20个中断线中的请求可以被正确地响应。 中断发生条件,不仅需要中断通道打开,而且需要中断源不被屏蔽。
2、是否设置触发方式?触发寄存器 3、该中断是否被屏蔽?(中断屏蔽寄存器) 硬件中断选择 通过下面的过程来配置20个线路做为中断源: ● 配置20个中断线的屏蔽位(EXTI_IMR) ● 配置所选中断线的触发选择位(EXTI_RTSR和EXTI_FTSR); ● 配置对应到外部中断控制器(EXTI)的NVIC中断通道的使能和屏蔽 位,使得20个中断线中的请求可以被正确地响应。 /*配置EXTI线9上出现下降沿,则产生中断*/ EXTI_InitStructure.EXTI_Line = EXTI_Line9; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断线使能 EXTI_Init(&EXTI_InitStructure); //初始化中断
到此中断配置完成,可以写中断处理函数,存在9_5中断请求,向中断向量表取中断函数地址,执行此函数 void EXTI9_5_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_LINE_KEY_BUTTON) != RESET) /* Toggle GPIO_LED pin 6 */ GPIO_WriteBit(GPIO_LED, GPIO_Pin_6, (BitAction)((1-GPIO_ReadOutputDataBit(GPIO_LED, GPIO_Pin_6)))); /* Clear the Key Button EXTI line pending bit */ EXTI_ClearITPendingBit(EXTI_LINE_KEY_BUTTON); 清除挂起 }
2、设置中断优先级分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); EXTI 线16 连接到PVD 输出 EXTI 线17 连接到RTC 闹钟事件 EXTI 线18 连接到USB 唤醒事件 EXTI 线19连接到以太网唤醒事件 小结:外部中断配置 20条中断线,即20个中断通道 1、分配中断向量表 2、设置中断优先级分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 3、 初始化外部中断(即选择并打开中断通道和优先级) NVIC_Init(&NVIC_InitStructure); 4、配置GPIO针脚作为外部中断(配置管脚为接收中断状态) GPIO_Init(GPIOD,&GPIO_InitStructure); 5、配置EXTI线(使中断线和IO针脚线连接上, 且不屏蔽中断源) EXTI_Init(&EXTI_InitStructure);
实验五 EXTI外部中断 实验目的及内容: 分析实验程序,学习使用ARM开发实验箱 STM32的中断向量 外部中断线相关原理及配置方法 理解中断的过程 在此基础上,编写中断配置及处理函数代码
中断相关库函数声明 NVIC寄存器组包括两个主要模块: 中断控制 NVIC 系统控制 SCB typedef struct { vuc32 CPUID; vu32 ICSR; vu32 VTOR; vu32 AIRCR; vu32 SCR; vu32 CCR; vu32 SHPR[3]; vu32 SHCSR; vu32 CFSR; vu32 HFSR; vu32 DFSR; vu32 MMFAR; vu32 BFAR; vu32 AFSR; } SCB_TypeDef; typedef struct { vu32 ISER[2]; //中断设置使能 u32 RESERVED0[30]; vu32 ICER[2];//中断除能 u32 RSERVED1[30]; vu32 ISPR[2];//中断挂起寄存器 u32 RESERVED2[30]; vu32 ICPR[2];//清除中断挂起 u32 RESERVED3[30]; vu32 IABR[2];//中断活跃位 u32 RESERVED4[62]; vu32 IPR[15];//中断优先级 } NVIC_TypeDef;
NVIC外设声明于文件“stm32f10x_map.h”: ... #define SCS_BASE ((u32)0xE000E000) #define NVIC_BASE (SCS_BASE + 0x0100) #define SCB_BASE (SCS_BASE + 0x0D00) #ifndef DEBUG #ifdef _NVIC #define NVIC ((NVIC_TypeDef *) NVIC_BASE) #define SCB ((SCB_TypeDef *) SCB_BASE) #endif /*_NVIC */ #else /* DEBUG */ EXT NVIC_TypeDef *NVIC; EXT SCB_TypeDef *SCB;
NVIC库函数 NVIC_PriorityGroupConfig 函数名 NVIC_PriorityGroupConfig 函数原形 void NVIC_PriorityGroupConfig(u32 NVIC_PriorityGroup) 功能描述 设置优先级分组:抢占优先级和从优先级 输入参数 NVIC_PriorityGroup:优先级分组位长度 先决条件 优先级分组只能设置一次 NVIC_PriorityGroup 描述 NVIC_PriorityGroup_0 先占优先级0位 从优先级4位 NVIC_PriorityGroup_1 先占优先级1位 从优先级3位 NVIC_PriorityGroup_2 先占优先级2位 从优先级2位 NVIC_PriorityGroup_3 先占优先级3位 从优先级1位 NVIC_PriorityGroup_4 先占优先级4位 从优先级0位
库函数 函数NVIC_Init 函数名 NVIC_Init 函数原形 void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct) 功能描述 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 输入参数 NVIC_InitStruct:指向结构NVIC_InitTypeDef的指针,包含了外设GPIO的配置信息 typedef struct { u8 NVIC_IRQChannel; u8 NVIC_IRQChannelPreemptionPriority; u8 NVIC_IRQChannelSubPriority; FunctionalState NVIC_IRQChannelCmd; } NVIC_InitTypeDef;
使用方法: NVIC_InitStructure.NVIC_IRQChannel = 43个通道; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
函数NVIC_SetVectorTable 函数名 NVIC_SetVectorTable 函数原形 void NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset) 功能描述 设置向量表的位置和偏移 输入参数1 NVIC_VectTab:指定向量表位置在RAM还是在程序存储器 参阅Section:NVIC_VectTab查阅更多该参数允许取值范围 输入参数2 Offset:向量表基地址的偏移量对FLASH,该参数值必须高于0x08000100;对RAM必须高于0x100。它同时必须是256(64×4)的整数倍
指定系统异常的优先级
外部中断EXTI寄存器结构,EXTI_TypeDef,在文件“stm32f10x_map.h”中定义如下: typedef struct { vu32 IMR; //中断屏蔽寄存器 vu32 EMR; //事件屏蔽寄存器 vu32 RTSR; //上升沿触发寄存器 vu32 FTSR; //下降沿触发寄存器 vu32 SWIER; //软中断 vu32 PR; //挂起寄存器 } EXTI_TypeDef; #define PERIPH_BASE ((u32)0x40000000) #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) #define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
EXTI_InitStructure.EXTI_Line = EXTI_Line9; typedef struct { u32 EXTI_Line; EXTIMode_TypeDef EXTI_Mode; EXTIrigger_TypeDef EXTI_Trigger; FunctionalState EXTI_LineCmd; } EXTI_InitTypeDef; EXTI_InitStructure.EXTI_Line = EXTI_Line9; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
几个概念: 1、关于嵌套的中断 a、 NVIC和CM3处理器排出优先级的顺序。因此,在某个异常正在响应时,所有优先级不高于它的异常都不能抢占之,而且它自己也不能抢占自己。 b、 支持自动入栈和出栈,就不用担心在中断发生嵌套时,会使寄存器的数据损毁,从而可以放心地执行服务例程。 注意:选择堆栈的大小
起因:所有服务例程都只使用主堆栈。所以当中断嵌套加深时,对主堆栈的压力会增大:每嵌套一级,就至少再需要8个字,即32字节的堆栈空间 因此:stm32最多16级中断,16*8=128个字ram.icf选择堆栈大小为0x400=256个字,以完全满足需求
2、咬尾中断 针对问题:紧接着处理悬起中断时,堆栈里的数据如何处理? Cm3处理方法,不pop数据,直接继续执行新的中断
3、晚到(的高优先级)异常 针对问题:当CM3对某异常的响应还处在入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,如何处理? Cm3:本次入栈就成了为高优先级中断所做的了——入栈后,将执行高优先级异常的服务例程。 在ISR #2执行完毕后,则以刚刚讲过的“咬尾中断”方式,来启动ISR #1的执行。 如果异常#2来得太晚,以至于已经执行了ISR #1的指令,则按普通的抢占处理。则需将#1号push堆栈,多增加32字节的堆栈空间
定义是:从检测到某中断请求,到执行了其服务例程的第一条指令,消耗的时间。 4、中断延迟 定义是:从检测到某中断请求,到执行了其服务例程的第一条指令,消耗的时间。 Cm3理想情况:12个周期。 若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应 在此12个周期内,处理器进行入栈、取向量、更新寄存器以及服务例程取指的一系列操作 当处理咬尾中断时,省去了堆栈操作,因此切入新异常服务例程的耗时可以短至6周期。
5、Systick定时器 Cortex‐M3处理器内部包含了一个简单的定时器。 作用:定时产生一个systick中断(中断号:15 ) 大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。 例如,为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 Cortex‐M3处理器内部包含了一个简单的定时器。 作用:定时产生一个systick中断(中断号:15 )
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常
系统时钟节拍(SysTick)控制与状态寄存器 使用系统时钟节拍(SysTick)控制与状态寄存器来使能SysTick 功能。
系统时钟节拍(SysTick)重装值寄存器 初始值可以是1 到0x00FFFFFF 之间的任何值。 所以,systick是一个递减的24位定时器
系统时钟节拍(SysTick)校准值寄存器 使用系统时钟节拍(SysTick)校准值寄存器通过乘法和除法运算可以将寄存器调节成 任意所需的时钟速率。 系统时钟节拍(SysTick)校准值寄存器 使用系统时钟节拍(SysTick)校准值寄存器通过乘法和除法运算可以将寄存器调节成任意所需的时钟速率。
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等 2、配置系统滴答定时器 (1)计数器当前值初始化为0; (2)根据时钟速度和定时需要初始化重装值 假如系统时钟为20M Hz, SYSTICK_RELOAD=20000;定时为1ms; 假如系统时钟为72MHz,需定时1ms,则 SYSTICK_RELOAD=72000; 假如系统时钟为72MHz,最长定时为? (3)选择时钟源并使能中断 对于stm32而言,时钟源可以为系统时钟(1),也可是外部时钟(此时为系统时钟的8分频) SYSTICK_CSR|=0x06; (4)在使用systick时,将其打开 SYSTICK_CSR|=0x01;
实现流程代码: 配置部分: /* AHB时钟作为systick时钟源 */ SysTick->CTRL |= 0x04; /* 系统抢占优先级为 1 */ NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, 1, 0); /* SysTick interrupt each 1ms with HCLK equal to 72MHz */ SysTick->LOAD=72000; /* Enable the SysTick Interrupt */ SysTick->CTRL |= 0x02;
调用部分 while (1) { GPIO_Write(GPIOC, (u16)~GPIO_ReadOutputData(GPIOC)); Delay(3000); } void Delay(vu32 nTime) { /* 使能 SysTick 定时器 */ SysTick->CTRL |=0x01; TimingDelay = nTime;//每1ms进入一次中断 while(TimingDelay != 0) ;//直到进入了3000次中断 才停止 /* 禁止 SysTick 定时器 */ SysTick->CTRL &=0xFFFFFFFE; /* 清除当前计数寄存器值 */ SysTick->VAL = 0x0; void SysTickHandler(void) { TimingDelay--; }
实验六 优先级抢占实验 实验目的: 观察不同优先级之间的抢占 掌握外部中断和系统中断的配置方法 实验涉及3个中断: 外部中断0; 中断9_5; 系统滴答中断
初始状态: EXTI9_5: 抢占优先级0,响应优先级1 EXTI0: 抢占优先级1,响应优先级0 Systick: 抢占优先级2,响应优先级0 现象: 1、若不触发两个外部中断,6灯不断闪烁 2、若在执行systick中断程序时,按下wakeup,即EXTI0,则抢占systick 3、若按下EXTI9_5,则不论以上两个中断谁正执行,都将抢占,且改变两个中断的优先级别
NVIC_SetSystemHandlerPendingBit(SystemHandler_SysTick); 没有真正产生systick定时,只是设置其挂起位,让NVIC响应一个假中断 并没有打开systick