【STM32--平衡车】编码器——用于测速

电机驱动模块👇

【STM32--平衡车】TB6612FNG详解[特殊字符]------用于电机驱动https://blog.csdn.net/2301_76153977/article/details/155577282?spm=1001.2014.3001.5501本篇我们学习
编码器模块------用于测速

1. 介绍------定时器编码器模式

其实属于定时器内容,但是都放一块内容太多了~

初识 定时器https://blog.csdn.net/2301_76153977/article/details/154427841?spm=1001.2014.3001.5501https://blog.csdn.net/2301_76153977/article/details/154427841?spm=1001.2014.3001.5501速度、方向、位置

想象编码器内有两个马盘,一个接入A相,一个接入B相,有所错开

正交编码器输出 A/B 两相脉冲(相位差 90°),TIM2/TIM3 的 CH1/CH2 引脚分别接 A/B 相,定时器会根据 A/B 相的上升 / 下降沿自动增减计数 ------ 正转时计数增加,反转时计数减少,无需软件中断处理,硬件自动完成,效率极高。

再复习一下定时器框图

2. encoder.c

2.1 核心逻辑

编码器要有两个,编码器一般和电机是放在一起的

  1. 配置 TIM2/TIM3 工作在编码器模式,对接两路正交编码器的 A/B 相脉冲;
  2. 初始化编码器对应的 GPIO 引脚(输入模式 + 上拉);
  3. 提供读取编码器计数值的接口(读取后清零,便于计算增量);
  4. 整体用于检测左右两个编码器的脉冲数(反映电机转动的方向和步数)。

2.2 写出encoder.c

全局变量定义

cpp 复制代码
TIM_HandleTypeDef encoder_left_handle = {0};
TIM_HandleTypeDef encoder_right_handle = {0};

TIM_HandleTypeDef:STM32 HAL 库的定时器句柄结构体,封装了定时器的实例(如 TIM2)、初始化参数、状态等,是 HAL 库操作外设的核心载体;

全局定义的原因:编码器计数值需要在函数间共享,且 HAL 库的回调 / 操作函数需要传入句柄指针;

初始化={0}:避免未初始化的随机值导致 HAL 库函数异常(嵌入式中全局变量默认初始化为 0,但显式初始化更规范)。

编码器初始化函数(以encoder_left_init为例)

cpp 复制代码
void encoder_left_init(void)
{
    TIM_Encoder_InitTypeDef config = {0};
    
    // 1. 定时器基础配置
    encoder_left_handle.Instance = TIM2;                  // 绑定TIM2外设
    encoder_left_handle.Init.Period = 65536 - 1;          // 自动重装值:65535(16位定时器最大值)
    encoder_left_handle.Init.Prescaler = 0;               // 预分频器:不分频
    encoder_left_handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式:向上(编码器模式下实际由脉冲方向决定)
    encoder_left_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频:不分频
    
    // 2. 编码器模式专属配置
    config.EncoderMode = TIM_ENCODERMODE_TI12;            // 编码器模式:TI1/TI2两路脉冲都参与计数
    // 通道1配置
    config.IC1Polarity = TIM_ICPOLARITY_RISING;           // 触发极性:上升沿
    config.IC1Selection = TIM_ICSELECTION_DIRECTTI;       // 输入捕获映射:直接映射到TI1引脚
    config.IC1Prescaler = TIM_ICPSC_DIV1;                 // 输入预分频:不分频(每个脉冲都捕获)
    config.IC1Filter = 0xF;                               // 输入滤波器:15个定时器时钟周期滤波
    // 通道2配置(和通道1一致)
    config.IC2Polarity = TIM_ICPOLARITY_RISING;
    config.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    config.IC2Prescaler = TIM_ICPSC_DIV1;
    config.IC2Filter = 0xF;
    
    // 3. 应用配置并启动编码器计数
    HAL_TIM_Encoder_Init(&encoder_left_handle, &config);  // 初始化编码器模式
    HAL_TIM_Encoder_Start(&encoder_left_handle, TIM_CHANNEL_ALL); // 启动所有通道计数
}

