1.旋转编码器原理
旋转编码器旋转的时候A、B两个引脚会产生相位差90度的方波,这个地方要尤其注意:
1.方波的相位差决定了中断触发方式,是上升沿还是下降沿。
2.方波的相位差决定了判断逻辑。
逻辑:
1.A引脚的上升沿对应B引脚的低电平时,编码器是正转
2.B引脚的上升沿对应A引脚的低电平时,编码器是反转
因此,中断的触发方式我们选择上升沿触发,设置两个中断,一个由外部输入A触发,另一个由外部输入B触发,一旦监测到上升沿就进入中断程序,判断两个引脚的高低电平,从而判断出编码器是在正转还是反转。
2.新建工程(略)
3.硬件驱动
Encoder.c中首先要时钟使能和配置中断,使用中断的配置顺序见前面博文。主要是:
1.配置RCC时钟;
2.配置GPIO,端口配置为输入模式;
3.配置AFIO,选择GPIO链接到EXTI;
4.配置EXTI,选择边沿触发方式和触发的响应方式;
5.配置NVIC,配置中断优先级。
3.1配置RCC时钟
使能GPIOB和AFIO的时钟,因为编码器是接在了GPIOB0/1上,所以必须使能这一片区域。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//开启GPIOB的时钟,因为传感器引脚接在了GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//AFIO时钟开启
3.2配置GPIO
跟往常一样定义GPIO的结构体完成配置,GPIO_Mode选择上拉输入,使能的管脚是GPIOB_0和GPIOB_1。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//EXTI输入,推荐配置是浮空,上拉或下拉
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
3.3配置AFIO
其实就是AFIO中断线的定义,函数第一个参数是GPIO片区,我们用的是GPIOB,第二个参数是本片区的哪个管脚链接到AFIO,是0号和1号。(两个基佬???)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);//参见函数定义,实际上是用来选择GPIO_pin作为中断线,第一个参数是GPIO分区,
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
3.4配置EXTI
EXTI的配置也是采用结构体形式,首先是中断线,本次用了两个中断,分别为EXTI_Line0和EXTI_Line1。触发方式选择上升沿:EXTI_Trigger_Rsing。这个很重要
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_Rising;//触发中断方式,选择上升沿触发
EXTI_Init(&EXTI_InitStructure);
3.5.配置NVIC
因为由两个中断,因此NVIC配置两次,分组2方式,结构体只用定义一次,第二次配置NVIC的时候可以直接引用参数结构体。第一个中断的优先级是1,第二个是2.我现在还没整明白这一块。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先集分组定义
//调用NVIC初始化函数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;//指定NVIC的通道
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的通道
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStructure);
3.6定义中断函数
分别定义两个中断函数EXTI0_IRQHandler和EXTI1_IRQHandler。基本逻辑跟第一小节讲的一样,先判断第一个中断是不是被触发了,即A管脚是否是上升沿。A管脚接的是GPIOB_0,对应的中断0.然后判断B管脚是不是低电平。如果是那就是在正转,因此Encoder_Count计数加一,否则就减去1.这里注意,教程里老师讲错了。
void EXTI0_IRQHandler(void)//正转:A的上升沿对应B的低电平;反转:B的上升沿对应A的低电平。
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)//判断编码器A是否为高电平,中断是否触发
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) ==0)//判断编码器B是否为低电平
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line0);//如果是,则清空标志位
}
}
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)//判断编码器B是否为高电平,中断是否触发
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) ==0)//判断编码器A是否为低电平
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line1);//如果是,则清空标志位
}
}
3.7完整的Encoder.c
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;//定义带符号变量,因为涉及正转和反转
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//开启GPIOB的时钟,因为传感器引脚接在了GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//AFIO时钟开启
//EXTI和NVIC的时钟默认打开,不需要配置;NVIC是内核外设,不用开时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//EXTI输入,推荐配置是浮空,上拉或下拉
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//AFIO外设并没有分配专门的库函数,它的库函数跟GPIO在同一个文件中,GPIO.h中第350行开始就是AFIO的库函数了
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);//参见函数定义,实际上是用来选择GPIO_pin作为中断线,第一个参数是GPIO分区,
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
//第二是引脚号,
//第四步,配置EXTI
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_Rising;//触发中断方式,选择上升沿触发
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先集分组定义
//调用NVIC初始化函数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;//指定NVIC的通道
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的通道
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;
Temp = Encoder_Count;
Encoder_Count=0;
return Temp;
}
void EXTI0_IRQHandler(void)//正转:A的上升沿对应B的低电平;反转:B的上升沿对应A的低电平。
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)//判断编码器A是否为高电平,中断是否触发
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) ==0)//判断编码器B是否为低电平
{
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line0);//如果是,则清空标志位
}
}
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)//判断编码器B是否为高电平,中断是否触发
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) ==0)//判断编码器A是否为低电平
{
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line1);//如果是,则清空标志位
}
}
4.主函数
首先定义一个有符号的整型变量Num。在死循环中,调用Encoder_Get()函数,这个函数的具体定义在完整的中断函数中,作用是返回当前的计数值。
在死循环中的操作就是:新的旋转计数=已有的计数+Encoder_Get(),而这个Encoder_Get()的返回值,如果是正转就是+1,反转就是-1.
#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);
}
}