
目录
[一、E6B2-CWZ1X 核心工作原理](#一、E6B2-CWZ1X 核心工作原理)
[1. 编码器基础特性](#1. 编码器基础特性)
[2. 正交编码核心原理(A/B 相)](#2. 正交编码核心原理(A/B 相))
[3. Z 相(零位脉冲)作用](#3. Z 相(零位脉冲)作用)
[4. NPN 集电极开路输出特性](#4. NPN 集电极开路输出特性)
前言
最近用到了STM32驱动编码器(E6B2-CWZ1X)传感器模块,我这里用的STM32是STM32F407IGT6,这个模块作为编码器使用还是挺不错的,精度也够高,所以记录一下。
一、E6B2-CWZ1X 核心工作原理
E6B2-CWZ1X 是欧姆龙经典的增量式旋转编码器 ,核心用于检测旋转的角度、速度、方向,广泛应用于电机闭环控制、位置检测等场景。
1. 编码器基础特性
| 特性 | 说明 |
|---|---|
| 输出类型 | NPN 集电极开路输出(需外接上拉电阻到 3.3V/5V,兼容 STM32 电平) |
| 输出相 | A 相、B 相(正交脉冲)+ Z 相(零位脉冲,每转 1 个) |
| 分辨率 | 常见 100P/R、200P/R、360P/R、500P/R、1000P/R(每转脉冲数,可四倍频) |
| 供电电压 | DC5~12V(推荐 5V 供电,和 STM32 共地) |
| 响应频率 | 最高 100kHz(满足大部分低速 / 中速旋转场景) |
2. 正交编码核心原理(A/B 相)
E6B2-CWZ1X 的 A、B 相输出相位差 90° 的方波脉冲(正交信号),通过相位差判断旋转方向,通过脉冲数计算旋转角度 / 圈数:
- 正转:A 相超前 B 相 90°(比如 A 相上升沿时,B 相为低电平);
- 反转:B 相超前 A 相 90°(比如 A 相上升沿时,B 相为高电平);
- 脉冲计数:每一个 A/B 相的跳变都代表旋转了一个最小角度(分辨率越高,角度精度越高)。
方向判断示意图
3. Z 相(零位脉冲)作用
Z 相每旋转一圈输出1 个窄脉冲,用于:
- 清零计数寄存器,实现 "绝对零位" 校准;
- 计算旋转的总圈数(每检测到 1 个 Z 相脉冲,圈数 + 1/-1)。
4. NPN 集电极开路输出特性
E6B2-CWZ1X 的输出引脚为 NPN 三极管集电极,默认悬空,需外接4.7KΩ 上拉电阻到 3.3V(STM32 电平):
- 编码器输出低电平:三极管导通,引脚拉到 GND;
- 编码器输出高电平:三极管截止,引脚通过上拉电阻到 3.3V;
- 无外部上拉时,引脚电平不确定,无法检测脉冲。
二、接线方式
| E6B2-CWZ1X 引脚 | STM32 引脚 | 硬件说明 |
|---|---|---|
| VCC | DC5V | 编码器供电(不能接 3.3V,需 5V) |
| GND | STM32 GND | 必须共地,否则脉冲信号不稳定 |
| A 相 | PA0(TIM2_CH1) | 接 4.7KΩ 上拉电阻到 3.3V,NPN 输出需上拉 |
| B 相 | PA1(TIM2_CH2) | 同上 |
| Z 相 | PA2(EXTI2) | 同上(可选,用于零位校准) |
三、软件程序
头文件(encoder_e6b2.h)
#ifndef __ENCODER_E6B2_H
#define __ENCODER_E6B2_H
#include "stm32f4xx.h"
// ==================== 编码器参数配置 ====================
#define ENCODER_RESOLUTION 1000 // E6B2-CWZ1X分辨率(P/R,根据实际型号修改)
#define ENCODER_TIM TIM2 // 选用的定时器(TIM2/TIM3/TIM4等支持编码器模式)
#define ENCODER_TIM_CLK RCC_APB1Periph_TIM2
#define ENCODER_PPR (ENCODER_RESOLUTION * 4) // 四倍频后每转脉冲数(提高精度)
// ==================== 引脚定义 ====================
// A相:TIM2_CH1 → PA0
#define ENCODER_A_GPIO_PORT GPIOA
#define ENCODER_A_GPIO_PIN GPIO_Pin_0
#define ENCODER_A_GPIO_CLK RCC_AHB1Periph_GPIOA
#define ENCODER_A_GPIO_AF GPIO_AF_TIM2
#define ENCODER_A_GPIO_SRC GPIO_PinSource0
// B相:TIM2_CH2 → PA1
#define ENCODER_B_GPIO_PORT GPIOA
#define ENCODER_B_GPIO_PIN GPIO_Pin_1
#define ENCODER_B_GPIO_CLK RCC_AHB1Periph_GPIOA
#define ENCODER_B_GPIO_AF GPIO_AF_TIM2
#define ENCODER_B_GPIO_SRC GPIO_PinSource1
// Z相:EXTI2 → PA2(可选)
#define ENCODER_Z_GPIO_PORT GPIOA
#define ENCODER_Z_GPIO_PIN GPIO_Pin_2
#define ENCODER_Z_GPIO_CLK RCC_AHB1Periph_GPIOA
#define ENCODER_Z_EXTI_LINE EXTI_Line2
#define ENCODER_Z_EXTI_PORT EXTI_PortSourceGPIOA
#define ENCODER_Z_EXTI_PIN EXTI_PinSource2
#define ENCODER_Z_IRQn EXTI2_IRQn
// ==================== 函数声明 ====================
void Encoder_E6B2_Init(void); // 编码器初始化(TIM编码器模式+Z相中断)
int32_t Encoder_Get_Count(void); // 获取当前脉冲计数值
void Encoder_Clear_Count(void); // 清零脉冲计数值
float Encoder_Get_Angle(void); // 获取当前旋转角度(°)
int32_t Encoder_Get_Revolutions(void); // 获取旋转圈数(带方向)
int8_t Encoder_Get_Direction(void); // 获取旋转方向(1=正转,-1=反转,0=静止)
#endif
源文件(encoder_e6b2.c)
#include "encoder_e6b2.h"
#include "stm32f4xx_tim.h"
#include "stm32f4xx_exti.h"
#include "stm32f4xx_syscfg.h"
// 全局变量:旋转圈数(Z相中断更新)
static int32_t g_encoder_revolutions = 0;
/**
* @brief 配置编码器A/B相引脚(复用为TIM编码器接口)
*/
static void Encoder_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
// 1. 使能GPIO时钟
RCC_AHB1PeriphClockCmd(ENCODER_A_GPIO_CLK | ENCODER_B_GPIO_CLK | ENCODER_Z_GPIO_CLK, ENABLE);
// 2. 配置A/B相:复用功能输入(上拉,匹配NPN输出)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; // 复用功能
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; // 上拉(匹配NPN集电极开路)
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽(复用模式无影响)
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; // 高速(匹配100kHz脉冲)
// 配置A相
GPIO_InitStruct.GPIO_Pin = ENCODER_A_GPIO_PIN;
GPIO_Init(ENCODER_A_GPIO_PORT, &GPIO_InitStruct);
GPIO_PinAFConfig(ENCODER_A_GPIO_PORT, ENCODER_A_GPIO_SRC, ENCODER_A_GPIO_AF);
// 配置B相
GPIO_InitStruct.GPIO_Pin = ENCODER_B_GPIO_PIN;
GPIO_Init(ENCODER_B_GPIO_PORT, &GPIO_InitStruct);
GPIO_PinAFConfig(ENCODER_B_GPIO_PORT, ENCODER_B_GPIO_SRC, ENCODER_B_GPIO_AF);
// 3. 配置Z相:外部中断输入(上拉)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; // 输入模式
GPIO_InitStruct.GPIO_Pin = ENCODER_Z_GPIO_PIN;
GPIO_Init(ENCODER_Z_GPIO_PORT, &GPIO_InitStruct);
}
/**
* @brief 配置Z相外部中断(零位清零)
*/
static void Encoder_Z_EXTI_Config(void)
{
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 1. 使能SYSCFG时钟(外部中断必需)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
// 2. 连接EXTI线到GPIO引脚
SYSCFG_EXTILineConfig(ENCODER_Z_EXTI_PORT, ENCODER_Z_EXTI_PIN);
// 3. 配置EXTI
EXTI_InitStruct.EXTI_Line = ENCODER_Z_EXTI_LINE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;// 上升沿触发(Z相脉冲为高电平窄脉冲)
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
// 4. 配置NVIC(中断优先级)
NVIC_InitStruct.NVIC_IRQChannel = ENCODER_Z_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级0
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
/**
* @brief 配置TIM编码器模式
*/
static void Encoder_TIM_Config(void)
{
TIM_EncoderInitTypeDef TIM_EncoderInitStruct;
// 1. 使能定时器时钟
RCC_APB1PeriphClockCmd(ENCODER_TIM_CLK, ENABLE);
// 2. 复位定时器配置
TIM_DeInit(ENCODER_TIM);
// 3. 编码器模式配置
TIM_EncoderInitStruct.TIM_EncoderMode = TIM_EncoderMode_TI12; // 同时检测TI1/TI2跳变(四倍频)
TIM_EncoderInitStruct.TIM_IC1Polarity = TIM_IC1Polarity_Rising; // TI1上升沿有效
TIM_EncoderInitStruct.TIM_IC1Selection = TIM_IC1Selection_DirectTI; // 直接连接到TI1
TIM_EncoderInitStruct.TIM_IC1Prescaler = TIM_ICPSC_DIV1; // 不分频
TIM_EncoderInitStruct.TIM_IC1Filter = 0x0F; // 滤波(减少干扰,0x0F为最大滤波)
TIM_EncoderInitStruct.TIM_IC2Polarity = TIM_IC2Polarity_Rising; // TI2上升沿有效
TIM_EncoderInitStruct.TIM_IC2Selection = TIM_IC2Selection_DirectTI;
TIM_EncoderInitStruct.TIM_IC2Prescaler = TIM_ICPSC_DIV1;
TIM_EncoderInitStruct.TIM_IC2Filter = 0x0F;
TIM_EncoderInterfaceConfig(ENCODER_TIM, &TIM_EncoderInitStruct);
// 4. 设置计数器范围(32位,无需溢出处理)
TIM_SetAutoreload(ENCODER_TIM, 0xFFFFFFFF); // 最大计数值
TIM_SetCounter(ENCODER_TIM, 0); // 初始计数清零
// 5. 使能定时器
TIM_Cmd(ENCODER_TIM, ENABLE);
}
/**
* @brief E6B2-CWZ1X编码器初始化(整合所有配置)
*/
void Encoder_E6B2_Init(void)
{
Encoder_GPIO_Config(); // 引脚配置
Encoder_TIM_Config(); // 定时器编码器模式
Encoder_Z_EXTI_Config(); // Z相中断配置
}
/**
* @brief 获取当前脉冲计数值(带方向,正转+,反转-)
* @retval 32位计数值(范围:-2^31 ~ 2^31-1)
*/
int32_t Encoder_Get_Count(void)
{
// TIM2计数器为16位?F4的TIM2是32位,直接读取
return (int32_t)TIM_GetCounter(ENCODER_TIM);
}
/**
* @brief 清零脉冲计数值
*/
void Encoder_Clear_Count(void)
{
TIM_SetCounter(ENCODER_TIM, 0);
}
/**
* @brief 获取当前旋转角度(°)
* @retval 角度值(范围:-360° ~ +∞,正转为正,反转为负)
*/
float Encoder_Get_Angle(void)
{
int32_t count = Encoder_Get_Count();
// 角度 = 计数值 / 每转脉冲数 * 360°
return (float)count / ENCODER_PPR * 360.0f;
}
/**
* @brief 获取旋转圈数(带方向)
* @retval 圈数(正转+,反转-)
*/
int32_t Encoder_Get_Revolutions(void)
{
return g_encoder_revolutions;
}
/**
* @brief 获取旋转方向
* @retval 1:正转,-1:反转,0:静止
*/
int8_t Encoder_Get_Direction(void)
{
// TIM_GetFlagStatus:判断计数器方向(TIM_FLAG_DIR:方向标志位)
if(TIM_GetFlagStatus(ENCODER_TIM, TIM_FLAG_DIR) != RESET)
{
return -1; // 反转(计数器递减)
}
else
{
// 检测是否静止(计数值无变化,可选)
static int32_t last_count = 0;
int32_t current_count = Encoder_Get_Count();
if(current_count == last_count)
{
last_count = current_count;
return 0; // 静止
}
last_count = current_count;
return 1; // 正转(计数器递增)
}
}
/**
* @brief Z相中断服务函数(零位清零,更新圈数)
*/
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(ENCODER_Z_EXTI_LINE) != RESET)
{
// 根据旋转方向更新圈数
if(Encoder_Get_Direction() == 1)
{
g_encoder_revolutions++;
}
else if(Encoder_Get_Direction() == -1)
{
g_encoder_revolutions--;
}
// 清零脉冲计数(回到零位)
Encoder_Clear_Count();
// 清除中断标志位
EXTI_ClearITPendingBit(ENCODER_Z_EXTI_LINE);
}
}
测试代码(main.c)
#include "stm32f4xx.h"
#include "encoder_e6b2.h"
#include "delay.h"
#include "stdio.h"
// 重定向printf到串口1(用于打印编码器数据)
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
int main(void)
{
// 系统初始化
SystemInit();
// 延时初始化(SysTick)
SysTime_Init();
// 编码器初始化
Encoder_E6B2_Init();
// 清零初始计数
Encoder_Clear_Count();
while(1)
{
// 读取编码器数据
int32_t count = Encoder_Get_Count();
float angle = Encoder_Get_Angle();
int32_t revolutions = Encoder_Get_Revolutions();
int8_t dir = Encoder_Get_Direction();
// 串口打印(波特率115200)
printf("计数值:%d | 角度:%.2f° | 圈数:%d | 方向:%s\r\n",
count, angle, revolutions,
dir == 1 ? "正转" : (dir == -1 ? "反转" : "静止"));
// 500ms打印一次
Delay_Ms(500);
}
}
关键注意事项!!!!
- 定时器选择 :
- STM32F4 的 TIM2/TIM3/TIM4/TIM5 是 32 位定时器,适合长时间计数(无溢出);TIM1/TIM8 是 16 位,需注意溢出处理。
- 滤波配置 :
TIM_EncoderInitStruct.TIM_IC1Filter = 0x0F;:增加输入滤波,减少电磁干扰导致的误计数(工业场景必配)。
- 中断优先级 :
- Z 相中断优先级需高于普通任务,避免零位脉冲丢失。
- 供电与共地 :
- 编码器 VCC 接 5V,STM32 接 3.3V,必须共地,否则脉冲信号会有电平偏移。
总结
- E6B2-CWZ1X 核心原理:通过 A/B 相正交脉冲的相位差判断旋转方向,脉冲数计算角度 / 圈数,Z 相实现零位校准,NPN 集电极开路输出需上拉;
- STM32 驱动核心:使用定时器编码器接口自动解码正交脉冲,无需手动处理中断,CPU 占用低,精度高;
- 关键要点:引脚配置为上拉输入(适配 NPN 输出),开启四倍频提升精度,Z 相中断实现零位清零,32 位定时器避免计数溢出。