From d6cc99a25b18731e1daaae1eb71401f9b24bc9f1 Mon Sep 17 00:00:00 2001 From: Razvalyaev Date: Fri, 9 Oct 2026 02:32:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20GPIO-=D1=8D=D0=BD=D0=BA=D0=BE=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encoder_Init привязывает порты и пины A/B. Encoder_Update самостоятельно читает GPIO, обрабатывает направление и дребезг и возвращает готовые шаги. Число переходов на детент задаётся при инициализации, экземпляры независимы. Добавлены только general_encoder.h и general_encoder.c в стиле существующих модулей. Готовые файлы, документация и структура библиотеки сохранены. --- Inc/general_encoder.h | 39 ++++++++++++++++++++ Src/general_encoder.c | 85 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 Inc/general_encoder.h create mode 100644 Src/general_encoder.c diff --git a/Inc/general_encoder.h b/Inc/general_encoder.h new file mode 100644 index 0000000..ee97247 --- /dev/null +++ b/Inc/general_encoder.h @@ -0,0 +1,39 @@ +/** +************************************************************************** +* @file general_encoder.h +* @brief Заголовочный файл для работы с квадратурными энкодерами через GPIO. +************************************************************************** +* @defgroup GENERAL_ENCODER Encoder Tools +* @ingroup STM32_GENERAL +* @brief Опрос GPIO и обработка шагов механического энкодера. +* @details +Порты и пины задаются при инициализации. Обработчик самостоятельно читает +фазы A/B, учитывает направление, дребезг и число переходов на детент. +*************************************************************************/ +#ifndef __ENCODER_GENERAL_H_ +#define __ENCODER_GENERAL_H_ + +#include "mylibs_config.h" + +/** Структура энкодера; каждый экземпляр имеет собственное состояние. */ +typedef struct +{ + GPIO_TypeDef *port_a; + GPIO_TypeDef *port_b; + uint16_t pin_a; + uint16_t pin_b; + uint8_t state; + uint8_t steps_per_detent; + int8_t partial_steps; + uint32_t invalid_transitions; +} Encoder_HandleTypeDef; + +/** Привязать настроенные GPIO A/B; steps_per_detent — от 1 до 4. */ +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); +/** Самостоятельно опросить GPIO, обработать фазы и вернуть -1, 0 или +1 детент. */ +int Encoder_Update(Encoder_HandleTypeDef *encoder); + +#endif diff --git a/Src/general_encoder.c b/Src/general_encoder.c new file mode 100644 index 0000000..efd1759 --- /dev/null +++ b/Src/general_encoder.c @@ -0,0 +1,85 @@ +/** +************************************************************************** +* @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) +{ + if (!encoder || !port_a || !port_b || !pin_a || !pin_b || + !steps_per_detent || steps_per_detent > 4U) + { + 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->steps_per_detent = (uint8_t)steps_per_detent; + encoder->partial_steps = 0; + encoder->invalid_transitions = 0U; + return HAL_OK; +} + +/** + * @brief Превратить последовательность фаз в законченный шаг вращения. + * @details Обратные переходы компенсируют дребезг. Одновременная смена обеих + * фаз обрывает незавершённый детент и учитывается как пропуск опроса. + * Функция сама читает оба 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->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; +}