STM32 学习 —— 个人学习笔记6-3(TIM 输入捕获 & 编码器接口)

声明

文中内容为观看 BiliBili 视频【STM32入门教程-2023版 细致讲解 中文字幕】后学习并扩展总结。

本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。

一、输入捕获

1.1 简介

输入捕获(Input Capture, 简称IC)是嵌入式微控制器定时器模块的核心功能之一,主要用于对外部输入信号的时序特性进行高精度检测与测量 ,广泛应用于电机控制、传感器信号解码、脉冲参数分析等各类需要精准时序感知的场景中,是连接外部模拟/数字信号与嵌入式系统时序分析的关键技术纽带。

在输入捕获模式下,定时器的通道输入引脚会持续监测外部信号的电平变化,当检测到预设的电平跳变(可配置为上升沿、下降沿或双边沿)时,硬件电路会立即将定时器当前的计数器(CNT)数值锁存至对应的捕获/比较寄存器(CCR)中,该锁存过程由硬件自动完成,响应延迟仅为几个时钟周期,有效避免了软件干预带来的测量误差,确保了捕获数据的准确性与实时性。基于这一核心机制,输入捕获功能可实现对多种脉冲信号参数的精准测量,其中最典型的应用包括 PWM(脉冲宽度调制)波形的频率、占空比,以及各类脉冲信号的脉冲间隔、高低电平持续时间等关键时序参数,通过对CCR中锁存的计数值进行计算与分析,即可还原出外部信号的时序特征,为后续的系统控制与信号处理提供可靠的数据支撑。

在硬件资源配置方面,主流嵌入式微控制器的高级定时器与通用定时器均具备完善的输入捕获能力,通常每个定时器模块均集成有 4 个独立的输入捕获通道,各通道可独立配置触发方式、滤波参数与预分频系数,互不干扰,既可以实现对多个独立外部信号的同步检测,也可以通过多通道协同配置,完成更为复杂的时序测量任务,极大地提升了功能的灵活性与适用性。为进一步优化 PWM 波形的测量效率,输入捕获功能可配置为PWMI(PWM Input)模式,该模式本质上是输入捕获的一种特殊应用形式,通过两个捕获通道的协同工作,将同一PWM输入信号分别映射至两个通道,其中一个通道配置为上升沿触发以捕获 PWM 波形的周期,另一个通道配置为下降沿触发以捕获 PWM 波形的高电平(或低电平)持续时间,无需软件频繁切换触发边沿,即可同时完成 PWM 波形频率与占空比的同步测量,大幅简化了软件编程复杂度,同时提升了测量的实时性与准确性。

此外,输入捕获功能还可与定时器的主从触发模式相结合,实现硬件层面的全自动时序测量,进一步降低 CPU 的负载。在主从触发模式配置下,可将输入捕获的触发信号设置为从模式的触发源,当输入捕获通道检测到指定电平跳变并完成计数值锁存后,硬件会自动触发计数器(CNT)复位,为下一次捕获做好准备,整个测量过程从信号触发、计数值锁存到计数器复位,完全由硬件电路自动完成,无需 CPU 进行中断响应与软件操作,仅需在需要读取测量结果时,由 CPU 读取 CCR 寄存器中的数据即可。这种硬件全自动测量模式,不仅有效避免了软件延迟带来的测量误差,提升了测量精度,还显著降低了 CPU 的资源占用率,使 CPU 能够专注于其他核心控制任务,尤其适用于高频脉冲信号测量或多任务并发运行的嵌入式系统中,进一步拓展了输入捕获功能的应用场景与实用价值。

1.2 频率测量

频率测量是电子测量领域的基础技术之一,用于精确获取周期性信号的频率参数,常用方法包括测频法、测周法,二者在不同频率范围下的测量精度存在差异,其误差相等的临界频率称为中界频率。

