STM32F4 编码器计数在不应该发生变化时发生变化

STM32F4 Encoder count is changing when it should not

我目前正在使用 STM32F4 和 STM32F429ZI Nucleo-144 开发板。我希望使用这个微控制器通过正交编码器接口评估旋转编码器的位置。查看文档,这是通过计时器完成的。我有 A/B 编码器输出连接到微型 PA6 和 PC7,但我注意到计数似乎在漂移。

在调试过程中,我注意到如果我断开连接到微控制器的编码器输出之一并移动电机,计数仍然 increment/decrement,即使只连接了一个编码器线。因为我指望 TI1 和 TI2 边缘,所以这不应该发生。如果我正确阅读下图,因为我的一条线路使用内部上拉电阻保持高电平,另一个输入上的时钟脉冲应该 up/down/up/down 并且实际上只是在两个不同的计数之间循环。但是,如果我旋转编码器,计数会根据方向不断递增或递减。

为什么只连接一个编码器输入时编码器计数会发生变化?我还附加了范围跟踪以证明只有一个计数处于活动状态,以及代码。

编辑:我也曾尝试将极性从 BOTH EDGE 更改为 RISING EDGE,但没有明显的好处。

#include "stm32f4xx_hal.h"
#include "encoder_test.h"

GPIO_InitTypeDef  GPIO_InitStruct;
TIM_HandleTypeDef Timer_InitStruct;
TIM_Encoder_InitTypeDef Encoder_InitStruct;

void EncoderTest_Init()
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_TIM3_CLK_ENABLE();

    /**TIM3 GPIO Configuration
    PA6     ------> TIM3_CH1
    PC7     ------> TIM3_CH2
    */

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    Timer_InitStruct.Instance = TIM3;
    Timer_InitStruct.Init.Period = 0xFFFF;
    Timer_InitStruct.Init.CounterMode = TIM_COUNTERMODE_UP;
    Timer_InitStruct.Init.Prescaler = 1;
    Timer_InitStruct.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    Encoder_InitStruct.EncoderMode = TIM_ENCODERMODE_TI12;
    Encoder_InitStruct.IC1Filter = 0x00;
    Encoder_InitStruct.IC1Polarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE;
    Encoder_InitStruct.IC1Prescaler = TIM_ICPSC_DIV1;
    Encoder_InitStruct.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    Encoder_InitStruct.IC2Filter = 0x00;
    Encoder_InitStruct.IC2Polarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE;
    Encoder_InitStruct.IC2Prescaler = TIM_ICPSC_DIV1;
    Encoder_InitStruct.IC2Selection = TIM_ICSELECTION_DIRECTTI;

    if (HAL_TIM_Encoder_Init(&Timer_InitStruct, &Encoder_InitStruct) != HAL_OK)
    {
        while (1);
    }

    if (HAL_TIM_Encoder_Start_IT(&Timer_InitStruct, TIM_CHANNEL_1) != HAL_OK)
    {
        while (1);
    }
}


void TIM3_IRQHandler()
{
    HAL_TIM_IRQHandler(&Timer_InitStruct);
}

经进一步调查,问题似乎出在预分频器上。当您提供偶数值时,预分频器在编码器模式下不起作用。由于预分频器是输入值+1,使用STM32F4 HAL,输入的预分频器必须是偶数。

我发现我不是唯一遇到此问题的人 forum post。 post 上有一些讨论认为预分频器可能与编码器模式不兼容,但这尚未得到证实。我已经向 ST 发送了一封电子邮件,以深入了解它。如果不支持,输入预分频器值 0 是安全的。

下面是工作代码:

#include "stm32f4xx_hal.h"
#include "encoder_test.h"

GPIO_InitTypeDef  GPIO_InitStruct;

TIM_HandleTypeDef Timer3_InitStruct;
TIM_Encoder_InitTypeDef EncoderTim3_InitStruct;

