在上一篇文章《TIM输入捕获(STM32标准库学习笔记)》中,我们学习了如何利用定时器的输入捕获功能测量输入信号的频率、周期和脉宽。输入捕获本质上是对外部信号边沿的捕捉与计时,它能够帮助我们精确测量外部脉冲的时间信息。然而,在实际的电机控制、位置检测等场景中,我们往往不仅需要测量脉冲的时间,更需要实时获取旋转机构的位置、方向和速度,这时仅仅依靠输入捕获就显得力不从心了。
为了解决这一问题,STM32 的定时器在输入捕获的基础上,进一步提供了编码器接口(Encoder Interface)功能。编码器接口可以接收增量(正交)编码器输出的两路正交信号,由硬件自动完成信号的解码与计数,从而直接得到旋转的位置、方向和速度信息,无需 CPU 参与繁琐的信号处理。可以说,编码器接口是输入捕获功能在工程应用中的一次重要延伸。
本文作为《STM32标准库学习笔记》系列的延续,将系统讲解 TIM 编码器接口的原理、基本结构、工作模式、配置流程,并通过一个编码器测速实例,帮助大家把理论知识落实到实际代码中。下面我们就正式开始学习。
如果觉得有收获可以为我的文章点点小赞,这将激励我创作更好的内容!谢谢!
一、TIM编码器接口
1.1 编码器接口简介
Encoder Interface 编码器接口
编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
每个高级定时器和通用定时器都拥有1个编码器接口
两个输入引脚借用了输入捕获的通道1和通道2

1.2 正交编码器

1.3 编码器接口基本结构

1.4 工作模式

1.5 实例(均不反相)

1.6 实例(TI1反相)
极性选择–>反相


1.7 编码器接口的配置流程
第一步,RCC开启时钟,开启GPIO和定时器的时钟;
第二步,配置GPIO,这里需要把PA6和PA7配置成输入模式;
第三步,配置时基单元,这里预分频器我们一般选择不分频,自动重装,一般给最大65535,只需要个CNT执行计数就行了;
第四步,配置输入捕获单元,这里输入捕获单元只有滤波器和极性这两个参数有效,其他差数与编码器无关;
第五步,配置编码器接口模式;
最后,调用TIM_Cmd,启动定时器。
1.8 GPIO口输入模式的选择
看外部模块引脚输出的默认电平选择GPIO口的输入模式
上拉输入:如果外部模块空闲默认输出高电平,我们就选择上拉输入,默认输入高电 平;
下拉输入:如果外部模块空闲默认输出低电平,我们就选择下拉输入,默认输入低电 平,和外部模块保持默认状态一致,防止默认电平打架;
浮空输入:如果不确定外部模块空闲默认输出状态,或者外部信号输出功率非常小, 这时尽量选择浮空输入,浮空输入,没有上拉电阻和下拉电阻去影响外部信 号,但是缺点就是当引脚悬空时,没有默认的电平了,输入就会受噪声干扰, 来回不断地跳变;
1.9 实例一 6.8.1 编码器接口测速
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_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
/*
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);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//GPIO_Pin_6
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*时基单元初始化*/
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_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_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//选择配置定时器通道2
TIM_ICInitStructure.TIM_ICFilter = 0xF;//输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Falling,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;
}
—————————–
main.c↓
—————————–
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder2.h"
int16_t Speed;
int main(void)
{
OLED_Init();
Timer_Init();
Encoder_Init();
OLED_ShowString(1,1,"Speed:");
//OLED_ShowString(2,1,"CNT:");
while(1)
{
OLED_ShowSignedNum(1,7,Speed,5);
//OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2,TIM_IT_Update)==SET )
{
Speed = Encoder_Get();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
—————————–

1.10 问题
发现显示屏的计数没有负数,记录了+65532?
问题产生原因:显示屏显示的变量“Speed”定义时类型错定义为无符号16位整型,0再减数值溢出成65535了
解决方案:修改变量“Speed”类型为有符号16位整型即可
二、总结
本文围绕 STM32 的 TIM 编码器接口展开,系统梳理了编码器接口的工作原理、基本结构、工作模式以及完整的配置流程,并通过一个编码器测速实例,将理论知识落实到实际代码中。
编码器接口的核心价值在于:它能够直接接收增量(正交)编码器的两路正交信号,由硬件自动控制计数器 CNT 的自增或自减,从而无需 CPU 干预即可实时获取编码器的位置、旋转方向和旋转速度。每个高级定时器和通用定时器都内置一个编码器接口,两个输入引脚复用输入捕获的通道 1 和通道 2,使用起来非常方便。
在配置流程上,主要分为以下几步:先通过 RCC 开启 GPIO 和定时器的时钟;再将 PA6、PA7 配置为输入模式;随后配置时基单元,预分频器一般不分频,自动重装值设为最大 65535,仅让 CNT 执行计数;接着配置输入捕获单元,其中只有滤波器和极性两个参数有效;然后配置编码器接口模式;最后调用 TIM_Cmd 启动定时器。整个流程清晰,按部就班即可完成初始化。
GPIO 输入模式的选择需要结合外部模块的默认电平:外部空闲默认输出高电平时选择上拉输入,默认输出低电平时选择下拉输入,若不确定默认状态或信号功率较小时则选择浮空输入。正确选择输入模式,可以避免默认电平冲突,保证信号稳定。
在实例环节,通过 Timer.c、Encoder.c 和 main.c 三个文件的配合,实现了编码器测速功能。其中 Encoder.c 完成了 GPIO、时基单元、输入捕获和编码器接口模式的初始化,main.c 在定时中断中周期性读取编码器计数并换算为速度显示在 OLED 上。针对测速过程中出现的计数无负数、显示 +65532 的问题,定位为变量 Speed 被错误定义为无符号 16 位整型,导致 0 减数值溢出为 65535,将其改为有符号 16 位整型后问题即得到解决。
总的来说,TIM 编码器接口将复杂的正交信号解码工作交由硬件完成,大大简化了编码器应用的开发难度。掌握其工作原理和配置流程后,可以方便地将其应用到电机测速、位置检测、旋转方向判断等实际项目中。后续若需进一步提高测量精度,还可以结合定时器中断、滤波参数调整以及更精细的计数周期设计来优化。
都看到这了点点赞吧!
网硕互联帮助中心






评论前必须登录!
注册