Счетчик энкодера STM32F4 меняется, когда не должен

В настоящее время я использую STM32F4 с платой STM32F429ZI Nucleo-144. Я хочу использовать этот микроконтроллер для оценки положения поворотного энкодера через интерфейс квадратурного энкодера. Глядя на документацию, это делается с помощью таймеров. У меня есть выходы энкодера A/B, подключенные к PA6 и PC7 на микроконтроллере, но я заметил, что счетчики дрейфуют.

Во время отладки я заметил, что если я отсоединяю один из выходов энкодера от микроконтроллера и перемещаю двигатель, счетчики все равно увеличиваются/уменьшаются, даже если подключена только одна из линий энкодера. Поскольку я рассчитываю как на TI1, так и на TI2, этого не должно происходить. Если я правильно читаю приведенную ниже диаграмму, поскольку одна из моих линий удерживается на высоком уровне с помощью внутреннего подтягивания, тактовые импульсы на другом входе должны идти вверх/вниз/вверх/вниз и на самом деле просто циклически переключаться между двумя разными счетчиками. Однако, если я вращаю энкодер, счетчики продолжают увеличиваться или уменьшаться в зависимости от направления.

Почему счетчик энкодера меняется, когда подключен только один вход энкодера? У меня также есть трассировка области, чтобы доказать, что активен только один счетчик, а также код.

РЕДАКТИРОВАТЬ: Я также пытался изменить полярность с ОБА КРАЯ на ВОСХОДЯЩИЙ КРАЙ, но без видимой пользы.

введите здесь описание изображения

введите здесь описание изображения

#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);
}

person Seidleroni    schedule 12.09.2016    source источник
comment
Похоже, это инкрементный энкодер. Если вам нужно вычислить угол, может быть, лучше всего подойдет абсолютный поворотный энкодер? Вы все еще можете оценить позицию из начальной позиции (если она известна), но я предполагаю, что в какой-то момент вы пропустите прерывания и потеряете счет   -  person Emilien    schedule 12.09.2016


Ответы (1)


При дальнейшем расследовании выясняется, что проблема связана с предварительным масштабированием. Предварительный делитель не работает в режиме кодировщика, когда вы предоставляете четные значения. Поскольку предварительный делитель — это введенное значение + 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-битного пространства, используя подход, предложенный . Я перепечатал подход здесь на случай, если ссылка не работает:


От пользователя goosen.kobus.001 19.11.2013 на форуме ST:

По моему опыту, использование прерывания переполнения для масштабирования энкодера ненадежно, особенно если у вас есть энкодеры с высоким разрешением: время от времени случается, что значение энкодера меняет знак в тот момент, когда вы вводите прерывание, вызывая увеличение системы. старшее слово, когда оно должно было уменьшиться и т. д. Это особенно верно, если предполагается, что энкодер остановится на 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 между чтениями. Также должна быть возможность поместить эту логику в другое прерывание таймера, которое работает, скажем, на частоте 10 кГц. таким образом, у вас всегда будет актуальное значение кодировщика.


ОТВЕТ ST:

Hi,

Я получил обратную связь от дизайнера и архитектора таймеров. Интерфейс энкодера был разработан для работы без предварительного делителя, чтобы не снижать разрешение энкодера.

Как вы заметили, они подтвердили, что он не может работать с четными значениями предделителя, а только с нечетными. У нас есть вспомогательный счетчик для предварительного делителя, который является однонаправленным, поэтому на него не влияет направление счетчика, и он увеличивается на каждый нарастающий фронт часов таймера (без предварительного делителя). Направление счетчика обновляется по каждому переднему фронту тактового сигнала таймера (без предварительного делителя), но счетчик увеличивается только тогда, когда вспомогательный счетчик предварительного делителя достигает запрограммированного значения и в соответствии со значением в бите направления.

Таким образом, в одном случае поведение такое же, как и без предделителя, потому что счетчик обновляется с другим направлением (каждое нечетное количество тактов), но в другом случае направление всегда одно и то же, когда счетчик обновляется и интерфейс энкодера работает некорректно.

Таким образом, вы можете использовать прескалер, но с нечетным значением.

Рекомендуемый вариант использования — без предварительного масштабирования.

С наилучшими пожеланиями

Техническая поддержка микроконтроллера ST

person Seidleroni    schedule 12.09.2016