旋转编码器的原理
-
旋转编码器通常有三个引脚:A、B 和 C(公共端)。
-
顺时针旋转时,A引脚和B引脚会输出一个正交编码的信号(A引脚的相位领先B引脚)。
-
逆时针旋转时,A引脚和B引脚的相位关系会反转(B引脚的相位领先A引脚)。
-
通过检测A和B引脚的信号变化,可以判断旋转方向和旋转速度。
信号变化和中断处理
- A引脚和B引脚的信号变化是正交的,即每次旋转会有四种状态变化:
-
顺时针:A上升,B不变 -> A不变,B上升 -> A下降,B不变 -> A不变,B下降
-
逆时针:B上升,A不变 -> B不变,A上升 -> B下降,A不变 -> B不变,A下降
- 通过设置A和B引脚的中断,可以捕捉到这些状态变化。
双边沿中断的必要性
-
仅设置一个引脚的上升沿或下降沿中断,会漏记另一引脚的状态变化,导致计数错误。
-
当设置为双边沿触发时,可以捕获每个引脚的上升沿和下降沿变化,从而避免漏记。
双边沿中断的实现
-
配置A和B引脚的中断为双边沿触发,即上升沿和下降沿都触发中断。
-
在中断服务程序中,分别处理上升沿和下降沿的情况,读取另一个引脚的状态,判断旋转方向。
重要代码示例
旋转编码器初始化代码
#include "stm32f10x.h" // Device header
int16_t Encoder_Count;
void Encoder_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Enable GPIOB clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Enable AFIO clock
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); // Initialize PB0 and PB1 as input pull-up
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); // Map EXTI Line0 to PB0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // Map EXTI Line1 to PB1
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_Falling; // Trigger on both edges
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);
}
中断服务程序
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) == SET) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET) {
Encoder_Count++; // 顺时针旋转
} else {
Encoder_Count--; // 逆时针旋转
}
EXTI_ClearITPendingBit(EXTI_Line0); // Clear interrupt flag
}
}
void EXTI1_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line1) == SET) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == Bit_RESET) {
Encoder_Count--; // 逆时针旋转
} else {
Encoder_Count++; // 顺时针旋转
}
EXTI_ClearITPendingBit(EXTI_Line1); // Clear interrupt flag
}
}
计数获取函数
int16_t Encoder_Get(void) {
int16_t temp;
temp = Encoder_Count;
Encoder_Count = 0;
return temp;
}
总结
-
旋转编码器的信号特点:A和B引脚输出正交编码信号。
-
中断处理的必要性:需要使用双边沿中断,以确保捕获每一次状态变化,避免漏记。
-
通过双边沿中断捕获:同时设置A和B引脚的中断,并在中断服务程序中处理每个引脚的状态变化,可以确保准确计数。
这种方式可以最大限度地减少漏记的可能性,并提高旋转编码器的计数精度。通过这些讨论和代码示例,解决了在高速旋转时可能发生的计数错误问题。