前言

          第六章 TIM 定时器主要包含以下四个部分:

  • 第一部分:定时器基本定时功能(已学习)
  • 第二部分:定时器输出比较功能(已学习)
  • 第三部分:定时器输入捕获功能(已学习)
  • 第四部分:定时器编码器接口功能(本篇内容)

          在深入探讨了通用定时器(TIM)的时基单元计数逻辑、输出比较(OC)波形合成技术,以及输入捕获(IC)信号特征分析之后,本篇技术文档将聚焦定时器在运动控制领域的进阶应用——编码器接口。

          作为电机闭环控制与伺服驱动中不可或缺的硬件模块,编码器接口通过内部硬件状态机对正交信号进行自动化解码,彻底取代了传统的“外部中断+软件计次”方案。本章将从增量式正交编码器的物理特性出发,深度剖析 STM32 硬件架构下的解码逻辑,并阐述双定时器协同架构下测速方案的底层工程实现。


目录

前言

1 编码器接口概述

1.1 系统架构中的功能定位

1.2 核心优势:全硬件托管机制

2 正交编码器信号特性

2.1 正交信号(Quadrature)的物理与数学表征

2.2 分辨率与测速机制

2.3 计数模式与四倍频(4x)解析技术

2.4 硬件抗抖动与噪声抑制机制(重要特性)

3 编码器接口核心硬件机制

3.1 信号链路与预处理电路

3.2 硬件正交解码状态机逻辑

3.3 计数器溢出与有符号数映射机制

3.3.1 计数器的下溢现象

3.3.2 补码特性与强制类型转换

3.3.3 数值映射关系表

3.3.4 代码实现

4 测速算法

4.1 本篇实验方案:相对速度表征法(脉冲频率法)

4.2 工业级扩展:M 法绝对测速模型(进阶补充)

4.3 两种方案的逻辑对比

5 底层驱动配置与代码解析

5.1 编码器接口模块初始化(TIM3)

5.1.1 开启时钟与 GPIO 配置

5.1.2 时基单元配置

5.1.3 输入捕获与编码器接口配置

5.2 定时采样模块初始化(TIM2)

5.3 测速逻辑的闭环实现

5.3.1 增量读取函数

5.3.2 中断服务函数处理

6 相关元器件简介

6.1 旋转编码器模块

6.1.1 旋转编码器概述

6.1.2 工作原理

7. 本章节实验

7.1 编码器接口测速

7.1.1 实验目标

7.1.2 硬件设计

7.1.3 软件设计

7.1.4 实验现象


1 编码器接口概述

          编码器接口(Encoder Interface)是 STM32 定时器模块中专为高频运动控制设计的硬件外设单元。其主要功能是自动解析增量式正交编码器输出的脉冲信号,为闭环控制系统提供高精度的位置反馈与速度感知。

          在 STM32 的外设架构中,该接口深度集成于通用定时器与高级定时器的从模式控制器(Slave Mode Controller)中。通过复用定时器的输入通道(通常为 TI1 和 TI2),硬件电路能够实时追踪机械轴的旋转方向与位移量。

1.1 系统架构中的功能定位

          在运动控制系统中,编码器接口的功能定位显著区别于传统的定时、OC 及 IC 模式。其核心差异体现在以下几个维度:

  • 基本定时模式(Base Timer):侧重于精确的时间间隔控制与周期性任务触发。
  • 输出比较模式(OC):侧重于电信号的周期控制与波形生成(如 PWM 波形)。
  • 输入捕获模式(IC):核心是记录信号边沿出现的时间位置,从而完成频率或占空比等时域分析。
  • 编码器接口模式(Encoder Interface):其关注点不再是边沿发生在何时,而是相位变化代表的位移方向与增量。它将 A/B 两路正交信号的相位关系直接转换为定时器计数器(CNT)的硬件级自增或自减。

1.2 核心优势:全硬件托管机制

          传统方案通常依赖外部中断(EXTI)触发 CPU 执行状态机判断,在电机高速旋转、高频脉冲涌入的环境下,极易引发 CPU 占用率过高及中断嵌套导致的“丢步”问题。STM32 的硬件编码器接口通过以下机制彻底解决了这一痛点:

