嵌入式-stm32-江科大-EXTI外部中断

文章目录

    • 一:EXTI外部中断(external interrupt)
      • 1.1 STM32 中断系统
      • 1.2 STM32外部中断EXTI
      • 1.3 实验:对射式红外传感器计次
        • 1.31 编程感想
      • 1.4 实验:旋转编码器计次
        • 1.41 编程感想
      • 道友:没有永久的巅峰也没有永远的低谷,真正的强大不是忘记而是接受,接受世事无常、接受孤独挫败、接受突如其来的无力感、接受自己的不完美、接受困惑不安的焦虑和遗憾。

一:EXTI外部中断(external interrupt)

1.1 STM32 中断系统

在这里插入图片描述

中断是指在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续执行,当中断发生时是由硬件自动调用中断函数执行的,期间编译器会保护现场最后还原现场。使得中断系统极大程度地提高程序的效率,就像是给自己定闹钟,可以不用担心错过时间而可以安心睡觉,在这个过程中,有如下概念:

  • 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
  • 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

stm32的F1系列总共有68个可屏蔽中断通道(中断源),包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。所有的中断使用嵌套向量中断控制器NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。具体到某一个型号的芯片可能不会有这多中断,具体需要查看的芯片手册。下面是手册中的中断向量表节选:
在这里插入图片描述

  • 地址(最后一列):存储中断地址,这个地址列表也称为中断向量表。因为程序中的中断函数地址由编译器来分配,所以中断函数地址不固定。但由于硬件的限制,中断跳转只能跳转到固定的地址执行程序。所以为了让硬件能跳转到(一个地址不固定的)中断函数里,就需要在内存中定义一个固定的地址列表。当中断发生后,首先跳转到这个固定的地址列表,编译器会在这个固定的位置加上一条跳转到中断函数的代码,于是中断跳转就可以跳转到任意位置了。中断地址列表就是中断向量表,相当于是中断跳转的跳板。C语言编程无需关注中断向量表,汇编语言需要

在这里插入图片描述

上图给出了嵌套向量中断控制器NVIC的基本结构示意图。在stm32中,NVIC用于统一管理中断和分配中断优先级,属于内核外设,是CPU的小助手,可以让CPU专注于运算。从上图可以看出:

  • NVIC有很多输入口,每个都代表一个中断线路,如EXTI、TIM、ADC等。
  • 每个中断线路上的斜杠n表示n条线,因为一个外设可能会同时占用多个中断通道。
  • NVIC只有一个输出口,通过中断优先级确定中断执行的顺序。

