系列文章目录
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
}
}