(1)全硬件自动化解码(零软件开销)

          接口内部集成了专门的正交解码状态机。硬件实时采集 TI1 与 TI2 信号的电平跳变序列,自动判定旋转方向,并同步驱动 CNT 寄存器。全过程无需 CPU 介入,确保了微秒级的确定性响应。

(2)高频信号的稳定处理能力

          依托于底层硬件触发网络,该接口可稳定处理高达数 MHz 的输入脉冲频率,充分满足高速伺服电机的反馈需求。

(3)内建的硬件抗干扰逻辑

  • 数字滤波器(Digital Filter):输入捕获通道内置可编程的数字滤波器(通过配置 CCMRx 寄存器的 ICxF 位),通过采样时钟与阈值设定,在物理层直接滤除机械触点抖动产生的毛刺。
  • 状态机纠错闭锁:基于正交解码的天然特性,对单路毛刺具备极强的抑制能力(详见 2.3 节逻辑图解),有效防止因电磁干扰(EMI)导致的坐标漂移。

          依托该硬件接口,开发者可极简实现位移绝对量化、相对旋转角度检测,以及基于 M 法(测频法)或 T 法(测周法)的高精度转速解算。这构成了平衡小车、多轴 CNC 运控、无刷直流电机(BLDC)FOC 控制等高频闭环场景的基础保障。


2 正交编码器信号特性

          增量式旋转编码器基于光电或磁电转换原理,将转轴的机械角位移转化为电脉冲。为准确识别旋转方向,编码器输出两路相位差严格保持在 90° 的方波信号——A 相(Phase A)B 相(Phase B)。如图所示:

2.1 正交信号(Quadrature)的物理与数学表征

          在信号系统工程中,“正交”指两路同频信号相位相差 1/4 周期(90°)。当编码器匀速旋转时,A、B 两相的逻辑电平状态交替跃变,在任意时刻构成了一组标准的2 位格雷码(2-bit Gray Code)序列(如:00 → 01 → 11 → 10)。

(1)位差机制

          在理想正交序列中,无论编码器转速快慢,A、B 两相始终保持 90° 的相位偏移。这意味着在任意时刻,只有一个通道的电平发生跳变,这种“单步跳变”特性从信息论角度极大降低了并发边沿导致的误码率。

(2)方向判定(相位超前/滞后)

          旋转方向是由 A、B 两路脉冲的相对超前/滞后关系决定的。

  • 正转(CW, Clockwise):A 相相位超前 B 相 90°。触发 A 相上升沿时,B 相必然为低电平(0)。
  • 反转(CCW, Counter-Clockwise):A 相相位滞后 B 相 90°。触发 A 相上升沿时,B 相必然为高电平(1)。

2.2 分辨率与测速机制

          正交信号的波形特征直接映射了机械运动的运动学参数:

  • 位置量化(脉冲数化位移):编码器的物理分辨率通常以 PPR(Pulses Per Revolution,每转脉冲数) 表示。总计数值与机械位移成严格的正比例关系。
  • 速度量化(频率化转速):信号频率(单位时间内捕获的边沿数量)即转速的导数。转速越快,脉冲频率越高。
  • 绝对零点校准(Z 相/Index):部分工业级编码器提供第三路 Z 相信号(每转输出一个极窄脉冲),用于标记机械绝对零位。在系统中常用于上电寻零或周期性消除增量累计误差。

技术笔记:辨析 PPR 与 CPR

  • 线数(PPR, Pulses Per Revolution): 指编码器码盘上的物理刻线数。轴旋转一圈,单相输出的完整脉冲个数。
  • 分辨率(CPR, Counts Per Revolution): 指编码器在经过倍频逻辑后,轴旋转一圈定时器计数的总次数。
  • 换算关系: 在 STM32 默认推荐的四倍频模式(TIM_EncoderMode_TI12)下,CPR = PPR X 4。

