STM32F101-107 HAL Encoder timer настройка таймера контроллера для работы с механическим-инкрементальным энкодером.
Для тех, кто не понимает принцип работы инкрементальных (квадратурных) энкодеров, то читаем статью.
В этой статье для примера будем использовать плату Blue Pill (STM32F103C8) и механический энкодер, который применяется в электронных устройствах.
Прежде чем начать, нам нужно будет выбрать подходящий таймер в контроллере с интерфейсом энкодера, а значит, у этого таймера должна быть возможность отсчета вверх-вниз (up/down) одновременно, это именно те таймеры, которые имеют интерфейс энкодера, и для статьи я выберу TIM2.

Схема интерфейса энкодера.

Обратите внимание: сигнал от энкодера проходит через цифровые фильтры на схеме. Эти фильтры играют важную роль в работе с механическими энкодерами, они устраняют дребезг контактов. Как работают цифровые фильтры и как их настроить, читаем в статье.
STM32F101-107 HAL Input filters цифровые фильтры на входах портов таймеров.
Подключаем энкодер к контроллеру: A0 к S2, A1 к S1 и B11 к KEY.

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

Пример кода программы настройки и использования энкодера.
Если используете среду IAR или хотите добавить код в STM32CubeIDE без использования CubeMX, то раскомментируйте в файле stm32f1xx_hal_conf.h библиотеку для работы с таймерами.


