STM32学习笔记(5_2)- EXTI外部中断代码

news/2024/4/29 18:22:13

无人问津也好,技不如人也罢,都应静下心来,去做该做的事。

最近在学STM32,所以也开贴记录一下主要内容,省的过目即忘。视频教程为江科大(改名江协科技),网站jiangxiekeji.com

本期介绍外部中断和中断系统,中断就是主程序执行时,发生了中断触发条件,当这些紧急事件处理完后,CPU继续执行程序。

外部中断的使用场景:外部驱动很快的突变信号

相关实验有对射式红外传感器计次和旋转编码器计次 

EXTI基本结构

主程序里查看和清除标志位:Flagstatus EXTI GetFlagstatus (uint32 t EXTI Line);
void EXTI ClearFlag(uint32 t EXTI Line);

中断程序里查看和清除标志位 :ITstatus EXTI GetITstatus (uint32 t EXTI Line);
void EXTI ClearITPendingBit (uint32 t EXTI Line);

对射式红外传感器计次

接线图

对射式红外传感器计次的DO为数字输出端,随意接一个GPIO,图上接到PB14。当挡光片或编码盘在对射式红外传感器的中间经过时,DO就会输出电平跳变的信号,进而触发PB14的中断,在中断函数里执行变量++的程序即可,然后在OLED上显示这个变量。

程序现象

操作步骤

首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。

1、配置RCC(打开GPIOB时钟)

2、配置AFIO时钟

EXTI和NVIC(内核外设)不需要开启时钟

	/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟

3、配置GPIO外设

选择端口为输入模式,具体选用哪种输入模式,需在STM32F10xxx参考手册-通用和复用功能I/O(GPIO和AFIO) 里查看

视频里采用输入上拉模式

	/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB14引脚初始化为上拉输入

4、配置AFIO外设

GPlO_PinRemapConfig(重映射的方式,新的状态):引脚重映射,本期不用此函数

GPIO_EXTILineConfig(GPIO PortSourceGPIOB,GPIO PinSource14):本期外部中断需用到的函数,调用此函数可配置AFIO的数据选择器,选择想要的中断引脚 

	/*AFIO选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚

 5、配置EXTI

在配置中断前,先指定中断的分组。然后用NVIC_Init函数初始化NVIC就行。

	/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line14;					//选择配置外部中断的14号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设

6、配置中断函数

中断向量表里以IRQHandler结尾的字符串就是中断函数的名字,中断函数的名字最好直接从启动文件复制过来。

/*** 函    数:EXTI15_10外部中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI15_10_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){CountSensor_Count ++;					//计数值自增一次}EXTI_ClearITPendingBit(EXTI_Line14);		//清除外部中断14号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

7、需要一个数字统计中断触发次数

 在模块前定义一个变量CountSensor_Count,在中断函数里写CountSensor_Count++,最后还需在模块里写一个get函数,返回这个变量CountSensor_Count

uint16_t CountSensor_Count;				//全局变量,用于计数/*** 函    数:获取计数传感器的计数值* 参    数:无* 返 回 值:计数值,范围:0~65535*/
uint16_t CountSensor_Get(void)
{return CountSensor_Count;
}

完整代码展示 

和之前的操作一样,先在Hardware下新建一个文件(Countsensor)的.c、.h文件,把对射式红外传感器的驱动函数封装起来。

main函数
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{/*模块初始化*/OLED_Init();			//OLED初始化CountSensor_Init();		//计数传感器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Count:");	//1行1列显示字符串Count:while (1){OLED_ShowNum(1, 7, CountSensor_Get(), 5);		//OLED不断刷新显示CountSensor_Get的返回值}
}
 CountSensor.h函数
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_Hvoid CountSensor_Init(void);
uint16_t CountSensor_Get(void);#endif

 CountSensor.s函数