提示: 很多初学者发现编码器/电机转一圈 CNT 变化量不对,通常是因为忽视了 STM32 硬件接口会自动捕获 A/B 相的上升沿和下降沿。例如:一个 500 线的编码器,在 TIM_EncoderMode_TI12 模式下,电机转动一圈 CNT 会增/减 500 X 4 = 2000 个数值。在算法解算时,必须以 CPR 为准。

2.3 计数模式与四倍频(4x)解析技术

          为最大化系统测量分辨率,嵌入式工程中普遍采用多边沿评估逻辑(Edge Evaluation),即倍频技术。由于标准的正交信号在一个完整的电周期内包含 4 个物理跳变沿(A 上升、B 上升、A 下降、B 下降),通过捕获不同通道的边沿,可以实现不同倍数的物理分辨率提升。如图所示:

          在 STM32 中,该功能通过配置从模式控制寄存器(TIMx_SMCR)的 SMS 位段来实现。硬件编码器接口主要提供以下三种解析模式:

(1)编码器模式 1(单通道双边沿评估,等效 2 倍频)

  • 库函数配置:TIM_EncoderMode_TI1
  • 触发机制:仅在 TI1 通道(A 相)的上升沿与下降沿进行触发计数,而 TI2 通道仅用于方向状态参考。
  • 分辨率推算:若物理线数为 100 PPR,则机械旋转一周对应 200 个计数值(CPR = 200)。

(2)编码器模式 2(单通道双边沿评估,等效 2 倍频)

  • 库函数配置:TIM_EncoderMode_TI2
  • 触发机制:仅在 TI2 通道(B 相)的上升沿与下降沿进行触发计数,TI1 用于方向参考。分辨率表现与模式 1 相同。

(3)编码器模式 3(双通道双边沿评估,等效 4 倍频)

  • 函数配置:TIM_EncoderMode_TI12
  • 触发机制:在 TI1 与 TI2 通道(A 相与 B 相)的所有有效边沿均进行触发计数。
  • 分辨率推算:硬件能感知最微小的相位变化,将系统分辨率直接提升至物理线数的 4 倍(CPR = PPR × 4)。  如下图所示:

          模式 3(在TI1和TI2上计数)是工业控制与电机驱动中的标准首选。它不仅将传感器的物理分辨率最大化,而且依托于双路交叉校验的底层状态机,提供了最为卓越的抗机械抖动与电气噪声抑制能力(详见下一小节的抗抖动机制分析)。这种精度增强对于低速平稳运行和高动态响应的位置环控制至关重要。


2.4 硬件抗抖动与噪声抑制机制(重要特性)

          除了提升物理分辨率,四倍频技术在工程应用中更核心的贡献在于其硬件级噪声抑制能力,能从底层消除机械振动与电气干扰产生的累积误差。

          在工业现场,电机在静止或低速状态下,机械轴常因环境振动在某个信号跳变沿附近发生微小往复摆动。若使用传统的单向脉冲计数器,这种摆动会被误判为单向运动,导致计数值出现“虚假漂移”。

          正交解码器通过内置的硬件状态机强制要求两路电平(TI1、TI2)必须交替跳变方可判定为有效位移。

          当某一相信号(如下图中的 TI1)受干扰产生高频毛刺,而另一路信号(TI2)保持电平不变时,硬件逻辑会识别出这种非法的相位变化。根据表77(TI1和TI2计数)所示的计数逻辑与信号关系,状态机将陷入“加一即减一”的震荡逻辑,使计数器在当前值基础上执行 +1 和 -1 的原地摆动。这种由硬件实现的双通路互锁特性,保证了干扰产生的偏差在微秒级内被自动抵消,确保 CNT 寄存器的值能始终准确反映机械轴的真实物理位置,彻底杜绝了累积误差的产生。如图所示:


