目录
旋转编码器简介
旋转编码器是一种将旋转角位移转换为一连串数字脉冲信号的旋转式电位器。当编码器的旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的正交方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度,位置,旋转方向
正交信号为两个一样的波形但相位差为90°,这提高输出信号的质量和稳定性,从而消除了干扰。
本博客使用的是EC11型号360度旋转编码器,旋转编码器一圈为20格,一格一完整脉冲,在静止的时候,AB两线输出都是高电平
旋转编码器旋转的输出动作,由于编码器的不同,A、B端输出脉冲不同,我这一种就是转一格,A、B对C端输出一个完整脉冲。
更多详细见:
我的旋转编码器不能使用按键,本博客就不涉及按键检测了
模块接线
旋转编码器电路
按键电路内部没接,C相内部已经已接入GND
旋转编码器 | STM32F103C8T6 |
---|---|
VCC | 3.3V |
GND | GND |
A | PB0 |
B | PB1 |
正交编码器控制原理
正交编码器正反转由自己定义
A、B两相空闲状态都为高电平,编码器旋转时都输出方波,但两个信号有90°相位差
注:下面图形中的波形不是从空闲状态开始的
正转
当是顺时针旋转时,B相超前A相90度,即A相为下降沿时,B相为高电平;A相为上升沿时,B相为低电平
反转
当是逆时针旋转时,A相超前B相90度,即A相为下降沿时,B相为低电平;A相为上升沿时,B相为高电平。
编码器正交波形动态展示:视频时间段为00:50至1:57 旋转编码器|从零开始认识各种传感器_哔哩哔哩_bilibili
模块代码
思路一:A相下降沿进入中断函数,判断B相的高低电平确定编码器旋转方向。
思路二:A相和B相下降沿都进入中断函数,判断另一相的高低电平确定编码器旋转方向。
思路三:A相的上升沿和下降沿都进入中断函数,在A相下降沿时记录B相电平,A相上升沿时再记录B相电平,判断B相电平在这期间是上升沿还是下降沿以确定编码器旋转方向。(推荐此法)
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
Encoder.c
该代码实现思路二,消抖效果不佳,请见思路三消抖代码
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
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_PortSourceGPIOB, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, 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 = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
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);
}
}
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);
}
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t Num;
int main(void)
{
OLED_Init();
Encoder_Init();
OLED_ShowString(1, 1, "Num:");
while (1)
{
Num += Encoder_Get();
OLED_ShowSignedNum(1, 5, Num, 5);
}
}
消抖代码
A相下降沿消抖:A相和B相默认输出高电平,先检测A相下降沿,若发生抖动,不断进入上升沿和下降沿的if判断,但是这时(A相下降沿时),B相电平不会发生变化,不会执行下面的代码。这样A相下降沿时A相电平的抖动就过滤了。
//判断A相低电平时,B相边沿变化则确定编码器转向,若不变化则过滤计数,用来消除A相和B相边沿变化的抖动
if(B_level==1 && BOUT == 0) //B相为下降沿
{
Encoder_Count++;//顺时针转
}
if(B_level==0 && BOUT == 1)//B相为上升沿
{
Encoder_Count--;//逆时针转
}
B相电平变化的抖动:检测到A下降沿后,若B相下降沿则正转,上升沿则反转。 B相电平变化也会有抖动。但并不影响程序对B相电平检测,也不阻塞程序。A相下降沿时,if判断会将A相下降沿时最后的B相电平存储,A相上升沿时,if会检查B相电平并判断B相电平变化确定编码器计数方向。
A相上升沿抖动:和A相下降沿消抖类似。A相上升沿时,B相电平不会发生变化,不会执行上面的if语句,不计数
Encoder.c
该代码实现思路三,消抖效果好
#include "stm32f10x.h"
int16_t Encoder_Count;//计数次数
#define AOUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0)//编码器A相输出
#define BOUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)//编码器B相输出
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
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_PortSourceGPIOB, GPIO_PinSource0);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_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);
}
int16_t Encoder_Get(void)
{
int16_t Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
//检测到A相信号上升沿和下降沿进入中断函数
void EXTI0_IRQHandler(void)
{
static uint8_t B_level;
//B_level:A相下降沿时B相电平,范围0~1;
//A相下降沿判断下面逻辑
if(AOUT == 0)//A相下降沿触发中断
{
B_level=0;//默认读取B相电平为0,记录A相下降沿时B相电平
if(BOUT == 1)//若为B相高电平则B_level置1
{
B_level=1;
}
}
//A相上升沿时判断下面逻辑
if(AOUT == 1)
{
//判断A相低电平时,B相边沿变化则确定编码器转向,若不变化则视为抖动,过滤计数
if(B_level==1 && BOUT == 0) //B相为下降沿
{
Encoder_Count++;//顺时针转
}
if(B_level==0 && BOUT == 1)//B相为上升沿
{
Encoder_Count--;//逆时针转
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
参考博客