中断
所谓的中断是对于CPU而言,简单说起来就是当没有中断触发时,CPU处理main()中或其他不是中断函数,一旦中断触发(相应的中断标志位变化),CPU会立即退出现在正在执行的main()函数,转而执行中断函数内部的内容(名称不可修改)。
其中事件不需要CPU的参与,需要特定的硬件电路连接。
外部中断
外部中断/事件控制器(EXTI)
外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每个中断线都可以独立地配置它的触发事件(上升沿或下降沿或双边沿),并能够单独地被屏蔽:有一一个挂起寄存器维持所有中断请求的状态。EXTI可以检测到脉冲宽度小于内部APB2的时钟周期。多达80个通用I/O口连接到16个外部中断线。
STM32F1支持将所有GPIO设置为中断输入。
外部I0可由上升沿、下降沿、高低电平的三种方式触发。
可选择中断或事件触发。
上升沿、下降沿的意义如下图:
各个IO端口对应的中断标志位及中断函数如下表:
嵌套的向量式中断控制器(NVIC)
STM32F103xx增强型产品内置嵌套的向量式中断控制器,能够处理多达43个可屏蔽中断通道(不包括16个Cortex TM-M3的中断线)和16个优先级。
1、紧耦合的NVIC能够达到低延迟的中断响应处理
2、中断向量入口地址直接进入内核
3、紧耦合的NVIC接口
4、允许中断的早期处理
5、处理晚到的较高优先级中断
6、支持中断尾部链接功能
7、自动保存处理器状态
8、中断返回时自动恢复,无需额外指令开销
该模块以最小的中断延迟提供灵活的中断管理功能。
cortex-m3支持256个中断,其中包含了16个内核中断,240个外部中断
stm32只有84个中断,包括1 6个内核中断和68个可屏蔽中断
stm32f103上只有60个可屏蔽中断
具体中断和相应的控制器连接以及和CPU的连接关系图如下:
关于抢占优先级和响应优先级
抢占优先级和响应优先级,其实是一个中断所包含的两个优先级,其中前者是对抢占优先级的级别划分,后者是相同抢占优先级的优先级别的划分。比如:中断A抢占优先级比B高,那么A的中断可以在B里面触发,忽略响应优先级;A和B抢占优先级相同,则A、B的响应优先级决定谁先响应;
注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看 哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级 中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。抢占式优先级别相同的中断源之间没有嵌套关系;
优先级的设置:
NVIC_ InitStruct.NVIC IRQChannelPreemptionPriority=2; //抢 占优先级
NVIC_ .InitStruct.NVIC .IRQChannelSubPriority=2; // 子优先级
设置值越小,优先级越高
通过下面函数设置优先级
void NVIC_Configuration(void){ //嵌套中断向量控制器 的设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
}
具体意义如下图:
具体中断初始化函数如下:
u8 INT_MARK;//中断标志位
void KEYPAD4x4_INT_INIT (void){ //按键中断初始化
NVIC_InitTypeDef NVIC_InitStruct; //定义结构体变量
EXTI_InitTypeDef EXTI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //启动GPIO时钟 (需要与复用时钟一同启动)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);//配置端口中断需要启用复用时钟
//第1个中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4); //定义 GPIO 中断
EXTI_InitStruct.EXTI_Line=EXTI_Line4; //定义中断线
EXTI_InitStruct.EXTI_LineCmd=ENABLE; //中断使能
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式为 中断
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(& EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=EXTI4_IRQn; //中断线
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级 2
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; //子优先级 2
NVIC_Init(& NVIC_InitStruct);
//第2个中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5); //定义 GPIO 中断
EXTI_InitStruct.EXTI_Line=EXTI_Line5; //定义中断线
EXTI_InitStruct.EXTI_LineCmd=ENABLE; //中断使能
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式为 中断
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(& EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=EXTI9_5_IRQn; //中断线
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级 2
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; //子优先级 2
NVIC_Init(& NVIC_InitStruct);
//第3个中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6); //定义 GPIO 中断
EXTI_InitStruct.EXTI_Line=EXTI_Line6; //定义中断线
EXTI_InitStruct.EXTI_LineCmd=ENABLE; //中断使能
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式为 中断
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(& EXTI_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=EXTI9_5_IRQn; //中断线
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //使能中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级 2
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; //子优先级 2
NVIC_Init(& NVIC_InitStruct);
第4个中断与前面类似。
}
void EXTI4_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line4)!=RESET){//判断某个线上的中断是否发生
INT_MARK=1;//标志位置1,表示有按键中断
EXTI_ClearITPendingBit(EXTI_Line4); //清除 LINE 上的中断标志位
}
}
void EXTI9_5_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line5)!=RESET){//判断某个线上的中断是否发生
INT_MARK=2;//标志位置1,表示有按键中断
EXTI_ClearITPendingBit(EXTI_Line5); //清除 LINE 上的中断标志位
}
if(EXTI_GetITStatus(EXTI_Line6)!=RESET){//判断某个线上的中断是否发生
INT_MARK=3;//标志位置1,表示有按键中断
EXTI_ClearITPendingBit(EXTI_Line6); //清除 LINE 上的中断标志位
}
if(EXTI_GetITStatus(EXTI_Line7)!=RESET){//判断某个线上的中断是否发生
INT_MARK=4;//标志位置1,表示有按键中断
EXTI_ClearITPendingBit(EXTI_Line7); //清除 LINE 上的中断标志位
}
}
对于两条中断线的解释,其中第一条中断线EXTI_Line_n是外部中断的中断线即相应的GPIO、串口等的中断线,并不共用,而是每一个中断有一根中断线。第二个中断线NVIC控制器直接接至CPU的中断线,CPU中断通过此中断线进入中断函数。需要简单区分一下。比如上面程序CPU通过中断控制器的中断线EXTI9_5_IRQn进入中断函数EXTI9_5_IRQHandler()之后,还需要进一步根据第一条中断线判断到底是哪个中断源引起的中断,因为它们通过中断控制器,和CPU中断的通信共用中断线和中断函数。
主函数的调用如下:
while(1){
//其他程序内容
if(INT_MARK){ //中断标志位为1表示有按键中断
INT_MARK=0;//标志位清0
s=KEYPAD4x4_Read();//读出按键值
if(s!=0){ //如按键值不是0,也就是说有按键操作,则判断为真
//-------------------------"----------------"
OLED_DISPLAY_8x16_BUFFER(6," KEY NO. "); //显示字符串
OLED_DISPLAY_8x16(6,8*8,s/10+0x30);//
OLED_DISPLAY_8x16(6,9*8,s%10+0x30);//
}
}
}
}
通过设置全局变量INT_MARK记录哪个中断源的中断。