3 编码器接口核心硬件机制

          STM32 的编码器接口并非单纯的脉冲累加器,而是一个高度集成的硬件自动化外设单元。它涵盖了信号预处理、正交解码状态机以及方向自动判定逻辑。其核心设计理念在于“硬件托管(Hardware Offloading)”——将高频的实时解码任务固化于底层硅片电路中,从而彻底释放 CPU 资源,规避了软件中断处理带来的延迟与丢步风险。

3.1 信号链路与预处理电路

          增量式编码器的 A 相与 B 相物理信号在进入定时器核心逻辑前,必须经历一系列严格的硬件预处理链路,以确保捕获信号的抗干扰性与逻辑确定性:

          (1)输入通道映射(Input Mapping)

          编码器的 A、B 两路正交信号必须接入定时器的 TI1TI2 通道(对应硬件引脚的 CH1 和 CH2)。这是由 STM32 定时器的内部硬件拓扑决定的:内部正交解码器仅接收来自 TI1FP1TI2FP2 的信号输入。经过数字滤波器处理后的信号,将被送入状态机进行边沿检测与方向判定。如图所示:

          (2)输入滤波器(Digital Filter)

          通过配置捕获/比较模式寄存器(TIMx_CCMR1)中的 ICxF 位段,可启用硬件级数字滤波器。该滤波器基于定时器内部时钟($f_{DTS}$)对输入电平进行连续采样。只有当信号在连续 N 个采样周期内保持稳定电平状态时,硬件才确认该边沿跳变有效。这一机制是滤除机械触点弹跳与高频电磁毛刺的关键。

          (3)极性选择与信号反相(Polarity Selection)

          这是编码器模式下最易产生误解的环节。在常规输入捕获中,极性决定触发边沿;但在编码器模式下,硬件会强制捕获所有边沿。此时,库函数中的 Rising 或 Falling 参数不再代表上升沿/下降沿触发,而是重映射为是否需要内部反相:

  • TIM_ICPolarity_Rising(非反相):信号直通,逻辑电平保持原始状态(对应部分库函数中的配置 TIM_ICPolarity_Rising )。
  • TIM_ICPolarity_Falling(反相):信号通过内部硬件非门,高低电平发生逻辑翻转(对应部分库函数中的配置 TIM_ICPolarity_Falling )。

          若发现读取的位移方向与实际机械转向相反,无需物理调整 A/B 相接线,只需在软件中将其中一个通道的极性由 Rising 改为 Falling 即可完成逻辑调相。例如:

TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

3.2 硬件正交解码状态机逻辑

          编码器接口的核心调度单元是内置的硬件状态机。它以极高的时钟频率实时监测 TI1FP1(A相)与 TI2FP2(B相)的电平组合态。

          以工程中最常用的编码器模式 3(在 TI1 和 TI2 均计数)为例,状态机严格遵循下图所示的计数逻辑。当检测到任意一相发生边沿跳变时,硬件会结合另一相当前的静态电平,自动判定旋转方向,并生成递增(Up-counting)或递减(Down-counting)信号直接驱动计数器(CNT)寄存器。

3.3 计数器溢出与有符号数映射机制

          在 STM32 的硬件架构中,通用定时器(TIMx)的计数器 CNT 是一个 16 位无符号整型(uint16_t) 寄存器。这意味着其物理数值的合法范围始终在 0 ~ 65535(0 ~ 0xFFFF) 之间循环。

          当我们将该计数器用于编码器测速时,如何通过无符号寄存器来表征“反向运动”产生的负位移,是底层驱动开发中的一个核心逻辑点。

3.3.1 计数器的下溢现象

          当电机正向旋转时,CNT 从 0 开始递增,在未触及 65535 上限前,数值与物理位移呈线性正相关。

          然而,当电机或编码器反向旋转时,若当前 CNT 值为 0,根据硬件减计数逻辑,下一个脉冲将触发下溢(Underflow),计数值会直接由 0 回绕至 65535(即 0x0000 —> 0xFFFF),随后继续递减为 65534、65533 ......。

          这种由 0 突变为 65535 的数值回绕现象,如果不经处理,直接代入公式计算,会导致系统误判为极高的正向速度。

