Files
STM32_General/Src/general_encoder.c
Razvalyaev 6d5c91cb23 Устранена мёртвая зона энкодера при реверсе
Фаза фиксации передаётся в Encoder_Init явно и не зависит от положения контактов при включении. Для режимов с двумя и четырьмя переходами детент завершается только на заданных границах; повреждённый остаток сбрасывается при возвращении к ним.

Сохранён счётчик недопустимых переходов. Проверены старт во всех фазах, дребезг, реверсы и восстановление после пропуска переходов в host-тестах основного проекта; измерения установленного энкодера не выполнялись.
2026-10-23 13:57:08 +03:00

104 lines
4.7 KiB
C

/**
**************************************************************************
* @file general_encoder.c
* @brief Модуль для работы с квадратурными энкодерами через GPIO.
**************************************************************************
* @details
Инициализация входов энкодера и получение готовых шагов вращения.
*************************************************************************/
#include "general_encoder.h"
/**
* @brief Прочитать фазы через HAL в едином формате A/B.
* @details Порты могут различаться; A занимает старший бит снимка.
*/
static uint8_t read_phases(const Encoder_HandleTypeDef *encoder)
{
return (uint8_t)(((HAL_GPIO_ReadPin(encoder->port_a, encoder->pin_a) == GPIO_PIN_SET) << 1U) |
(HAL_GPIO_ReadPin(encoder->port_b, encoder->pin_b) == GPIO_PIN_SET));
}
/**
* @brief Подготовить независимый экземпляр энкодера к опросу.
* @details Входы предварительно настраивает CubeMX или вызывающий код.
* Начальный снимок предотвращает событие при включении питания.
* Число переходов и фаза фиксации заданы конфигурацией энкодера.
* Промежуточное положение при включении не становится фазой фиксации.
* @return HAL_OK при успехе, HAL_ERROR при неверной конфигурации.
*/
HAL_StatusTypeDef Encoder_Init(Encoder_HandleTypeDef *encoder,
GPIO_TypeDef *port_a, uint16_t pin_a,
GPIO_TypeDef *port_b, uint16_t pin_b,
unsigned steps_per_detent, unsigned detent_state)
{
if (!encoder || !port_a || !port_b || !pin_a || !pin_b ||
!steps_per_detent || steps_per_detent > 4U || detent_state > 3U)
{
return HAL_ERROR;
}
encoder->port_a = port_a;
encoder->port_b = port_b;
encoder->pin_a = pin_a;
encoder->pin_b = pin_b;
encoder->state = read_phases(encoder);
encoder->detent_state = (uint8_t)detent_state;
encoder->steps_per_detent = (uint8_t)steps_per_detent;
encoder->partial_steps = 0;
encoder->invalid_transitions = 0U;
return HAL_OK;
}
/**
* @brief Превратить последовательность фаз в законченный шаг вращения.
* @details Обратные переходы компенсируют дребезг. Одновременная смена обеих
* фаз обрывает незавершённый детент и учитывается как пропуск опроса.
* Для 4/2 переходов шаг завершается только в опорной/противоположной
* фазе покоя. Возврат к ней сбрасывает остаток повреждённого детента:
* пропуск не сдвигает последующие шаги и не создаёт люфт при реверсе.
* Функция сама читает оба GPIO; вызывающий код задаёт частоту опроса.
*/
int Encoder_Update(Encoder_HandleTypeDef *encoder)
{
static const int8_t direction[16] = {0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0};
uint8_t previous, state;
if (!encoder || !encoder->port_a || !encoder->port_b || !encoder->pin_a || !encoder->pin_b ||
!encoder->steps_per_detent)
{
return 0;
}
state = read_phases(encoder);
previous = encoder->state;
encoder->state = state;
if ((previous ^ state) == 3U)
{
++encoder->invalid_transitions;
encoder->partial_steps = 0;
return 0;
}
encoder->partial_steps += direction[previous * 4U + state];
if (encoder->steps_per_detent == 4U || encoder->steps_per_detent == 2U)
{
int step;
if (state != encoder->detent_state &&
!(encoder->steps_per_detent == 2U && state == (encoder->detent_state ^ 3U)))
{
return 0;
}
step = encoder->partial_steps >= (int)encoder->steps_per_detent ? 1 :
encoder->partial_steps <= -(int)encoder->steps_per_detent ? -1 : 0;
encoder->partial_steps = 0;
return step;
}
if (encoder->partial_steps >= (int)encoder->steps_per_detent)
{
encoder->partial_steps -= (int8_t)encoder->steps_per_detent;
return 1;
}
if (encoder->partial_steps <= -(int)encoder->steps_per_detent)
{
encoder->partial_steps += (int8_t)encoder->steps_per_detent;
return -1;
}
return 0;
}