目录
| 类型 | 典型代表 | 归属 | 核心配置逻辑 |
|---|---|---|---|
| 外设级中断 | EXTI、USART、TIM | STM32 外设模块 | 1. 配置外设自身参数(如 EXTI 的触发方式、USART 的中断源);2. 配置 NVIC(使能中断 + 设优先级) |
| 内核级中断 | SysTick、SVCall、PendSV | Cortex-M4 内核 | 1. 配置内核自身寄存器(如 SysTick 的 LOAD/VAL/CTRL);2. 配置 NVIC(仅设优先级,无需外设参数) |
按键中断

做开发一般不用子优先级,也就是4个bit抢占优先级,0个子优先级



目的:通过两个按键中断控制两个灯的亮灭
配置EXTI然后再配置NVIC
cpp
#include <stdint.h>
#include <string.h>
#include "stm32f4xx.h"
/**
* @brief 主函数:初始化硬件(LED、按键中断),程序入口
* @note 硬件对应关系:
* - LED1 → PB0(高电平灭,低电平亮)
* - LED2 → PB1(高电平灭,低电平亮)
* - KEY2 → PC4(下降沿触发中断,控制LED1翻转)
* - KEY3 → PC5(下降沿触发中断,控制LED2翻转)
* @retval int 程序返回值(嵌入式中无实际意义)
*/
int main(void)
{
// 1. 使能外设时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // 使能GPIOB时钟(LED1/LED2挂载在GPIOB)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); // 使能GPIOC时钟(KEY2/KEY3挂载在GPIOC)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // 使能SYSCFG时钟(EXTI中断必须依赖此时钟)
// 2. 配置NVIC优先级分组:分组4(仅抢占优先级有效,0-15级,子优先级无效)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// 3. 初始化LED引脚(PB0、PB1)为推挽输出模式
GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // 模式:输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 输出类型:推挽输出(适合驱动LED)
GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed; // 输出速度:中等速度(50MHz,LED无高速需求)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // 配置引脚:PB0(LED1)、PB1(LED2)
GPIO_Init(GPIOB, &GPIO_InitStruct); // 将配置写入GPIOB寄存器,使配置生效
GPIO_WriteBit(GPIOB, GPIO_Pin_0 | GPIO_Pin_1, Bit_SET); // 初始状态:LED1/LED2灭(高电平)
// 4. 初始化按键引脚(PC4、PC5)为输入模式
GPIO_StructInit(&GPIO_InitStruct); // 初始化GPIO结构体为默认值(避免随机值)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; // 模式:输入模式
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 上下拉:无上下拉(依赖硬件外部上拉/下拉)
GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed; // 输入模式下速度无实际作用,仅规范配置
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; // 配置引脚:PC4(KEY2)、PC5(KEY3)
GPIO_Init(GPIOC, &GPIO_InitStruct); // 将配置写入GPIOC寄存器,使配置生效
// 5. 将按键引脚绑定到EXTI中断线
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource4); // PC4 → EXTI4中断线
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource5); // PC5 → EXTI5中断线
// 6. 配置EXTI外部中断参数
EXTI_InitTypeDef EXTI_InitStruct; // 定义EXTI初始化结构体
EXTI_StructInit(&EXTI_InitStruct); // 初始化EXTI结构体为默认值
EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line5; // 中断线:EXTI4(KEY2)、EXTI5(KEY3)
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 模式:中断模式(非事件模式)
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 触发方式:下降沿触发(按键按下时电平从高→低)
EXTI_InitStruct.EXTI_LineCmd = ENABLE; // 使能该中断线
EXTI_Init(&EXTI_InitStruct); // 将配置写入EXTI寄存器,使配置生效
// 7. 配置NVIC(嵌套向量中断控制器),使能中断并设置优先级
NVIC_InitTypeDef NVIC_InitStruct; // 定义NVIC初始化结构体
memset(&NVIC_InitStruct, 0, sizeof(NVIC_InitStruct)); // 结构体清零(避免未初始化的随机值)
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 5; // 抢占优先级:5(0-15可选,数值越小优先级越高)
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级:0(分组4下无实际作用)
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道
// 配置KEY2对应的中断通道(EXTI4_IRQn)
NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_Init(&NVIC_InitStruct);
// 配置KEY3对应的中断通道(EXTI9_5_IRQn:EXTI5~EXTI9共用的中断通道)
NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_Init(&NVIC_InitStruct);
// 8. 主循环:程序常驻此处,等待中断触发
while(1)
{
}
}
/**
* @brief EXTI4中断服务函数(KEY2(PC4)触发)
* @note 功能:翻转LED1(PB0)的状态,执行后清除中断标志
*/
void EXTI4_IRQHandler(void)
{
GPIO_ToggleBits(GPIOB, GPIO_Pin_0); // 翻转PB0电平(LED1亮/灭切换)
EXTI_ClearITPendingBit(EXTI_Line4); // 清除EXTI4中断挂起标志(必须清除,否则会重复触发中断)
}
/**
* @brief EXTI9_5中断服务函数(KEY3(PC5)触发)
* @note 功能:翻转LED2(PB1)的状态,执行后清除中断标志
* @warning 该中断通道为EXTI5~EXTI9共用,原代码未判断具体中断源,快速按键易串灯(需加消抖+中断源判断)
*/
void EXTI9_5_IRQHandler(void)
{
GPIO_ToggleBits(GPIOB, GPIO_Pin_1); // 翻转PB1电平(LED2亮/灭切换)
EXTI_ClearITPendingBit(EXTI_Line5); // 清除EXTI5中断挂起标志(必须清除,否则会重复触发中断)
}
SYSTICK中断
目的:通过systick内核中断,实现灯500ms闪一次,不用像按键那样外部触发
配置systick,然后再配置NVIC,因为systick是内部中断,不需要像外部中断那样配置exti,exti是针对外部中断
SysTick->LOAD=SystemCoreClock/1000 -1; SystemCoreClock是内核主频,因为我用的芯片主频168MHz,其实啥主频都是这么写,SystemCoreClock如果是168MHz,也就是每秒能数 1.68 亿次(168000000Hz,1MHz=1000KHz=1000000Hz),数完也就是过了1s,1s/1000=1ms,减1的原因是(比如10到1,就正好10个,但是现在是10到0,最终到0,所以多了一次要减1),所以SysTick->LOAD=SystemCoreClock/1000 -1这个表示load减到0就过去了1ms。
SysTick 是向下递减计数器 :从
LOAD值开始,每 1 个时钟周期减 1,减到 0 就触发中断
cpp
#include <stdint.h>
#include <string.h>
#include "stm32f4xx.h"
#include "led.h"
int main(void)
{
//开启时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
//配置中断优先级组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//初始化GPIO 实现LED闪烁
led_init();
led_all_off();
//配置SYSTICK
SysTick->LOAD=SystemCoreClock/1000 -1; //1ms
SysTick->VAL=0; //当前计数器清0
//设定 SysTick 的时钟源、开启中断
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk ;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;//最终启动计数器
//配置SYSTICK中断优先级
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = (uint8_t)SysTick_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 5 ;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0 ;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
while(1)
{
}
}
void SysTick_Handler(void)
{
static bool led_state = false ;
static int count = 0 ;
if(++count >= 500)
{
count = 0;
led_state = !led_state;
led_set(1,led_state);
}
}
这种NVIC也可以
NVIC_SetPriority(SysTick_IRQn,5);等价于原来的 NVIC 结构体配置,只是更简洁
cpp
#include <stdint.h>
#include <string.h>
#include "stm32f4xx.h"
#include "led.h"
int main(void)
{
//开启时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
//配置中断优先级组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//初始化GPIO 实现LED闪烁
led_init();
led_all_off();
//配置SYSTICK
SysTick->LOAD=SystemCoreClock/1000 -1; //1ms
SysTick->VAL=0; //当前计数器清0
//设定 SysTick 的时钟源、开启中断
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk ;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;//最终启动计数器
NVIC_SetPriority(SysTick_IRQn,5);
while(1)
{
}
}
void SysTick_Handler(void)
{
static bool led_state = false ;
static int count = 0 ;
if(++count >= 500)
{
count = 0;
led_state = !led_state;
led_set(1,led_state);
}
}
串口中断
口中断的触发源是 USART 外设内部,而非 GPIO 引脚,USART 外设本身就内置了中断逻辑,当满足中断条件时,会直接向 NVIC 发送中断请求(对应 USART1_IRQn 通道),完全不需要 EXTI 做中转。不需要像按键中断那样写EXTI
下个这个串口中断,只能回显一个字符,多个字符要用到ringbuffer
cpp
#include <stdint.h>
#include <stdint.h>
#include <stdio.h>
#include "stm32f4xx.h"
volatile uint8_t rxdata = 0;
int main(void)
{
//串口用PA9 PA10
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//初始化串口
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_OType =GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置串口
USART_InitTypeDef USART_InitStruct;
USART_StructInit(&USART_InitStruct);
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No; //0校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStruct);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//使能串口
USART_Cmd(USART1,ENABLE);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 5;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStruct);
char msg[64];
while(1)
{
if(rxdata!=0)
{
int len=snprintf(msg, sizeof(msg), "receive : %c\r\n", (char)rxdata);
for(int i=0;i<len;i++)
{
while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));
USART_SendData(USART1,(uint16_t)msg[i]);
}
rxdata = 0;
}
}
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
rxdata =USART_ReceiveData(USART1);
}
}