当前位置: 首页 > news >正文

STM32单片机入门学习——第12节: [5-2]对射式红外传感器计次旋转编码器计次

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.04.03

STM32开发板学习——第12节: [5-2]对射式红外传感器计次&旋转编码器计次

  • 前言
  • 开发板说明
  • 引用
  • 解答和科普
  • 一、外部中断代码
  • 二、旋转编码器计次
  • 问题
  • 总结

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
   原理图如下
1、开发板原理图
在这里插入图片描述
2、STM32F103C6和51对比
在这里插入图片描述
3、STM32F103C6核心板
在这里插入图片描述

视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。

下图是实物图
在这里插入图片描述

引用

【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
在这里插入图片描述
数据手册

解答和科普

一、外部中断代码

在这里插入图片描述
DO数字输出端,随便接一个GPIO,当我们的挡光片或者编码器在对射式红外传感器中间经过时,这个DO就会输出电平跳变的信号,就会触发STM32 PB14口的中断,我们在中断函数中,执行变量++的程序,然后主循环里调用OLED显示这个变量。
在这里插入图片描述
第一步,配置RCC,把我们这里涉及的外设的时钟都打开
第二步,配置GPIO。选择我们的端口为输入模式
第三步,配置AFIO,选择我们用的这一路GPIO,连接到后面的EXTI
第四步, 配置EXTI,选择边沿触发方式,比如上升沿、下降沿或者双边沿
还有选择触发响应方式,可以选择中断响应和事件响应
第五步,配置NVIC,给我们这个中断选择一个合适的优先级,最后通过NVIC,外部中断信号就能进入CPU了。
这样CPU才能收到中断信号,才能跳转到中断函数里执行中断程序。

配置时钟

void CounterSensor_Init(void)		//外部中断配置
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);		//开启APB2的GPIOB的外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);			//开启APB2的AFIO的时钟//接着还有EXTI和NVIC两个外设:这两个外设时钟一直都是打开着的,不需要我们再开启时钟了

EXTI和NVIC这两个外设的时钟是一直都制开着的,不需要我们再开启时钟了.
NVIC是内核的外设,内核的外设都是不需要开启时钟的,和CPU一起住在皇宫里的。

配置GPIO

	GPIO_InitTypeDef   GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;			//上拉输入默认高电平GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);

对于中断来说:要选择浮空输入、上拉输入、或者下拉输入。这其中的一个模式(可以看参考文档)

配置AFIO
在这里插入图片描述

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8);

当执行完这个函数后,AFIO的第8个数据选择器就拨好了,其中输入端被拨到了GPIOA外设上,对应的就是PA8引脚,输出端固定连接的是EXIT的第8个中断线路,这样PA8引脚的电平信号就可以顺利通过AFIO,进入到后级EXTI电路了。

配置EXTI
在这里插入图片描述
对于标志位,有的比较紧急,在指标值位后会触发中断,在中断函数里,如果你想查看标志位和清除标志位,就用下面两个函数;
所以你现在主程序里查看和清除标志位,就用上面两个函数;
如果想在中断函数里查看和清除标志位,就用下面两个函数。
都是读写寄存器,只不过下面两个函数只能读写与中断有关的标志位,并且对中断是否允许做出来判断,而上面的这两个函数只是一般的读写标志位,没有额外的处理,能不能触发中断的标志位都能读取,所以建议主程序用上面两个,中断程序里用下面两个。只不过是进行了区分。

	EXTI_InitTypeDef  EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line8 ;EXTI_InitStructure.EXTI_LineCmd=ENABLE;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger= EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure);

现在的配置是:将EXTI的第8个线路配置为中断模式,下降沿触发,然后开启中断,这样PA8的电平信号就能通过EXTI通向下一级NVIC了。

配置NVIC
NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,打开misc.h文件
在这里插入图片描述
1、分组
2、配置一下初始化参数

在这里插入图片描述
定义不在本文件在stm32f10x.h文件
中断通道列表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

外部中断函数配置完成
外部信号从GPIO到AFIO,再到EXTI,再到NVIC,最终通向CPU,这样才能让CPU由主程序跳转到中断程序执行。中断程序应该放到哪里呢?

中断函数
在STM32中,中断函数都是固定的,每个中断通道都对应一个中断函数,中断函数的名字可以参考一下启动文件,
在这里插入图片描述