测频法 的核心原理是基于闸门时间的计数测量,设定固定的闸门时间 T ,在此时间区间内,对被测信号的上升沿进行精准计数,得到计数结果 N 。由于闸门时间 T 保持恒定,被测信号的频率 f x f_x fx 可通过计数结果与闸门时间的比值计算得出,其计算公式 为 f x = N / T f_x=N / T fx=N/T 该方法适用于高频信号的测量,闸门时间越长,计数结果的相对误差越小,测量精度越高。

测周法 与测频法的测量逻辑相反,主要用于低频信号的精确测量,其核心是利用标准频率信号进行计时计数。选取被测信号的两个连续上升沿作为计时起点与终点,此时间间隔即为被测信号的周期 T x T_x Tx;在该时间间隔内,以已知精确值的标准频率 f c f_c fc 为基准进行计数,得到计数结果 N 。由于标准频率 f c f_c fc 的周期 T c = 1 / f c T_c=1/f_c Tc=1/fc,被测信号的周期 T x = N ⋅ T c T_x=N \cdot T_c Tx=N⋅Tc,进而可推导出被测信号的频率计算公式 为 f x = f c / N f_x=f_c / N fx=fc/N该方法的测量精度与标准频率的精度正相关,被测信号频率越低,周期越长,计数结果 N 越大,相对误差越小。

中界频率 f m f_m fm 是衡量测频法与测周法测量精度的关键参数,定义为两种测量方法的测量误差相等时对应的被测信号频率。通过对两种方法的误差源及误差公式进行推导分析,可得出中界频率的计算公式 为 f m = f c / T f_m=\sqrt{f_c/T} fm=fc/T 。当被测信号频率 f x > f m f_x > f_m fx>fm 时,测频法的测量误差更小,更适合作为首选测量方法;当 f x < f m f_x < f_m fx<fm 时,测周法的测量精度更优,应选用测周法进行测量;当 f x = f m f_x = f_m fx=fm 时,两种方法的测量误差一致,可根据实际测量场景灵活选用。

1.3 输入捕获通道

输入捕获通道是 STM32 定时器的核心功能模块,用于对外部或内部信号的边沿事件进行时间戳记录、频率测量及脉冲宽度检测。以通道 1 输入部分为例,其信号处理流程可分为以下关键阶段:

(1)信号滤波与采样 :外部输入信号 TI1 首先进入由 滤波器 (向下计数器)构成的预处理单元,该单元通过 TIMx_CCMR1 寄存器的 ICF[3:0] 位配置滤波参数,对高频噪声进行抑制,输出滤波后的信号 TI1F。此过程确保后续边沿检测的可靠性,避免由信号抖动导致的误触发。

(2)边沿检测与极性选择 :滤波后的信号 TI1F 进入边沿检测器 ,分别产生上升沿事件 TI1F_Rising 和下降沿事件 TI1F_Falling。通过 TIMx_CCER 寄存器的 CC1P 位,可选择捕获触发的极性(上升沿、下降沿或双边沿),输出 TI1FP1 信号。同时,边沿检测器还输出 TI1F_ED 信号,用于从模式控制器的事件驱动。

(3)多路复用与分频 :捕获信号通过多路选择器(由 TIMx_CCMR1 的 CC1S[1:0] 位控制),可选择来自通道 1 的 TI1FP1、通道 2 的 TI2FP1 或内部触发信号 TRC。选择后的信号 IC1 进入分频器 ,通过 TIMx_CCMR1 的 ICPS[1:0] 位配置分频系数(/1, /2, /4, /8),输出 IC1PS 信号。最后,由 TIMx_CCER 的 CC1E 位使能捕获功能,将计数器值锁存至 TIMx_CCR1 寄存器,完成一次捕获操作。

1.4 主从触发模式

定时器的主从触发模式(Master/Slave Trigger Mode)是实现定时器间同步、外部事件控制及复杂时序逻辑的关键机制,其架构由主模式 (Master Mode)、触发源选择 (Trigger Source Selection)和从模式 (Slave Mode) 三部分构成:

(1)主模式(Master Mode) :主模式的核心功能是将定时器内部的特定事件或信号(如更新事件 Update、比较输出 OCxREF 等)通过 TRGO(Trigger Output)引脚输出,作为其他定时器或外设的触发信号。TRGO 的输出源由 TIMx_CR2 寄存器的 MMS[2:0] 位选择,可选源包括:复位事件(Reset)、使能信号(Enable)、更新事件(Update)和比较输出信号(OC1, OC1REF, OC2REF 等)。

(2)触发源选择(Trigger Source Selection) :触发源选择模块负责从多个候选信号中选取 TRGI(Trigger Input),作为从模式控制器的输入。候选信号包括:内部触发信号(ITR0~ITR3,用于定时器级联)、通道输入信号(TI1F_ED, TI1FP1, TI2FP2)和外部触发信号(ETRF)。

选择逻辑由 TIMx_SMCR 寄存器的 TS[2:0] 位控制,确保从模式能够响应精确的触发事件。

(3)从模式(Slave Mode) :从模式控制器根据 TRGI 信号执行预设的操作,实现对计数器的精确控制。主要从模式包括:编码器模式(Encoder Mode),将 TI1FP1 和 TI2FP2 作为正交编码器的 A、B 相输入,实现对电机转速和方向的测量;复位模式(Reset Mode),当 TRGI 有效时,计数器自动清零,实现同步复位;门控模式(Gated Mode),计数器的计数使能由TRGI的电平控制,实现对计数过程的门控;触发模式(Trigger Mode),当 TRGI 有效时,计数器开始计数,实现单次触发启动;外部时钟模式 1(External Clock Mode 1),将 TRGI 作为计数器的时钟源,实现外部事件驱动的计数。

1.5 输入捕获基本结构

输入捕获基本结构以外部信号经 GPIO 引脚进入滤波器模块,通过 TIMx_CCMR1 寄存器的 ICF[3:0] 位配置滤波深度以抑制高频噪声与信号抖动,输出稳定信号后进入边沿检测与极性选择模块,依据 TIMx_CCER 寄存器的 CC1P 位选择有效触发边沿并输出 TI1FP1 信号;该信号一方面作为触发源驱动从模式控制器,在复位模式下将时基单元的计数器 CNT 清零,另一方面触发捕获操作,将当前 CNT 值锁存至捕获 / 比较寄存器 CCR1 中形成时间戳,时基单元则由预分频器 PSC、计数器 CNT 与自动重装载寄存器 ARR 构成,为捕获操作提供精确的时间基准,各模块协同实现对外部周期性信号的高精度时间测量与周期分析。

1.6 脉冲宽度测量模式基本结构

PWMI(PWM Input Mode,脉冲宽度测量模式)是 STM32 定时器输入捕获功能的一种扩展应用,其结构在单通道输入捕获的基础上,通过双通道协同工作与从模式复位机制,实现对脉冲信号周期与占空比的同步测量。外部脉冲信号经 GPIO 引脚进入定时器后,首先通过滤波器抑制噪声,再经边沿检测与极性选择模块,分别输出对应上升沿的 TI1FP1 和对应下降沿的 TI1FP2 信号;其中 TI1FP1 作为触发源驱动从模式控制器工作于复位模式,在每次有效边沿到来时将时基单元的计数器 CNT 清零,同时 TI1FP1 触发通道 1 的捕获操作,将当前 CNT 值锁存至捕获 / 比较寄存器 CCR1 中,用于记录脉冲周期,而 TI1FP2 则触发通道 2 的捕获操作,将对应 CNT 值锁存至 CCR2 中,用于记录脉冲高电平宽度;时基单元由预分频器 PSC、计数器 CNT 与自动重装载寄存器 ARR 构成,为捕获操作提供精确的时间基准,通过读取 CCR1 与 CCR2 的数值,结合定时器时钟频率即可计算出脉冲信号的周期、频率及占空比等关键参数。

