知识点1【中断的介绍】
单片机的中断——硬件中断
Linux操作系统的中断——软件中断
中断是指计算机运行过程中,出现某种意外情况需要主机干预,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后有返回原本暂停的程序继续运行。
1、程序中断和简单中断
程序中断:系统外部、内部或者现行程序本身若出现紧急事件,处理机立即中止现行程序的运行,自动转入相应的中断服务程序,待处理完毕后,再返回原来的程序运行,这整个过程称为程序中断
简单中断:当处理机接收中断后,只需暂停一个或几个周期而不执行对应的中断服务函数,称为简单中断
2、中断相关的概念
中断是一种硬件处理机制——一旦触发,就不需要程序判断,CPU内部自动执行。
中断源:能够触发中断的事件(如按键:按压按键会产生一个跳变沿),串口的收发也可以触发中断。一旦中断源被触发,那么在芯片内部就会产生一个中断请求。
事件:在中断部分,我们在使用中断之前,需要首先确定产生中断的事件。
在单片机中不是所有的事件都可以触发中断的,在手册中我们可以查到,那些事件可以触发中断。我们以串口为例
上图便是USART能够触发中断的请求。
中断服务程序:一旦中断触发,CPU需要停止当前的任务,去执行其他的任务,我们称其他任务为中断服务程序。
优先级:当有多个中断触发的时候,中断的处理顺序——数字越大,优先级越低
中断嵌套,在一个中断中可以出发另一个中断。
(1)中断优先级
抢占优先级:决定一个中断能否打断当前正在执行的中断。
抢占优先级高的可以抢占抢占优先级低的中断。 如果抢占优先级相同,则无法互相抢占。
次级优先级:当多个中断共享相同抢占优先级时,决定它们的处理顺序。
刺激优先级高的中断先被处理,但不能打断正在执行的同抢占优先级的中断
次级优先级 是不具有打断功能的。
那么 抢占优先级 和次级优先级是如何管理的呢?
是通过优先级分组 来管理的
(2)优先级分组
在ARM Cortex-M中,优先级数值的位数的分配是由 优先级分组 决定的。通过分组,可以分配抢占优先级和次级优先级的位数。
NVIC(嵌套向量中断控制器)
NVIC可配置的中断最大数目是240,每个中断的中断优先级 是1-256级
如上图使用8位来配置中断优先级的
那么 如果小于八位是如何配置呢?
——通过牺牲低位来配置,如 如果配置4位,就只有4-7位有效
3、中断注意事项:
终端服务函数中,不能出现死循环,也不能写入大量的循环语句。
原则:快进快出
知识点2【外部中断】
1、外部中断框图
流程介绍:
1、触发条件判断
外部中断信号 通过输入线进入,首先经过边沿检测电路,判断是上边沿,下边沿还是双边沿触发
判断方式即是由上边沿触发选择寄存器和下边沿触发选择寄存器决定。
2、经过 或门
软件和硬件都可以触发中断事件
3、事件屏蔽寄存器
用来选择传递的中断事件。
未被屏蔽的事件可以继续传递到后续模块
4、脉冲发生器
被筛选后的事件被转换为单周期脉冲信号,发出 事件请求信号
以上两个步骤是事件的处理流程,满足事件不一定会触发中断,可能是为了实现其他功能。
5、中断状态记录
请求挂起寄存器:锁存当前有效的中断请求状态,知道处理器响应后清除
6、二次屏蔽
中断屏蔽寄存器,通过APB总线 过滤请求,避免非使能中断占用NVIC资源
2、外部中断请求
外部中断/事件控制器 是由19个产生事件/中断请求的边沿检测器组成
所以说:外部中断,其实就是IO口跳变产生的中断
知识点3【外部中断配置寄存器介绍】
1、外部中断配置寄存器
我们从上一张图片可以看到,每一个外部中断端口,都对应着很多个引脚如EXTI1对应这A1,B1……,因此我们需要配置其定义关系。
这里我们以外部中断寄存器2为例
可见上面有EXTI4-EXTI7,那么可以这样的寄存器一共4个。
注意
我们配置的时候,这个寄存器是按照数组的方式 来操作的。
详细配置 看下面的代码演示即可,很易理解,写代码的时候细心就好。
2、中断屏蔽寄存器
这里是配置 哪一个中断使能的
3、上 下边沿寄存器
知识点4【外部中断代码演示】
exti.c
void Exit1_Key_Config()
{
//配置时钟:按键(PE4)和复用按键
RCC->APB2ENR |= (0x01 << 0);
RCC->APB2ENR |= (0x01 << 6);
//中断配置
//由于映射到EXIT2的端口有很多,因此需要选择
AFIO->EXTICR[1] |= (0x04 << 0);
//下边沿触发(线4)
EXTI->FTSR |= (0x01 << 4);
//中断屏蔽设置
EXTI->IMR |= (0x01 << 4);
//NVIC配置
NVIC_SetPriority(EXTI4_IRQn,0x06);
NVIC_EnableIRQ(EXTI4_IRQn);
}
//按键中断服务函数
void EXTI4_IRQHandler(void)
{
//中断触发条件判断
if(EXTI->PR & (0x01 << 4))
{
//清除中断挂起位
EXTI->PR &= ~(0x01 << 4);
//执行中断函数
while(1)
{
//延时消抖
if((GPIOE->IDR & (0x01 << 4)) == 0)
{
Delay_ms(40);
if((GPIOE->IDR & (0x01 << 4)) == 0)
{
while((GPIOE->IDR & (0x01 << 4)) == 0);
printf("你好\\n");
}
}
}
}
}
main.c
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
#include "stdio.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M?
//第五组
NVIC_SetPriorityGrouping(0X05);
Exit1_Key_Config();
Usart1_Config(9600);
printf("USART1 is ok!!\\r\\n");
while(1)
{
}
}
代码感悟
1、串口一次只能接收 / 发送一位数据
2、中断服务函数 需要 软件清除中断标志位
3、外部中断是通过 辅助接口(AFIO) 来设置的
知识点5【事件中断】
首先我们要清楚,不是所有事件都能触发中断的,因此我们要实现知道那些事件能够产生中断,我们以串口为例。可以在寄存器手册中查到。
可见,事件中断触发,的前提是 使能对应事件的中断标志位,如当 接收事件完毕即RXNE被置位 后,并且软件置为RXNEIE,会产生一个对应的串口中断
在事件触发,并且对应的中断标志位被置位 的情况下,事件中断就会被触发,即会执行对应的中断服务程序。
这里寄存器不过多介绍 我们直接进入实战
知识点6【事件中断代码演示】
逻辑:
只有RXNE 被置位,且RXNEIE 被置位,才会发生中断
我们知道串口每次接收到一个数据,RXNE会被置位,又由于我们循环软件置为RXNEIE 是的接收数据中断持续生效。
那么我们该如何判断所有位的数据 都被接收呢
我们就需要利用串口的空闲中断 即 IDLE状态位,详细大家看手册。
这是我们就 引入一个flag位,当进入空闲状态,flag置1,即接收全部数据完成。
代码演示
exti.c文件
#include "exti.h"
u8 over_flag=0;
u8 rev_data[64]={0};
u8 rev_count=0;
void USART1_IRQHandler(void)
{
u8 data=0;
if((USART1->SR &(0X01<<5)))
{
USART1->SR &=~(0X01<<5);
//进入了接收中断,可以接受外部传输过来的数据
rev_data[rev_count++]=USART1->DR;
}
//当数据接受完成的时候,就会进入空闲中断
if((USART1->SR &(0X01<<4)))
{
USART1->SR &=~(0X01<<4);
//清除空闲中断标志
data=USART1->DR;
//设置一个接收完成的标志位
over_flag=1;
}
}
//中断接受完成数据之后,在以下函数中处理数据
void Usart1_CtrlLED(void)
{
int i=0;
if(over_flag==1)
{
over_flag=0;//避免影响下一次的数据接收中断
//处理接收到的数据
printf("接收到的数据个数:%d\\r\\n",rev_count);
for(i=0;i<rev_count;i++)
{
printf("rev_data[%d]=%x\\r\\n",i,rev_data[i]);
}
if(rev_data[0]==0xaa && rev_data[2]==0xff)
{
//控制LED1
if(rev_data[1]==0x11) GPIOB->ODR &=~(0X01<<5);
else GPIOB->ODR |=(0X01<<5);
}
rev_count=0;
memset(rev_data,0,sizeof(rev_data));
}
}
int fputc(int c, FILE * stream)
{
while(!(USART1->SR &(0X01<<7)));
USART1->DR=c;
return c;
}
main函数
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
#include "stdio.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M?
//第五组
NVIC_SetPriorityGrouping(0X05);
Led_Config();
Usart1_Config(9600);
printf("USART1 is ok!!\\r\\n");
while(1)
{
Usart1_RevInterrupt();
Usart1_CtrlLED();
}
}
结束
最近的分析是关于寄存器的,当然后面我会分享标准库的,因为寄存器是基础,我们先学习寄存器有利于我们深度理解后面标准库的原理,我的分享是一个由繁入简的过程,希望大家能够理解。
补充:
这两天由于身体状态原因,没有正常更新,但学习过程没有间断。大家也注意身体,毕竟身体是革命的本钱。
望体谅!
最后:
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助。如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!