stm32系列之编码器功能

文章目录

一、stm32编码器功能检测原理

STM32编码器功能的检测原理核心是:通过定时器内置的「正交解码硬件逻辑」,无需CPU干预,自动检测增量式编码器A/B相正交脉冲的边沿和相位关系,实现旋转方向判断与脉冲计数。以下从「信号基础」→「硬件解码逻辑」→「计数/方向机制」逐层拆解,结合寄存器级原理讲透检测本质。

1、前置基础:增量式正交编码器的信号特性

STM32编码器检测的核心是适配「增量式正交编码器」的输出信号,这是理解检测原理的前提:

①. 正交信号的核心特征
  • 编码器旋转时输出A相、B相两路脉冲,相位差固定为90°(正交);
  • 旋转方向决定相位超前/滞后:
    • 正转:A相超前B相90°(如A相上升沿对应B相低电平);
    • 反转:B相超前A相90°(如A相上升沿对应B相高电平);
    • (可选)Z相:每转1圈输出1个脉冲,用于零位复位(STM32需额外用输入捕获处理)。
②. 脉冲与物理量的对应关系
  • 脉冲数 → 旋转角度/位置(如1000线编码器=4000脉冲/圈,1脉冲=0.09°);
  • 脉冲频率 → 旋转速度(单位时间内的脉冲数);
  • 相位关系 → 旋转方向。

2、STM32编码器检测的核心:定时器正交解码硬件

STM32的通用定时器(TIM2~TIM5)、高级定时器(TIM1/TIM8)内置正交解码逻辑(属于输入捕获单元的特殊模式),检测过程完全由硬件完成,CPU仅需读取结果(CNT寄存器、DIR位),极大降低CPU占用。

整个检测流程可拆解为5个核心步骤,对应硬件逻辑链:

复制代码
编码器A/B相信号 → GPIO复用映射 → 输入滤波 → 边沿检测 → 方向判断 → 计数更新
步骤1:信号输入与GPIO复用(物理层)

编码器的A/B相信号需接入STM32定时器的CH1/TI1、CH2/TI2引脚,且必须配置为定时器复用功能(AF)

  • GPIO模式:GPIO_MODE_AF_PP(复用推挽)+ GPIO_PULLUP/PULLDOWN(上拉/下拉,避免信号浮空);
  • 复用映射:不同定时器对应不同AF编号(如TIM2_CH1=PA0=AF1,TIM3_CH1=PA6=AF2),本质是将GPIO引脚的信号路由到定时器的输入捕获单元(IC1/IC2)。
步骤2:输入滤波(抗干扰层)

编码器信号易受电机/机械噪声干扰,STM32通过「数字滤波器」对输入脉冲做滤波处理:

  • 原理:定时器时钟(CK_INT)作为采样时钟,连续采样N次(滤波值IC1Filter/IC2Filter),只有连续N次采样结果一致,才认为是有效边沿;
  • 配置:IC1Filter=8 表示连续8次采样一致才判定为有效边沿(推荐8~16,平衡抗干扰与响应速度);
  • 寄存器:TIM_CCMR1 寄存器的 IC1F[3:0]/IC2F[3:0] 位配置滤波值。
步骤3:边沿检测(触发层)

定时器通过「输入捕获单元」检测A/B相的脉冲边沿,核心由「解码模式」(SMS位)和「输入极性」(IC1P/IC2P位)决定:

(1)解码模式(3种,TIM_SMCR寄存器的SMS[2:0]位)

这是检测原理的核心,决定哪些边沿触发计数、哪些信号判断方向:

解码模式 宏定义(HAL库) 检测逻辑 计数精度 适用场景
模式0 - 关闭编码器模式 -
模式1 TIM_ENCODERMODE_TI1 仅TI1(A相)的边沿触发计数,TI2(B相)电平判断方向 1倍频 单相信号+方向信号(非正交)
模式2 TIM_ENCODERMODE_TI2 仅TI2(B相)的边沿触发计数,TI1(A相)电平判断方向 1倍频 同上(反向)
模式3 TIM_ENCODERMODE_TI12 TI1/TI2(A/B相)的上升/下降沿都触发计数,相位关系判断方向 4倍频 正交编码器(主流)
(2)输入极性(TIM_CCER寄存器的IC1P/IC2P位)
  • TIM_ICPOLARITY_RISING:仅检测上升沿;
  • TIM_ICPOLARITY_FALLING:仅检测下降沿;
  • TIM_ICPOLARITY_BOTHEDGE:检测上升+下降沿(模式3下默认用此,实现4倍频)。
