STM32正交编码器接口指南

STM32正交编码器解决方案,包含硬件连接、定时器配置、代码实现和速度计算,适用于F1/F4/G0等主流系列。

一、硬件连接与原理

1.1 正交编码器工作原理

复制代码
编码器输出信号(A相和B相):
A相: ___|‾‾‾|___|‾‾‾|___|‾‾‾|___
B相: ___|‾‾‾|___|‾‾‾|___|‾‾‾|___
        ↑   ↑   ↑   ↑   ↑   ↑
        位置计数: +1  +1  +1  +1  +1

正转时:A相超前B相90°
反转时:B相超前A相90°

Z相(可选):每转一个脉冲,用于零点校准

1.2 硬件连接

复制代码
STM32F103C8T6    增量式编码器
PA0(TIM2_CH1) ←--- A相输出
PA1(TIM2_CH2) ←--- B相输出
PA2(TIM2_CH3) ←--- Z相输出(可选)
GND          ←--- GND
+5V/+3.3V    ←--- VCC(根据编码器规格)

注意:
1. 编码器电压要与STM32 IO电平匹配
2. 长线传输建议加100nF滤波电容
3. 开漏输出编码器需要上拉电阻

二、STM32CubeMX配置

2.1 定时器配置(以TIM2为例)

复制代码
TIM2 Configuration:
├── Clock Source: Internal Clock
├── Channel 1: Encoder Mode
├── Channel 2: Encoder Mode  
├── Channel 3: Encoder Mode (Z相,可选)
└── Parameter Settings:
    ├── Counter Settings:
    │   ├── Prescaler: 0
    │   ├── Counter Mode: Up
    │   ├── Counter Period: 65535 (16位最大值)
    │   └── Repetition Counter: 0
    └── Encoder Mode:
        ├── Encoder Mode: Encoder Mode TI1 and TI2
        ├── Encoder Polarity: Rising Edge
        └── Input Filter: 6 (根据噪声情况调整,0-15)

2.2 GPIO配置

复制代码
PA0: Alternate Function Push Pull, No pull-up/pull-down
PA1: Alternate Function Push Pull, No pull-up/pull-down  
PA2: Alternate Function Push Pull, No pull-up/pull-down

三、完整代码实现

3.1 编码器驱动头文件 (encoder.h)

c 复制代码
#ifndef __ENCODER_H
#define __ENCODER_H

#include "stm32f1xx_hal.h"

// 编码器参数定义
#define ENCODER_RESOLUTION     1000    // 编码器线数(每转脉冲数)
#define ENCODER_REDUCTION      4       // 4倍频(正交编码)
#define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * ENCODER_REDUCTION)
#define ENCODER_TIMER_MAX      65535   // 16位定时器最大值
#define ENCODER_OVERFLOW       32768   // 溢出阈值

// 编码器数据结构体
typedef struct {
    TIM_HandleTypeDef *htim;       // 定时器句柄
    int32_t position;              // 当前位置(累计计数)
    int32_t last_position;         // 上次位置
    int32_t speed;                 // 当前速度(计数/秒)
    int32_t last_timer_cnt;        // 上次定时器计数值
    uint32_t last_time;            // 上次采样时间(ms)
    float rpm;                     // 转速(RPM)
    uint8_t direction;             // 旋转方向:0=停止,1=正转,2=反转
} Encoder_HandleTypeDef;

// 函数声明
HAL_StatusTypeDef Encoder_Init(Encoder_HandleTypeDef *hencoder, TIM_HandleTypeDef *htim);
void Encoder_Start(Encoder_HandleTypeDef *hencoder);
void Encoder_Stop(Encoder_HandleTypeDef *hencoder);
void Encoder_Reset(Encoder_HandleTypeDef *hencoder);
int32_t Encoder_GetPosition(Encoder_HandleTypeDef *hencoder);
float Encoder_GetAngle(Encoder_HandleTypeDef *hencoder, float gear_ratio);
int32_t Encoder_GetSpeed(Encoder_HandleTypeDef *hencoder);
float Encoder_GetRPM(Encoder_HandleTypeDef *hencoder);
void Encoder_Update(Encoder_HandleTypeDef *hencoder);
void Encoder_IRQHandler(Encoder_HandleTypeDef *hencoder);

#endif /* __ENCODER_H */

3.2 编码器驱动实现 (encoder.c)

c 复制代码
#include "encoder.h"
#include "math.h"