3.3.2 补码特性与强制类型转换

          为了建立物理意义上的“负数”映射,我们巧妙地利用了计算机系统中 二进制补码(Two's Complement) 的表示特性。

          在 C 语言中,通过将 uint16_t 类型的 CNT 值强制转换为有符号的 int16_t 类型,硬件底层的数据位(Bit Pattern)并不会改变,但其权重解释方式发生了变化:

  • 0xFFFF:在无符号解释下为 65535,在 16 位有符号补码解释下,其值恰好等于 -1
  • 0xFFFE:在有符号补码解释下映射为 -2,以此类推。

          通过这种强制类型转换,我们成功地将寄存器的“回绕”转化为符合物理规律的“负位移”。

3.3.3 数值映射关系表

          下表展示了硬件 CNT 原始值与经过转换后的物理意义映射关系:

运动状态原始 CNT 值 (uint16_t)十六进制 (Hex)映射后值 (int16_t)物理意义 (增量)
正向极值327670x7FFF32767正向最大量程
正向运行............
20x00022正向移动 2 步
10x00011正向移动 1 步
静止00x00000无位移/旋转
临界点655350xFFFF-1反向移动 1 步
反向运行655340xFFFE-2反向移动 2 步
655330xFFFD-3反向移动 3 步
............
反向极值327680x8000-32768反向最大量程

3.3.4 代码实现

          获取增量时,必须严格执行读取后立即清零。在多任务系统中,建议将此操作视为一个逻辑上的原子过程,确保读取的数值与下一次计数的起点之间不存在位移丢失。

/**
 * @brief  获取并清零编码器脉冲增量
 * @return 带有方向特性的增量值 (int16_t)
 */
int16_t Encoder_Get(void)
{
    int16_t Temp;
    // 1. 读取硬件寄存器并执行强制类型转换
    // 此处将 0~65535 映射为 -32768~32767
    Temp = (int16_t)TIM_GetCounter(TIM3); 
    
    // 2. 清零计数器,为下一个采样周期的增量统计做准备
    TIM_SetCounter(TIM3, 0); 
    
    return Temp;
}

4 测速算法

          速度的物理本质是位移对时间的导数:

$v = \frac{dx}{dt}$

          在基于微控制器(MCU)的数字离散系统中,测速的核心逻辑演变为:在严格固定的时间窗口(闸门时间)内,统计编码器产生的脉冲增量$\Delta M$

4.1 本篇实验方案:相对速度表征法(脉冲频率法)

          为了最直观地验证编码器测速原理,本实验采用了简化的“相对速度表征法”。方案设计如下所示:

  • 时基设定:利用定时器 TIM2 配置一个严格为 1秒(1Hz) 的更新中断,作为测速的闸门时间(Ts = 1s)。
  • 数据获取:每秒中断触发时,读取 TIM3 的计数器增量 $\Delta M$
  • 速度表征:直接将该脉冲增量 $\Delta M$ 作为速度的物理量度进行OLED显示或打印。

          在 TIM2_IRQHandler 中,程序每秒执行一次 Speed = Encoder_Get()。由于采样周期 Ts = 1,此时获取的脉冲增量 $\Delta M$ 在数值上直接等同于频率 f:

$V_{\text{speed}} = \frac{\Delta M}{T_s} = \Delta M \text{ (Counts/s)}$

          这种方案无需任何浮点运算或复杂的除法公式,直接利用 每秒脉冲数 来线性表征电机的旋转快慢。同时保留了速度的方向性(增量为正代表正转,为负代表反转)与相对大小(增量绝对值越大,转速越快)。

4.2 工业级扩展:M 法绝对测速模型(进阶补充)

          在真实的电机闭环控制系统(如 PID 速度环)中,需要将相对的脉冲数转化为具有绝对物理意义的单位,最常用的是 RPM(Revolutions Per Minute,每分钟转速)。这就需要引入标准的 M 法(测速频率法)数学模型。推导逻辑如下所示:

          要计算出真实的物理转速 n,我们需要经过以下三个推导步骤:

(1)计算单圈总计数值

          若编码器的物理线数为 Z(PPR),系统倍频系数为 L(如四倍频下 L=4),则编码器物理旋转一圈,系统实际记录的总脉冲数(CPR)为 Z X L。

(2)计算采样时间内的旋转圈数

          设定时器(如 TIM2)的采样周期为 Ts(单位:秒),在此期间获取到的有效脉冲增量为 $\Delta M$。那么在这 Ts 秒内,编码器旋转的机械圈数为:

$\frac{\Delta M}{Z \times L}$

(3)换算为每分钟转速(RPM)

          先将上述圈数除以 Ts 得到每秒转速,再乘以时间常数 60(1分钟 = 60秒),即可得到最终的绝对转速公式:

$n = \frac{\Delta M}{Z \times L} \times \frac{1}{T_s} \times 60$

          参数物理定义:

变量物理意义工程单位备注说明
n绝对物理转速rpm (转/分)最终求解的工程量,带正负号表示方向
$\Delta M$脉冲增量Counts (个)采样周期内 CNT 寄存器的带符号变化量
Z编码器物理线数PPR (线/圈)由编码器硬件参数决定(如 100 线、500 线)
L倍频系数无量纲STM32 编码器模式 3(双通道双边沿)下为 4
Ts采样周期 (闸门时间)s (秒)定时器中断触发的时间间隔

技术笔记: 虽然 M 法公式在理论上非常严谨,但在嵌入式 C 语言实现时,需特别注意数据类型的溢出风险。例如,公式中的 60 X $\Delta M$ 在高速旋转时极易突破 16 位整型的上限,因此在编码时必须强制转换为 32 位整型(int32_t)或浮点型进行解算。

4.3 两种方案的逻辑对比

维度实验简化方案 (本工程使用)工业绝对方案 (进阶参考)
计算开销零开销(直接读取即使用)高(涉及 32 位乘除法或浮点运算)
物理量纲脉冲数/秒 (Counts/s)转/分钟 (rpm)
适用场景逻辑验证、趋势观察、基础教学精准闭环控制、上位机数据上报
主要优点响应快,不占用 CPU 浮点单元符合国际通用工业标准

5 底层驱动配置与代码解析

         在嵌入式运动控制系统中,实现高精度编码器测速通常基于双定时器协同架构:利用 TIM3 的编码器接口模式作为“被动计数器”实时累加位移,利用 TIM2 作为“主动节拍器”提供精确的采样闸门(Gate Time)。

5.1 编码器接口模块初始化(TIM3)

         该模块的配置核心在于将从模式控制器(Slave Mode Controller)切换至编码器模式,使硬件状态机接管 CNT 的逻辑驱动。CH

5.1.1 开启时钟与 GPIO 配置

         编码器 A/B 正交信号分别接入 TIM3 的通道 CH1 (PA6) 与通道 CH2 (PA7)。引脚必须配置为上拉输入(IPU),以确保在编码器悬空或信号切换时,电平具备确定的物理逻辑状态。

// 1. 开启外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2. GPIO 初始化配置
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 启用内部上拉输入,钳制悬空电平,抑制共模干扰
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

5.1.2 时基单元配置

         在编码器模式下,TIM3 内部预分频器(PSC)失效,计数脉冲由外部正交边沿提供。但自动重装载寄存器(ARR)仍决定了计数的溢出边界。

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

TIM_TimeBaseStructure.TIM_Period = 65535;           // ARR 设为 16 位满量程 (0xFFFF)
TIM_TimeBaseStructure.TIM_Prescaler = 0;            // 预分频设为 0(即 1 分频),不丢失脉冲细节
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

深度解析:为何 ARR (TIM_Period) 必须设为 65535?

  • 补码对齐: 只有将 ARR 设为 16 位满量程(0xFFFF),硬件计数器的回绕点才能与 C 语言 int16_t 类型的补码边界严格对齐。
  • 自动负数转换: 当 CNT 为 0 时,若反向旋转,下一个计数值会直接跳变为 65535。此时强制转换为 int16_t,0xFFFF 会被直接解析为 -1,这与物理意义完美契合。

         反例: 如果你误将 ARR 设为 10000,当 0 减 1 发生回绕时,CNT 会变成 10000。此时 (int16_t)10000 仍然是正数 10000,你的测速程序会瞬间判定电机在以极高速度正向旋转,导致闭环系统失控。

5.1.3 输入捕获与编码器接口配置

         此步骤用于启用硬件滤波器并确立倍频逻辑。

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);