步骤4:方向判断(核心逻辑层)

STM32通过硬件逻辑自动判断旋转方向,无需软件计算,原理是「边沿时刻的另一相信号电平」:

模式3(TI12)+ 极性为上升沿为例:

触发边沿 另一相信号电平 方向判断 DIR位(TIM_CR1 计数行为
A相上升沿 B相低电平 正转 0(递增) CNT+1
A相上升沿 B相高电平 反转 1(递减) CNT-1
B相上升沿 A相高电平 正转 0(递增) CNT+1
B相上升沿 A相低电平 反转 1(递减) CNT-1

👉 本质:硬件在检测到A/B相边沿时,立即采样另一相的电平,根据正交关系(90°相位差)判定方向,并设置TIM_CR1寄存器的DIR位(0=递增/正转,1=递减/反转)。

步骤5:计数更新(结果层)

方向判断完成后,硬件自动更新「计数器寄存器(TIM_CNT)」:

  • 正转(DIR=0):每次有效边沿触发 → CNT += 1
  • 反转(DIR=1):每次有效边沿触发 → CNT -= 1
  • 计数范围:16位定时器(如TIM2)065535,32位定时器(如TIM2在F4/F7)04294967295;
  • 溢出处理:递增到最大值后变为0,递减到0后变为最大值(需软件/中断处理溢出)。

3、关键:模式3的4倍频计数原理(最高精度)

模式3是正交编码器的核心用法,能实现「4倍频计数」,极大提升检测精度,原理如下:

假设编码器为1000线(每转输出1000个A相脉冲、1000个B相脉冲):

  • 1倍频:仅检测A相上升沿 → 每转1000脉冲;
  • 4倍频:检测A相上升/下降沿 + B相上升/下降沿 → 每转4000脉冲(1000×4);

例:1000线编码器旋转1圈,模式3下CNT寄存器会增加/减少4000,对应角度分辨率从0.36°(1倍频)提升到0.09°(4倍频)。

4、寄存器层面的检测原理(实战关联)

所有检测逻辑最终映射到定时器寄存器,以下是核心寄存器与检测行为的对应关系:

寄存器 关键位 作用
TIM_SMCR SMS[2:0] 配置解码模式(0=关闭,1=TI1,2=TI2,3=TI12)
TIM_CCMR1 IC1F[3:0]/IC2F[3:0] 配置输入滤波值(抗干扰)
TIM_CCER IC1P/IC2P 配置输入极性(上升/下降/双边沿)
TIM_CR1 DIR 硬件自动设置,指示当前计数方向(0=递增,1=递减)
TIM_CNT 全部位 硬件自动更新,存储当前脉冲计数值
TIM_ARR 全部位 计数器最大值(溢出阈值,如65535)

5、检测原理的实战避坑(原理→应用)

①. 方向判断反向

  • 原理:A/B相相位关系判断错误;
  • 解决:① 交换编码器A/B相接线;② 反转IC1P/IC2P极性(如从RISING改为FALLING)。

②. 计数乱跳

  • 原理:信号浮空/噪声干扰导致无效边沿被检测;
  • 解决:① GPIO配置上拉/下拉;② 提高IC1Filter/IC2Filter值(如8→16);③ 屏蔽编码器接线。

③. 计数精度低

  • 原理:未启用模式3(TI12)或仅检测单边沿;
  • 解决:配置SMS=3(模式3)+ IC1P/IC2P=BOTHEDGE(双边沿),开启4倍频。

④. 计数溢出

  • 原理:16位计数器范围有限,超出后回绕;
  • 解决:通过TIM_IT_UPDATE更新中断,累加32位总计数(如溢出时总计数±65536)。

6、总结:STM32编码器检测的核心本质

STM32并非通过软件采样A/B相电平来判断方向和计数,而是通过硬件正交解码逻辑

  1. 编码器A/B相信号经GPIO复用进入定时器输入捕获单元;
  2. 滤波后检测有效边沿,同时采样另一相信号电平;
  3. 硬件根据正交相位关系自动判断方向(DIR位);
  4. 按方向自动增减CNT寄存器值(无需CPU干预);
  5. CPU仅需读取TIM_CNT(计数值)和DIR位(方向),即可获取编码器的位置/方向信息。

这种硬件解码方式的优势是:低CPU占用、高响应速度、高抗干扰性,这也是STM32编码器功能适用于高速电机/精密定位场景的核心原因。

二、实战配置:CubeMX + HAL 库(STM32F407 为例)

STM32的编码器功能是利用定时器的「编码器模式」对接增量式正交编码器,实现旋转位置、速度、方向的精准检测,广泛用于电机测速/定位、机械臂关节检测、云台控制等场景。以下是从「核心原理」到「实战落地」的完整解析,结合HAL库(主流开发方式)讲解。

1、核心基础:增量式正交编码器与STM32适配

①. 增量式正交编码器(核心外设)
  • 输出信号:A相、B相(正交,相位差90°),部分带Z相(零位/复位);
  • 核心特性
    • 旋转时A/B相交替产生脉冲,通过脉冲数计算位置/角度
    • 通过A/B相相位超前/滞后判断旋转方向(正转/反转);
    • 脉冲频率对应旋转速度(频率越高,速度越快)。
②. STM32编码器模式的本质

STM32定时器(TIM1/TIM2/TIM3等,不同系列支持的定时器不同)内置正交解码逻辑,无需CPU干预,可自动:

  • 检测A/B相脉冲边沿;
  • 根据相位关系增减计数器值(正转递增、反转递减);
  • 直接输出计数结果(TIM_CNT寄存器),CPU仅需读取即可,占用资源极低。

2、STM32编码器模式核心参数

① 解码模式(3种,TIM_SMCR寄存器的SMS位)
模式 宏定义(HAL库) 说明 适用场景
模式1 TIM_ENCODERMODE_TI1 仅TI1(CH1)边沿触发计数,TI2(CH2)判断方向 单相信号+方向信号(非正交)
模式2 TIM_ENCODERMODE_TI2 仅TI2(CH2)边沿触发计数,TI1(CH1)判断方向 同上(反向)
模式3 TIM_ENCODERMODE_TI12 TI1/TI2双边沿触发计数(4倍频),相位判断方向 正交编码器(主流,精度最高)
②. 关键配置项
配置项 作用 推荐值
预分频(Prescaler) 对输入脉冲分频 0(不分频,保留最高精度)
计数器周期(Period) 16/32位计数器最大值 16位:65535;32位:4294967295
输入滤波(IC1Filter/IC2Filter) 过滤高频干扰(如电机噪声) 8~16(根据实际噪声调整)
输入极性(IC1Polarity/IC2Polarity) 触发边沿(上升/下降/双边沿) TIM_ICPOLARITY_RISING(默认)
GPIO配置 编码器信号输入 上拉/下拉输入 + 定时器复用功能

3、编码器功能配置流程(CubeMX + HAL库)

以STM32F407(通用款)、TIM2(CH1=PA0、CH2=PA1)、增量式正交编码器为例,分步骤实现:

步骤1:CubeMX可视化配置
  1. 芯片选择:STM32F407ZG(或其他型号);
  2. 定时器配置
    • 找到TIM2,模式(Mode)→ Encoder Mode
    • Encoder Mode:选择 Encoder Mode TI1 and TI2(模式3,最高精度);
    • IC1/IC2 Polarity:Rising(上升沿触发);
    • IC1/IC2 Filter:8(滤波,抗干扰);
    • Prescaler:0,Period:65535(16位最大值);
  3. GPIO配置
    • PA0(TIM2_CH1)、PA1(TIM2_CH2)→ 模式:Alternate Function Push Pull
    • 上拉/下拉:Pull Up(编码器信号默认高电平,避免浮空);
    • 复用功能(AF):AF1_TIM2(TIM2的复用映射);
  4. 生成代码 :选择HAL库,生成初始化代码(自动生成MX_TIM2_Init)。
步骤2:核心代码实现(HAL库)
c 复制代码
#include "stm32f4xx_hal.h"
#include <stdio.h>

/* 定时器句柄(CubeMX自动生成) */
TIM_HandleTypeDef htim2;

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

int main(void) {
  /* 1. 初始化HAL库、系统时钟、GPIO、定时器 */
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();

  /* 2. 启动编码器模式计数(必须传TIM_CHANNEL_ALL) */
  if (HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL) != HAL_OK) {
    Error_Handler(); // 启动失败处理
  }

  /* 3. 清零计数器(初始位置归0) */
  __HAL_TIM_SET_COUNTER(&htim2, 0);

  /* 主循环:读取编码器数据 */
  while (1) {
    /* 读取当前计数值(16位,范围0~65535) */
    int16_t cnt = __HAL_TIM_GET_COUNTER(&htim2);
    /* 读取旋转方向:0=递增(正转),1=递减(反转) */
    uint8_t dir = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2);
    /* 打印结果(需提前配置串口,如USART1) */
    printf("计数值:%d,方向:%s\r\n", cnt, dir ? "反转" : "正转");
    
    HAL_Delay(100); // 100ms刷新一次
  }
}