在这里插入图片描述

这就是中断函数的格式,中断函数都是无参无返回值的,中断函数名字不要写错了,写错了就进不了中断了,最好是直接从启动文件复制过来,这样就不会有问题。
在中断函数里,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI5和EXTI9都能进来,所以要先判断是不是我们想要的EXTI8进来的,
这个时候就需要去exti.h看一下,

if(EXTI_GetITStatus(EXTI_Line8)==SET){}

判断一下是不是返回值为SET,如果是的话,我们就可以执行中断程序了,最后中断程序结束后,一定要调用一下清除中断标志位的函数,因为只要中断标志位置1了,程序就会跳转到中断函数,如果你不清除中断标志位,那它就会一直申请中断,这样程序就会一直响应中断,执行中断函数,那么程序就卡死在中断函数里了,所以我们每次中断函数结束后,都应该清除一下中断标志位。

void  EXTI9_5_IRQHandler (void)
{if(EXTI_GetITStatus(EXTI_Line8)==SET){EXTI_ClearITPendingBit(EXTI_Line8);}}

测试能不能进入中断

将 Dialog修改为DARMSTM.DLL Parameter修改为-pSTM32F103C8(这里请根据你的芯片填写型号)

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{OLED_Init();OLED_ShowString(1,1,"Hello STM32 MCU");OLED_ShowString(2,1,"Count:");CounterSensor_Init();while(1){OLED_ShowNum(2,7,CounterSensor_Get(),5);}
}

Key.C和H

#include "stm32f10x.h"                  // Device headeruint16_t  CounterSensor_Count;void CounterSensor_Init(void)		//外部中断配置
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);		//开启APB2的GPIOB的外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);			//开启APB2的AFIO的时钟//接着还有EXTI和NVIC两个外设:这两个外设时钟一直都是打开着的,不需要我们再开启时钟了GPIO_InitTypeDef   GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;			//上拉输入默认高电平GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource8);EXTI_InitTypeDef  EXTI_InitStructure;EXTI_InitStructure.EXTI_Line=EXTI_Line8 ;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=  EXTI9_5_IRQn     ;	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);}uint16_t  CounterSensor_Get(void)
{return  CounterSensor_Count;
}
void  EXTI9_5_IRQHandler (void)
{if(EXTI_GetITStatus(EXTI_Line8)==SET){CounterSensor_Count++;EXTI_ClearITPendingBit(EXTI_Line8);}}
#ifndef  __COUNT_SENSOR_H
#define  __COUNT_SENSOR_Hvoid CounterSensor_Init(void);
uint16_t  CounterSensor_Get(void);
#endif

实现现象
按键触发外部中断,实现计数加1,下降沿触发,感觉按键必须得加入消抖程序。

按键触发外部中断计数加1

二、旋转编码器计次

在这里插入图片描述
旋转编码器,下面这两个A,B相的输出引脚,分别接到了STM32的PB0和PB1两个引脚。
在这里插入图片描述
正向旋转时,A,B相输出的是这样的波形,反向旋转时,输出是下面的波形,如果把一相的下降沿用作触发中断,在中断时刻读取另一相的电平,正转就是高电平,反转就是低电平,这样就能区分旋转方向了,这不过有一点小瑕疵;
A,B相都触发中断,只有在B相下降沿和A相低电平时,才判断为正转,在A相下降沿和B相低电平时,才判断为反转,这样就能保证正转和反转都是转到位了,才执行数字加减的操作,同时也演示一下两个中断的初始化代码。
main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include  "Encoder.h"int16_t Num;int main(void)
{OLED_Init();Encoder_Init();OLED_ShowString(1,2,"Hello STM32 MCU");OLED_ShowString(2,1,"Num:");while(1){Num+=Encoder_Get();		//Get是旋转编码器产生的正负脉冲数,所以这个返回值+=Num,就能对Num进行加减操作了OLED_ShowSignedNum(2,5,Num,5);}
}

Encoder.c和h

#include "stm32f10x.h"                  // Device headerint16_t		Encoder_Count;void Encoder_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);		//开启APB2的GPIOB的外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);			//开启APB2的AFIO的时钟//接着还有EXTI和NVIC两个外设:这两个外设时钟一直都是打开着的,不需要我们再开启时钟了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);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);	//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);}int16_t Encoder_Get(void)	//返回变化值用于外部加减一个变量
{int16_t Temp;Temp=Encoder_Count;Encoder_Count=0;return Temp;
}void EXTI0_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line0)==SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)){Encoder_Count--;}EXTI_ClearITPendingBit(EXTI_Line0);}
}void EXTI1_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line1)==SET){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)){Encoder_Count++;}EXTI_ClearITPendingBit(EXTI_Line1);}
}
#ifndef   __ENCODER_H
#define	  __ENCODER_Hvoid Encoder_Init(void);
int16_t  Encoder_Get(void);#endif