// 1. 配置输入捕获通道与硬件数字滤波器
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0x0F;            // 设定滤波物理采样级数,滤除触点抖动毛刺
TIM_ICInit(TIM3, &TIM_ICInitStructure);

TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM3, &TIM_ICInitStructure);

// 2. 配置编码器接口状态机:实现四倍频解析
// 参数:模式 3 (TI12 均计数),TI1/TI2 极性均设为 Rising (代表不反相)
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, 
                           TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

TIM_Cmd(TIM3, ENABLE); // 使能定时器,开始硬件自动计数

5.2 定时采样模块初始化(TIM2)

         TIM2 负责建立稳定的时间闸门 Ts。若主频为 72MHz,配置如下可产生严格的 1S (1Hz) 中断周期,作为测速的基础时基。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // 72MHz / 7200 = 10kHz
TIM_TimeBaseStructure.TIM_Period = 10000 - 1;  // 10kHz 计数 10000 次 = 1s
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);      // 开启更新中断
// (此处需配置 NVIC 中断优先级,确保 TIM2 中断能及时响应)
TIM_Cmd(TIM2, ENABLE);

5.3 测速逻辑的闭环实现

         为了获取单位时间(1秒)内的脉冲增量值,我们需要在读取当前计数值后立即重置计数器。