/* TIM2编码器模式初始化(CubeMX自动生成,关键参数已标注) */
static void MX_TIM2_Init(void) {
  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;        // 不分频
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 编码器模式下方向自动切换,此配置无影响
  htim2.Init.Period = 65535;       // 16位最大计数值
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12; // 模式3(TI1+TI2双边沿)
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; // CH1上升沿触发
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; // 直接映射到CH1
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1; // 输入不分频
  sConfig.IC1Filter = 8; // 8位滤波,减少噪声
  
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; // CH2上升沿触发
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 8; // 8位滤波
  
  if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK) {
    Error_Handler();
  }
  
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {
    Error_Handler();
  }
}

/* GPIO初始化(TIM2_CH1=PA0, CH2=PA1) */
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_encoderHandle->Instance==TIM2) {
    __HAL_RCC_TIM2_CLK_ENABLE();  // 使能TIM2时钟
    __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
    
    /* PA0-TIM2_CH1, PA1-TIM2_CH2 */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽
    GPIO_InitStruct.Pull = GPIO_PULLUP;     // 上拉输入(关键,避免浮空)
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2; // 复用功能映射到TIM2
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

/* 错误处理函数 */
void Error_Handler(void) {
  __disable_irq();
  while (1) {}
}

/* 系统时钟配置(CubeMX自动生成,无需修改) */
void SystemClock_Config(void) {
  // 省略,CubeMX生成的时钟配置代码
}

4、进阶功能:解决核心痛点

①. 计数器溢出处理(16位定时器必做)

16位定时器计数值范围0~65535,旋转超出范围会溢出(递增到65535后变0,递减到0后变65535),需通过更新中断累加总计数:

c 复制代码
/* 全局变量:总计数(32位,避免溢出) */
int32_t total_cnt = 0;

/* 主函数中启用更新中断 */
int main(void) {
  // ... 初始化代码 ...
  
  /* 启用TIM2更新中断 */
  __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
  /* 启动编码器+中断模式 */
  HAL_TIM_Encoder_Start_IT(&htim2, TIM_CHANNEL_ALL);
  
  while (1) {
    /* 读取当前总计数 */
    int16_t curr_cnt = __HAL_TIM_GET_COUNTER(&htim2);
    int32_t real_cnt = total_cnt + curr_cnt;
    printf("实际计数值:%ld\r\n", real_cnt);
    HAL_Delay(100);
  }
}

/* 定时器更新中断回调函数(溢出时触发) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == TIM2) {
    /* 判断溢出方向:递减溢出(0→65535)则总计数减65536,递增溢出则加65536 */
    if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2)) {
      total_cnt -= 65536;
    } else {
      total_cnt += 65536;
    }
  }
}
②. 速度计算(脉冲频率→转速)

