STM标准库-TIM旋转编码器
文章目录
- 一、编码器接口
- 1.1简介
- 1.2正交编码器
- 1.3编码器接口基本结构
- **1. 模块与 STM32 配置的映射关系**
- **2. 设计实现步骤(核心流程)**
- **① 硬件规划**
- **② 时钟使能**
- **③ GPIO 配置(对应架构图 “GPIO” 模块)**
- **④ 时基单元配置(对应架构图 “时基单元”)**
- **⑤ 输入滤波 + 极性配置(对应架构图 “滤波器、边沿 / 极性选择”)**
- **⑥ 编码器接口模式(对应架构图 “编码器接口”)**
- **⑦ 使能定时器,开始计数**
- **⑧ 数据读取(轮询 / 中断)**
- **3. 关键逻辑说明**
- 1.4工作模式
- 1.5实例(均不反相)
- 1.6实例(TI1反相)
- 二、编码器接口测速
- 2.1接线图
- 2.2相关API
- 一、编码器接口配置 API
- 1. `TIM_EncoderInterfaceConfig()`
- 二、计数器操作 API
- 2. `TIM_SetCounter()`
- 3. `TIM_GetCounter()`
- 三、编码器方向控制 API
- 4. `TIM_GetCounterMode()`
- 四、编码器溢出标志 API
- 5. `TIM_GetFlagStatus()`
- 五、编码器中断配置 API
- 6. `TIM_ITConfig()`
- 六、编码器接口状态读取 API
- 7. `TIM_GetCapture1()` / `TIM_GetCapture2()`
- 2.3代码
- 2.4现象
一、编码器接口
1.1简介
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT
- 自增或自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有1个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2
1.2正交编码器
- 正交编码器:两个方波信号相位相差90°,超前90°或者滞后90°,分别代表正转和反转。
- 工作流程:根据编码器旋转产生的正交信号脉冲自动控制CND自增或自减,从而指示编码器的位置、旋转方向和旋转速度。
1.3编码器接口基本结构
-
编码器测速实际上就是测频法测正交脉冲的频率。
-
要基于该架构图设计实现编码器接口,可按 “模块映射→配置步骤→代码实现” 逻辑展开,核心是将架构图的功能模块对应到 STM32的寄存器配置:
1. 模块与 STM32 配置的映射关系
架构图模块 | STM32 功能配置 | 关键 API / 参数 |
---|---|---|
GPIO | 编码器信号输入引脚初始化(上拉输入) | GPIO_Mode_IPU |
滤波器 | 输入信号滤波(抗抖动) | TIM_ICFilter = 0xF (高滤波) |
边沿 / 极性选择 | 信号边沿检测与极性反转 | TIM_ICPolarity_Rising/Falling |
编码器接口 | 正交信号计数模式(双相 / 单相) | TIM_EncoderMode_TI12 |
时基单元 | 计数器范围(ARR)和预分频(PSC) | TIM_Period=65535 、TIM_Prescaler=0 |
2. 设计实现步骤(核心流程)
① 硬件规划
- 选定定时器(如
TIM3
),其 CH1(PA6)、CH2(PA7) 引脚连接编码器 A 相、B 相。
② 时钟使能
c
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
③ GPIO 配置(对应架构图 “GPIO” 模块)
c
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,稳定信号
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // PA6(CH1)、PA7(CH2)
GPIO_Init(GPIOA, &GPIO_InitStructure);
④ 时基单元配置(对应架构图 “时基单元”)
c
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_Period = 65535; // ARR=65535(最大计数范围)
TIM_TimeBaseInitStructure.TIM_Prescaler = 0; // PSC=0(计数器由外部信号驱动,预分频无实际作用)
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 初始方向(编码器会覆盖)
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
⑤ 输入滤波 + 极性配置(对应架构图 “滤波器、边沿 / 极性选择”)
c
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure); // 结构体默认值初始化
TIM_ICInitStructure.TIM_ICFilter = 0xF; // 最高滤波等级,抗抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure); // 配置CH1
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; // 切换到CH2
TIM_ICInit(TIM3, &TIM_ICInitStructure); // 配置CH2
⑥ 编码器接口模式(对应架构图 “编码器接口”)
c
TIM_EncoderInterfaceConfig( TIM3, // 定时器 TIM_EncoderMode_TI12, // 双相计数(TI1、TI2边沿均响应,精度×2) TIM_ICPolarity_Rising, // CH1不反相(上升沿触发) TIM_ICPolarity_Rising // CH2不反相(上升沿触发)
);
⑦ 使能定时器,开始计数
c
TIM_Cmd(TIM3, ENABLE); // 启动计数器,响应编码器信号
⑧ 数据读取(轮询 / 中断)
c
// 轮询方式:直接读取计数值
int16_t encoderValue = TIM_GetCounter(TIM3); // 中断方式:配合TIM2定时中断(如题目代码),周期性读取增量
3. 关键逻辑说明
- 架构图的信号流:
编码器 A/B 相 → GPIO 输入 → 滤波器(TIM_ICFilter
)→ 边沿 / 极性处理(TIM_ICPolarity
)→ 生成 TI1FP1/TI2FP2 → 编码器接口 → 驱动时基单元 CNT 计数。 - 计数方向自动控制:
编码器接口根据 TI1、TI2 的边沿跳变顺序,自动控制 CNT 向上 / 向下计数(正转 / 反转),无需软件判断方向。
1.4工作模式
1.5实例(均不反相)
1.6实例(TI1反相)
二、编码器接口测速
2.1接线图
- 编码器需要接入两路正交信号(A 相和 B 相),对应定时器的 通道 1(CH1)和通道 2(CH2)。
- CH1 对应 PA6,CH2 对应 PA7(参考 STM32F103C8T6 数据手册)。
TIM3 作为 通用定时器,支持编码器接口模式(基本定时器 TIM6/7 不支持),而高级定时器 TIM1 虽支持但通常用于更复杂功能(如互补 PWM 输出)。 - 编码器接口模式下,TIM3 可通过硬件自动处理正交信号的计数方向(自增 / 自减),无需软件干预,符合 “节约 CPU 资源” 的设计目标。
- 若选用其他定时器(如 TIM2)
- 引脚映射:TIM2 的 CH1/CH2 对应 PA0/PA1,若 PA0/PA1 已被其他外设(如 PWM 输出)占用,则优先选择 TIM3 的 PA6/PA7。
- 功能冲突:若项目中 TIM2 用于 PWM(如电机驱动),则 TIM3 更适合编码器接口,避免资源冲突。
- 高级定时器 TIM1 的局限性
- TIM1 虽支持编码器接口,但其引脚(如 PA8/PA9)常作为高级 PWM 输出(如三相电机控制),且配置复杂(需额外使能 APB2 时钟),不如通用定时器 TIM3。
2.2相关API
一、编码器接口配置 API
1. TIM_EncoderInterfaceConfig()
运行
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode, uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
-
功能:配置定时器为编码器接口模式
-
参数:
-
TIMx
:定时器选择(如TIM3
) -
TIM_EncoderMode
:编码器模式
TIM_EncoderMode_TI1
:仅 TI1 计数(A 相)TIM_EncoderMode_TI2
:仅 TI2 计数(B 相)TIM_EncoderMode_TI12
:TI1 和 TI2 同时计数(双相计数,精度 ×2)
-
TIM_IC1Polarity
:TI1 极性配置
TIM_ICPolarity_Rising
:不反相(上升沿触发)TIM_ICPolarity_Falling
:反相(下降沿触发)
-
TIM_IC2Polarity
:TI2 极性配置(同上)
-
-
示例
运行
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
二、计数器操作 API
2. TIM_SetCounter()
运行
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
-
功能:直接设置定时器计数器值(CNT)
-
参数:
TIMx
:定时器选择Counter
:目标计数值(0~65535)
-
示例:
运行
TIM_SetCounter(TIM3, 0); // 清零计数器(常用于读取增量值后复位)
3. TIM_GetCounter()
运行
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
-
功能:获取当前计数器值(CNT)
-
参数 :
TIMx
:定时器选择
-
返回值:当前计数值(0~65535)
-
示例
运行
uint16_t count = TIM_GetCounter(TIM3); // 读取当前计数值
三、编码器方向控制 API
4. TIM_GetCounterMode()
运行
uint16_t TIM_GetCounterMode(TIM_TypeDef* TIMx);
-
功能:获取计数器计数模式(向上 / 向下)
-
参数 :
TIMx
:定时器选择
-
返回值 :
TIM_CounterMode_Up
:向上计数(编码器正转)TIM_CounterMode_Down
:向下计数(编码器反转)
-
示例
运行
uint16_t mode = TIM_GetCounterMode(TIM3); // 获取当前计数方向
四、编码器溢出标志 API
5. TIM_GetFlagStatus()
运行
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
-
功能:检查定时器标志位状态(如溢出标志)
-
参数:
-
TIMx
:定时器选择 -
TIM_FLAG
:标志位选择
TIM_FLAG_Update
:更新事件标志(计数器溢出 / 下溢)
-
-
返回值 :
SET
:标志位被置位RESET
:标志位未被置位
-
示例:
运行
if (TIM_GetFlagStatus(TIM3, TIM_FLAG_Update) == SET) {// 处理计数器溢出/下溢事件TIM_ClearFlag(TIM3, TIM_FLAG_Update); // 清除标志位 }
五、编码器中断配置 API
6. TIM_ITConfig()
运行
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
-
功能:使能或禁用定时器中断
-
参数 :
-
TIMx
:定时器选择 -
TIM_IT
:中断源选择
TIM_IT_Update
:更新中断(计数器溢出 / 下溢)
-
NewState
:使能状态
ENABLE
:使能中断DISABLE
:禁用中断
-
-
示例 :
c
运行
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 使能TIM3更新中断
六、编码器接口状态读取 API
7. TIM_GetCapture1()
/ TIM_GetCapture2()
运行
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
-
功能:获取编码器接口的捕获值(CCR 寄存器)
-
参数 :
TIMx
:定时器选择
-
返回值 :
- 通道 1/2 的捕获值(编码器接口中较少使用,主要用于输入捕获模式)
2.3代码
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更新事件的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}
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; //选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2TIM_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;
}
模块 | 作用 |
---|---|
TIM3 | 编码器接口,硬件自动计数(自增 / 自减),记录编码器旋转的增量值 |
TIM2 | 定时器,周期性触发中断(如 100ms),在中断中读取 TIM3 的计数值并更新 Speed |
主函数 | 循环显示 Speed 值,依赖 TIM2 中断更新数据 |
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_Hvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif
2.4现象
- OLED 显示内容
初始状态:OLED 第一行显示 Speed:0,表示编码器未旋转时速度为 0。
旋转编码器时:实时显示速度值,格式为 Speed:±XXXX(± 表示方向,XXXX 为数值)。
- 旋转方向与数值正负
正转(向右旋转):Speed 显示正数值,旋转越快数值越大(如 Speed:+120)。
反转(向左旋转):Speed 显示负数值,旋转越快数值越小(如 Speed:-80)。
停止旋转:Speed 逐渐归零(取决于编码器机械特性和滤波配置)。
- 旋转速度与数值大小
慢速旋转:数值绝对值小(如 ±10~±50)。
快速旋转:数值绝对值大(如 ±100~±200)。
- 编码器接口连接
PA6(TIM3_CH1)接编码器 A 相,PA7(TIM3_CH2)接 B 相,若接线反相:
正转显示负数值,反转显示正数值(方向相反)。