二、编码器接口

2.1 简介

编码器接口是 STM32 系列微控制器中高级定时器(TIM1/TIM8)与通用定时器(TIM2~ TIM5/TIM9 ~TIM14)标配的核心功能模块,专为增量式正交编码器的信号采集与解析设计,是实现位置、旋转方向及转速测量的关键硬件接口。

该接口复用定时器输入捕获通道 1 和通道 2 作为两路正交信号(A 相、B 相)的输入引脚,可直接接收增量编码器旋转产生的正交脉冲信号;其核心工作机制为:根据 A、B 两相信号的相位差(超前 / 滞后),硬件自动控制定时器计数器(CNT)递增或递减,无需软件中断或轮询干预,既能通过 CNT 的绝对值表征编码器的绝对位置,也可通过 CNT 的变化方向判断旋转方向,还能结合定时采样周期计算 CNT 的变化率,实现旋转速度的精准测量。

编码器接口的硬件化处理大幅降低了 CPU 的运算负载,且每个高级 / 通用定时器均独立配置 1 个编码器接口,支持多通道编码器的并行采集,广泛应用于电机闭环控制、精密位移检测等工业场景。

2.2 工作模式

下表定义了 STM32 定时器编码器接口在不同计数模式下,计数方向与两路正交输入信号(TI1FP1、TI2FP2)有效边沿及电平状态的映射关系。在仅 TI1 计数、仅 TI2 计数及 TI1/TI2 双路计数三种模式下,硬件会根据另一路信号的电平状态,自动判断当前有效边沿对应的计数方向(向上 / 向下),从而精准解析增量式正交编码器的旋转方向与位置信息,为电机控制、位移检测等应用提供可靠的硬件级信号处理能力。

有效边沿 相对信号的电平 (TI1FP1 对应 TI2, TI2FP2 对应 TI1) TI1FP1 信号 TI1FP1 信号 TI2FP2 信号 TI2FP2 信号
上升 下降 上升 下降
仅在 TI1 计数 向下计数 向上计数 不计数 不计数
仅在 TI1 计数 向上计数 向下计数 不计数 不计数
仅在 TI2 计数 不计数 不计数 向上计数 向下计数
仅在 TI2 计数 不计数 不计数 向下计数 向上计数
在 TI1 和 TI2 上计数 向下计数 向上计数 向上计数 向下计数
在 TI1 和 TI2 上计数 向上计数 向下计数 向下计数 向上计数
2.3 编码器模式下计数器操作实例(均不反向)

在编码器接口配置为 TI1 和 TI2 双路计数且均不反向的模式下,计数器的操作行为由两路正交信号(TI1、TI2)的相位关系与边沿触发共同决定。当编码器正向旋转时,TI1 与 TI2 保持 90° 相位差,TI1FP1 与 TI2FP2 的有效边沿按固定顺序出现,硬件依据另一路信号的电平状态自动执行向上计数;当编码器反向旋转时,相位关系反转,有效边沿顺序改变,硬件切换为向下计数。对于信号毛刺等干扰,由于其不满足正常相位差条件,计数器会产生短暂的反向抖动,但整体趋势仍能准确反映实际旋转方向与位置变化。这一硬件级的信号解析机制,确保了在复杂工况下对编码器位置、方向及转速的可靠测量。

有效边沿 相对信号的电平 (TI1FP1 对应 TI2, TI2FP2 对应 TI1) TI1FP1 信号 TI1FP1 信号 TI2FP2 信号 TI2FP2 信号
上升 下降 上升 下降
在 TI1 和 TI2 上计数 向下计数 向上计数 向上计数 向下计数
在 TI1 和 TI2 上计数 向上计数 向下计数 向下计数 向上计数
2.4 编码器模式下计数器操作实例(TI1反相)