NVIC的中断优先级由优先级寄存器的4位二进制(十进制0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低(4-n)位的响应优先级。
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。这个中断号就是指中断向量表的第二列“优先级”。

用医院的叫号系统来举例子。假设医生正在给某个病人看病,外面还有很多病人排队:

  1. 新来的病人 抢占优先级高就相当于直接进屋打断医生,给自己看病。
  2. 新来的病人 响应优先级高就相当于不打扰医生,但直接插队,排在队伍的第一个。

下表是NVIC优先级的分组方式

分组方式抢占优先级响应优先级
分组0(n = 0)0位,取值为04位,取值为0~15
分组1(n = 1)1位,取值为0~13位,取值为0~7
分组2(n = 2)2位,取值为0~32位,取值为0~3
分组3(n = 3)3位,取值0~ 71位,取值为0~ 1
分组4(n = 4)4位,取值为0~150位,取值为0

注:NVIC是内核外设,更多关于NVIC的介绍参考“STM32F10xxx Cortex-M3编程手册”。 NVIC

  • 中断分组的配置寄存器,在SCB_AIRCR中,PRIGROUP这三位就是用于配置中断分组的。

1.2 STM32外部中断EXTI

下图是外部中断向量表片选
在这里插入图片描述

中断系统是管理和执行中断的逻辑结构,外部中断是众多能产生中断的外设之一,而EXTI就是其中之一(比如USART、I2C,参考上面的NVIC基本结构),上图给出了外部中断向量表。EXTI(external interrupt)外部中断可以监测指定的GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使得CPU执行EXTI对应的中断程序。

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发(上升沿就是低电平到高电平触发)
  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(比如gpioa_Pin1与gpiob_pin1不能同时触发中断)。
  • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒,共20个。

注:后面这四个功能是为了实现一些特殊的功能,比如想实现某个时间让stm32退出停止模式,由于外部中断可以在低功耗模式的停止模式下唤醒stm32,就可以在GPIO口上连接一个RTC时钟作为外部中断。

  • 触发响应方式:中断响应/事件响应。
  • 注意:
    中断响应就是正常的中断流程,申请中断让CPU执行中断函数;
  • 事件响应就是外部中断发生时,不把外部信号给CPU,而是选择触发一个事件,将这个信号通向其他外设,来触发其他外设的操作,可以实现外设之间的联合工作(中介分包)。

下图是EXTI的基本结构
在这里插入图片描述Alternate Fuction I/O:数据选择器(交替作用)
记形状:梯形(多个输入一个输出)

  • 最左侧:GPIO口的外设,每个外设都有16个引脚。
  • AFIO中断引脚选择:本质上就是数据选择器,从前面16*n 个引脚中选择16根端口号不重复的引脚出来,连接到后面的EXTI通道中。在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择。下面是中断引脚选择的AFIO示意图:
    在这里插入图片描述
  • PVD、RTC、USB、ETH:四个特殊功能的外设。
  • EXTI 边沿检测及控制:20个输入通道、两类输出。一类输出到NVIC中,并且将这20路输出的9、10 路外部中断合并在一起以节省通道;另一类输出到其他外设,直接就是20路输出。
    注:上面这个EXTI的基本结构也是编写代码时的主要参考图!

下图是EXTI 框图(方向为从左到右)-stm32F10系列参考手册
在这里插入图片描述

上图给出了参考手册中的EXTI框图。基本逻辑与“EXTI的基本结构”中所述相同,另外还有一些细节:

  • 边沿检测电路+软件中断事件寄存器:这个几个进行或门输出,便可以实现“上升沿/下降沿/双边沿/软件触发”这四种触发方式。
  • 请求挂起寄存器:相当于一个中断标志位,通过读取该寄存器可以判断是哪个通道触发的中断。
  • 中断屏蔽寄存器/事件屏蔽寄存器:相当于开关,只有置1,中断信号才能继续向左走。
  • 脉冲发生器:产生一个电平脉冲,用于触发其他外设工作。

在这里插入图片描述

最后一个问题,到底什么样的设备需要用到外部中段呢?

答:对于stm32来说,若想获取一个由外部驱动的很快的突发信号,就需要外部中断。

  • 如旋转编码器,平常不会有什么变化,但是一旦拧动时,会产生一段时间变化很快的突发信号,就需要stm32能在短时间内快速读取并处理掉这个数据。

  • 再如红外遥控接收头,平常也不会有什么变化,但是一旦接收到信号时,这个信号也是转瞬即逝的。

  • 但是不推荐按键使用外部中断。因为外部中断不能很好的处理按键抖动和松手检测的问题,所以要求不高时,还是建议在主函数内部循环读取。

1.2 旋转编码器介绍
对射式红外传感器就是一种通用传感器模块,已经在第三节“GPIO通用输入输出口”中介绍过,不再赘述。本实验只介绍旋转编码器。

旋转编码器是一种用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向。
在这里插入图片描述

如上图,旋转编码器主要有三种类型:光栅式/机械触点式/霍尔传感器式。下面是这三种形式的介绍:

  1. 光栅式(老款鼠标):配合对射式红外传感器使用,在旋转过程中光栅式编码盘会不断地阻挡/透过红外射线,于是模块便会输出高低电平交替的方波,方波的频率便代表了旋转速度。缺点是只有一路输出,无法判断转动方向。
  2. 机械触点式:内部使用机械触点检测通断,A口和B口输出的方波正交,具体看下面的介绍。当然,也有机械触点式编码器可以一个引脚输出速度信息,一一个引脚输出旋转方向信息。
  3. 霍尔传感器式:直接附在电机后面的编码器,中间是一个圆形磁铁,旋转时两侧的霍尔传感器便可输出正交的方波信号。
  4. 独立的编码器元件:输入轴转动时,输出便有波形。

注:触点式不适合高速旋转的场景,常用于音量调节。非接触式形式的电机可以用于电机测速。
在这里插入图片描述

上图是机械触点式旋转编码器-实物拆解

  • 图片右侧是旋转编码器的旋钮,可以看到下面是一圈可以导电的金属片。
  • 中间有一个大的按键开关结构,也可以检测通断,但是该旋转编码器模块没有使用到该功能。
  • 左右两组金属触点。内部实际的连接如红线标注,C口接地,于是旋钮在旋转过程中就可以使A口、B口输出高低交替的方波。方波频率表示旋转速度。
  • A口、B口配合旋钮,可以产生相位相差90°的方波,称为正交信号。顺时针旋转A口相位超前,逆时针旋转B口相位超前。

在这里插入图片描述

  • R1、R2:上拉电阻。
  • R3、R4:输出限流电阻,防止引脚电流过大。
  • C1、C2:滤波电容,滤除高频不稳定纹波。
    注:C口已经默认接地,只需关心A口、B口的高低变化及相位差即可。

1.3 实验:对射式红外传感器计次

需求:利用stm32的外部中断,对 对射式红外传感器 产生的下降沿进行计次。

在这里插入图片描述上图是对射式红外传感器计次-接线图
下图是对射式红外传感器计次-代码调用
在这里插入图片描述代码展示:OLED.h、OLED.c、OLED_Font.h代码见第四节“OLED调试工具”,本节省略。
文章在这《嵌入式-stm32-江科大-OLED调试工具》

main.c

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{//模块初始化OLED_Init();       //OLED初始化CountSensor_Init(); //计数传感器初始化//OLED显示,显示静态字符串OLED_ShowString(1,1,"Count:");  //一行一列显示字符串Delay_ms(500);while(1){OLED_ShowNum(2,1,CountSensor_Get(),5);}}

CountSensor.h

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_Hvoid CountSensor_Init(void);
uint16_t CountSensor_Get(void);#endif

CountSensor.c

#include "stm32f10x.h"uint16_t  CountSensor_Count;     //全局变量,中断触发次数,用于计数
//默认初始化为0/*** 函    数:对射式红外传感器-计数传感器初始化 PB14* 参    数:无* 返 回 值:无*/
void CountSensor_Init(void)
{//1.开启时钟//2.GPIO初始化//3.AFIO选择中断引脚//4.EXTI初始化//5.//NVIC配置,NVIC中断分组//EXTI初始化//1.开启GPIO&AFIO的外设时钟(EXTI和NVIC的时钟是一直打开的)RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//2.配置GPIO—PB14上拉输入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);//3.配置AFIO(库函数在GPIO中)中断引脚选择GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚//4.配置EXTI初始化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);//5.NVIC配置(库函数在misc.h文件中,杂项)//配置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);
}	
/*** 函    数:获取计数传感器的计数值,输出中断触发的次数* 参    数:无* 返 回 值:计数值,范围: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_Line4);//清除外部中断14号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}
1.31 编程感想
  1. 对射式传感器采用下降沿触发(移除遮挡触发,硬件设计)。
    传感器无遮挡时,DO输出低电平;传感器有遮挡时,DO输出高电平。所以放入遮挡物意味着触发上升沿,移除遮挡相当于下降沿。采用上升沿触发计数可能不准确,下降沿触发计数准确(实测),若传感器采用上升沿触发那么结论和上面相反,移除遮挡物的时候比较准确。

  2. 中断函数的名字从启动文件“stratup_stm32f10x_md”中来,并且中断函数都是无参无返回值的。

  3. 在CountSensor.c里面GPIO使用结构体来初始化函数、外部中断、ADC、串口都是一样的操作。

  4. 学会使用keil调试,博主第一次烧录程序发现计数传感器没有返回值,灯会闪计数,但是不会反馈到显示屏上。于是我先把老师的代码烧录运行成功,说明硬件没问题,就是自己代码问题,然后一直排除,通过用源码替换自己写的代码过程中,发现是上一个实验的OLED.c问题,CV工程师。

OLED.c(可以跳过)

#include "stm32f10x.h"
#include "OLED_Font.h"/*引脚配置*/
#define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))/*引脚初始化*/
void OLED_I2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_Init(GPIOB, &GPIO_InitStructure);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief  I2C开始* @param  无* @retval 无*/
void OLED_I2C_Start(void)
{OLED_W_SDA(1);OLED_W_SCL(1);OLED_W_SDA(0);OLED_W_SCL(0);
}/*** @brief  I2C停止* @param  无* @retval 无*/
void OLED_I2C_Stop(void)
{OLED_W_SDA(0);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief  I2C发送一个字节* @param  Byte 要发送的一个字节* @retval 无*/
void OLED_I2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i++){OLED_W_SDA(Byte & (0x80 >> i));OLED_W_SCL(1);OLED_W_SCL(0);}OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号OLED_W_SCL(0);
}/*** @brief  OLED写命令* @param  Command 要写入的命令* @retval 无*/
void OLED_WriteCommand(uint8_t Command)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78);		//从机地址OLED_I2C_SendByte(0x00);		//写命令OLED_I2C_SendByte(Command); OLED_I2C_Stop();
}/*** @brief  OLED写数据* @param  Data 要写入的数据* @retval 无*/
void OLED_WriteData(uint8_t Data)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78);		//从机地址OLED_I2C_SendByte(0x40);		//写数据OLED_I2C_SendByte(Data);OLED_I2C_Stop();
}/*** @brief  OLED设置光标位置* @param  Y 以左上角为原点,向下方向的坐标,范围:0~7* @param  X 以左上角为原点,向右方向的坐标,范围:0~127* @retval 无*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{OLED_WriteCommand(0xB0 | Y);					//设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}/*** @brief  OLED清屏* @param  无* @retval 无*/
void OLED_Clear(void)
{  uint8_t i, j;for (j = 0; j < 8; j++){OLED_SetCursor(j, 0);for(i = 0; i < 128; i++){OLED_WriteData(0x00);}}
}/*** @brief  OLED显示一个字符* @param  Line 行位置,范围:1~4* @param  Column 列位置,范围:1~16* @param  Char 要显示的一个字符,范围:ASCII可见字符* @retval 无*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{      	uint8_t i;OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容}
}/*** @brief  OLED显示字符串* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  String 要显示的字符串,范围:ASCII可见字符* @retval 无*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i++){OLED_ShowChar(Line, Column + i, String[i]);}
}/*** @brief  OLED次方函数* @retval 返回值等于X的Y次方*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1;while (Y--){Result *= X;}return Result;
}/*** @brief  OLED显示数字(十进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~4294967295* @param  Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++)							{OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}/*** @brief  OLED显示数字(十进制,带符号数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:-2147483648~2147483647* @param  Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{uint8_t i;uint32_t Number1;if (Number >= 0){OLED_ShowChar(Line, Column, '+');Number1 = Number;}else{OLED_ShowChar(Line, Column, '-');Number1 = -Number;}for (i = 0; i < Length; i++)							{OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}/*** @brief  OLED显示数字(十六进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~0xFFFFFFFF* @param  Length 要显示数字的长度,范围:1~8* @retval 无*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i, SingleNumber;for (i = 0; i < Length; i++)							{SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;if (SingleNumber < 10){OLED_ShowChar(Line, Column + i, SingleNumber + '0');}else{OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');}}
}/*** @brief  OLED显示数字(二进制,正数)* @param  Line 起始行位置,范围:1~4* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~1111 1111 1111 1111* @param  Length 要显示数字的长度,范围:1~16* @retval 无*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++)							{OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');}
}/*** @brief  OLED初始化* @param  无* @retval 无*/
void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++)			//上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init();			//端口初始化OLED_WriteCommand(0xAE);	//关闭显示OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);	//设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);	//设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);	//设置显示开始行OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);	//设置对比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9);	//设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭OLED_WriteCommand(0xA6);	//设置正常/倒转显示OLED_WriteCommand(0x8D);	//设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF);	//开启显示OLED_Clear();				//OLED清屏
}

