由于现在学习的是以寄存器库为基础来实现功能,所以以后的知识点和代码都是以寄存器库来配置的,还有目前现在用的板子是STM32F407VET6,该笔记只是自己的见解和理解,大佬勿喷。
4.1 中断的概述
4.1.1 什么是中断
从上往下跑主函数(int main()),但是有一些事件(紧急事件/异常事件)打断主程序,打断之后会跑到这些事件运行
CPU在执行主程序时,接收到处理紧急事件的请求(标志位变化),CPU中断当前操作(中断执行主程序),去执行紧急事件(运行中断服务函数),紧急事件处理完成后会继续执行主程序
总结:也就是说中断就是程序在正常运行的过程中发生紧急的事情,必须要暂停一下去处理这个紧急的事情,然后跑回来继续干正常的事情。
进一步理解中断过程:
场景1:看书时候来电话,去接电话,接完电话继续看书
看书:一直在做事情(主程序一直跑的程序)
接电话:紧急事件
选择: ①跑去接电话的时候,把书直接盖起来 (麻烦)
②跑去接电话的时候,贴书签记录书看到位置(入栈和出栈操作)
入栈:保存现场(代码运行的位置;变量、数组....值)
出栈:恢复现场(代码运行的位置;变量、数组....值)
场景2:看书时候,来电话,并且门铃也响了
看书:一直在做事情(主程序一直跑的程序)
紧急事件:接电话、开门
选择:①先跑去接电话,再去开门,重新回去看书
②先跑去开门,再去接电话,重新回去看书
总结:紧急事件有紧急的程度,用户可以根据紧急事件的紧急程度,决定紧急事件执行的优先级
场景3:看书时候来电话,去接电话,在接电话的过程中门铃响了
①在接电话的过程中,暂停接电话跑去开门,开完门之后继续回去接电话
②在接电话的过程中,继续回去看书
总结:中断嵌套;中断优先级高可以抢断优先低,优先低抢断不了优先级高的中断
中断工作原理:
正常运行的程序是主函数,代码是由CPU运行的。CPU在主函数里运行是正常的执行过程,当在这个过程中突然发生了紧急事件,CPU必须暂停当前的工作,然后跑去能处理这个紧急事件的函数中做异常处理,处理完这个异常事件后,CPU就会跑回刚才的断点处,继续正常运行下去。
中断优点:减少CPU占用,防止程序卡死,增加程序实时性
说明:为了不影响主程序运行,所以中断服务函数里面不能有大量的延时和死循环
紧急事件执行顺序,用户来决定(用户根据紧急事件的紧急程度设置优先级)
①产生紧急事件(打开中断开关,标志位置一)
②写一个中断服务函数
③决定中断优先级
4.1.2 中断控制系统
NVIC特性
嵌套向量中断控制器 NVIC 包含以下特性:
● STM32F405xx/07xx 和 STM32F415xx/17xx 具有 82 个可屏蔽中断通道(82个中断源),STM32F42xxx
和 STM32F43xxx 具有多达 86 个可屏蔽中断通道(不包括 Cortex™-M4F 的 16 根中
断线)
● 16 个可编程优先级(使用了 4 位中断优先级)
● 低延迟异常和中断处理
●电源管理控制
● 系统控制寄存器的实现
ARM的体系:
ARM设计/开发内核,中断体系ARM公司设计和开发,因为STM32F407使用的也是ARM的中断体系,在学习完STM32F407的中断后,中断的内容所有使用ARM内核芯片通用
当前我们使用的STM32F407VET6,使用的就是ARM的内核,使用的中断体系也是ARM的中断体系(M0、M3、M4、M7都是用这种体系的---M系列通用)。 M4系列里面,中断配置方式一样
中断体系就是管理中断的一套机制,在CM4中集成了一个用来管理中断机制的控制器-----NVIC中断管理控制器(内核里面 --- 与内核相关手册)。 NVIC在内核里面,需要用到M4或者M3内核手册进行配置
NVIC控制器的作用:
接收中断请求信号①,判断中断事件优先级②,指挥CPU按 先后顺序执行紧急事件③(中断服务函数)
NVIC控制器属于内核级的模块,专门做中断管理,包括中断响应,优先级的设置,接收中断请求...
手册:技术参考手册、内核手册
特别注意:中断服务函数中不要有大量延时和死循环
4.1.3中断优先级
优先级:决定事件执行顺序
C语言运算符:等级范围1 – 15,级别越小优先级越高,级别越大优先级越小
不用死记硬背,翻表查看 使用括号()
NVIC优先级:从0开始的,级别(优先级标号)越小优先级越高,级别越大优先级越小
中断优先级分类:(设置中断优先级名词)
ARM中断优先级分类分为3个类型:
抢占优先级/占先优先级(占先):抢占优先级高的函数可以抢断抢占优先级低的函数
响应有效级/次级优先级(次级):如抢占优先级相同,则会比较响应优先级(不具有抢占特性)
自然优先级:厂家固定,如抢占优先级相同和响应优先级相同,则遵循厂家设定的自然优先级
总结:NVIC优先级比较顺序 抢占优先级 > 响应优先级 > 自然优先级
自然优先级:去哪里查 中文参考手册
NVIC优先级说明(事件说明例子)
事件 | 占先 | 次级 |
A | 1 | 6 |
B | 2 | 2 |
C | 2 | 4 |
遵循级别越小优先级越高的特点
事件A与事件B同时到来:先执行事件A
事件B与事件C同时到来:先执行事件B
- 如果事件C正在执行,事件B到来:
占先:可抢断正在执行的优先级低的任务
继续执行事件C,当事件C结束后才可以执行事件B
- 如果事件C正在执行,事件A到来:
事件A抢断事件C,事件A执行完之后再回到事件C执行
总结:通过设置抢占优先级和次级优先级决定紧急事件执行顺序,抢占优先级具有抢断低优先级任务特性,而次级优先级没有抢断特性
注意:在设置NVIC优先级时,抢占优先级和次级优先级都必须设置
4.1.4 NVIC优先级分配方式
ARM中断体系:
NVIC 支持由软件指定的优先级。 通过对中断优先级寄存器的 8 位 PRI_N 区执行写操作,来将中断的优先级指定为 0~255。(设置占先和次级优先级) 设置抢占和次级优先级
占先优先级和次级优先级公用一个PRI_N区域 PRI_N区域8bit 0 - 255
为了对具有大量中断的系统加强优先级控制, NVIC 支持优先级分组机制。
例子:分组设置为3,占先设置为4,次级设置3,求PRI_N写入值为多少?
PRIGROUP |= 3 << x;//设置优先级分组为3
占先 | 次级
0100 0011 --> 0100 0011 --> PRI_N填入值:0x43 67
配置方法:
- 决定分组,将分组写入值PRIGROUP区域里面
- 决定占先优先级和次级优先级,计算出PRI_N的写入值,将写入值写入到对应PRI_N区域里
思考:同一个程序里面可以设置多个分组吗?
不可以,一个程序只能有一个分组(主函数硬件初始化地方)
STM32FXX中断体系:(重点)
ST公司精简了ARM的分组机制,占先和次级的分配区域为PRI_N高四位
使用的是ST芯片,所以需要遵循ST公司精简过的分组机制
例子:设置分组为5,占先设置为2,次级设置为2,PRI_N写入值?
PRIGROUP |= 5 << x;
占先 次级
10 10 ---> PRI_N:10 10 --> 10 --> 0xa
精简ST芯片分组表:
写入值/分组号 | 占先位数 | 次级位数 | 占先取值范围 | 次级取值范围 |
3 | 4 | 0 | 0-15 | None |
4 | 3 | 1 | 0-7 | 0-1 |
5 | 2 | 2 | 0-3 | 0-3 |
6 | 1 | 3 | 0-1 | 0-7 |
7 | 0 | 4 | None | 0-15 |
例子:
占先级别值设置为 1 次级级别值设置为1 //分组如何选择 4、5、6
练习:
占先 级别值3 次级 级别值 2 //分组如何选择 5
占先 级别值4 次级 级别值1 //分组如何选择 4
占先 级别值1 次级 级别值 1 //分组如何选择 4、5、6
占先 级别值5 次级 级别值 2 //分组如何选择 none
公式:设置分组写入值 = 7 – 占先所占位数
4.1.5 NVIC相关配置函数
中断概述 ----> 中文参考手册
由于NVIC属于CM4内核级的外设,所有芯片厂家在寄存器上是完全相同的(配置方式,作用)。
所以ARM公司提供了一份通用的NVIC配置函数。我们只需要学会这些函数的使用方法和作用就可以配置 中断分组和优先级了,中断相关的配置函数在core_cm4.h 头文件中.
如何在工程中找到NVIC配置函数:搜索大法(CTRL + F)
①使用CTRL + F 进入搜索窗口
②点击在所有文件中查找
③在Find what输入NVIC,点击Find All
NVIC设置的步骤和设置函数说明:
1.NVIC里中断源优先级分组设置
设置分组(决定抢占优先级的位数)
函数:
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
函数名:NVIC_SetPriorityGrouping
函数功能:设置中断的优先级分组, 分配抢占和响应的位数
函数返回值:无
函数参数:uint32_t PriorityGroup // 分组写入值 3 4 5 6 7
注意:一个工程里只需要一个中断分组,所有中断源共用一个优先级分组
位置:通常放在主函数中的其他模块初始化函数上面
公式:优先级分组写入值 = 7 – 占先所占位数
练习:
抢占优先级设置为占2位 0 - 3
利用公式:分组号/写入值 = 7 – 需要设置的占先优先级位数
NVIC_SetPriorityGrouping(7 - 2);//设置优先级分组为第五组 占先:0-3 次级:0-3
2.计算优先级编码(计算PRI_N的值)
设置具体的抢占优先级和响应优先级级别值
函数:
uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)------core-cm4 1592
函数名:NVIC_EncodePriority
功能:将优先级分组的写入值、抢占优先级值、响应优先级值、计算成一个整型数据返回 //PRI_N编码值 0- 15
返回值:u32 //PRI_N编码值(PRI_N写入值) 0- 15
参 数:优先级分组(寄存器的 8~10 位 的写值) //写入值
抢占优先级值(抢占优先级值)
响应优先级值(响应优先级值)
注意:注意各优先级的值的范围
说明: 将优先级分组值,抢占级别值,响应级别值,计算成一个u32的数据,返回.
位置: 设置哪个中断源,就把此函数写在哪个中断源初始化函数中
练习:
设置抢占优先级为1,响应优先级为2
NVIC_SetPriorityGrouping(7 - 2);//设置优先级分组为第五组 占先:0-3 次级:0-3
u32 pri = NVIC_EncodePriority (5, 1, 2);
3.具体某个中断源的优先级设置
将刚才计算出来的优先级编码值,与具体某个中断源联系起来
函数:
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)-------------core-cm4 1550
函数名:NVIC_SetPriority
功能:设置具体某个中断源的优先级(编码) 0 - 15
返回值:无
参数:IRQn_Type IRQn // 中断源名字(中断源编号)
uint32_t priority //计算出来的中断优先级编码(PRI_N写入值)
如何找中断源:
每个中断源的结尾:IRQn
搜索:ctrl + F IRQn
练习:
将usart1中断,抢占优先级为1,响应优先级为2的中断
NVIC_SetPriorityGrouping(7 - 2);//设置优先级分组为第五组 占先:0-3 次级:0-3
u32 pri = NVIC_EncodePriority (5, 1, 2);
NVIC_SetPriority(USART1_IRQn, pri);
4.NVIC中断信号响应通道使能
NVIC模块响应片上外设中断源的开关 //使能NVIC响应通道
函数:
void NVIC_EnableIRQ(IRQn_Type IRQn)
函数名:NVIC_EnableIRQ
作 用:NVIC模块响应片上外设中断源的信号
返回值:无
参 数:IRQn_Type IRQn ----具体中断的编号(可以直接写名字)
练习:
将usart1中断,抢占优先级为1,响应优先级为2的中断
NVIC_SetPriorityGrouping(7 - 2);//设置优先级分组为第五组 占先:0-3 次级:0-3
u32 pri = NVIC_EncodePriority (5, 1, 2);
NVIC_SetPriority(USART1_IRQn, pri);
NVIC_EnableIRQ(USART1_IRQn);
将usart2中断,抢占优先级为3,响应优先级为1的中断
NVIC_SetPriorityGrouping(7 - 2);//设置优先级分组为第五组 占先:0-3 次级:0-3
u32 pri = NVIC_EncodePriority (5, 3, 1);
NVIC_SetPriority(USART2_IRQn, pri);
NVIC_EnableIRQ(USART2_IRQn);
4.4.6 中断服务函数
中断服务函数本质就是一个函数,此函数运行就是紧急事件
注意:中断服务函数有名字固定、格式固定;中断服务函数不需要调用
例子:USART1中断服务函数
格式:
void 中断服务函数名字(void)
{
//紧急事件
}
①中断服务函数如何查找
搜索大法:CTRL + F “IRQH”
②鼠标双击进入此函数的定义,复制粘贴此函数名字使用
USART1_IRQHandler
void USART1_IRQHandler(void)
{
}
总结:习惯使用CTRL + F
- CTRL + F :NVIC 找到配置优先级分组和优先级的函数
- CTRL + F:IRQn 找到中断源名称
- CTRL + F:IRQH 找到中断服务函数名称
注意:一定复制粘贴,千万手敲(很容易出错)
4.2 串口中断的使用
4.2.1串口接收和串口空闲中断
配置串口1接收中断和串口空闲中断
接收中断:接收到数据触发
①USART1初始化补充将CR1寄存器中RXNEIE位置1
②当RXNE标志位为1时触发中断,进入中断服务函数 ---> 接收数据
空闲中断:记录数据是否接收完成
①在USART1初始化中补充CR1寄存器IDLEIE位置1
代码步骤:
Int main()
{
//配置优先级分组
While(1)
{
}
}
①USART1初始化
{
USART1初始化
补充:CR1 RXNEIE 置一
CR1 IDLEIE置一
计算优先级编码置
配置USART1优先级
使能USART1中断源
}
②USART1中断服务函数
中断服务函数在工程中任意都能写,千万不要写错名字,建议复制粘贴
4.2.2程序设计
usart.c
#include "usart1.h"
/*
函数功能:USART1初始化
返回值:void
形参:u32 bps 波特率
作者:jerry
版本:1.0
函数说明:
USART1_TX --- PA9 --- 复用模式
USART1_RX --- PA10 --- 复用模式
低位寄存器:GPIOA->AFR[0]
高位寄存器:GPIOA->AFR[1]
*/
void Usart1_Init(u32 bps)
{
float USARTDIV = 0;
u32 DIV_M ,DIV_F;
//1、打开GPIOA、USART1时钟
RCC->AHB1ENR |= (1 << 0);
RCC->APB2ENR |= (1 << 4);//打开USART1时钟
//2、PA9、PA10初始化
GPIOA->MODER &=~(0xf << 18);//清零
GPIOA->MODER |= (0xa << 18);//将PA9和PA10配置为复用模式
//3、配置复用关系(IO映射)
GPIOA->AFR[1] |= 7 << 4; //将PA9复用到USART1
GPIOA->AFR[1] |= 7 << 8; //将PA10复用到USART1
//4、USART1初始化
USART1->CR1 &=~ (1 << 15); //16倍过采样:OVER8 = 0
USART1->CR1 &=~ (1 << 12); //数据长度为8bit
USART1->CR1 |= (1 << 3); //发送器使能
USART1->CR1 |= (1 << 2); //接收器使能
USART1->CR2 &=~(3 << 12); //停止位为1bit
//5、计算波特率
USARTDIV = 84000000 / 16.0 / bps;
DIV_M = (u32)USARTDIV; //整数部分
DIV_F = (USARTDIV-DIV_M) * 16; //小数部分
USART1->BRR |= DIV_M << 4 | DIV_F; //设置BRR寄存器
//6、使能接收中断和空闲中断
USART1->CR1 |= (1 << 5); //使能接收中断 RXNE
USART1->CR1 |= (1 << 4); //空闲接收中断 IDLE
//7、配置NVIC
//计算编码值 占先:1 次级:1
u32 pri=NVIC_EncodePriority (5, 1, 1);
//设置优先级
NVIC_SetPriority(USART1_IRQn, pri);
//使能中断源
NVIC_EnableIRQ(USART1_IRQn);
//使能USART1
USART1->CR1 |= (1 << 13); //使能USART1
}
/*
函数功能:USART1中断服务函数
注意:中断服务函数没有返回值,也没有形参
作者:jerry
版本:1.0
函数说明:CTRL + F IRQH
*/
U1_SRTUCT u1 = {0};
void USART1_IRQHandler(void)
{
u8 data = 0;
if(USART1->SR & (1 << 5))//判断进入接收中断 0-->1
{
//清除接收标志位
USART1->SR &=~ (1 << 5);
//执行接收中断的紧急事件
data = USART1->DR;
u1.buff[u1.len++] = data;
}
if(USART1->SR & (1 << 4))//判断进入空闲中断 0-->1
{
//清除空闲标志位
USART1->SR;
USART1->DR; //***调用过程就是读取过程
//空闲中断紧急事件 表示数据接收完成
u1.buff[u1.len] = '\0'; //在字符串结尾补'\0'
u1.len = 0; //接收下一次的数据
printf("%s",u1.buff); //回显接收到的数据
}
}
usart.h
#ifndef _USART1_H
#define _USART1_H
#include "stm32f4xx.h"
#include "stdio.h"
#include "string.h"
void Usart1_Init(u32 bps);
#endif
以上就是我对中断的一点拙见,由于我现在用的板子是STM32F407VET6,所以后面的代码和图片都是基于这块板子的。后面会继续更新相关我的STM32的学习之路。