// 编码器初始化
HAL_StatusTypeDef Encoder_Init(Encoder_HandleTypeDef *hencoder, TIM_HandleTypeDef *htim)
{
    hencoder->htim = htim;
    hencoder->position = 0;
    hencoder->last_position = 0;
    hencoder->speed = 0;
    hencoder->last_timer_cnt = 0;
    hencoder->last_time = HAL_GetTick();
    hencoder->rpm = 0.0f;
    hencoder->direction = 0;
    
    // 配置定时器为编码器模式
    TIM_Encoder_InitTypeDef encoder_config = {0};
    
    encoder_config.EncoderMode = TIM_ENCODERMODE_TI12;  // 使用TI1和TI2
    encoder_config.IC1Polarity = TIM_ICPOLARITY_RISING;
    encoder_config.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    encoder_config.IC1Prescaler = TIM_ICPSC_DIV1;
    encoder_config.IC1Filter = 6;  // 输入滤波器
    
    encoder_config.IC2Polarity = TIM_ICPOLARITY_RISING;
    encoder_config.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    encoder_config.IC2Prescaler = TIM_ICPSC_DIV1;
    encoder_config.IC2Filter = 6;
    
    if (HAL_TIM_Encoder_Init(htim, &encoder_config) != HAL_OK) {
        return HAL_ERROR;
    }
    
    // 配置Z相(如果存在)
    if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) {
        TIM_IC_InitTypeDef sConfigIC = {0};
        sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
        sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
        sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
        sConfigIC.ICFilter = 0;
        HAL_TIM_IC_ConfigChannel(htim, &sConfigIC, TIM_CHANNEL_3);
    }
    
    return HAL_OK;
}

// 启动编码器
void Encoder_Start(Encoder_HandleTypeDef *hencoder)
{
    HAL_TIM_Encoder_Start(hencoder->htim, TIM_CHANNEL_ALL);
    
    // 启动Z相输入捕获(如果存在)
    if (hencoder->htim->Instance == TIM2) {
        HAL_TIM_IC_Start(hencoder->htim, TIM_CHANNEL_3);
    }
    
    // 初始化计数器
    __HAL_TIM_SET_COUNTER(hencoder->htim, 0);
    hencoder->last_timer_cnt = 0;
    hencoder->last_time = HAL_GetTick();
}

// 停止编码器
void Encoder_Stop(Encoder_HandleTypeDef *hencoder)
{
    HAL_TIM_Encoder_Stop(hencoder->htim, TIM_CHANNEL_ALL);
    if (hencoder->htim->Instance == TIM2) {
        HAL_TIM_IC_Stop(hencoder->htim, TIM_CHANNEL_3);
    }
}

// 复位编码器
void Encoder_Reset(Encoder_HandleTypeDef *hencoder)
{
    __HAL_TIM_SET_COUNTER(hencoder->htim, 0);
    hencoder->position = 0;
    hencoder->last_position = 0;
    hencoder->speed = 0;
    hencoder->last_timer_cnt = 0;
    hencoder->rpm = 0.0f;
}

// 获取当前位置(处理溢出)
int32_t Encoder_GetPosition(Encoder_HandleTypeDef *hencoder)
{
    uint16_t current_cnt = __HAL_TIM_GET_COUNTER(hencoder->htim);
    int16_t diff = (int16_t)(current_cnt - hencoder->last_timer_cnt);
    
    // 处理溢出
    if (diff > ENCODER_OVERFLOW) {
        // 向下溢出
        hencoder->position -= (ENCODER_TIMER_MAX - current_cnt + hencoder->last_timer_cnt + 1);
    } else if (diff < -ENCODER_OVERFLOW) {
        // 向上溢出
        hencoder->position += (current_cnt + ENCODER_TIMER_MAX - hencoder->last_timer_cnt + 1);
    } else {
        // 正常情况
        hencoder->position += diff;
    }
    
    hencoder->last_timer_cnt = current_cnt;
    return hencoder->position;
}

// 获取角度(度)
float Encoder_GetAngle(Encoder_HandleTypeDef *hencoder, float gear_ratio)
{
    int32_t position = Encoder_GetPosition(hencoder);
    float total_counts_per_rev = ENCODER_TOTAL_RESOLUTION * gear_ratio;
    return (float)position * 360.0f / total_counts_per_rev;
}

// 获取速度(计数/秒)
int32_t Encoder_GetSpeed(Encoder_HandleTypeDef *hencoder)
{
    uint32_t current_time = HAL_GetTick();
    uint32_t time_diff = current_time - hencoder->last_time;
    
    if (time_diff > 0) {
        int32_t position_diff = Encoder_GetPosition(hencoder) - hencoder->last_position;
        hencoder->speed = (position_diff * 1000) / time_diff;  // 转换为计数/秒
        
        hencoder->last_position = Encoder_GetPosition(hencoder);
        hencoder->last_time = current_time;
    }
    
    return hencoder->speed;
}

