1、中断系统
关于图中的中断地址的问题:我们程序中的中断函数的地址由编译器来分配的,是不固定的。而中断跳转,由于硬件的限制,只能跳到固定的地址执行程序,所以为了能让硬件跳转到一个不固定的中断函数里,就需要在内存中定义一个地址的列表,这个列表地址是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置由编译器再加上一条跳转到中断函数的代码,进行中断跳转便可以跳转到任意位置。这个中断地址的列表就是中断向量表,相当于一个中断跳转的跳板,不过用C语言编程不需要管这个编程XL表,因为编译器已经帮我们做好了。
2、NVIC基本结构
NVIC:嵌套中断向量控制器,一个内核外设,是CPU的小助手。在STM32中,用于统一分配中断优先级和管理中断。
抢占优先级:插进程中间(中断嵌套)
响应优先级:插进程首或尾,相比而言抢占优先级甚于响应。
值越小,优先级越高。
3、EXTI外部中断
中断响应:申请中断,让CPU执行中断函数。
事件响应:不会触发中断,而是触发别的外设操作,属于外设左键的联合工作。
AFIO是一个数据选择器,PA0/PB0/PC0只能有一个接到EXTI的通道0上 。
加上4个“蹭网”的4个外设,一共有20个输入。
经过EXTI后分成两种输出:
(1)NVIC:用来触发中断, 其中5~9和10~15都分别被分配到同一个通道里,即外部中断5~9会触发同一个中断,10~15也 会触发同一个中断。
(2)20条线路连接其他外设
4、外部中断实现案例——旋转编码器
对于突发信号,可以考虑使用外部中断,比如旋转编码器、红外传感器等。这里以旋转编码器为例,
(1)旋转编码器简介
光栅式不能判断旋转左右,而触点式和霍尔传感器使用正交输出方波信号,可以测量。但触点接触式的传感器不适合电机这种高速旋转的场景,另外几种都是非接触式,可用于电机测速。
我们使用的是机械触点式编码器,编码盘在旋转时,依次接通和断开两边的触点,其位置经过特殊设计,能让两侧触点的通断产生一个90°的相位差,配合外部电路,便可输出以下波形——正交波形(可测方向)。
方波的个数代表旋转的次数,方波的频率代表旋转的速度。可以用外部中断捕获方波的边沿,以此来判断次数和转速。
(2)旋转编码器硬件电路
当旋转轴旋转时,两个触点以相位相差90度的方式交替导通,因为这只是个开关信号,所以要配合外围电路才能输出高低电平。未旋转时A、B输出高电平,旋转时,两触点导通,A、B输出低电平。R1、R2为上拉电阻,R3、R4为限流电阻(防止模块引脚电流过大),C1、C2为滤波电容(防止一些输出信号抖动)。
(3)旋转编码器初始化代码(标准库)
初始化步骤:开启并配置GPIO->开启并配置AFIO时钟->配置EXTI->配置NVIC
void Encoder_Init(void)
{
//开启GPIOB和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//还有EXTI和NVIC两个外设的时钟,但因为其一直打开,故不需要再开启
//配置GPIO初始化
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_PinSource0);//配置AFIO的数据选择器,来选择我们想要的中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
//配置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;//下降沿触发:Falling;上升沿触发:Rising;双触发:Rising_Falling
EXTI_Init(&EXTI_InitStructure);//因为EXTI只有一个,故无需指定外设
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
5、EXTI的常用的库函数以及NVIC配置变量
(1)EXTI的常用的库函数
EXTI的库函数 | 作用 |
void EXTI_DeInit(void); | 把EXTI的配置都清除,恢复成上电默认状态 |
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); | 可根据该结构体里的参数配置EXTI外设 |
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct); | 可把参数传递的结构体变量赋一个默认值 |
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line); | 软件触发外部中断 |
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line); | 获取指定的标志位是否被置1(一般在主程序中调用) |
void EXTI_ClearFlag(uint32_t EXTI_Line); | 对置1的标志位进行清除(一般在主程序中调用) |
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); | 获取中断标志位是否被置1(一般在中断函数中调用) |
void EXTI_ClearITPendingBit(uint32_t EXTI_Line); | 清除中断挂起标志位(一般在中断函数中调用) |
(2)NVIC配置变量
NVIC配置变量 | 作用 |
NVIC_IRQChannel | 指定要启用或禁用的IRQ通道。该参数可以是IRQn_Type的值。 |
NVIC_IRQChannelPreemptionPriority | 指定IRQ通道的抢占优先级。在NVIC_IRQChannel中指定,该参数可以是一个值。 |
NVIC_IRQChannelSubPriority | 指定IRQ通道的子优先级。在NVIC_IRQChannel中指定,该参数可以是一个值在0到15之间。 |
NVIC_IRQChannelCmd | 启用或禁用NVIC_IRQChannel中定义的IRQ通道。设置为ENABLE或DISABLE。 |
6、中断编程的建议
(1)在中断函数里,最后不要执行耗时过长的代码;
(2)最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件;
(3)实现中断功能,最好是在中断里操作变量或者标志位,当中断返回时,再对这个变量进行显示和操作。