6.对射式红外传感器和旋转编码器

_

旋转编码器简介

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

类型:机械触点式/霍尔传感器式/光 栅式

1.下面的是一种最简单的编码器样式,这里使用的也是对射式红外传感器来测速的,为了测速还需配合一个光栅编码盘(银色圆圈),当这个编码盘转动时,红外传感器的红外光就会出现遮挡、透过、遮挡、透过这样的现象,对应模块输出的电平就是高低电平交替的方波,方波的个数代表了转过的角度,方波的频率表示转速,我们就可以用外部中断来捕获这个方波的边沿,以此来判断位置和速度,不过这个模块只有一路输出,正转反转输出波形没法区分,所以这种测试方法只能测位置和速度,不能测量旋转方向,为了进一步测量方向,我们就可以用后面的几种编码器。

2.如下是我们接下来将要用过的旋转编码器,左边是外观,右边是内部拆解的结构;可以看到内部是用金属触电进行通断的,所以它是一种机械触电式编码器,左右是两部分开关触电;中间银色圆形金素片为一个按键,这个旋转编码器的轴是可以按下去的,这种编码器一般是用来进行调节的,比如音响调节音量,因为它是触电接触的形式,所以不适合电机这种高速旋转的地方,另外三种都是非接触的形式,可以用于电机测速(电机测速在电机驱动的应用中还是很常见的)

下面为详细讲解旋转编码器的硬件部分:

金属触点
内侧的两根细的触电都是和中间的引脚c连接的,外侧触电一个连接A,一个连接B。

圆形金属片(按键)的两根线,就在上面引出来了;按键的轴按下,上面两根线短路,松手,上面两根线断开,就是个普通的按键

这个旋转编码器的轴是可以按下去的;轴的外侧是白色的编码盘,它也是一系列光栅一样的东西,只不过这是金属触电,在旋转时,依次接通和断开两边的触电;这个金属盘的位置是经过设计的,它能让两侧触电的通断产生一个90度的相位差,最终配合一下外部电路,这个编码器的两个输出就会输出如下这样的正交波形,带正交波形输出的编码器是可以用来测方向的(这就是单相输出和两相正交输出的区别),当然还有的编码器不是输出正交波形,而是一个引脚输出方波信号代表转速,另一个输出高低电平代表旋转方向,这种不是正交输出的编码器也是可以测方向的。 

当正转时,A相引脚输出一个方波波形,B相引脚输出一个和它相位相差90的波形(正交波形),如下。

当反向旋转时,A相引脚还是方波信号,B相引脚会提前90度,如下。

3.霍尔传感器形式编码器,这种是直接附在电机后面的编码器,中间是一个圆形磁铁,边上有两个位置错开的活儿传感器,当磁铁旋转时,通过霍尔传感器就可以输出正交的方波信号,如下。

4.这是独立的编码器元件,它的输入轴转动时,输出就会波形,这个也是可以测速和测方向的,具体用法再看相应的手册。如下。

旋转编码器的硬件电路

模块的电路图 如下,图中正方形区域就是旋转编码器,上面按键的两根线这个模块没有使用,是悬空的

下面为模块电路细节介绍:

这里是编码器内部的两个触电,旋转轴旋转时,这两个触电以相位相差90度的方式交替导通,因为这只是个开关信号,所以要配合外围电路才能输出高低电平

左边接了一个10k的上拉电阻,默认没旋转的情况下,这个点被上拉为高电平,再通过R3这个电阻输出到A端口的就也是高电平,当旋转时,内部触电导通,那C端口处就直接被拉低到GND,再通过R3输出,A端口就是低电平了,之后这个R3是一个输出限流电阻(是为了防止模块引脚电流过大的);C1是输出滤波电容,可以防止一些输出信号抖动。剩下的右边电路和左边是雷同的。

使用这个模块时的接线如下,下面的A相输出和B相输出接到STM32的两个引脚上(主要引脚的尾数不能一样),中间的C引脚就是GND,我们暂时不用

接线图

对射式红外传感器

旋转编码器

程序-对射式红外传感器(+代码注释)

当我们的挡光片或者编码盘在这个对射式红外传感器中间经过时,这个DO就会输出电平跳变的信号,然后这个电平跳变的信号触发STM32 PB14号口的中断,我们在中断函数里,执行变量++的程序,然后主循环里用OLED显示这个变量,这样第一个程序就完成了。

main.c


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

int main(void)
{
	OLED_Init();
	CounterSensor_Init();
	OLED_ShowString(1,6,"ZETAI");
	OLED_ShowString(2,1,"Count:");
	while(1)
	{
		OLED_ShowNum(2,7,CounterSensor_Get(),5);
	}
}
//CounterSensor.c

#include "stm32f10x.h"                  // Device header