定时器编码器模式原理:

正交编码器输出 A/B 两相脉冲(相位差 90°),TIM2/TIM3 的 CH1/CH2 引脚分别接 A/B 相,定时器会根据 A/B 相的上升 / 下降沿自动增减计数 ------ 正转时计数增加,反转时计数减少,无需软件中断处理,硬件自动完成,效率极高。
TIM_ENCODERMODE_TI12:

编码器模式有 3 种:

TIM_ENCODERMODE_TI1:仅 TI1(A 相)脉冲触发计数;

TIM_ENCODERMODE_TI2:仅 TI2(B 相)脉冲触发计数;

TIM_ENCODERMODE_TI12:TI1 和 TI2 都触发(最常用,计数精度最高)。
Period = 65535:

16 位定时器的最大计数值是 65535,计数到 65535 后会溢出到 0(向上计数),或从 0 下溢到 65535(向下计数);如果需要更大的计数范围,需在软件中处理溢出(后面扩展会讲)。
IC1Filter = 0xF:

编码器脉冲可能有电磁干扰导致毛刺,滤波器会 "平滑" 输入信号 ------ 只有当引脚电平稳定 15 个定时器时钟周期后,才认为是有效脉冲,滤除短于 15 个周期的毛刺。
Prescaler = 0:

预分频器值为 0 表示 "不分频",定时器时钟 = APB1 时钟(STM32F4 中 APB1 最大 42MHz,F1 中 72MHz);如果设置为 1,则每 2 个脉冲才计数 1 次,用于降低计数频率。

左边编码器会了,右边编码器同理

cpp 复制代码
void encoder_right_init(void)
{
    TIM_Encoder_InitTypeDef config = {0};
    
    encoder_right_handle.Instance = TIM3;
    encoder_right_handle.Init.Period = 65536 - 1;
    encoder_right_handle.Init.Prescaler = 0;
    encoder_right_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    encoder_right_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    
    config.EncoderMode = TIM_ENCODERMODE_TI12;
    config.IC1Polarity = TIM_ICPOLARITY_RISING;
    config.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    config.IC1Prescaler = TIM_ICPSC_DIV1;
    config.IC1Filter = 0xF;
    config.IC2Polarity = TIM_ICPOLARITY_RISING;
    config.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    config.IC2Prescaler = TIM_ICPSC_DIV1;
    config.IC2Filter = 0xF;
    HAL_TIM_Encoder_Init(&encoder_right_handle, &config);
    HAL_TIM_Encoder_Start(&encoder_right_handle, TIM_CHANNEL_ALL);
}

MSP 初始化函数(HAL_TIM_Encoder_MspInit)

cpp 复制代码
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        __HAL_RCC_TIM2_CLK_ENABLE();    // 使能TIM2时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();   // 使能GPIOA时钟(TIM2_CH1=PA0,CH2=PA1)
        
        GPIO_InitTypeDef gpio_initstruct;
        gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;  // TIM2_CH1=PA0,CH2=PA1
        gpio_initstruct.Mode = GPIO_MODE_INPUT;         // 输入模式(编码器脉冲是输入)
        gpio_initstruct.Pull = GPIO_PULLUP;             // 上拉电阻(编码器通常是开漏输出,需上拉)
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   // 高速模式(适配编码器高频脉冲)
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    }
    else if(htim->Instance == TIM3)
    {
        __HAL_RCC_TIM3_CLK_ENABLE();    // 使能TIM3时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();   // 使能GPIOA时钟(TIM3_CH1=PA6,CH2=PA7)
        
        GPIO_InitTypeDef gpio_initstruct;
        gpio_initstruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;  // TIM3_CH1=PA6,CH2=PA7
        gpio_initstruct.Mode = GPIO_MODE_INPUT;         
        gpio_initstruct.Pull = GPIO_PULLUP;             
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    }
}