void EncoderTest_Init_Tim3()
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_TIM3_CLK_ENABLE();

    /**TIM3 GPIO Configuration
    PA6     ------> TIM3_CH1
    PC7     ------> TIM3_CH2
    */

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    Timer3_InitStruct.Instance = TIM3;
    Timer3_InitStruct.Init.Period = 0xFFFF;
    Timer3_InitStruct.Init.CounterMode = TIM_COUNTERMODE_UP;
    Timer3_InitStruct.Init.Prescaler = 10;
    Timer3_InitStruct.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    EncoderTim3_InitStruct.EncoderMode = TIM_ENCODERMODE_TI12;
    EncoderTim3_InitStruct.IC1Filter = 0x00;
    EncoderTim3_InitStruct.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
    EncoderTim3_InitStruct.IC1Prescaler = TIM_ICPSC_DIV4;
    EncoderTim3_InitStruct.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    EncoderTim3_InitStruct.IC2Filter = 0x00;
    EncoderTim3_InitStruct.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
    EncoderTim3_InitStruct.IC2Prescaler = TIM_ICPSC_DIV4;
    EncoderTim3_InitStruct.IC2Selection = TIM_ICSELECTION_DIRECTTI;

    if (HAL_TIM_Encoder_Init(&Timer3_InitStruct, &EncoderTim3_InitStruct) != HAL_OK)
    {
        while (1);
    }

    if (HAL_TIM_Encoder_Start_IT(&Timer3_InitStruct, TIM_CHANNEL_1) != HAL_OK)
    {
        while (1);
    }
}



void TIM3_IRQHandler()
{
    HAL_TIM_IRQHandler(&Timer3_InitStruct);
}

编辑: 在与 ST 技术支持交谈后,编码器接口不打算与预分频器值一起使用,偶数或奇数。我在下面粘贴了他们的回复,但即使使用似乎有效的预分频器值,编码器计数似乎也可能随时间漂移。

另一种解决方案是不使用预分频器,而是使用建议的方法 here 将 16 位值扩展为 32 位值 space。我已经在此处转载了该方法,以防 link 失效:


来自 ST 论坛用户 goosen.kobus.001 于 11/19/2013:

根据我的经验,使用溢出中断来放大编码器是不可靠的,尤其是当你有高分辨率编码器时:有时会发生编码器值在你进入中断的瞬间改变符号的情况,导致系统在应该递减等的时候递增上位字。如果编码器应该停在 0,则尤其如此,就像命令伺服电机转到编码器位置 0 一样。

我发现的最佳方法是手动完成。这是我的程序:

  1. 确保读取编码器值的控制回路经常 运行,(即,如果您的编码器全速旋转,编码器值仍至少读取 10-溢出之间有 20 次。对于我的伺服电机应用程序,1 毫秒的循环间隔就足够了。

  2. 跟踪最后读取的编码器值。

  3. 将当前和最后一个编码器值分成象限(最高有效 2 位)。即 pos_now &= 0xC000; pos_last &= 0xC000;

  4. 检查编码器是否在最后一步从象限0移动到象限3或从3移动到0:

    4.1 if(pos_now == 0 && pos_last == 0xC000) upper_word++;

    4.2 if(pos_now == 0xC000 && pos_last == 0) upper_word--;

这就是为什么我说编码器读取循环需要经常 运行;您需要确保该值被足够频繁地读取,以至于不可能在两次读取之间从象限 0->1->2->3 移动。 也应该可以将此逻辑放在另一个定时器中断中,例如 10kHz 运行s。这样你就有了一个总是最新的编码器值。


ST 响应:

嗨,

我收到了定时器的设计和架构师的反馈。 编码器接口设计为无需预分频器即可工作,以免降低编码器的分辨率。

如您所见,他们已经确认它不能使用偶数预分频器值,只能使用奇数。 我们有一个用于预分频器的子计数器,它是单向的,因此不受计数器方向的影响,并且在定时器时钟的每个上升沿递增(没有预分频器)。 计数器方向在定时器时钟的每个上升沿更新(没有预分频器),但计数器仅在预分频器的子计数器达到编程值时根据方向位中的值递增。

因此,在一种情况下,行为与没有预分频器的行为相同,因为计数器以不同的方向更新(每个奇数时钟周期数),但在另一种情况下,方向始终相同计数器已更新,编码器接口无法正常工作。

因此您可以使用预分频器,但值为奇数。

推荐的用例是没有预分频器。

此致

ST MCU 技术支持