// 获取转速(RPM)
float Encoder_GetRPM(Encoder_HandleTypeDef *hencoder)
{
    int32_t speed = Encoder_GetSpeed(hencoder);
    hencoder->rpm = (float)speed * 60.0f / ENCODER_TOTAL_RESOLUTION;
    
    // 判断方向
    if (hencoder->rpm > 0.1f) {
        hencoder->direction = 1;  // 正转
    } else if (hencoder->rpm < -0.1f) {
        hencoder->direction = 2;  // 反转
    } else {
        hencoder->direction = 0;  // 停止
    }
    
    return hencoder->rpm;
}

// 更新编码器状态(在主循环中定期调用)
void Encoder_Update(Encoder_HandleTypeDef *hencoder)
{
    static uint32_t last_update_time = 0;
    uint32_t current_time = HAL_GetTick();
    
    // 每10ms更新一次
    if (current_time - last_update_time >= 10) {
        Encoder_GetRPM(hencoder);
        last_update_time = current_time;
    }
}

// Z相中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) {
        // Z相触发,重置位置计数
        // 这里可以通过全局变量通知主程序
        __HAL_TIM_SET_COUNTER(htim, 0);
    }
}

3.3 主程序集成 (main.c)

c 复制代码
#include "main.h"
#include "encoder.h"

// 全局变量
TIM_HandleTypeDef htim2;
Encoder_HandleTypeDef encoder1;

// 函数声明
void SystemClock_Config(void);
static void MX_TIM2_Init(void);
static void MX_GPIO_Init(void);

int main(void)
{
    // HAL初始化
    HAL_Init();
    SystemClock_Config();
    
    // GPIO初始化
    MX_GPIO_Init();
    
    // 定时器初始化
    MX_TIM2_Init();
    
    // 编码器初始化
    if (Encoder_Init(&encoder1, &htim2) == HAL_OK) {
        printf("编码器初始化成功!\n");
    } else {
        printf("编码器初始化失败!\n");
        Error_Handler();
    }
    
    // 启动编码器
    Encoder_Start(&encoder1);
    
    // 主循环
    while (1) {
        // 更新编码器数据
        Encoder_Update(&encoder1);
        
        // 获取编码器数据
        int32_t position = Encoder_GetPosition(&encoder1);
        float angle = Encoder_GetAngle(&encoder1, 1.0f);  // 无减速比
        float rpm = Encoder_GetRPM(&encoder1);
        
        // 每500ms打印一次数据
        static uint32_t last_print_time = 0;
        if (HAL_GetTick() - last_print_time >= 500) {
            printf("位置: %ld, 角度: %.2f°, 转速: %.2f RPM, 方向: %s\n", 
                   position, angle, rpm, 
                   encoder1.direction == 0 ? "停止" : 
                   encoder1.direction == 1 ? "正转" : "反转");
            last_print_time = HAL_GetTick();
        }
        
        HAL_Delay(10);
    }
}

// TIM2初始化函数
static void MX_TIM2_Init(void)
{
    __HAL_RCC_TIM2_CLK_ENABLE();
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 65535;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
        Error_Handler();
    }
}

// GPIO初始化
static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // PA0, PA1, PA2 配置为复用功能
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 系统时钟配置(根据实际晶振调整)
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    // 使能HSE
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;  // 8MHz * 9 = 72MHz
    
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }
    
    // 配置系统时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                                  RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }
}

四、高级功能扩展

4.1 多编码器支持 (multi_encoder.c)

c 复制代码
// 支持多个编码器
#define MAX_ENCODERS 2

typedef struct {
    Encoder_HandleTypeDef encoders[MAX_ENCODERS];
    uint8_t active_count;
} MultiEncoder_HandleTypeDef;

MultiEncoder_HandleTypeDef multi_encoder;

// 初始化多个编码器
HAL_StatusTypeDef MultiEncoder_Init(void)
{
    // 编码器1:TIM2
    if (Encoder_Init(&multi_encoder.encoders[0], &htim2) != HAL_OK) {
        return HAL_ERROR;
    }
    
    // 编码器2:TIM3(如果有第二个编码器)
    if (Encoder_Init(&multi_encoder.encoders[1], &htim3) != HAL_OK) {
        return HAL_ERROR;
    }
    
    multi_encoder.active_count = 2;
    return HAL_OK;
}

// 更新所有编码器
void MultiEncoder_Update(void)
{
    for (int i = 0; i < multi_encoder.active_count; i++) {
        Encoder_Update(&multi_encoder.encoders[i]);
    }
}

4.2 速度滤波 (encoder_filter.c)

c 复制代码
// 低通滤波器,平滑速度数据
typedef struct {
    float alpha;           // 滤波系数 (0-1)
    float filtered_speed;  // 滤波后的速度
    float raw_speed;       // 原始速度
} LowPassFilter;