在编码器接口配置为 IC1FP1 反相(即 TI1 信号极性反转)的模式下,计数器的操作逻辑与均不反向模式形成镜像关系。当编码器正向旋转时,由于 TI1 信号被反相,两路正交信号的相位关系等效于反向旋转,硬件依据信号边沿与电平状态自动执行向下计数;当编码器反向旋转时,相位关系等效于正向旋转,硬件切换为向上计数。信号毛刺等干扰仍会导致计数器产生短暂抖动,但整体趋势与实际旋转方向相反,这一特性可用于适配特定安装方向的编码器或实现方向反转的控制需求。

有效边沿 相对信号的电平 (TI1FP1 对应 TI2, TI2FP2 对应 TI1) TI1FP1 信号 TI1FP1 信号 TI2FP2 信号 TI2FP2 信号
上升 下降 上升 下降
在 TI1 和 TI2 上计数 向下计数 向上计数 向上计数 向下计数
在 TI1 和 TI2 上计数 向上计数 向下计数 向下计数 向上计数

三、频率、占空比测量和编码器接口测速的实现

3.1 频率、占空比测量的实现
  • 首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 PA6(输入引脚)与 PA0(输出引脚)短接,以实现信号自环测试,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。

  • 直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。

    #include "stm32f10x.h" // Device header
    #include <OLED.h>
    #include <PWM.h>
    #include <IC.h>

    uint16_t Prescaler;

    int main(void){
    OLED_Init();
    PWM_Init();
    IC_Init();

    复制代码
      OLED_ShowString(1, 1, "Freq: 00000 Hz");
      OLED_ShowString(2, 1, "Duty: 00 %");
      
      // Current: ARR + 1 = 100
      // Freq = 72M / (PSC + 1) / (ARR + 1)	
      PWM_SetPrescaler(7200 - 1);  
      
      // Duty = CCR / (ARR + 1)
      PWM_SetCompare1(80);
      
      while(1){
      	OLED_ShowNum(1, 7, IC_GetFreq(), 5);
      	OLED_ShowNum(2, 7, IC_GetDuty(), 2);
      }

    }

  • 通过配置定时器参数:预分频器值 PSC = 7200 - 1、捕获 / 比较寄存器值 CCR = 80、自动重装载寄存器值 ARR = 100 - 1,可计算得到输出 PWM 信号的频率与占空比:频率 Freq = 系统时钟频率 / (PSC + 1) / (ARR + 1) = 72000000 / 7200 / 100 = 100 Hz,占空比 Duty = CCR / (ARR + 1) = 80 / 100 = 80%;最终将上述计算结果通过 OLED 显示屏输出,以验证参数配置的正确性。

3.2 编码器接口测速的实现
  • 首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏和旋转编码器,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。

  • 直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。

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

    int16_t Speed;

    int main(void){
    OLED_Init();
    Timer_Init();
    Encoder_Init();

    复制代码
      OLED_ShowString(1, 1, "Speed:");
      
      while(1){
      	OLED_ShowSignedNum(1, 7, Speed, 5);
      }

    }

  • 首先初始化 TIM3 为编码器接口模式,将 PA6/PA7 配置为上拉输入并接入编码器 A/B 相,通过设置 TIM3 预分频器为 0、自动重装载值为 65535,配合输入捕获滤波(0xF)实现编码器脉冲的精准计数;同时配置 TIM2 为定时中断模式,在中断服务函数中周期性读取 TIM3 计数值(Encoder_Get 函数)并清零计数器,将计数值赋值给 Speed 变量以表征转速;最终通过 OLED 显示屏实时显示该转速数值,完成编码器旋转速度的实时测量与可视化输出。

