基于STM32F407定时器5输入捕获的双编码器波形捕捉程序
一、系统概述
基于STM32F407ZGT6(Cortex-M4,168MHz),利用定时器5(TIM5)的四个输入捕获通道,实现对两个数字编码器(每个编码器输出A/B两相正交脉冲)的波形捕捉与脉冲计数。核心功能包括:
- 四路输入捕获:TIM5_CH1~CH4分别连接两个编码器的A/B相(编码器1:CH1=A1、CH2=B1;编码器2:CH3=A2、CH4=B2);
- 边沿检测:双边沿触发(上升沿+下降沿),提高分辨率;
- 方向与计数:通过A/B相相位关系判断编码器转向(正转/反转),累计脉冲数;
- 实时读取:支持主循环或中断中获取编码器位置。
二、硬件设计
2.1 核心组件
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| 主控 | STM32F407ZGT6(TIM5,4通道输入捕获) | 编码器信号捕获、脉冲计数、方向判断 |
| 编码器 | 2×EC11旋转编码器(A/B两相,5V) | 输出正交脉冲(每圈20~30脉冲,可扩展) |
| 接口电路 | 光耦隔离(TLP521)+ 上拉电阻(4.7kΩ) | 隔离编码器与MCU,防止干扰 |
2.2 硬件连接
| 信号 | STM32F407引脚(TIM5_CHx) | 编码器接口 | 说明 |
|---|---|---|---|
| 编码器1_A | PA0(TIM5_CH1) | 编码器1 A相 | 正交脉冲输入(双边沿捕获) |
| 编码器1_B | PA1(TIM5_CH2) | 编码器1 B相 | 正交脉冲输入(双边沿捕获) |
| 编码器2_A | PA2(TIM5_CH3) | 编码器2 A相 | 正交脉冲输入(双边沿捕获) |
| 编码器2_B | PA3(TIM5_CH4) | 编码器2 B相 | 正交脉冲输入(双边沿捕获) |
| 电源 | 3.3V/GND | 编码器VCC/GND | 编码器供电(需与MCU共地) |
三、软件设计(STM32 HAL库)
3.1 开发环境
- IDE:STM32CubeIDE 1.13.0+
- 库:STM32Cube_FW_F4_V1.28.0(HAL库)
- 工具链:GCC ARM Embedded
3.2 核心原理
数字编码器输出A/B两相正交脉冲(相位差90°),通过捕获A/B相的边沿并判断相位关系,可实现:
- 转向判断:A相上升沿时,B相为高电平→正转;B相为低电平→反转;
- 脉冲计数:每检测到一次有效边沿(如A相上升沿),脉冲数±1(根据方向);
- 分辨率提升:双边沿触发(上升沿+下降沿),脉冲数×2(相当于4倍频)。
3.3 核心代码实现
3.3.1 头文件 encoder_capture.h(寄存器定义与全局变量)
#ifndef __ENCODER_CAPTURE_H
#define __ENCODER_CAPTURE_H
#include "stm32f4xx_hal.h"
// ==================== TIM5通道定义(对应编码器引脚) ====================
#define ENCODER1_A_CH TIM_CHANNEL_1 // PA0(TIM5_CH1)
#define ENCODER1_B_CH TIM_CHANNEL_2 // PA1(TIM5_CH2)
#define ENCODER2_A_CH TIM_CHANNEL_3 // PA2(TIM5_CH3)
#define ENCODER2_B_CH TIM_CHANNEL_4 // PA3(TIM5_CH4)
// ==================== 编码器状态结构体 ====================
typedef struct {
int32_t count; // 脉冲计数(正转+1,反转-1)
uint8_t direction; // 方向:0=停止,1=正转,2=反转
} Encoder_TypeDef;
// ==================== 全局变量 ====================
extern TIM_HandleTypeDef htim5;
extern Encoder_TypeDef encoder1; // 编码器1状态
extern Encoder_TypeDef encoder2; // 编码器2状态
// ==================== 函数声明 ====================
void MX_TIM5_Init(void); // TIM5初始化(输入捕获模式)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim); // 输入捕获中断回调
void Encoder_Process(Encoder_TypeDef *enc, uint32_t channel, GPIO_TypeDef *gpio_port, uint16_t gpio_pin_a, uint16_t gpio_pin_b); // 编码器数据处理
#endif
3.3.2 TIM5初始化(输入捕获模式)
#include "encoder_capture.h"
#include "gpio.h"
TIM_HandleTypeDef htim5;
Encoder_TypeDef encoder1 = {0}; // 编码器1初始计数0
Encoder_TypeDef encoder2 = {0}; // 编码器2初始计数0
// TIM5初始化(4通道输入捕获,双边沿触发)
void MX_TIM5_Init(void) {
TIM_IC_InitTypeDef sConfigIC = {0};
htim5.Instance = TIM5;
htim5.Init.Prescaler = 0; // 不分频(168MHz时钟,计数频率168MHz)
htim5.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
htim5.Init.Period = 0xFFFFFFFF; // 最大计数值(4通道独立捕获,无需ARR限制)
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&htim5);
// 配置通道1(编码器1_A相):双边沿触发,无滤波
sConfigIC.ICPolarity = TIM_ICPOLARITY_BOTHEDGE; // 双边沿触发
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接映射
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频(每个边沿捕获)
sConfigIC.ICFilter = 0x00; // 无滤波(如需抗噪可设为0x0F)
HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, ENCODER1_A_CH);
// 配置通道2(编码器1_B相):同上
HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, ENCODER1_B_CH);
// 配置通道3(编码器2_A相):同上
HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, ENCODER2_A_CH);
// 配置通道4(编码器2_B相):同上
HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, ENCODER2_B_CH);
// 使能TIM5中断(输入捕获中断)
HAL_TIM_IC_Start_IT(&htim5, ENCODER1_A_CH); // 启动通道1捕获
HAL_TIM_IC_Start_IT(&htim5, ENCODER1_B_CH); // 启动通道2捕获
HAL_TIM_IC_Start_IT(&htim5, ENCODER2_A_CH); // 启动通道3捕获
HAL_TIM_IC_Start_IT(&htim5, ENCODER2_B_CH); // 启动通道4捕获
}
3.3.3 GPIO初始化(TIM5通道引脚配置)
void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// TIM5通道引脚(PA0~PA3):复用功能输入
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽(输入捕获)
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻(编码器输出开漏时需上拉)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; // TIM5复用功能(AF2)
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
3.3.4 输入捕获中断回调(核心处理逻辑)
// 输入捕获中断回调函数(HAL库自动调用)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance != TIM5) return; // 仅处理TIM5中断
// 编码器1_A相(CH1)捕获事件
if (htim->Channel == ENCODER1_A_CH) {
Encoder_Process(&encoder1, ENCODER1_A_CH, GPIOA, GPIO_PIN_0, GPIO_PIN_1);
}
// 编码器1_B相(CH2)捕获事件(可选:若仅需A相触发,可忽略B相中断)
else if (htim->Channel == ENCODER1_B_CH) {
// 可在此处补充B相处理逻辑(如冗余校验)
}
// 编码器2_A相(CH3)捕获事件
else if (htim->Channel == ENCODER2_A_CH) {
Encoder_Process(&encoder2, ENCODER2_A_CH, GPIOA, GPIO_PIN_2, GPIO_PIN_3);
}
// 编码器2_B相(CH4)捕获事件(可选)
else if (htim->Channel == ENCODER2_B_CH) {
// 可在此处补充B相处理逻辑
}
}
// 编码器数据处理函数(判断方向+计数)
void Encoder_Process(Encoder_TypeDef *enc, uint32_t channel, GPIO_TypeDef *gpio_port, uint16_t gpio_pin_a, uint16_t gpio_pin_b) {
uint8_t a_level = HAL_GPIO_ReadPin(gpio_port, gpio_pin_a); // A相当前电平
uint8_t b_level = HAL_GPIO_ReadPin(gpio_port, gpio_pin_b); // B相当前电平
// 仅处理A相捕获事件(通过A相边沿判断方向)
if (channel == ENCODER1_A_CH || channel == ENCODER2_A_CH) {
if (a_level) { // A相上升沿(从高到低?需根据硬件确认,此处假设捕获上升沿)
enc->direction = (b_level) ? 1 : 2; // B相高→正转(1),低→反转(2)
} else { // A相下降沿
enc->direction = (b_level) ? 2 : 1; // B相高→反转(2),低→正转(1)
}
// 更新计数(正转+1,反转-1)
enc->count += (enc->direction == 1) ? 1 : -1;
}
}
3.3.5 主函数(读取编码器数据)
#include "main.h"
#include "encoder_capture.h"
int main(void) {
HAL_Init();
SystemClock_Config(); // 168MHz时钟配置
MX_GPIO_Init(); // GPIO初始化(TIM5引脚)
MX_TIM5_Init(); // TIM5输入捕获初始化
while (1) {
// 读取编码器1数据(示例:通过串口打印)
printf("Encoder1: Count=%ld, Direction=%d\r\n", encoder1.count, encoder1.direction);
printf("Encoder2: Count=%ld, Direction=%d\r\n", encoder2.count, encoder2.direction);
HAL_Delay(100); // 100ms刷新一次
}
}
// printf重定向到USART1(调试用)
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
return ch;
}
参考代码 基于stm32f407,利用定时器5的四个输入捕获通道,来捕捉两个数字编码器输出的波形 www.youwenfan.com/contentcss/182710.html
四、关键问题与解决方案
4.1 编码器信号干扰
-
问题:电机运转时编码器信号抖动,导致误计数。
-
解决:
- 硬件:编码器输出串联100Ω电阻,并联0.1μF电容滤波;
- 软件:在
Encoder_Process中添加软件消抖(连续3次检测到相同边沿才计数)。
4.2 中断优先级冲突
-
问题:多通道中断同时触发导致计数丢失。
-
解决:
- 配置TIM5中断优先级为较高(如NVIC_PRIORITYGROUP_4,抢占优先级2);
- 在中断回调函数中精简代码,仅标记事件,在主循环中处理计数(降低中断占用时间)。
4.3 方向判断错误
- 问题:A/B相相位关系与代码逻辑不符(如编码器反向安装)。
- 解决:交换A/B相引脚连接,或修改
Encoder_Process中方向判断条件(如b_level取反)。
五、测试与验证
- 硬件连接:按2.2节连接编码器与STM32,确保光耦隔离电路正常;
- 功能测试:手动旋转编码器1,观察
encoder1.count是否递增/递减(正转+1,反转-1); - 精度测试:编码器旋转1圈(如20脉冲),计数应为20×4=80(双边沿触发,4倍频),误差<±1%。
六、总结
基于STM32F407的TIM5四通道输入捕获,实现了双编码器的波形捕捉与脉冲计数。核心是双边沿触发提高分辨率、相位关系判断方向、中断回调实时处理。通过模块化设计,可扩展至多编码器系统或结合PID算法实现闭环控制。