Encoder skips pulses

Thanks for the prompt reply.

For the first issue (not finding the right timer for the PB6 and PB7 pins), the problem is in how the board variant is defined in ArduinoSTM32. in the file variants/STM32G4xx/G431C(6-8-B)U_G441CBU/PeripheralPins_B_G431B_ESC1 the definitions for PB_6 and PB_7 are commented out (for reasons I don’t understand… most pins are commented out in that file. It could be that the board has no exposed pins like a Nucleo, so the owner doesn’t expose them… even if PB6, PB7 and PB8 are used for the encoder). By removing the // only for PB_6 and PB_7, the code starts working better, but it still fails in multiple ways: the function pinmap_function() doesn’t return the correct value for Alternate (which should be 2), and PB7 returns TIM3, which is not correct either.

It seems that having a custom encoder for the B431_ESC (or at least a portion of #ifdef code) would be simpler than trying to fix the broken variant pinout definition and figuring out how to find the correct alternate configuration.

Just in case, the code below works on the B431_ESC with no changes to the ARDUINO board files (I changed only the init function), using PB6 and PB7 as A and B. No Z (index) support

#include "STM32HWEncoder.h"

#if defined(_STM32_DEF_)

/*
  HardwareEncoder(int cpr)
*/
STM32HWEncoder::STM32HWEncoder(unsigned int _ppr, int8_t pinA, int8_t pinB, int8_t pinI)
{
  rotations_per_overflow = 0;
  ticks_per_overflow = 0;

  overflow_count = 0;
  count = 0;
  prev_count = 0;
  prev_overflow_count = 0;
  pulse_timestamp = 0;

  cpr = _ppr * 4; // 4x for quadrature

  // velocity calculation variables
  prev_timestamp = getCurrentMicros();
  pulse_timestamp = getCurrentMicros();

  _pinA = pinA;
  _pinB = pinB;
  _pinI = pinI;
}

void STM32HWEncoder::update()
{
  // handle overflow of the 16-bit counter here
  // must be called at least twice per traversal of overflow range
  // TODO(conroy-cheers): investigate performance impact
  prev_count = count;
  count = encoder_handle.Instance->CNT;

  prev_timestamp = pulse_timestamp;
  pulse_timestamp = getCurrentMicros();

  prev_overflow_count = overflow_count;
  if (prev_count > (ticks_per_overflow - overflow_margin) &&
      prev_count <= ticks_per_overflow && count < overflow_margin)
    ++overflow_count;
  if (prev_count >= 0 && prev_count < overflow_margin &&
      count >= (ticks_per_overflow - overflow_margin))
    --overflow_count;
}

/*
  Shaft angle calculation
*/
float STM32HWEncoder::getSensorAngle() { return getAngle(); }

float STM32HWEncoder::getMechanicalAngle()
{
  return _2PI * (count % static_cast<int>(cpr)) / static_cast<float>(cpr);
}
float STM32HWEncoder::getAngle()
{
  return _2PI * (count / static_cast<float>(cpr) +
                 overflow_count * rotations_per_overflow);
}
double STM32HWEncoder::getPreciseAngle()
{
  return _2PI * (count / static_cast<double>(cpr) +
                 overflow_count * rotations_per_overflow);
}
int32_t STM32HWEncoder::getFullRotations()
{
  return count / static_cast<int>(cpr) +
         overflow_count * rotations_per_overflow;
}

/*
  Shaft velocity calculation
*/
float STM32HWEncoder::getVelocity()
{
  // sampling time calculation
  float dt = (pulse_timestamp - prev_timestamp) * 1e-6f;
  // quick fix for strange cases (micros overflow)
  if (dt <= 0 || dt > 0.5f)
    dt = 1e-3f;

  // time from last impulse
  int32_t overflow_diff = overflow_count - prev_overflow_count;
  int32_t dN = (count - prev_count) + (ticks_per_overflow * overflow_diff);

  float pulse_per_second = dN / dt;

  // velocity calculation
  return pulse_per_second / (static_cast<float>(cpr)) * _2PI;
}

// getter for index pin
int STM32HWEncoder::needsSearch() { return false; }

// private function used to determine if encoder has index
int STM32HWEncoder::hasIndex() { return 0; }

// encoder initialisation of the hardware pins
// and calculation variables
void STM32HWEncoder::init()
{
  // overflow handling
  rotations_per_overflow = 0xFFFF / cpr;
  ticks_per_overflow = cpr * rotations_per_overflow;

  GPIO_InitTypeDef gpio;

  __HAL_RCC_TIM4_CLK_ENABLE();

  __HAL_RCC_GPIOB_CLK_ENABLE();

  /**TIM4 GPIO Configuration
  PB6     ------> TIM4_CH1
  PB7     ------> TIM4_CH2
  */
  gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7;
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_LOW;
  gpio.Alternate = GPIO_AF2_TIM4;
  HAL_GPIO_Init(GPIOB, &gpio);

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE END TIM4_Init 1 */
  encoder_handle.Instance = TIM4;
  encoder_handle.Init.Prescaler = 0;
  encoder_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  encoder_handle.Init.Period = ticks_per_overflow - 1;
  encoder_handle.Init.ClockDivision = 0;
  encoder_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 0;
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  if (HAL_TIM_Encoder_Init(&encoder_handle, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&encoder_handle, &sMasterConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
  if (HAL_TIM_Encoder_Start(&encoder_handle, TIM_CHANNEL_ALL) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  // counter setup
  overflow_count = 0;
  count = 0;
  prev_count = 0;
  prev_overflow_count = 0;

  prev_timestamp = getCurrentMicros();
  pulse_timestamp = getCurrentMicros();
}

#endif