一、中断原理
灰色为内核中断,中断地址是每次中断后跳转的地址,是固定的。
相同的PIN不能同时触发中断,比如GPIOA1,GPIOB1.,可触发中断响应,也可以触发事件响应
2、内部电路运行逻辑
1、输入进入,根据触发边沿选择,选择是否触发进入信号。有边沿触发和软件触发
2、然后进入两路,分别是触发中断和触发事件,触发中断会经过请求挂起寄存器(硬件中断标志位,等待处理)。中断屏蔽寄存器是中断总线上的中断使能。
触发事件会通过事件屏蔽寄存器,同使能,然后到达脉冲发生器,及电平脉冲,用来触发动作。
3、外部中断配合的传感器
外部驱动的很快的突发信号。比如编码器和红外遥控接收头的输出,按键虽然也是,但是中断不好处理按键抖动的问题。都是转瞬即逝的。
传感器1,对射式红外传感器测速,红外光受到遮挡和透过交替,来获取转速,和角度的方波。但是模块只有1路输出,正反转无法区分。
传感器2(2个图):是旋转编码器,内部使用金属触点来通断,是机械触点式编码器。AB点通过机械触点的角度差在旋转时可以得到正交波形,来判断转速和方向。上面的两个点用于按钮导通,按下导通,松手断开。一般用于调节音量等作为旋钮。
传感器3:是附在电机后面的霍尔式编码器,中间是一个圆形磁铁,边上两个位置错开的霍尔传感器,磁铁旋转时,通过霍尔传感器可以输出正交的方波信号。一般用于测速。
旋转编码器的远离电路:
上拉Vcc用来保持默认高电平,当旋钮导通,变为低电平,连接到GND,AB做AB相,引脚用上拉输入即可。
二、示例
1、外部中断对红外传感器计次
根据流程配置外部中断
1、RCC时钟开启
2、输入端口模式
3、配置AFIO,选择使用的GPIO,连接到后免得EXTI
4、配置EXTI,选择边沿触发方式,选择触发响应方式,可以选择中断响应和事件响应
5、配置NVIC,配置优先级
CountSensor.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int Count = 0;
void CountSensor_Init(void){
//RCC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//引脚时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//AFIO时钟使能
//GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
//EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//NVIC misc.h stm32f10x.h - 根据实际使用的芯片选择,STM32F103C8T6使用的是MD
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//固定通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
NVIC_Init(&NVIC_InitStructure);
}
//中断函数 startup_stm32f10x_hd.s
void EXTI15_10_IRQHandler(){//固定名称
if(EXTI_GetITStatus(EXTI_Line14)== SET){//防止中断判断错误,对中断标志位进行判断
Delay_ms(10);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)== RESET);
Delay_ms(10);
Count++;
EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志位
}
}
CountSensor.h
#ifndef __COUNTSENSOR_H
#define __COUNTSENSOR_H
#include "stm32f10x.h" // Device header
extern int Count;
void CountSensor_Init(void);
#endif
main.c
#include "stdio.h"
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
extern int Count;
int main(){
OLED_Init();
CountSensor_Init();
OLED_ShowString(1,1,"Infrared:");
while(1){
//LED
OLED_ShowNum(1,10,Count,4);
}
return 0;
}
中断函数命名使用唯一名称。
2、旋转编码器采集测速
方法1: 使用A的下降沿中断,判断B的电平来判断正反转
方法2:使用A、B的下降沿中断,判断A、B的电平来判断正反转
此处使用方法二
main.c
#include "stdio.h"
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num;//全局变量默认初始化0
int main(){
OLED_Init();
Encoder_Init();
OLED_ShowString(1,1,"Count:");
while(1){
//LED
Num += Encoder_Get();
OLED_ShowSignedNum(1,7,Num,4);
}
return 0;
}
Encoder.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int16_t Encoder_Count = 0;
void Encoder_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//AFIO 中断引脚选择
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//AFIO配置中断引脚的通道
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
//EXTI 边沿检测以及控制
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE ;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
//引脚1 NVIC配置-中断优先级和中断对应通道使能
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
//引脚1 NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
//每次获取编码器的增量,获取后对编码器计数进行清零
int16_t Encoder_Get(void){
int16_t Temp=0;
Temp = Encoder_Count;
Encoder_Count=0;
return Temp;
}
//GPIOB0外部中断函数
void EXTI0_IRQHandler(){
if(EXTI_GetFlagStatus(EXTI_Line0) == SET){//判断A相下降沿触发中断
Delay_ms(3);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == RESET){//再次判断A相电平,防止抖动
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){//判断B相
Encoder_Count--;//反转 --
}
}
EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位 否则卡死
}
}
//GPIOB1外部中断函数
void EXTI1_IRQHandler(){
if(EXTI_GetFlagStatus(EXTI_Line1) == SET){//B相下降沿触中断
Delay_ms(3);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){//再次判断B相电平,防止抖动
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == RESET){//判断A相电平
Encoder_Count++;//正转 --
}
}
EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位 否则卡死
}
}
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
#include "stm32f10x.h" // Device header
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
三、中断编程建议
1、不要再中断函数中处理耗时过长的代码,主程序会受到严重的阻塞
2、中断中不要使用例如屏幕显示等这种有原子性的程序,这样会导致中断程序和主程序的调用冲突。
3、多使用变量和标志位,增加程序内部的内聚性,减少模块之间的耦合性。