【嵌入式学习-STM32F103-EXTI外部中断】

目录

1、EXTI基础知识补充

2、编程要点

3、对射式红外传感器计次完整代码(注释)

4、旋转编码器计次完整代码(注释)

参考江科大32单片机教学视频!
【嵌入式学习-STM32F103-EXTI外部中断】_第1张图片
旋转编码器工作原理
https://www.bilibili.com/video/BV1JJ411s7p3/?spm_id_from=333.337.search-card.all.click&vd_source=a302e304b0aa60652c390b422ff81ab8

1、EXTI基础知识补充

【嵌入式学习-STM32F103-EXTI外部中断】_第2张图片
对于旋转编码器,正向旋转时,A、B相输出的波形为下图(上),反向旋转时,A、B相输出的波形为下图(下)。
1、如果把一相的的下降沿用作触发中断,在中断时刻读取另一相的电平,此时正转就是高电平,反转就是低电平,这样就能区分旋转方向。
(小瑕疵:正转时,由于A相先出现下降沿,旋转编码器刚开始动,就进入中断,而反转时,A相后出现下降沿,只有转到位了,才能进入中断)
【嵌入式学习-STM32F103-EXTI外部中断】_第3张图片
2、改进方案:只有在B相下降沿和A相低电平时,才判断为正转,在A相下降沿和B相低电平时,才判断为反转。这样能够保证正转和反转都是转到位了,才执行数字的加减操作。
【嵌入式学习-STM32F103-EXTI外部中断】_第4张图片

上图对应的中断服务函数为中断0服务函数和中断1服务函数

//中断0服务函数
void EXTI0_IRQHandler(void)
{
	//检查函数标志位
	if (EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		/* 如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动 */
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
			{
				Encoder_Count --;
			}
		}
		//清除中断标志位
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}
//中断1服务函数
void EXTI1_IRQHandler(void)
{
	//检查函数标志位
	if (EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		/* 如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动 */
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
			{
				Encoder_Count ++;
			}
		}
		//清除中断标志位
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}

2、编程要点

1)初始化用来产生中断的GPIO
2)初始化EXTI
3)配置NVIC
4)编写中断服务函数

3、对射式红外传感器计次完整代码(注释)

接线图

【嵌入式学习-STM32F103-EXTI外部中断】_第5张图片

注意

如果你想在主程序力查看和清除标志位,就用(一般的读写标志位)

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);

CountSensor.c

#include "stm32f10x.h"                  // Device header

//我们想要一个数字来统计中断触发的次数
uint16_t CountSensor_Count;

//设计的外设包括RCC GPIO AFIO EXTI NVIC
void CountSensor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	//EXTI(原因不详) 和 NVIC(内核外设都不需要开启时钟)的时钟一直都打开,不需要我们开启时钟
	
	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);
	
	/* 与AFIO相关的重要的函数配置 */
	//GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);  //作用是可用来进行引脚重映射,第一个参数可以选择重映射的方式 第二个参数是新的状态
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);           //配置AFIO的数据选择器,来选择我们想要的中断引脚
	
	//将EXTI的第14个线路配置为中断模式,下降沿触发,开启中断,这样PB14的电平信号就能够通过EXTI通向下一级的NVIC
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;  //指定我们需要配置的中断线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;    //指定选择的中断线的新状态
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;     //中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  //下降沿触发
	EXTI_Init(&EXTI_InitStructure);

	//配置NVIC
	//中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //这个分组方式整个芯片只能用一种,因此这个分组的代码整个工程只需要执行一次即可
	//中断初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //优先级是在多个中断源同时申请,产生拥挤时才有作用,这只有一个中断,随机匹配即可
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
}

//返回变量CountSensor_Count
uint16_t CountSensor_Get(void)
{
	return CountSensor_Count;
} 

