个人技术分享

系列文章目录

STM32单片机系列专栏

C语言术语和结构总结专栏


文章目录

1. 编码器接口简介

2. 旋转编码器简介

3. 正交编码器工作模式

4. 基本结构

5. 编码器工作模式示例

6. 代码示例

6.1 Encoder.c

6.2 Encoder.h

6.3 main.c


1. 编码器接口简介

在STM32中,编码器接口(Encoder Interface)允许通过连接到定时器的输入引脚,来直接与旋转编码器进行接口,利用定时器的计数器(CNT)来跟踪编码器的相位变化,从而确定位置、速度和方向。

  • 编码器接口模式:允许定时器捕获从编码器接收到的相位A和相位B的信号,也就是接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度。
  • 自动计数功能:在编码器模式下,定时器的计数器(CNT)会根据接收到的信号自动增加或减少。例如,相位A和相位B的每一个状态变化都可以被配置为导致计数器的增加或减少,这依赖于编码器的旋转方向和连接方式。
  • 对于每个编码器的状态变化,计数器都有相应的响应:例如,在正常的四相编码器中,每个完整的状态循环(由相位A和相位B的四个状态变化组成)可能导致计数器增加或减少4次,从而提供更高的测量精度和更好的反应速度。
  • 每个高级定时器和通用定时器都拥有一个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2(CH1和CH2)

2. 旋转编码器简介

旋转锁编码器允许用户在输入旋转命令时设置一个“锁”,使得在旋转到特定位置后,输出的信号或电路状态被“锁定”,不会因继续旋转而改变。这通常用于需要精确控制到特定位置并保持该状态的场合,如高精度的调节或选择操作。

  • 步进控制:编码器通过内部的机械结构或电子控制来实现步进控制。每次旋转到一个设定的点,编码器会产生一个停止信号,阻止进一步的旋转或保持当前的输出状态。
  • 位置锁定:旋转到特定位置后,通过物理锁定机制或电子信号锁定,确保输出状态不会因旋转器继续旋转而变化。这种锁定通常用于防止误操作或提供一种用户反馈,确认操作正确执行。

3. 正交编码器工作模式

图片中展示了一个旋转编码器产生的A相和B相信号的两种主要模式:正转和反转。这种编码器通常用于确定机械设备如电机的位置、速度和方向。通过状态变化,可以精确地监测和控制编码器的旋转方向和角度。

波形图

  • A相和B相信号:编码器的输出信号通常包含两个90度相位差的脉冲序列,即A相和B相。
  • 正转:当编码器向正方向转动时,A相信号先于B相信号变化(上升或下降)。
  • 反转:当编码器向反方向转动时,B相信号先于A相信号变化。

状态变化和方向判断:

  • 正转时的状态变化

    • 当A相从低到高(上升沿)时,如果B相处于低状态,表示编码器正在进行正转。
    • 当A相从高到低(下降沿)时,如果B相处于高状态,也表示编码器正在进行正转。
    • 当B相从低到高(上升沿)时,如果A相处于高状态,同样表示正转。
    • 当B相从高到低(下降沿)时,如果A相处于低状态,同样表示正转。
  • 反转时的状态变化

    • 当A相从低到高(上升沿)时,如果B相处于高状态,表示编码器正在进行反转。
    • 当A相从高到低(下降沿)时,如果B相处于低状态,也表示编码器正在进行反转。
    • 当B相从低到高(上升沿)时,如果A相处于低状态,同样表示反转。
    • 当B相从高到低(下降沿)时,如果A相处于高状态,同样表示反转。
有效边沿 相对信号的电平 TI1FP1信号(A相) TI2FP2信号(B相)
上升 下降 上升 下降
仅在TI1计数 向下计数 向上计数 不计数 不计数
向上计数 向下计数 不计数 不计数
仅在TI2计数 不计数 不计数 向上计数 向下计数
不计数 不计数 向下计数 向上计数
在TI1和TI2上计数 向下计数 向上计数 向上计数 向下计数
向上计数 向下计数 向下计数 向上计数

4. 基本结构

 首先输入捕获的前两个通道,通过GPIO口接入编码器的A、B相,然后通过滤波器和边沿检测极性选择,产生TI1FP1和TI2FP2,通向编码器接口,这里的边沿检测极性选择用来处理要不要加一个非门,反转一下极性。接着编码器接口通过预分频器控制CNT计数器的时钟。同时,编码器接口还根据编码器的旋转方向,控制CNT的计数方向。

编码器正转时,CNT自增,编码器反转时,CNT自减。这里ARR一般也是设置65535。

5. 编码器工作模式示例

在图中,展示了向上和向下计数的工作模式所对应的电平状态,这里是TI1和TI2都不反相,也就是经过边沿检测极性选择时不使用非门。其中毛刺部分体现了正交编码器抗噪的原理,这里TI2没有变化,但是TI1却跳变了,因为正交信号是两个输出交替变化,所以不符合正交编码器的信号规律,所以查看表格中的状态就可以看出,这里计数器就会在加和减之间来回跳动。

下面的示例是TI1反相,TI2不反相,也就是TI1经过边沿检测极性选择时使用非门.

6. 代码示例

代码实现的功能为使用旋转编码器实现计数功能,也就是数值的增加和减少,并在oled上显示。

这里前面的设置方式和其他定时器的示例类似,下面的文章代码示例中可以找到。

STM32的TIM输入捕获和PWMI详解

6.1 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;
	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线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_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线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2
	NVIC_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号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

6.2 Encoder.h

接着是Encoder.h文件,这部分引用声明一下即可

#ifndef __ENCODER_H
#define __ENCODER_H

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

#endif

6.3 main.c

#include "stm32f10x.h" 
#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
	}
}

 

7. 完整工程文件

STM32通过编码器接口测速