1.4 实验:旋转编码器计次

需求:利用stm32的外部中断,对旋转编码器的转动进行计次,顺时针加、逆时针减,并显示在OLED显示屏上。
在这里插入图片描述上图是旋转编码器计次的接线图,下图是代码调用(除库函数以外)
在这里插入图片描述
main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
#include "Encoder.h"int16_t Num;			//定义待被旋转编码器调节的变量int main(void)
{/*模块初始化*/OLED_Init();			//OLED初始化CountSensor_Init();		//计数传感器初始化Encoder_Init();            //旋转编码器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Num:");	//1行1列显示字符串Count:OLED_ShowString(2, 1, "Num+=:");	//1行1列显示字符串Count:while (1){	Num += Encoder_Get();OLED_ShowSignedNum(1, 7, CountSensor_Get(), 5);		//OLED不断刷新显示CountSensor_Get的返回值OLED_ShowSignedNum(2,8,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"int16_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;//AB相分别接的是PB0和PB1GPIO_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);		}//打算每次调用get函数之后,返回count的变化值,用于外部加减一个变量
int16_t Encoder_Get(void)
{//使用Temp变量作为中继,目的是返回Encoder_Count后将其清零,目的是给count清零//在这里,也可以直接返回Encoder_Count,直接返回就退出函数了没办法给count清零//下面这样子又能获取到count变化值,又能给count清零 //直接返回count会导致你扭一下,count就有一个值,Num就会一直变化了int16_t Temp;Temp = Encoder_Count;Encoder_Count= 0;return Temp;
}void EXTI0_IRQHandler(void)//A口下降沿中断函数
{if(EXTI_GetITStatus(EXTI_Line0) == SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) //PB1的下降沿触发中断,此时检测另一相,等于1正转,等于0反转{Encoder_Count --;  //此方向定义为反转,计数变量自减}EXTI_ClearITPendingBit(EXTI_Line0); //清除标志中断0号线的中断标志位}}void EXTI1_IRQHandler(void)//B口下降沿中断函数
{if(EXTI_GetITStatus(EXTI_Line1) == SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) //PB1的下降沿触发中断,此时检测另一相{Encoder_Count ++;  //此方向定义为正转,计数变量自增}EXTI_ClearITPendingBit(EXTI_Line1); //清除标志中断1号线的中断标志位}}
1.41 编程感想
  1. 管理Hardware文件夹。本次实验继承的是“OLED显示屏”实验的代码,而非“对射式红外传感器计次”。
  2. 注意每个模块在使用的时候都要进行初始化。
  3. 注意进入中断函数的时候要检查中断标志位,,退出的时候清零中断标志位。
  4. 注意主函数和中断函数不要操控同一个硬件,避免不必要的硬件冲突。中断函数一般执行简短快速的代码,如操作中断标志位等。
  5. 在中断函数内部不要执行长时间函数,中断是快速的突发事件,必须要短。

参考:B站STM32江协自动化&【哈工大虎慕】

道友:没有永久的巅峰也没有永远的低谷,真正的强大不是忘记而是接受,接受世事无常、接受孤独挫败、接受突如其来的无力感、接受自己的不完美、接受困惑不安的焦虑和遗憾。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Influxdb系列(一)influxdb2.7.x的部署安装

一、influxdb的介绍 InfluxDB 是一种时序数据库&#xff0c;时序数据库通常被用在监控场景&#xff0c;比如运维和 IOT&#xff08;物联网&#xff09;领域。这类数据库旨在存储时序数据并实时处理它们。 比如。我们可以写一个程序将服务器上 CPU 的使用情况每隔 10 秒钟向 In…

JVM-初始JVM

什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 Java源代码执行流程如下&#xff1a; JVM的功能 1 - 解释和运行 2 - 内存管理 3 - 即时编译 解释和运行 解释…

2024转行程序员的请注意:均月薪在40-70k

前言 2023年&#xff0c;对大多数行业来说都是不太好过的一年。 对程序员来说也是如此&#xff0c;很多粉丝朋友都在说android工作特别难找&#xff0c;一个岗位都是几千份简历…大家心里都是特别的焦虑&#xff0c;本以为2024年就业情况会有好转&#xff0c;但实际上并非如此…

《WebKit 技术内幕》学习之十三(1):移动WebKit

1 触控和手势事件 1.1 HTML5规范 随着电容屏幕的流行&#xff0c;触控操作变得前所未有的流行起来。时至今日&#xff0c;带有多点触控功能已经成为了移动设备的标准配置&#xff0c;基于触控的手势识别技术也获得巨大的发展&#xff0c;如使用两个手指来缩放应用的大小等。…

基于SSM的蛋糕甜品店管理系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的蛋糕甜品店管理系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

C++ 数论相关题目(欧拉函数、筛法求欧拉函数)

1、欧拉函数 给定 n 个正整数 ai &#xff0c;请你求出每个数的欧拉函数。 欧拉函数的定义 1∼N 中与 N 互质的数的个数被称为欧拉函数&#xff0c;记为 ϕ(N) 。 若在算数基本定理中&#xff0c;Npa11pa22…pamm &#xff0c;则&#xff1a; ϕ(N) Np1−1p1p2−1p2…pm−1p…

SpringBoot项目多数据源配置与MyBatis拦截器生效问题解析

在日常项目开发中&#xff0c;由于某些原因&#xff0c;一个服务的数据源可能来自不同的库&#xff0c;比如&#xff1a; 对接提供的中间库&#xff0c;需要查询需要的数据同步数据&#xff0c;需要将一个库的数据同步到另一个库&#xff0c;做为同步工具的服务对接第三方系统…

肺癌相关文献6

第十四篇 Classification of lung adenocarcinoma based on stemness scores in bulk and single cell transcriptomes IF&#xff1a;6.0 中科院分区:2区 生物学WOS分区&#xff1a;Q1被引次数&#xff1a; 4 背景&#xff1a;癌细胞具有无限期自我更新和增殖的能力[2]。在一…

JavaScript进阶:WebAPIs重点知识整理1

目录 1 DOM修改元素内容 2 DOM修改元素常见属性 3 修改元素样式属性 3.1 通过style修改元素样式 3.2 通过类名className修改元素样式 3.3 通过classList修改元素样式 4 操作表单元素属性 5 自定义属性 6 定时器 7 事件监听 7.1 点击事件 click 7.2 鼠mouseenter和移…

Laya3.0 相机使用

摄像机&#xff0c;是3D场景里边最经常使用的对象了。 官方文档&#xff1a;点击这里学习 1.投影 Projection 透视&#xff1a; 模拟人眼的视觉效果&#xff0c;近大远小。模拟物理世界的规律&#xff0c;将眼睛或相机抽象成一个点&#xff0c;此时视锥体内的物体投影到视平…

YOLO 自己训练一个模型

一、准备数据集 我的版本是yolov8 8.11 这个目录结构很重要 ultralytics-main | datasets|coco|train|val 二、训练 编写yaml 文件 # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] path…

Java项目:基于SSM框架实现同城蔬菜配送管理系统(SSM+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm825基于SSM框架实现同城蔬菜配送管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&…

【Web前端开发基础】CSS3之空间转换和动画

CSS3之空间转换和动画 目录 CSS3之空间转换和动画一、空间转换1.1 概述1.2 3D转换常用的属性1.3 3D转换&#xff1a;translate3d&#xff08;位移&#xff09;1.4 3D转换&#xff1a;perspective&#xff08;视角&#xff09;1.5 3D转换&#xff1a;rotate3d&#xff08;旋转&a…

集美大学“第15届蓝桥杯大赛(软件类)“校内选拔赛 D矩阵选数

经典的状态压缩DP int dp[15][(1<<14)10]; int a[15][15]; void solve() {//dp[i][st]考虑到了第i行 并且当前考虑完第i行以后的选择状态是st的所有方案中的最大值for(int i1;i<13;i)for(int j1;j<13;j)cin>>a[i][j];for(int i1;i<13;i){for(int j0;j<…

Web网页生成桌面应用

前言&#xff1a;网页生成桌面指的是将一个网页保存为桌面应用程序的形式&#xff0c;使得用户可以在桌面上直接打开该网页&#xff0c;而不必通过浏览器打开。这种桌面应用程序一般具有独立的窗口、菜单、工具栏等界面元素&#xff0c;能够提供更加方便快捷的使用体验。 实现…

宠物互联网医院系统

在数字时代&#xff0c;宠物医疗迎来了一场革新&#xff0c;动物互联网医院系统以其先进的技术和智能的特性成为宠物护理的领军者。本文将介绍宠物互联网医院系统的一些关键技术和代码示例&#xff0c;揭示这一科技奇迹的实现原理。 1. 远程医疗服务的实现 远程医疗服务是宠…

谷粒商城配置虚拟机

一、创建虚拟机 之前有在VM里面建一个ubuntu的虚拟机&#xff0c;准备拿来直接用&#xff0c;网络设置为NAT模式&#xff0c;查看我的虚拟机是虚拟机&#xff1a;192.168.248.128 主机&#xff1a; 192.168.2.12。可以互相ping通。 二、linux安装docker Docker docker是虚拟…

无人机航迹规划(六):七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划(提供MATLAB代码)

一、七种算法&#xff08;DBO、LO、SWO、COA、LSO、KOA、GRO&#xff09;简介 1、蜣螂优化算法DBO 蜣螂优化算法&#xff08;Dung beetle optimizer&#xff0c;DBO&#xff09;由Jiankai Xue和Bo Shen于2022年提出&#xff0c;该算法主要受蜣螂的滚球、跳舞、觅食、偷窃和繁…

【医学图像数据增强】切割-拼接(CS-DA)

切割-拼接CS-DA CS-DA 核心思想自然图像和医学图像之间的关键差异CS-DA 步骤确定增强后的数据数量 代码复现 CS-DA 核心思想 论文链接&#xff1a;https://arxiv.org/ftp/arxiv/papers/2210/2210.09099.pdf 大多数用于医学分割的数据增强技术最初是在自然图像上开发的&#x…

Spring Cloud 系列:基于Seata 实现 XA模式

https://seata.io/zh-cn/docs/user/mode/xa https://seata.io/zh-cn/docs/dev/mode/xa-mode XA 规范 是 X/Open 组织定义的分布式事务处理&#xff08;DTP&#xff0c;Distributed Transaction Processing&#xff09;标准&#xff0c;XA 规范 描述了全局的TM与局部的RM之间的…