【STM32】中断编程入门指南
目录
一、中断
1.中断处理流程
2.STM32中断和异常向量表
二、STM32F103按键中断点亮LED灯
1.Keil工程建立
2.代码解释
(1)GPIO初始化配置
(2)开启I/O端口的时钟和复用时钟
(3)设置I/O引脚与中断线路的映射关系
(4)初始化EXTI,配置EXTI相关参数并使能
(5)初始化NVIC,配置NVIC参数并使能
(6)编写中断服务程序
3.实物结果
三、采用串口中断方式重写上篇博客的串口通信任务
1.当stm32接收到1个字符“s”时,停止持续发送“hello windows!”; 当接收到1个字符“t”时,持续发送“hello windows!”
(1)代码实现
(2)代码解释
(3)实物连接和程序烧录
(4)实验结果
2.当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”
(1)代码实现
(2)实验结果
参考博客:
一、中断
计算机在执行程序过程中,当出现异常情况(断电等)或特殊请求(数据传输等)时,计算机暂停现行程序的运行,转向对这些异常情况或特殊请求进行处理,处理完毕后再返回到现行程序的中断处,继续执行原程序,这就是“中断”。
1.中断处理流程
中断请求、中断响应、中断服务和中断返回
单重中断的中断处理流程
多重中断的中断处理流程
2.STM32中断和异常向量表
中断向量表:当发生了异常或中断,内核要想响应这些异常或中断,就需要知道这些异常或中断的服务程序的入口地址,再由入口地址找到相应的中断服务程序
入口地址一般存放在程序存储器(ROM),默认情况下,Cortex-M3内核的中断向量表从零地址处开始,且每个向量占用4个字节。
二、STM32F103按键中断点亮LED灯
1.Keil工程建立
添加文件,添加的文件可与上一个博客中串口通信的文件相同,只是需要稍微改一下。具体改动如下:
将LED.c文件的代码更换为如下代码:
#include "stm32f10x.h"
uint16_t LED_Count;
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
EXTI_InitStruct.EXTI_Line = EXTI_Line14;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14)== SET)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_RESET)
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_SET)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
将LED.h的代码改为:
#ifndef _LED_H
#define _LED_H
void LED_Init(void);
#endif
将main.c的代码改为:
#include "stm32f10x.h"
#include "LED.h"
int main(void)
{
LED_Init();
while(1)
{
}
}
2.代码解释
下面我们介绍此次代码中EXTI标准外设库中断配置的步骤
(1)GPIO初始化配置
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
初始化I/O口为浮空输入模式
(2)开启I/O端口的时钟和复用时钟
GPIO用作EXTI外部中断功能引脚,必须开启复用功能,打开相应引脚的AFIO时钟,通过调用RCC_APB2PeriphClockCmd()函数实现
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
(3)设置I/O引脚与中断线路的映射关系
GPIO引脚作为中断功能引脚用来触发外部中断时,需将GPIO的引脚与相应的中断线关联在一起,使用库函数GPIO_EXTILineConfig来实现这种映射关系。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
(4)初始化EXTI,配置EXTI相关参数并使能
EXTI_InitStruct.EXTI_Line = EXTI_Line14;//设置外部中断线14中断
EXTI_InitStruct.EXTI_LineCmd = ENABLE; //使能该外部中断线
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//设置为中断模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//设置为下降沿触发
EXTI_Init(&EXTI_InitStruct);
(5)初始化NVIC,配置NVIC参数并使能
NVIC相关的配置主要包括配置中断优先级的中断分组,确定各具体中断的抢占优先级和响应优先级的大小。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
(6)编写中断服务程序
中断服务程序主要包括检测中断线路的状态、中断处理的内容和清除相关的中断
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14)== SET)//判断某个线上的中断是否发生
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_RESET)
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
}
else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_SET)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
}
EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志
}
}
3.实物结果
三、采用串口中断方式重写上篇博客的串口通信任务
1.当stm32接收到1个字符“s”时,停止持续发送“hello windows!”; 当接收到1个字符“t”时,持续发送“hello windows!”
Keil工程的建立和上个博客中串口通信的工程建立一致,添加的文件夹也一致。
(1)代码实现
将main.c改为:
#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
volatile uint8_t send_enabled = 0; // 全局变量,控制发送行为
void USART_Configuration(void) {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 打开 GPIO 与 USART 端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 配置 USART1 Tx (PA.09) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART1 Rx (PA.10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART 参数
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能 USART
USART_Cmd(USART1, ENABLE);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 配置 NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char data = USART_ReceiveData(USART1);
if(data == 's') { // 接收到 's' 停止发送
send_enabled = 0;
} else if (data == 't') { // 接收到 't' 开始发送
send_enabled = 1;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void Delay(__IO uint32_t nCount) {
for(; nCount != 0; nCount--);
}
int main(void) {
SystemInit();
USART_Configuration();
char *str = "hello windows!\r\n";
while(1) {
if(send_enabled) {
for(uint32_t i = 0; i < strlen(str); i++) {
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, str[i]);
}
}
Delay(5000000);
}
}
(2)代码解释
先通过函数SystemInit()进行系统时钟的初始化。
然后通过函数USART_Configuration()进行串口的配置和一系列设置。
根据接收到的字符来改变send_enabled的值,当接收字符't'时,send_enabled值为1,当接收字符's'时send_enabled值为0。在main函数中判断send_enabled的值。若为1则for循环输出字符串hello windows!。
Delay()用于延时,控制字符串发送速度。
(3)实物连接和程序烧录
(4)实验结果
2.当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”
(1)代码实现
将main.c的代码改为:
#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
#define BUFFER_SIZE 100
volatile char buffer[BUFFER_SIZE];
volatile int buffer_index = 0;
volatile int send_enabled = 0;
void Delay(__IO uint32_t nCount) {
for (; nCount != 0; nCount--);
}
void USART_Configuration(void) {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// USART Tx (PA.09) 配置为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART Rx (PA.10) 配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启接收中断
USART_Cmd(USART1, ENABLE);
}
void NVIC_Configuration(void) {
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char data = (char)USART_ReceiveData(USART1);
if (buffer_index < BUFFER_SIZE - 1) {
buffer[buffer_index++] = data;
buffer[buffer_index] = '\0'; // 保持字符串结尾
char* temp_buffer = (char*)buffer; // 创建一个非 volatile 指针
if (strstr(temp_buffer, "stop stm32!") != NULL) {
send_enabled = 0;
buffer_index = 0; // 清空缓冲区
} else if (strstr(temp_buffer, "go stm32!") != NULL) {
send_enabled = 1;
buffer_index = 0; // 清空缓冲区
}
}
}
}
int main(void) {
SystemInit();
USART_Configuration();
NVIC_Configuration();
char *str = "hello windows!\r\n";
while (1) {
if (send_enabled) {
for (uint32_t i = 0; i < strlen(str); i++) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, str[i]);
}
}
Delay(5000000);
}
}
(2)实验结果
参考博客:
STM32以中断的方式点亮LED小灯(标准库)_stm32按键中断控制led灯-CSDN博客
第12周–中断编程入门-CSDN博客
作者:肯德基疯狂星期四-V我50