3

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

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

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

编辑:我也尝试将极性从 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);
}
4

1 回答 1

4

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

我在此论坛帖子中发现我不是唯一遇到此问题的人。帖子中有一些讨论,预分频器可能与编码器模式不兼容,但这尚未得到证实。我已经向 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 技术支持交谈后,编码器接口不打算与预分频器值一起使用,偶数或奇数。我在下面粘贴了他们的响应,但即使使用似乎有效的预分频器值,编码器计数似乎也可能会随着时间的推移而漂移。

另一种解决方案是不使用预分频器,而是使用此处建议的方法将 16 位值扩展到 32 位空间。如果链接失效,我在这里重印了该方法:


来自 ST 论坛上的用户 goosen.kobus.001 于 2013 年 11 月 19 日:

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

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

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

  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 运行的定时器中断中。这样你就有一个始终是最新的编码器值。


ST 回应:

你好,

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

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

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

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

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

最好的祝福

ST MCU 技术支持

于 2016-09-12T19:22:05.893 回答