//中断函数都是无参无返回值
//中断函数不需要调用,它是自动执行的
void EXTI15_10_IRQHandler(void)
{
	//中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10到15都能进来,因此需要判断是否是EXTI14 
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		//如果是,我们就执行中断程序
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			CountSensor_Count ++;
		}
		//中断程序结束后,一定要再调用一下清除中断标志位的函数,因为只要中断标志位置1,程序就会跳转到中断函数
		//如果你不清除中断标志位,那他就会一直申请中断,程序就会不断响应中断,执行中断函数,程序就会卡死在中断函数里
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

CountSensor.h

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
uint16_t CountSensor_Get(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	OLED_Init();
	CountSensor_Init();
	
	OLED_ShowString(1, 1, "Count:");
	
	while (1)
	{
		//显示计次的数据
		OLED_ShowNum(1, 7, CountSensor_Get(), 5);
	}
}

4、旋转编码器计次完整代码(注释)

接线图

【嵌入式学习-STM32F103-EXTI外部中断】_第6张图片

Encoder.c

 #include "stm32f10x.h"                  // Device header

//由于需要正反转,因此定义一个带符号的变量
int16_t Encoder_Count;

//初始化PB0和PB1两个GPIO口的外部中断 
void Encoder_Init(void)
{ //初始化GPIO和AFIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	//初始化GPIO,PB0和PB1
	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);
	//AFIO中断引脚选择
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);  //将第0个线路拨到GPIOB上
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);  //将第1个线路拨到GPIOB上
	//指定中断线,同时将第0条线路和第1条线路初始化为中断模式,下降沿触发
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;  
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStructure);
	//中断分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//中断优先级,对两个通道分别设置优先级
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
}

//定义函数,返回变量,在这里不直接返回Encoder_Count变量,而是返回每次调用这个get函数后的Count变化值
//因为用count返回的话,return直接跳出函数,count就无法清零
int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = Encoder_Count;
	Encoder_Count = 0;
	return Temp;
}

//中断0服务函数
void EXTI0_IRQHandler(void)
{
	//检查函数标志位
	if (EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		/* 如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动 */
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
			{
				Encoder_Count --;
			}
		}
		//清除中断标志位
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}
//中断1服务函数
void EXTI1_IRQHandler(void)
{
	//检查函数标志位
	if (EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		/* 如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动 */
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
			{
				Encoder_Count ++;
			}
		}
		//清除中断标志位
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}


/* 中断编程的建议
1、在中断函数里,最好不要执行耗时过长的代码,中断函数要简短快速,别刚进中断就执行一个Delay多少毫秒的代码
因为中断时处理突发的事情,如果你为了一个突发的事情呆在中断里不出来,主程序就会收到严重的阻塞
2、最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件,如OLED显示函数,如果既在主函数里调用OLED函数
又在中断里调用OLED,OLED就会显示错误
为了避免问题,最好不要在主程序和中断程序里操作可能产生冲突的硬件
在实现功能的时候,可以在中断里操作变量或者标志位
当中断返回时,我在对这个变量进行显示和操作
这样既能保证中断函数的简短快速,又能保证不产生冲突的硬件操作


EXTI_GetITStatus() 函数用于获取指定的中断标志位的状态,返回值为标志位的状态,即是否被触发。如果返回值为 SET,则表示对应的中断已经被触发;如果返回值为 RESET,则表示对应的中断还未被触发。

EXTI_ClearITPendingBit() 函数用于清除指定的中断标志位。当某个中断被触发时,对应的中断标志位会被置位。在处理完中断后,需要通过调用 EXTI_ClearITPendingBit() 函数来清除该标志位,否则会一直保持置位状态,导致下一次中断无法正常触发。

因此,这两个函数常常一起使用,以确保外部中断能够正常工作。
*/




Encoder.h

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"

int16_t Num;

int main(void)
{
	OLED_Init();
	Encoder_Init();
	
	OLED_ShowString(1, 1, "Num:");
	
	while (1)
	{
		//因为Get函数返回的是调用这个函数的间隔里旋转编码器产生的正负脉冲数,所以返回值直接+=给Num,就能够对Num进行加减操作
		Num += Encoder_Get();
		OLED_ShowSignedNum(1, 5, Num, 5);
	}
}

你可能感兴趣的:(嵌入式学习-STM32,stm32,单片机,学习)