四、演示代码关联的头文件与源文件说明

  • OLED 相关头文件请从 STM32 学习 ------ 个人学习笔记4(OLED 显示屏及调试工具) 文末查看,此处不重复展示。

  • PWM.c

    #include "stm32f10x.h" // Device header

    void PWM_Init(void){
    // 提前声明需要使用的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    复制代码
      // 配置时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
      
      // 配置 GPIO
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA, &GPIO_InitStructure);
      
      // 选择为内部时钟
      TIM_InternalClockConfig(TIM2);
      
      // 初始化时基单元	
      TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      // CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1);  CK_PSC = 72M;
      TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;    // ARR
      TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  // PSC
      TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
      TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
      
      // 初始化输出比较单元
      TIM_OCStructInit(&TIM_OCInitStructure);
      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
      TIM_OCInitStructure.TIM_Pulse = 50;  // CCR
      TIM_OC1Init(TIM2, &TIM_OCInitStructure);
      
      // 手动清除更新中断标志位
      TIM_ClearFlag(TIM2, TIM_FLAG_Update);
      
      // 更新中断
      TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
      	
      // 启动定时器
      TIM_Cmd(TIM2, ENABLE);

    }

    void PWM_SetCompare1(uint8_t Compare){
    TIM_SetCompare1(TIM2, Compare);
    }

    void PWM_SetPrescaler(uint16_t Prescaler){
    TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
    }

  • PWM.h

    #include "stm32f10x.h" // Device header
    #ifndef __PWM_H
    #define __PWM_H

    void PWM_Init(void);
    void PWM_SetCompare1(uint8_t Compare);
    void PWM_SetPrescaler(uint16_t Prescaler);

    #endif

  • IC.c

    #include "stm32f10x.h" // Device header

    void IC_Init(void){
    // 提前声明需要使用的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;

    复制代码
      // 配置时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
      
      // 配置 GPIO
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA, &GPIO_InitStructure);		
      
      // 选择为内部时钟
      TIM_InternalClockConfig(TIM3);
      
      // 初始化时基单元	
      TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      // CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1);  CK_PSC = 72M;
      TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;    // ARR
      TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;  // PSC
      TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
      TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
      
      // 配置输入捕获单元
      TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
      TIM_ICInitStructure.TIM_ICFilter = 0xF;
      TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
      TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
      TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
      TIM_ICInit(TIM3, &TIM_ICInitStructure);

    // TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    // TIM_ICInitStructure.TIM_ICFilter = 0xF;
    // TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
    // TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
    // TIM_ICInit(TIM3, &TIM_ICInitStructure);

    复制代码
      // 配置交叉输入
      TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
      
      // 配置 TRGI 的触发源为 TI1FP1
      TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
      
      // 配置从模式为 Reset
      TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
      	
      // 启动定时器
      TIM_Cmd(TIM3, ENABLE);

    }

    uint32_t IC_GetFreq(void){
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);
    }

    uint32_t IC_GetDuty(void){
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
    }

  • IC.h

    #include "stm32f10x.h" // Device header

    void IC_Init(void){
    // 提前声明需要使用的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;

    复制代码
      // 配置时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
      
      // 配置 GPIO
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(GPIOA, &GPIO_InitStructure);		
      
      // 选择为内部时钟
      TIM_InternalClockConfig(TIM3);
      
      // 初始化时基单元	
      TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      // CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1);  CK_PSC = 72M;
      TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;    // ARR
      TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;  // PSC
      TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
      TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
      
      // 配置输入捕获单元
      TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
      TIM_ICInitStructure.TIM_ICFilter = 0xF;
      TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
      TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
      TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
      TIM_ICInit(TIM3, &TIM_ICInitStructure);

    // TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    // TIM_ICInitStructure.TIM_ICFilter = 0xF;
    // TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
    // TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
    // TIM_ICInit(TIM3, &TIM_ICInitStructure);

    复制代码
      // 配置交叉输入
      TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);
      
      // 配置 TRGI 的触发源为 TI1FP1
      TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
      
      // 配置从模式为 Reset
      TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
      	
      // 启动定时器
      TIM_Cmd(TIM3, ENABLE);

    }

    uint32_t IC_GetFreq(void){
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);
    }

    uint32_t IC_GetDuty(void){
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
    }

  • Encoder.c

    #include "stm32f10x.h" // Device header

    void Encoder_Init(void){
    // 提前声明需要使用的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;

    复制代码
      // 配置时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
      
      // 配置 GPIO
      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);		
      	
      // 初始化时基单元	
      TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      // CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1);  CK_PSC = 72M;
      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_ICStructInit(&TIM_ICInitStructure);
      TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
      TIM_ICInitStructure.TIM_ICFilter = 0xF;
      TIM_ICInit(TIM3, &TIM_ICInitStructure);
      
      TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
      TIM_ICInitStructure.TIM_ICFilter = 0xF;
      TIM_ICInit(TIM3, &TIM_ICInitStructure);
      
      // 配置编码器接口
      TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
      
      // 开启定时器
      TIM_Cmd(TIM3, ENABLE);

    }

    int16_t Encoder_Get(void){
    int16_t Temp;
    Temp = TIM_GetCounter(TIM3);
    TIM_SetCounter(TIM3, 0);
    return Temp;
    }

  • Encoder.h

    #include "stm32f10x.h" // Device header
    #ifndef __ENCODER_H
    #define __ENCODER_H

    void Encoder_Init(void);
    int16_t Encoder_Get(void);

    #endif

  • Timer.c

    #include "stm32f10x.h" // Device header
    #include <Encoder.h>

    // 声明其它文件已经定义过的变量
    extern int16_t Speed;

    void Timer_Init(void){
    // 提前声明需要使用的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    NVIC_InitTypeDef NVIC_InitStrucutre;

    复制代码
      // 配置时钟
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
      
      // 选择为内部时钟
      TIM_InternalClockConfig(TIM2);
      
      // 初始化时基单元	
      TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      // CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1);  CK_PSC = 72M;
      TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
      TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
      TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
      TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
      
      // 手动清除更新中断标志位
      TIM_ClearFlag(TIM2, TIM_FLAG_Update);
      
      // 更新中断
      TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
      
      // 配置 NVIC
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
      NVIC_InitStrucutre.NVIC_IRQChannel = TIM2_IRQn;
      NVIC_InitStrucutre.NVIC_IRQChannelCmd = ENABLE;
      NVIC_InitStrucutre.NVIC_IRQChannelPreemptionPriority = 2;
      NVIC_InitStrucutre.NVIC_IRQChannelSubPriority = 1;
      NVIC_Init(&NVIC_InitStrucutre);
      
      // 启动定时器
      TIM_Cmd(TIM2, ENABLE);

    }

    void TIM2_IRQHandler(void){
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){
    Speed = Encoder_Get();
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    }

  • Timer.h

    #ifndef __TIMER_H
    #define __TIMER_H

    void Timer_Init(void);
    void TIM2_IRQHandler(void);

    #endif


文中部分知识参考:B 站 ------ 江协科技;百度百科

相关推荐
努力学习的小廉2 小时前
redis学习笔记(三)—— hash数据类型
redis·笔记·学习
学编程的闹钟2 小时前
C语言WSAGetLastError函数
c语言·开发语言·学习
Struggle to dream2 小时前
STM32关于GPIO的模式配置详解
stm32·单片机·嵌入式硬件
Coisinilove2 小时前
MATLAB学习笔记——第一章
笔记·学习·matlab
努力学习的小廉2 小时前
redis学习笔记(四)—— list数据类型
redis·笔记·学习
xiaobobo33303 小时前
评判一下stm32的标准库函数写的怎样?
stm32·单片机·嵌入式硬件
Amazing_Cacao3 小时前
工艺师体系回顾|从参数到系统的能力跃迁(精品可可,精品巧克力)
学习
im_AMBER3 小时前
Leetcode 118 从中序与后序遍历序列构造二叉树 | 二叉树的最大深度
数据结构·学习·算法·leetcode
jay神3 小时前
基于SpringBoot的英语自主学习系统
java·spring boot·后端·学习·毕业设计
CappuccinoRose3 小时前
HTML语法学习文档(一)
前端·学习·html