5.3.1 增量读取函数

/**
 * @brief  获取并清零当前脉冲增量
 * @return 带方向的速度表示值 (int16_t)
 */
int16_t Encoder_Get(void)
{
    int16_t Temp;
    // 强制类型转换:利用补码特性将 0~65535 映射为 -32768~32767
    Temp = (int16_t)TIM_GetCounter(TIM3); 
    
    // 关键操作:清零 CNT,确保下一个 1 秒周期统计的是全新的增量
    TIM_SetCounter(TIM3, 0);               
    return Temp;
}

5.3.2 中断服务函数处理

         每隔 1 秒,中断会自动触发一次,更新全局变量 Speed

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        // 每秒触发一次,读取到的增量即为当前转速的量化表征值
        Speed = Encoder_Get(); 
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除标志位,防止死循环
    }
}

6 相关元器件简介

6.1 旋转编码器模块

6.1.1 旋转编码器概述

          旋转编码器(Rotary Encoder)是一种用于测量旋转位置、旋转速度以及旋转方向的传感器件。当编码器的旋转轴发生转动时,其输出端会产生与旋转状态相关的方波信号。通过读取这些方波信号的频率和相位关系,即可判断旋转轴的:

  • 转动方向(顺时针 / 逆时针)
  • 转动速度
  • 相对位移(步进量)

          因此,旋转编码器特别适合需要手动调节的人机交互场景,主要包括以下典型应用:

  • 旋钮式输入控制
  • 音量和频率调节
  • 菜单导航选择
  • 参数调整(如高度、温度等)

          实物如图所示:

          接口简介:

          其中,A 相与 B 相信号通常接入 STM32 的 GPIO 引脚,用于方向和计数判断。

6.1.2 工作原理

          EC11 旋转编码器在旋转轴发生顺时针或逆时针转动时,会在输出端产生一组具有固定相位差的方波信号,供单片机或微控制器进行采集与分析。

          EC11 编码器具有 A 相 B 相 两路信号输出,这两路信号均为方波信号,且它们之间始终保持 90° 的相位差。这种具有固定相位差的双路编码方式称为正交编码信号(Quadrature Encoder Signal)

          下图给出了旋转轴分别沿顺时针方向和逆时针方向转动时,A 相与 B 相的典型输出波形:

          如图所示,旋转方向与两相信号的相位先后关系如下:

  • 顺时针旋转(CW):B 相信号超前 A 相信号 90°
  • 逆时针旋转(CCW):A 相信号超前 B 相信号 90°