#include "stm32f10x.h"                  // Device headeruint16_t CountSensor_Count;				//全局变量,用于计数/*** 函    数:计数传感器初始化* 参    数:无* 返 回 值:无*/
void CountSensor_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB14引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line14;					//选择配置外部中断的14号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}/*** 函    数:获取计数传感器的计数值* 参    数:无* 返 回 值:计数值,范围:0~65535*/
uint16_t CountSensor_Get(void)
{return CountSensor_Count;
}/*** 函    数:EXTI15_10外部中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI15_10_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){CountSensor_Count ++;					//计数值自增一次}EXTI_ClearITPendingBit(EXTI_Line14);		//清除外部中断14号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

旋转编码器计次 

只有在B相下降沿和A相低电平时才识别为正转

接线图

操作步骤

首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。

具体代码和对射式红外传感器大同小异,就直接展示完整代码

完整代码展示 

和之前的操作一样,先在Hardware下新建一个文件(Encoder)的.c、.h文件,把旋转编码器的驱动函数封装起来。

 main函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"int16_t Num;			//定义待被旋转编码器调节的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化Encoder_Init();		//旋转编码器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:while (1){Num += Encoder_Get();				//获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上OLED_ShowSignedNum(1, 5, Num, 5);	//显示Num}
}

Encoder.h函数

#ifndef __ENCODER_H
#define __ENCODER_Hvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif

Encoder.c函数

#include "stm32f10x.h"                  // Device headerint16_t Encoder_Count;					//全局变量,用于计数旋转编码器的增量值/*** 函    数:旋转编码器初始化* 参    数:无* 返 回 值:无*/
void Encoder_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//选择配置NVIC的EXTI0线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}/*** 函    数:旋转编码器获取增量值* 参    数:无* 返 回 值:自上此调用此函数后,旋转编码器的增量值*/
int16_t Encoder_Get(void)
{/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*//*在这里,也可以直接返回Encoder_Count但这样就不是获取增量值的操作方法了也可以实现功能,只是思路不一样*/int16_t Temp;Temp = Encoder_Count;Encoder_Count = 0;return Temp;
}/*** 函    数:EXTI0外部中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI0_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)		//PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向{Encoder_Count --;					//此方向定义为反转,计数变量自减}}EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断0号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}/*** 函    数:EXTI1外部中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI1_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line1) == SET)		//判断是否是外部中断1号线触发的中断{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)		//PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向{Encoder_Count ++;					//此方向定义为正转,计数变量自增}}EXTI_ClearITPendingBit(EXTI_Line1);			//清除外部中断1号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}

中断使用建议

1、在中断里不要执行耗时过长的代码,比如在中断里执行delay函数,中断为了执行突发的事情。

2、最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件。比如OLED函数,如果你既在主程序里调用OLED,在中断里调用OLED,OLED就会显示错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.tangninghui.cn.cn/item-12151.htm

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

分布式异步任务框架celery

Celery介绍 github地址:GitHub - celery/celery: Distributed Task Queue (development branch) 文档地址:Celery - Distributed Task Queue — Celery 5.3.6 documentation 1.1 Celery是什么 celery时一个灵活且可靠的处理大量消息的分布式系统&…

基于UDP的网络聊天室

1、如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息 2、如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息 3、如果有人下线&#xff0c;其他用户可以收到这个人的下线信息 4、服务器可以发送系统信息 服务器 #include<myhead.h>typedef s…

Linux 进程信号:产生信号

目录 一、通过终端按键产生信号 1、signal()函数 2、核心转储 3、ulmit命令 二、调用系统函数向进程发信号 1、kill()函数 2、raise()函数 3、abort()函数 三、发送信号的过程 读端关闭、写端继续写入的情况 如何理解软件条件给进程发送信号: 四、软件条件产生信…

几个常用的控件(2)

目录 一、单选按钮Radiobutton和RadioButtonList 1、Radiobutton控件 &#xff08;1&#xff09;button控制方式 &#xff08;2&#xff09;Radiobutton控制方式 2、RadiobuttonList控件 二、列表框ListBox和下拉列表DropdownList 1、ListBox 2、DropdownList 三、面板…

第八章贪心算法——理论基础,分发饼干题目

目录 概念 什么时候使用 题目举例 分发饼干 力扣题号&#xff1b;455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 题目描述 示例 1: 示例 2: 解法一&#xff1a;排序暴力 解法二&#xff1a;贪心 思路 代码实现 总结 概念 贪心算法是一种在每一步选择中都采…

什么算法可以进行小语种的OCR?

对于小语种的OCR识别&#xff0c;可以采用以下算法和技术&#xff1a; 1. 迁移学习&#xff08;Transfer Learning&#xff09;&#xff1a;使用在大语种上预训练好的OCR模型&#xff0c;并通过迁移学习的方式对小语种进行微调。这样可以利用大语种上已有的丰富数据和知识&…