uint16_t Count;
void CounterSensor_Init(void)
{
	//把GPIO到NVIC这一路信号都打通	
	//step1 打开所有外设的RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO的时钟
	//EXTI和NVIC(内核外设)默认开启 不需要单独开启
	//step2 配置GPIO 选择端口为输入模式
	GPIO_InitTypeDef GPIO_Init_Structure;
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_14;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_Init_Structure);
	//step3 配置AFIO 选择使用的这一路GPIO连接到EXTI
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//将第14号线路拨到GPIOB
	//step4 配置EXTI 选择边沿触发方式、触发响应方式(事件、中断)
	EXTI_InitTypeDef EXTI_Init_Structure;
	EXTI_Init_Structure.EXTI_Line=EXTI_Line14;//配置中断线
	EXTI_Init_Structure.EXTI_LineCmd=ENABLE;//配置所设置中断线的新状态
	EXTI_Init_Structure.EXTI_Mode=EXTI_Mode_Interrupt;//配置触发响应方式为中断触发
	EXTI_Init_Structure.EXTI_Trigger=EXTI_Trigger_Falling;//配置为下降沿触发
	EXTI_Init(&EXTI_Init_Structure);
	//step5 配置NVIC 选择合适的优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组
	NVIC_InitTypeDef NVIC_Init_Structure;
	NVIC_Init_Structure.NVIC_IRQChannel= EXTI15_10_IRQn;//指定中断通道开启或关闭
	NVIC_Init_Structure.NVIC_IRQChannelCmd=ENABLE;//使能指定的IRQ通道
	//目前只有一个外设中断,不存在优先级问题,随意设置
	NVIC_Init_Structure.NVIC_IRQChannelPreemptionPriority=1; //指定选定通道的抢占优先级
	NVIC_Init_Structure.NVIC_IRQChannelSubPriority=1; //指定选定通道的响应优先级
  NVIC_Init(&NVIC_Init_Structure);
}

//中断函数 
//EXTI10-EXTI15都可以进入这个中断函数
void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line14)==SET)//判断触发中断函数的是否为14号中断线
	{
		Count++;
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

uint16_t CounterSensor_Get(void)
{
	return Count;
}
//CounterSensor.h


#ifndef __COUNTSENSOR_H
#define __COUNTSENSOR_H
 
void countsensor_init(void);
uint16_t countsersor_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();	//初始化OLED
	encoder_init();
		
 
	OLED_ShowString(1,1,"num:");//第一行第三列开始显示字符串hello word!
 
	while(1)
	{
		num += encoder_get();
		OLED_ShowSignedNum(1,5,num,5);
		
	}
 
}
 
 
 

Encoder.c

#include "stm32f10x.h"                  // Device header

uint16_t Encoder_Count;

void Encoder_Init(void)
{
		//把GPIO到NVIC这一路信号都打通	
	//step1 打开所有外设的RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO的时钟
	//EXTI和NVIC(内核外设)默认开启 不需要单独开启
	//step2 配置GPIO 选择端口为输入模式
	GPIO_InitTypeDef GPIO_Init_Structure;
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_Init_Structure);
	//step3 配置AFIO 选择使用的两路GPIO连接到EXTI
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);//将第0号线路拨到GPIOB
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//将第1号线路拨到GPIOB
	//step4 配置EXTI 选择边沿触发方式、触发响应方式(事件、中断)
	EXTI_InitTypeDef EXTI_Init_Structure;
	EXTI_Init_Structure.EXTI_Line=EXTI_Line0|EXTI_Line1;//配置中断线
	EXTI_Init_Structure.EXTI_LineCmd=ENABLE;//配置所设置中断线的新状态
	EXTI_Init_Structure.EXTI_Mode=EXTI_Mode_Interrupt;//配置触发响应方式为中断触发
	EXTI_Init_Structure.EXTI_Trigger=EXTI_Trigger_Falling;//配置为下降沿触发
	EXTI_Init(&EXTI_Init_Structure);
	//step5 配置NVIC 选择合适的优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组
	NVIC_InitTypeDef NVIC_Init_Structure;
	NVIC_Init_Structure.NVIC_IRQChannel= EXTI0_IRQn;//指定中断通道
	NVIC_Init_Structure.NVIC_IRQChannelCmd=ENABLE;//使能指定的IRQ通道
	NVIC_Init_Structure.NVIC_IRQChannelPreemptionPriority=1; //指定选定通道的抢占优先级
	NVIC_Init_Structure.NVIC_IRQChannelSubPriority=1; //指定选定通道的响应优先级
  NVIC_Init(&NVIC_Init_Structure);
	NVIC_Init_Structure.NVIC_IRQChannel= EXTI1_IRQn;//指定中断通道
	NVIC_Init_Structure.NVIC_IRQChannelCmd=ENABLE;//使能指定的IRQ通道
	NVIC_Init_Structure.NVIC_IRQChannelPreemptionPriority=1; //指定选定通道的抢占优先级
	NVIC_Init_Structure.NVIC_IRQChannelSubPriority=2; //指定选定通道的响应优先级
  NVIC_Init(&NVIC_Init_Structure);
}

//EXTI0中断函数
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);
	}
}

//EXTI1中断函数
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);
	}
}

uint16_t Encoder_Get(void)
{
	uint16_t temp;
	temp=Encoder_Count;
	Encoder_Count=0;
	return temp;
}

Encoder.h

#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
uint16_t Encoder_Get(void);

#endif

5.EXTI外部中断 2026-08-15
什么时候使用AFIO 2026-10-08

评论区

© 2026 ZETAI