HAL_TIM_Encoder_MspInit:
HAL 库把外设核心配置(如编码器模式、计数参数)和底层硬件配置(时钟、GPIO)分离

------HAL_TIM_Encoder_Init负责定时器功能配置,HAL_TIM_Encoder_MspInit负责 "MCU 硬件层" 配置(时钟、引脚),这个函数是 HAL 库的 "弱函数",用户需要重写实现具体硬件配置。
GPIO_PULLUP:

正交编码器的输出引脚通常是开漏型(无内部上拉),如果不加外部上拉,必须配置 GPIO 内部上拉,否则引脚电平会悬空,导致计数乱跳。
引脚映射:

TIM2_CH1=PA0、CH2=PA1;TIM3_CH1=PA6、CH2=PA7 是 STM32 的 "默认复用功能映射",如果需要改引脚,需配置 AFIO 复用映射(高级用法)。

编码器计数值读取函数

cpp 复制代码
int16_t encoder_left_get(void)
{
    int16_t temp = __HAL_TIM_GET_COUNTER(&encoder_left_handle);  // 读取当前计数值
    __HAL_TIM_SET_COUNTER(&encoder_left_handle, 0);              // 清零计数器
    return temp;                                                  // 返回本次读取的增量值
}

__HAL_TIM_GET_COUNTER:

宏定义,直接读取定时器的 CNT 寄存器(计数寄存器),是硬件寄存器级别的操作,效率极高;
读取后清零的设计:

这种方式获取的是 "两次读取之间的脉冲增量",适合计算电机的瞬时速度(速度 = 脉冲增量 / 时间间隔);如果需要累计位置,不能清零,需记录累计值并处理溢出。

同理,右边编码器

cpp 复制代码
int16_t encoder_right_get(void)
{
    int16_t temp = __HAL_TIM_GET_COUNTER(&encoder_right_handle);
    __HAL_TIM_SET_COUNTER(&encoder_right_handle, 0);
    return temp;
}

在main函数中使用

运行,打开串口调试助手

顺时针转,0→4→8→......变大,都是四的倍数

这是因为计数 要计A的上升沿、A的下降沿、B的上升沿、B的下降沿

逆时针转,从0→65532(满量程,+4溢出变成0)

我如果想 逆时针转 是负数呢?

把u去掉就好了

试一试

相关推荐
轻微的风格艾丝凡1 小时前
电力电子技术常用PI参数整定方法
嵌入式硬件·dsp开发
Dillon Dong1 小时前
桥接鸿沟:Simulink 与 STM32 底层驱动的完美拥抱
stm32·单片机·嵌入式硬件
LXY_BUAA1 小时前
《嵌入式操作系统》_uboot中lcd驱动与logo显示_20251205
嵌入式硬件
ytttr8732 小时前
基于STM32平台实现AD7606数据采集并存储到SD卡
stm32·单片机·嵌入式硬件
lingzhilab2 小时前
零知IDE——基于零知ESP32与DRV8833的稳定电机测速系统实现教程
stm32·单片机
hazy1k2 小时前
MSPM0L1306 从零到入门: 第九章 ADC-电压采集
stm32·单片机·嵌入式硬件·mcu·物联网·51单片机·esp32
ACP广源盛139246256732 小时前
GSV2221G@ACP#产品参数规格解析与应用方式分享
单片机·嵌入式硬件·音视频
猫猫的小茶馆3 小时前
【ARM】BootLoader(Uboot)介绍
linux·汇编·arm开发·单片机·嵌入式硬件·mcu·架构
雾削木3 小时前
STM32CubeHAL 外设仿真大合集 | Proteus 8.15 (LCD1602+OLED+DHT11+DS18B20+舵机+蜂鸣器)
单片机·嵌入式硬件