Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d5c91cb23 | ||
|
|
d6cc99a25b | ||
|
|
5ad51f0d61 | ||
|
|
c0733a1d31 | ||
|
|
11c00f1e0c | ||
|
|
74d692fdd5 | ||
|
|
2344926f92 | ||
|
|
272642b310 | ||
|
|
b3f118b074 | ||
|
|
2eae8d34d8 |
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
**************************************************************************
|
||||
* @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 detent_state;
|
||||
uint8_t steps_per_detent;
|
||||
int8_t partial_steps;
|
||||
uint32_t invalid_transitions;
|
||||
} Encoder_HandleTypeDef;
|
||||
|
||||
/** Привязать GPIO A/B; detent_state задаёт фазу фиксации, не фазу включения. */
|
||||
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);
|
||||
/** Самостоятельно опросить GPIO, обработать фазы и вернуть -1, 0 или +1 детент. */
|
||||
int Encoder_Update(Encoder_HandleTypeDef *encoder);
|
||||
|
||||
#endif
|
||||
+7
-3
@@ -63,8 +63,9 @@ typedef enum
|
||||
{
|
||||
LED_IS_OFF = 0, ///< Светодиод выключен
|
||||
LED_IS_ON = 1, ///< Светодиод включен
|
||||
LED_IS_BLINKING = 2, ///< Моргание светодиодом
|
||||
LED_IS_FADING = 3, ///< Плавное моргание светодиодом
|
||||
LED_IS_DUTY = 2, ///< Светодиод включен на частичную яркость
|
||||
LED_IS_BLINKING = 3, ///< Моргание светодиодом
|
||||
LED_IS_FADING = 4, ///< Плавное моргание светодиодом
|
||||
}GPIO_LEDStateTypeDef;
|
||||
|
||||
/**
|
||||
@@ -80,6 +81,7 @@ typedef struct
|
||||
|
||||
uint8_t LED_ActiveLvl; ///< Активный уровень ножки (при котором светодиод горит)
|
||||
uint32_t LED_Period; ///< Период моргания светодиода
|
||||
uint32_t LED_Duty; ///< Текущая яркость светодиода
|
||||
|
||||
uint32_t tickprev;
|
||||
}GPIO_LEDTypeDef;
|
||||
@@ -94,7 +96,7 @@ typedef struct
|
||||
uint32_t Sw_Pin; ///< GPIO пин ножки кнопки
|
||||
|
||||
uint8_t Sw_ActiveLvl; ///< Активный уровень ножки (при котором кнопка нажата)
|
||||
uint32_t Sw_PrevState; ///< Предыдущее состояние кнопки
|
||||
uint32_t Sw_CurrentState; ///< Текущее состояние кнопки
|
||||
uint32_t Sw_FilterDelay; ///< Фильтр от дребезга (в мс)
|
||||
|
||||
uint32_t tickprev;
|
||||
@@ -192,6 +194,8 @@ HAL_StatusTypeDef GPIO_LED_On (GPIO_LEDTypeDef *led);
|
||||
HAL_StatusTypeDef GPIO_LED_Off (GPIO_LEDTypeDef *led);
|
||||
/* Выставить светодиод по переменной */
|
||||
HAL_StatusTypeDef GPIO_LED_Set (GPIO_LEDTypeDef *led, uint8_t led_state);
|
||||
/* Выставить яркость светодиод */
|
||||
HAL_StatusTypeDef GPIO_LED_Duty_Set(GPIO_LEDTypeDef *led, uint8_t led_duty);
|
||||
/* Активировать моргание светодиодом */
|
||||
HAL_StatusTypeDef GPIO_LED_Blink_Start (GPIO_LEDTypeDef *led, uint32_t period);
|
||||
/* Активировать моргание светодиодом */
|
||||
|
||||
@@ -185,6 +185,55 @@ typedef struct // struct with variables for encoder
|
||||
@endcode
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Преобразование частоты в количество тиков таймера.
|
||||
* @param _freq_ Желаемая частота в Гц.
|
||||
* @param _timfreqMHz_ Тактовая частота таймера в МГц.
|
||||
* @return Количество тиков для достижения заданной частоты.
|
||||
* @details Расчет: (1 000 000 * частота_таймера) / желаемая_частота.
|
||||
*/
|
||||
#define TIM_FreqToTick(_freq_, _timfreqMHz_) (((1000000*(_timfreqMHz_))/(_freq_))-1)
|
||||
|
||||
/**
|
||||
* @brief Преобразование миллисекунд в количество тиков таймера.
|
||||
* @param _ms_ Время в миллисекундах.
|
||||
* @param _timfreqMHz_ Тактовая частота таймера в МГц.
|
||||
* @return Количество тиков для заданного времени.
|
||||
* @details Расчет: (мс * 1000 * частота_таймера).
|
||||
*/
|
||||
#define TIM_MillisToTick(_ms_, _timfreqMHz_) (((uint32_t)((_ms_) * 1000UL * (_timfreqMHz_)))-1)
|
||||
|
||||
/**
|
||||
* @brief Преобразование микросекунд в количество тиков таймера.
|
||||
* @param _us_ Время в микросекундах.
|
||||
* @param _timfreqMHz_ Тактовая частота таймера в МГц.
|
||||
* @return Количество тиков для заданного времени.
|
||||
* @details Расчет: (мкс * частота_таймера).
|
||||
*/
|
||||
#define TIM_MicrosToTick(_us_, _timfreqMHz_) (((uint32_t)((_us_) * (_timfreqMHz_)))-1)
|
||||
|
||||
/**
|
||||
* @brief Установка автоперезагрузки (ARR) с принудительным обновлением теневого регистра.
|
||||
* @param __HANDLE__ Указатель на хендл таймера.
|
||||
* @param __AUTORELOAD__ Значение автоперезагрузки.
|
||||
* @details Генерирует событие обновления для немедленного применения нового значения ARR.
|
||||
* Решает проблему обновления теневых регистров только при переполнении.
|
||||
*/
|
||||
#define __HAL_TIM_SET_AUTORELOAD_FORCE(__HANDLE__, __AUTORELOAD__) \
|
||||
do{ __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__); \
|
||||
HAL_TIM_GenerateEvent(__HANDLE__, TIM_EVENTSOURCE_UPDATE); } while(0);
|
||||
|
||||
/**
|
||||
* @brief Установка предделителя (PSC) с принудительным обновлением теневого регистра.
|
||||
* @param __HANDLE__ Указатель на хендл таймера.
|
||||
* @param __PRESC__ Значение предделителя.
|
||||
* @details Генерирует событие обновления для немедленного применения нового значения PSC.
|
||||
* Решает проблему обновления теневых регистров только при переполнении.
|
||||
*/
|
||||
#define __HAL_TIM_SET_PRESCALER_FORCE(__HANDLE__, __PRESC__) \
|
||||
do{ __HAL_TIM_SET_PRESCALER(__HANDLE__, __PRESC__); \
|
||||
HAL_TIM_GenerateEvent(__HANDLE__, TIM_EVENTSOURCE_UPDATE); } while(0);
|
||||
|
||||
/* Initialize TIM with TIM_SettingsTypeDef structure */
|
||||
HAL_StatusTypeDef TIM_Base_Init(TIM_SettingsTypeDef* stim);
|
||||
/* Initialize TIMs clock and interrupt */
|
||||
|
||||
@@ -5,7 +5,7 @@ STM32_General - это набор библиотек для удобной ра
|
||||
|
||||
## Структура библиотеки
|
||||
|
||||
*Note: STM32_General требует наличия библиотеки (MyLibs)[https://git.arktika.cyou/Razvalyaev/STM32_ExtendedLibs] для работы***
|
||||
*Note: STM32_General требует наличия библиотеки [MyLibs](https://git.arktika.cyou/Razvalyaev/STM32_ExtendedLibs) для работы*
|
||||
```
|
||||
ProjectRoot/
|
||||
└── STM32_General # Работа с периферией STM32
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "__general_flash.h"
|
||||
|
||||
#if 0
|
||||
FLASH_EraseInitTypeDef EraseInitStruct;
|
||||
extern HAL_StatusTypeDef res_hal;
|
||||
unsigned CRC_Update;
|
||||
@@ -190,3 +192,4 @@ HAL_StatusTypeDef FLASH_Write_Word(uint32_t Address, uint32_t Data)
|
||||
}
|
||||
//----------------------------------------------------------
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
**************************************************************************
|
||||
* @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;
|
||||
}
|
||||
+51
-28
@@ -136,6 +136,23 @@ HAL_StatusTypeDef GPIO_LED_Set(GPIO_LEDTypeDef *led, uint8_t led_state)
|
||||
return GPIO_LED_Off(led);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Выставить яркость светодиод
|
||||
* @param led Указатель на структуру светодиода
|
||||
* @param led_duty Яркость светодиода
|
||||
* @return HAL Status
|
||||
*/
|
||||
HAL_StatusTypeDef GPIO_LED_Duty_Set(GPIO_LEDTypeDef *led, uint8_t led_duty)
|
||||
{
|
||||
if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
|
||||
return HAL_ERROR;
|
||||
|
||||
led_duty = (led_duty*LED_PWM_TICKS)/100;
|
||||
led->state = LED_IS_DUTY;
|
||||
led->LED_Duty = led_duty;
|
||||
|
||||
return HAL_OK;
|
||||
}
|
||||
/**
|
||||
* @brief Активировать моргание светодиодом
|
||||
* @param led Указатель на структуру светодиода
|
||||
@@ -199,6 +216,21 @@ void GPIO_LED_Dynamic_Handle(GPIO_LEDTypeDef *led)
|
||||
led->tickprev = tickcurrent;
|
||||
}
|
||||
}
|
||||
/* Режим промежуточной яркости светодиода */
|
||||
else if(led->state == LED_IS_DUTY)
|
||||
{
|
||||
uint32_t tickcurrent = local_time();
|
||||
/* Формирование ШИМ для яркости */
|
||||
int duty_crt = (led->LED_Duty*led->LED_Duty/LED_PWM_TICKS);
|
||||
if(tickcurrent%LED_PWM_TICKS < duty_crt)
|
||||
{
|
||||
HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl);
|
||||
}
|
||||
else
|
||||
{
|
||||
HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, !led->LED_ActiveLvl);
|
||||
}
|
||||
}
|
||||
/* Режим плавного моргания светодиода */
|
||||
else if(led->state == LED_IS_FADING)
|
||||
{
|
||||
@@ -229,6 +261,7 @@ void GPIO_LED_Dynamic_Handle(GPIO_LEDTypeDef *led)
|
||||
}
|
||||
led->tickprev = tickcurrent;
|
||||
}
|
||||
led->LED_Duty = duty;
|
||||
/* Формирование ШИМ для изменения яркости */
|
||||
int duty_crt = (duty*duty/LED_PWM_TICKS);
|
||||
if(tickcurrent%LED_PWM_TICKS < duty_crt)
|
||||
@@ -280,47 +313,37 @@ int GPIO_Read_Switch(GPIO_SwitchTypeDef *sw)
|
||||
if(check_null_ptr_3(sw, sw->Sw_Port, sw->Sw_Pin))
|
||||
return -1;
|
||||
|
||||
if(HAL_GPIO_ReadPin(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl)
|
||||
int current_level = (HAL_GPIO_ReadPin(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl);
|
||||
|
||||
if(sw->Sw_FilterDelay) // если включена защита от дребезга
|
||||
{
|
||||
sw->Sw_PrevState = 1;
|
||||
|
||||
|
||||
if(sw->Sw_FilterDelay) // если включена защита от дребезга
|
||||
// Если таймер не запущен и состояние изменилось - запускаем таймер
|
||||
if(sw->tickprev == 0 && current_level != sw->Sw_CurrentState)
|
||||
{
|
||||
if(sw->tickprev == 0)
|
||||
sw->tickprev = local_time();
|
||||
sw->tickprev = local_time();
|
||||
}
|
||||
|
||||
// Если таймер запущен
|
||||
if(sw->tickprev != 0)
|
||||
{
|
||||
// Проверяем, прошел ли достаточный интервал для фильтрации
|
||||
if((local_time() - sw->tickprev) >= sw->Sw_FilterDelay)
|
||||
{
|
||||
if(HAL_GPIO_ReadPin(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl)
|
||||
// Обновляем состояние только если оно все еще отличается
|
||||
if(current_level != sw->Sw_CurrentState)
|
||||
{
|
||||
return 1;
|
||||
sw->Sw_CurrentState = current_level;
|
||||
}
|
||||
else
|
||||
{
|
||||
sw->tickprev = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // если нет защиты от дребезга
|
||||
{
|
||||
if(HAL_GPIO_ReadPin(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Останавливаем таймер в любом случае
|
||||
sw->tickprev = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else // если нет защиты от дребезга
|
||||
{
|
||||
sw->Sw_PrevState = 0;
|
||||
sw->Sw_CurrentState = current_level;
|
||||
}
|
||||
return 0;
|
||||
return sw->Sw_CurrentState;
|
||||
}
|
||||
//------------------------GPIO SW FUNCTIONS-------------------------
|
||||
//-------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user