通过固定时间窗口内的脉冲数计算速度(单位:脉冲/秒):

c 复制代码
/* 速度计算变量 */
int16_t last_cnt = 0; // 上一次计数值
uint32_t speed = 0;   // 速度(脉冲/秒)
#define SAMPLE_TIME 100 // 采样时间(ms)

while (1) {
  int16_t curr_cnt = __HAL_TIM_GET_COUNTER(&htim2);
  /* 计算采样周期内的脉冲差(处理溢出) */
  int16_t diff = curr_cnt - last_cnt;
  if (diff < 0) diff += 65536; // 处理递减溢出
  
  /* 计算速度:脉冲差 / 采样时间(秒) */
  speed = diff * (1000 / SAMPLE_TIME);
  printf("速度:%d 脉冲/秒\r\n", speed);
  
  last_cnt = curr_cnt;
  HAL_Delay(SAMPLE_TIME);
}

5、关键注意事项(避坑)

  1. GPIO配置错误

    • 必须配置为复用功能(AF),且AF编号与定时器匹配(如TIM2_CH1=AF1);
    • 必须启用上拉/下拉(推荐上拉),否则信号浮空会导致计数乱跳。
  2. 解码模式选择

    • 正交编码器必须选TIM_ENCODERMODE_TI12(模式3),否则精度减半/方向判断错误。
  3. 滤波参数

    • 噪声大的场景(如电机)需提高滤波器值(8~16),但过高会导致脉冲延迟。
  4. 定时器选择

    • 不同STM32系列支持的编码器定时器不同(如F103:TIM2/TIM3;F407:TIM1~TIM8);
    • 优先选32位定时器(如TIM2/TIM5),减少溢出处理复杂度。