// 初始化滤波器
void Filter_Init(LowPassFilter *filter, float cutoff_freq, float sample_rate)
{
    float RC = 1.0f / (2.0f * 3.1415926f * cutoff_freq);
    float dt = 1.0f / sample_rate;
    filter->alpha = dt / (RC + dt);
    filter->filtered_speed = 0.0f;
    filter->raw_speed = 0.0f;
}

// 应用滤波
float Filter_Apply(LowPassFilter *filter, float input)
{
    filter->raw_speed = input;
    filter->filtered_speed = filter->alpha * input + 
                           (1.0f - filter->alpha) * filter->filtered_speed;
    return filter->filtered_speed;
}

参考代码 STM32正交编码器 www.youwenfan.com/contentcsv/135914.html

五、调试与故障排除

5.1 常见问题及解决方案

问题 原因 解决方法
计数不稳定 接触不良或噪声干扰 1. 检查接线 2. 增加输入滤波器 3. 添加去耦电容
方向错误 A/B相接反 交换A/B相接线或反转计数方向
计数丢失 转速过快 1. 提高定时器时钟频率 2. 减少输入滤波器
位置漂移 未处理溢出 使用32位变量累计计数,正确处理溢出
Z相不触发 电平不匹配 检查Z相输出电压和STM32 IO电平

5.2 调试工具函数

c 复制代码
// 调试函数:打印编码器状态
void Encoder_PrintDebug(Encoder_HandleTypeDef *hencoder)
{
    printf("=== 编码器调试信息 ===\n");
    printf("定时器: TIM%d\n", hencoder->htim->Instance == TIM2 ? 2 : 3);
    printf("当前计数: %d\n", __HAL_TIM_GET_COUNTER(hencoder->htim));
    printf("累计位置: %ld\n", hencoder->position);
    printf("速度: %ld 计数/秒\n", hencoder->speed);
    printf("转速: %.2f RPM\n", hencoder->rpm);
    printf("方向: %s\n", hencoder->direction == 0 ? "停止" : 
                         hencoder->direction == 1 ? "正转" : "反转");
    printf("=====================\n");
}

// 测试编码器响应
void Encoder_TestResponse(Encoder_HandleTypeDef *hencoder)
{
    printf("手动旋转编码器,观察计数变化...\n");
    int32_t last_pos = Encoder_GetPosition(hencoder);
    
    for (int i = 0; i < 10; i++) {
        HAL_Delay(500);
        int32_t current_pos = Encoder_GetPosition(hencoder);
        printf("位置变化: %ld -> %ld (Δ=%ld)\n", 
               last_pos, current_pos, current_pos - last_pos);
        last_pos = current_pos;
    }
}

六、性能优化建议

6.1 定时器配置优化

c 复制代码
// 提高定时器时钟频率
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_TIM_SET_CLOCKPRESCALER(&htim2, TIM_CLOCKPRESCALER_DIV1);

// 使用32位定时器(如果可用)
#if defined(TIM2) && defined(STM32F4xx)
// STM32F4的TIM2是32位定时器,可以减少溢出处理
#endif

6.2 中断方式更新速度

c 复制代码
// 使用定时器中断定期更新速度,避免主循环延迟
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM6) {  // 使用基本定时器作为时基
        Encoder_Update(&encoder1);
    }
}
相关推荐
lin135380675731 小时前
AH810L输入 48~54V 转 5V/100mA 完整方案
嵌入式硬件·物联网
星夜夏空992 小时前
STM32单片机学习(36) —— RTC
stm32·单片机·学习
森利威尔电子-2 小时前
森利威尔 SL3042 | 9V-120V 宽压输入 1.25-50V 可调输出 峰值 10A 电源芯片
单片机·嵌入式硬件·电源芯片·降压恒压芯片
金线银线还是铜线?2 小时前
国产微能量收集PMIC芯片MF9005/MF9006如何选型?
嵌入式硬件·物联网·太阳能
Szime11 小时前
高速 ADC 国产替代选型:通信、雷达、仪器仪表项目要看哪些参数?
单片机·嵌入式硬件·fpga开发
灯琰113 小时前
# STM32L051K6U6 IAP Bootloader 开发踩坑实录
stm32
菜鸟的学习日记、13 小时前
GPIO的几种模式——以STM32为例
stm32·单片机·嵌入式硬件·gpio
辰哥单片机设计14 小时前
STM32智能睡眠检测系统
stm32·单片机·嵌入式硬件
隔窗听雨眠15 小时前
在STM32上跑通TinyML:从模型训练到推理优化的完整实战指南
stm32·单片机·嵌入式硬件