STM32(标准库)自学笔记——中断
STM32(标准库)自学笔记——中断
本文是一篇关于STM32微控制器中断机制的学习笔记,详细介绍了中断的基本概念、优先级设置方法以及一个具体的串口中断例程。通过这篇文章,读者可以深入了解STM32的中断机制,并掌握如何在实际项目中应用中断功能。
一、中断的概念
中断发生时,常规程序会暂停执行,单片机将调用中断响应函数。中断响应函数处理完后,程序将继续执行常规程序。
以串口调试助手控制板载LED的闪烁速度为例,实现过程分为两步:
- 闪灯程序:点亮LED一段时间,再熄灭LED一段时间(点亮+延迟+熄灭+延迟)。
- 串口数据接收程序:检测RxNE标志位是否接收到数据,调用函数读取数据,并根据数据改变闪烁速度。
int main(void)
{
while(1)
{
GPIO_WriteBit(GPIOx, GPIO_Pin_xx, Bit_SET); //点亮LED
delay(...); //延时一段时间
GPIO_WriteBit(GPIOx, GPIO_Pin_xx, Bit_RESET); //熄灭LED
delay(...); //延时一段时间
}
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) //判断是否接收到数据
{
uint8_t byteRcvd = USART_ReceiveData(USART1); //读取接收到的数据
}
}
×:如果采用上述合并程序的方式,需要等到亮灯和灭灯(一个周期)后才读取数据。假设串口速率为115200,一个字节包含起始位+数据位+停止位(共10位),则每秒可以传输11520个字节。每个字节的接收时间约为0.1ms,远小于延迟周期时间(假设为亮100ms,灭100ms,总200ms),因此中间字节会丢失。所以这种实现方式不可行。
为了解决这个问题,可以使用中断机制。将数据读取写在中断服务程序中,这样可以输入数据就读取数据,保证没有遗漏。
二、中断优先级
中断优先级由四个位表示,分为五个分组,由抢占优先级和子优先级组成。以下是不同分组的中断优先级表示方式:
中断嵌套:更高优先级的中断可以打断正在执行的中断。例如,普通病人在看病时突然来了急症病人,会优先处理急症病人再处理普通病人。
条件:抢占优先级更高的中断优先级更高,数字越小优先级越高。下图展示了以中断优先级为分组2时,1与2、3、4的执行顺序。
中断排队:优先级相仿时,等待前一个中断执行完再处理新中断,不打断当前中断执行。优先级高的排在前面,优先级相同则先来后到。
三、串口中断例程
配置RxNE标志位来产生中断。其中黑色月牙型是逻辑或门,标志位共用中断,只要其中一个为1则触发了中断。标志位和中断之间的开关用于中断屏蔽,断开则不会触发中断,这里需要把RxNE这的开关也打开。
闭合该开关需要使用到的函数:
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
参数说明:
- USARTx:串口名称
- USART_IT:标志位名称,例如USART_IT_TXE,USART_IT_RxNE
- NewState:开关状态,ENABLE—打开,DISABLE—关闭
NVIC模块(无需配置时钟,因为内核的时钟始终开启):
- 配置中断优先级分组,配置抢占优先级和子优先级的位
- 配置四个比特位位的中断优先级,代表优先级的紧急程度
- 闭合开关,中断才能使能
配置分组需要用到的编程接口,一般写在程序的一开始,即main后:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); //设置NVIC中断优先级分组函数
中断初始化是串口初始化的一部分,即写在串口初始化中。其中在设置抢占优先级时,由于例程所给为分组2,即抢占优先级占了2bit,有0011可设置,即对应十进制的03,所以所设值(对NVIC_IRQChannelPreemptionPriority的设置)不可超出这个范围,子优先级同理。参考代码如下:
NVIC_InitTypeDef NVIC_InitStruct; //定义NVIC初始化结构体变量
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //USART1中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStruct); //初始化NVIC
全部代码参考如下,编写思路:
- 编写闪灯代码
- 初始化串口(串口和串口引脚初始化)
- RxNE标志位触发中断
- 配置NVIC模块
- 编写中断响应函数
#include "Delay.h" //需要注意添加的头文件
void App_OnBoardLED_Init(void);
void App_USART1_Init(void);
uint32_t blinkInterval = 1000; //定义LED闪烁的时间间隔
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断优先级分组
App_OnBoardLED_Init(); //初始化板载LED的调用
App_USART1_Init(); //初始化串口1的调用
while(1)
{
GPIO_WriteBit(GPIOx, GPIO_Pin_xx, Bit_SET); //点亮LED
Delay(blinkInterval); //延时一段时间
GPIO_WriteBit(GPIOx, GPIO_Pin_xx, Bit_RESET); //熄灭LED
Delay(blinkInterval); //延时一段时间
}
}
void USART1_IRQHandler(void) //串口1中断函数,在startup文件中查找名称
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是RxNE触发的中断
{
uint8_t byteRcvd = USART_ReceiveData(USART1); //定义接收到的数据
if (byteRcvd == '0') //如果接收到的数据为0
{
blinkInterval = 1000; //LED闪烁的时间间隔为1s
}
else if (byteRcvd == '1') //如果接收到的数据为1
{
blinkInterval = 200; //LED闪烁的时间间隔为200ms
}
else if (byteRcvd == '2') //如果接收到的数据为2
{
blinkInterval = 50; //LED闪烁的时间间隔为50ms
}
}
}
void App_OnBoardLED_Init(void) //初始化板载LED
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); //开启GPIOx的时钟
GPIO_InitTypeDef GPIO_InitStruct; //定义GPIO初始化结构体变量
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_xx; //板载LED对应的引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //2MHz最大速度
GPIO_Init(GPIOx, &GPIO_InitStruct); //初始化GPIOx
}
void App_USART1_Init(void) //初始化串口1
{
GPIO_InitTypeDef GPIO_InitStruct; //定义GPIO初始化结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); //开启GPIOx的时钟
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_xx; //USART1的引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //10MHz最大速度
GPIO_Init(GPIOx, &GPIO_InitStruct); //初始化GPIOx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); //开启GPIOx的时钟
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_xx; //USART1的引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //输入上拉模式
GPIO_Init(GPIOx, &GPIO_InitStruct); //初始化GPIOx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
USART_InitTypeDef USART_InitStruct; //定义USART初始化结构体变量
USART_InitStruct.USART_BaudRate = 115200; //波特率115200
USART_InitStruct.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_InitStruct.USART_StopBits = USART_StopBits_1; //1位停止位
USART_InitStruct.USART_Parity = USART_Parity_No; //无校验
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送模式
USART_Init(USART1, &USART_InitStruct); //初始化USART1
USART_Cmd(USART1, ENABLE); //使能USART1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //配置中断
NVIC_InitTypeDef NVIC_InitStruct; //定义NVIC初始化结构体变量
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //USART1中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStruct); //初始化NVIC
}