Подключение библиотеки.
Ниже код программы для механического энкодера, в котором есть только нужные настройки таймера для работы в режиме энкодера. В программе показано, как настроить таймер для отсчёта одна риска энкодера +-1, и также показано, как ограничить программно значение счётчика таймера от 0 до 100.
При вращении энкодера будет изменяться частота мигания светодиода, значение счётчика таймера, которое ограничено от 0 до 100, будет устанавливаться в задержку для изменения состояния светодиода.
/*ProgCont.ru*/
#include "main.h"
TIM_HandleTypeDef htim2;
uint16_t count;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
int main(void){
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
__HAL_RCC_TIM2_CLK_ENABLE();
MX_TIM2_Init();
/*Включаем таймер в режиме интерфейс энкодера*/
HAL_TIM_Encoder_Start( &htim2, TIM_CHANNEL_ALL);
while (1){
/*В переменную count читаем значение счётчика таймера*/
count = __HAL_TIM_GET_COUNTER(&htim2);
/*Ограничиваем уход значения за 0*/
if( count>60000) count = 0;
/*Ограничиваем уход выше 100*/
if( count>100) count = 100;
/*Переписываем счётчик нужным значением*/
__HAL_TIM_SET_COUNTER(&htim2, count);
/*Устанавливаем задержку*/
HAL_Delay( count);
/*Изменяем состояние светодиода*/
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
/*Сброс счётчика энкодера по нажатию кнопки*/
if( !HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11))__HAL_TIM_SET_COUNTER(&htim2, 0);
}
}
void SystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
static void MX_TIM2_Init(void){
TIM_Encoder_InitTypeDef sConfig = {0};
htim2.Instance = TIM2;
/*Устанавливаем предделитель /2, одна риска один отсчёт*/
htim2.Init.Prescaler = 1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
/*Устанавливаем ограничение счёта до 65535*/
htim2.Init.Period = 65535;
/*Устанавливаем максимальный делитель для входных фильтров*/
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI1;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
/*Устанавливаем максимально продолжительный сигнал для входного фильтра входа 1*/
sConfig.IC1Filter = 15;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
/*Устанавливаем максимально продолжительный сигнал для входного фильтра входа 2*/
sConfig.IC2Filter = 15;
HAL_TIM_Encoder_Init(&htim2, &sConfig);
}
static void MX_GPIO_Init(void){
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line){}
#endif /* USE_FULL_ASSERT */
Код программы
Второй пример программы для механического энкодера с тремя прерываниями, которые будут отрабатываться по событиям: это по достижению максимального значения счётчика энкодера или его перегрузки и ещё два от регистров TIM2_CCR3(захват/сравнение_3), TIM2_CCR4(захват/сравнение_4). С помощью регистров TIM2_CCR3 и TIM2_CCR4 вы можете отслеживать любые значения счётчика энкодера, что исключает это делать с помощью программного кода. Значения, записываемые в эти регистры, не должны превышать максимального установленного значения для счётчика htim2.Init.Period = 14; или установленного вами.
После каждого прерывания светодиод будет мигать с разной интенсивностью.
/*ProgCont.ru*/
#include "main.h"
uint16_t delay = 0;
TIM_HandleTypeDef htim2;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
int main(void){
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
__HAL_RCC_TIM2_CLK_ENABLE();
MX_TIM2_Init();
/*Включаем таймер в режиме интерфейс энкодера*/
HAL_TIM_Encoder_Start( &htim2, TIM_CHANNEL_ALL);
/*Включаем отработку прерываний контроллером*/
__enable_irq ();
/*Устанавливаем приоритет и разрешаем прерывания от TIM2*/
HAL_NVIC_SetPriority( TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ( TIM2_IRQn);
/*Устанавливаем в регистр захват/сравнение_3 значение 4 для отработки преывания*/
TIM2->CCR3 = 4;
/*Устанавливаем в регистр захват/сравнение_4 значение 7 для отработки преывания*/
TIM2->CCR4 = 7;
/*Сброс флага прерывания от переполнения счётчика энкодера*/
TIM2->SR &= ~TIM_FLAG_UPDATE;
/*Сброс флага прерывания от захват/сравнение_3*/
TIM2->SR &= ~TIM_FLAG_CC3;
/*Сброс флага прерывания от захват/сравнение_4*/
TIM2->SR &= ~TIM_FLAG_CC4;
/*Включаем прерывание от переполнения счётчика*/
__HAL_TIM_ENABLE_IT( &htim2, TIM_IT_UPDATE);
/*Включаем прерывание от захват/сравнение_3*/
__HAL_TIM_ENABLE_IT( &htim2, TIM_IT_CC3);
/*Включаем прерывание от захват/сравнение_4*/
__HAL_TIM_ENABLE_IT( &htim2, TIM_IT_CC4);
while (1){
/*Устанавливаем задержку и изменяем состояние светодиода*/
HAL_Delay( delay);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
/*Сброс счётчика энкодера по нажатию кнопки*/
if( !HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11))__HAL_TIM_SET_COUNTER(&htim2, 0);
}
}
/*Обработчик прерывания*/
void TIM2_IRQHandler(void){
/*Отработка прерывания по переполнению счётчика*/
if( TIM2->SR & TIM_IT_UPDATE){
delay = 0;
/*Сброс флага прерывания от переполнения счётчика энкодера*/
TIM2->SR &= ~TIM_FLAG_UPDATE;
}
/*Отработка прерывания по захват/сравнение_3*/
if( TIM2->SR & TIM_FLAG_CC3){
delay = 50;
/*Сброс флага прерывания от захват/сравнение_3*/
TIM2->SR &= ~TIM_FLAG_CC3;
}
/*Отработка прерывания по захват/сравнение_4*/
if( TIM2->SR & TIM_FLAG_CC4){
delay = 100;
/*Сброс флага прерывания от захват/сравнение_4*/
TIM2->SR &= ~TIM_FLAG_CC4;
}
}
void SystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
static void MX_TIM2_Init(void){
TIM_Encoder_InitTypeDef sConfig = {0};
htim2.Instance = TIM2;
/*Устанавливаем предделитель /2, одна риска один отсчёт*/
htim2.Init.Prescaler = 1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
/*Устанавливаем ограничение счёта до 15*/
htim2.Init.Period = 14;
/*Устанавливаем масимальный делитель для входных фильтров*/
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI1;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
/*Устанавливаем максимально продолжительный сигнал для входного фильтра входа 1*/
sConfig.IC1Filter = 15;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
/*Устанавливаем максимально продолжительный сигнал для входного фильтра входа 2*/
sConfig.IC2Filter = 15;
HAL_TIM_Encoder_Init(&htim2, &sConfig);
}
static void MX_GPIO_Init(void){
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line){}
#endif /* USE_FULL_ASSERT */
Код программы с прерываниями
Структуры и функции библиотеки.
TIM_HandleTypeDef - структура для настройки тамера;
TIM_Encoder_InitTypeDef - структура для настройки интерфейса энкодера;
__HAL_RCC_TIM2_CLK_ENABLE() - функция включает тактирование таймера;
HAL_TIM_Encoder_Init( TIM_HandleTypeDef *htim, const TIM_Encoder_InitTypeDef *sConfig) - функция настраивает таймер для работы в режиме энкодера, принимает две структуры настройки таймера TIM_HandleTypeDef и интерфейса энкодера TIM_Encoder_InitTypeDef;
HAL_TIM_Encoder_Start( TIM_HandleTypeDef *htim, uint32_t Channel) - функция включает таймер и его выводы в режиме энкодер, принимает структуру настрой таймера и определение выводов которые необходимо включить;
__HAL_TIM_GET_COUNTER( TIM_HandleTypeDef *htim) - функция читает значение счётчика энкодера и возвращает значение от 0 до 65535, принимает структуру настройки таймера TIM_HandleTypeDef;
__HAL_TIM_SET_COUNTER( TIM_HandleTypeDef *htim, COUNTER) - функция устанавливает значение счётчика энкодера, принимает структуру настройки таймера и значение 0 до 65535;
__HAL_TIM_SET_PRESCALER( TIM_HandleTypeDef *htim, PRESC) - функция изменяет значение предделителя счётчика таймера, принимает структуру настройки таймера и значение 0 до 65535. Данная функция должна использоваться с генерацией флага переполнения TIM_EGR_UG в регистре TIMx_EGR, смотрите в примере как применять;
__HAL_TIM_SET_AUTORELOAD( TIM_HandleTypeDef *htim, AUTORELOAD) - функция устанавливает максимальное значение счётчика таймера, доходя до установленного значения счётчик сбрасывается. Принимает структуру настройки таймера и значение от 1 до 65535, нулевое значение устанавливать нельзя.
TIM_HandleTypeDef
Настройки структуры:
/*Выбор нужного таймера для настройки*/
.Instance = название таймера например TIM2;
/*Предделитель для счётчика*/
.Init.Prescaler = от 0 до 65535;
/*Направление подсчёта счётчика таймера*/
.Init.CounterMode =
TIM_COUNTERMODE_UP - счёт идёт от 0 до 65535 или до установленного значения потом сброс счётчика в 0
TIM_COUNTERMODE_DOWN - счёт идёт от 65535 или от установленного значения до 0 потом устанавливается 65535 или установленное значение;
/*Устанавливаемое максимальное значение счётчика таймера*/
.Init.Period = от 0 до 65535;
/*Входной предделитель всех цифровых фильтров на входах таймера*/
.Init.ClockDivision =
TIM_CLOCKDIVISION_DIV1
TIM_CLOCKDIVISION_DIV2
TIM_CLOCKDIVISION_DIV4;
/*Отключение регистра перегрузки Period*/
.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE.TIM_Encoder_InitTypeDef
Настройки структуры:
/*Режим работы энкодера*/
.EncoderMode =
TIM_ENCODERMODE_TI1 - изменение сигнала фиксируется на входе TIMx_CH1, счетчик увеличивает или уменьшает своё значение на 2
TIM_ENCODERMODE_TI2 - изменение сигнала фиксируется на входе TIMx_CH2, счетчик увеличивает или уменьшает своё значение на 2
TIM_ENCODERMODE_TI12 - изменение сигнала фиксируется на входе TIMx_CH1 и TIMx_CH2, счетчик увеличивает или уменьшает своё значение на 4;
/*Параметр для изменяет направление счёта*/
.IC1Polarity и .IC2Polarity =
TIM_ICPOLARITY_RISING - входной сигнал не меняется
TIM_ICPOLARITY_FALLING - входной сигнал инвертируется, можно использовать для изменения направление счёта;
/*В интерфейсе энкодера не используется*/
.IC1Selection и .IC2Selection = TIM_ICSELECTION_DIRECTTI;
/*В интерфейсе энкодера не используется*/
.IC1Prescaler и .IC2Prescaler = TIM_ICPSC_DIV1.
/*Входной цифровой фильтр, применяется для отсечения входного сигнала если он меньшей продолжительностью чем установленное значение*/
.IC1Filter и .IC2Filter = значение от 0 до 15.
HAL_TIM_Encoder_Init(TIM_HandleTypeDef *htim, const TIM_Encoder_InitTypeDef *sConfig);
/*Пример настройки интерфейса энкодера*/
TIM_HandleTypeDef htim2;
TIM_Encoder_InitTypeDef sConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI1;
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;
HAL_TIM_Encoder_Init(&htim2, &sConfig);
HAL_TIM_Encoder_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
/*Структура настройки таймера*/
TIM_HandleTypeDef
/*Выводы таймера которые нужно включить*/
Channel =
TIM_CHANNEL_1
TIM_CHANNEL_2
TIM_CHANNEL_3
TIM_CHANNEL_4
TIM_CHANNEL_ALL
/*Пример, включаем 1 и 2 входы таймера для энкодера*/
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);__HAL_TIM_GET_COUNTER(TIM_HandleTypeDef *htim);
/*Пример*/
uint16_t count;
count = __HAL_TIM_GET_COUNTER( &htim2);
__HAL_TIM_SET_COUNTER(TIM_HandleTypeDef *htim, COUNTER);
/*Пример*/
_HAL_TIM_SET_COUNTER( &htim2, 100);__HAL_TIM_SET_PRESCALER( TIM_HandleTypeDef *htim, PRESC);
/*Пример*/
__HAL_TIM_SET_PRESCALER( &htim2, 1);
TIM2->EGR = (uint16_t) TIM_EGR_UG;__HAL_TIM_SET_AUTORELOAD( TIM_HandleTypeDef *htim, AUTORELOAD);
/*Пример*/
__HAL_TIM_SET_AUTORELOAD( &htim2, 100);

