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

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

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



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

这个旋转编码器的轴是可以按下去的;轴的外侧是白色的编码盘,它也是一系列光栅一样的东西,只不过这是金属触电,在旋转时,依次接通和断开两边的触电;这个金属盘的位置是经过设计的,它能让两侧触电的通断产生一个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