7. 本章节实验

7.1 编码器接口测速

7.1.1 实验目标

  • 掌握硬件正交解码原理:理解 STM32 定时器编码器接口(Encoder Interface)如何通过硬件逻辑自动解析正交信号的相位关系,从而判定旋转方向与计数值。
  • 掌握基本的测速方法:学习利用定时器产生固定时间基准(采样周期),并在中断中获取编码器计数值的变化量,从而量化速度参数。
  • 熟练配置编码器接口模式:掌握 TIM_EncoderInterfaceConfig() 函数的使用,以及输入滤波器对机械编码器毛刺信号的抑制处理。
  • 实现双定时器协同架构:通过一个定时器(TIM3)负责硬件计数,另一个定时器(TIM2)负责精准采样,构建稳定的测速系统。

7.1.2 硬件设计

7.1.3 软件设计

          本实验采用双定时器协同工作的软件架构,将硬件计数与定时采样逻辑解耦,具体流程如下:

(1)编码器计数模块(基于 TIM3)

  • 底层硬件接口:配置 PA6 与 PA7 为上拉输入模式,作为编码器的 A、B 相信号输入端(TI1 与 TI2)。
  • 正交解码配置:将 TIM3 切换至编码器接口模式(TIM_EncoderMode_TI12)。在该模式下,硬件自动根据两路信号的电平跳变顺序进行加减计数,无需任何软件干预。
  • 信号预处理:配置输入级滤波器(Filter),有效滤除编码器旋转过程中因机械触点抖动产生的非法脉冲,确保计数器的稳定性。

(2)采样时基模块(基于 TIM2)

  • 采样周期定义:配置 TIM2 为标准定时中断模式。通过设置预分频器(PSC)与自动重装值(ARR),建立一个精确的时间窗口(本实验中设定为 1S)。
  • 中断触发逻辑:定时器溢出产生更新中断(Update Interrupt),作为测速逻辑的采样触发源。

(3)速度解算逻辑(主程序与中断服务)

  • 增量式数据提取:在 TIM2 的中断服务函数 TIM2_IRQHandler 中,调用 Encoder_Get 函数获取自上一次采样以来 TIM3 计数器的变化量(增量值)。
  • 自动清零机制:每次读取完计数值后,立即手动将 TIM3 的 CNT 寄存器复位清零。这样,读取到的原始数值直接对应了单位时间内产生的脉冲数,即 $v \propto \Delta CNT$
  • 结果实时刷新:解算出的速度值(带正负号,代表方向)通过主循环实时推送到 OLED 屏幕显示。

          具体代码如下:

          main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

int16_t Speed;			//定义速度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时器初始化
	Encoder_Init();		//编码器初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
	
	while (1)
	{
		OLED_ShowSignedNum(1, 7, Speed, 5);	//不断刷新显示编码器测得的最新速度
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Speed = Encoder_Get();								//每隔固定时间段读取一次编码器计数增量值,即为速度值
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

          Timer.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

          Encoder.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6和PA7引脚初始化为上拉输入
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输入捕获初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICStructInit(&TIM_ICInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;				//选择配置定时器通道2
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	
	/*编码器接口配置*/
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
																	//配置编码器模式以及两个输入通道是否反相
																	//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
																	//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:获取编码器的增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,编码器的增量值
  */
int16_t Encoder_Get(void)
{
	/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}

7.1.4 实验现象

          当编码器静止时,OLED 屏幕显示的 Speed 数值为 0。以不同的方向选装编码器时:

  • 顺时针旋转:屏幕显示正数,且数值大小随旋转速度的加快而线性增大。
  • 逆时针旋转:屏幕显示负数(由 int16_t 类型解析),体现了编码器接口对方向的自动识别功能。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