实验现象

旋转编码器计次

**第一个就是,在这个中断函数里。最好不要执行耗时过长的代码,:中断函数要简短快速。别刚进中断就执行一个Delay多少毫秒这样的代码, 因为中断是处理突发的事情。如果你为了一个突发的事情待着中断里不出来了, 那主程序就会受到严重的阻塞。

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

在主程序中,OLED刚显示一半,然后就进中断了,结果中断还是OLED显示函数,那OLED就挪到其他地方显示了,这时还没有问题,但当中断结束后,需要继续原来的显示,这时就出现问题了, == 因为硬件的显示位置被挪到其他位置了==,所以再回来的时候,继续显示的内容就会跟着跑到其他地方去,这就会造成问题,虽然中断有保护现场和恢复现场,但这只能保证CPU程序能正常返回不出问题,对于外部硬件的话,并没有在进入中断时,进行现场保护,所以中断返回后,就出现问题了。
最后不要在主函数和中断函数里,操作可能产生冲突的硬件,在实现功能的时候,可以在中断里操作变量或者标志位,当中断返回时,我再对这个变量进行显示和操作。
这样既能够保证中断函数的简单快捷,又能保证不产生冲突的硬件操作,这就是中断程序设计的注意事项。可以多用用变量或者标志位,来减少代码之间的耦合性,让部分代码相互独立,仅使用变量、标志位或者函数作为接口,这样能让程序更加清晰,代码更加强健。

问题

1、按键中断能不能消抖

总结

本节课主要是了学习了中断的代码实现,显示进行配置NVIC的整个路线打通,第一步,配置RCC,把我们这里涉及的外设的时钟都打开第二步,配置GPIO。选择我们的端口为输入模式第三步,配置AFIO,选择我们用的这一路GPIO,连接到后面的EXTI第四步, 配置EXTI,选择边沿触发方式,比如上升沿、下降沿或者双边沿还有选择触发响应方式,可以选择中断响应和事件响应第五步,配置NVIC,给我们这个中断选择一个合适的优先级,最后通过NVIC,外部中断信号就能进入CPU了。
然后还有配置两个中断时的操作,也是打来有的地方也是可以|,有的需要单独配置,再写一行打开所需要的时钟。


http://www.mrgr.cn/news/96991.html

相关文章:

  • 基于yolo11的BGA图像目标检测
  • 动、静态创建任务
  • MySQL - 事务隔离级别和锁的机制
  • WPF设计学习记录滴滴滴4
  • 基础科学中的人工智能︱如何用机器学习方法求解排列型组合优化问题?
  • 【11408学习记录】[特殊字符] 三步攻克英语长难句:嵌套结构×平行结构全解析
  • frp 让服务器远程调用本地的服务(比如你的java 8080项目)
  • CExercise04_2数组_1 利率在投资年份内每年的资产总价值
  • 【备忘】在Docker中安装宝塔面板,实现环境隔离,又能快速迁移服务器环境
  • CExercise04_1位运算符_1 用位运算符判断某个整数是否为奇数
  • 二极管正负极区分
  • 七种继电器综合对比——《器件手册--继电器》
  • 几何法证明卡特兰数_栈混洗
  • Vulkan进阶系列1 - Vulkan应用程序结构(完整代码)
  • 浅浅尝试Numpy的函数:
  • 五种音频器件综合对比——《器件手册--音频器件》
  • Tree - Shaking
  • PyTorch使用(7)-张量常见运算函数
  • Design Compiler:库特征分析(ALIB)
  • 第J3-1周:DenseNet算法 实现乳腺癌识别(含真实图片预测)