6、常见问题排查

问题 原因 解决方法
计数始终为0 1. GPIO引脚接错;2. 编码器未供电/接线断;3. 定时器时钟未使能 1. 核对引脚映射;2. 测量编码器A/B相电压;3. 检查__HAL_RCC_TIMx_CLK_ENABLE()
计数乱跳/不准确 1. GPIO未上拉/下拉;2. 无滤波;3. 编码器接线接触不良 1. 配置GPIO_PULLUP;2. 提高IC滤波器值;3. 检查接线,屏蔽线接地
方向判断反向 1. 编码器A/B相接反;2. IC极性配置错误 1. 交换A/B相接线;2. 修改IC1PolarityTIM_ICPOLARITY_FALLING
中断不触发 1. 未启用更新中断;2. NVIC未配置 1. 调用__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);2. CubeMX中启用TIM2中断

总结

STM32编码器功能的核心是定时器正交解码,无需额外外设即可实现高精度的位置/方向/速度检测;开发时重点关注「解码模式配置」「GPIO复用+上拉」「溢出处理」三个关键点,即可稳定落地。若需适配特定编码器(如1000线正交编码器),仅需调整滤波参数和速度计算的脉冲数→角度转换系数(如1000线=4000脉冲/圈,1脉冲=0.09°)。

相关推荐
三品吉他手会点灯2 小时前
STM32F103 学习笔记-20-通信的基本概念
笔记·stm32·单片机·嵌入式硬件·学习
悠哉悠哉愿意2 小时前
【嵌入式学习笔记】GPIO与LED
笔记·单片机·嵌入式硬件·学习
v先v关v住v获v取2 小时前
3D打印机的定量铺粉器设计13张 +三维图+设计说明书
科技·单片机·51单片机
点灯小铭2 小时前
基于单片机的双机串口通信与数字串存储系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
chipsense2 小时前
霍尔电流传感器量程怎么确认,能覆盖实际电流监测需求么?
stm32·单片机·嵌入式硬件·霍尔电流传感器
点灯小铭2 小时前
基于单片机的压力温度水位检测与安全控制高压锅设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
10岁的博客2 小时前
NVIDIA显卡疑难杂症全攻略
单片机·嵌入式硬件
qq_401700412 小时前
stm32如何了解栈的使用情况
stm32·单片机·嵌入式硬件
d111111111d3 小时前
在stm32中什么是hal库,什么是标准库,二者的区别?
笔记·stm32·单片机·嵌入式硬件·学习