PROGCONT.RU

Форма входа







Регистрация Вход/

No No No

STM32F101-107 HAL Encoder timer настройка таймера контроллера для работы с механическим-инкрементальным энкодером.

Для тех, кто не понимает принцип работы инкрементальных (квадратурных) энкодеров, то читаем статью.

В этой статье для примера будем использовать плату Blue Pill (STM32F103C8) и механический энкодер, который применяется в электронных устройствах.

Прежде чем начать, нам нужно будет выбрать подходящий таймер в контроллере с интерфейсом энкодера, а значит, у этого таймера должна быть возможность отсчета вверх-вниз (up/down) одновременно, это именно те таймеры, которые имеют интерфейс энкодера, и для статьи я выберу TIM2.

NO

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

NO

Обратите внимание: сигнал от энкодера проходит через цифровые фильтры на схеме. Эти фильтры играют важную роль в работе с механическими энкодерами, они устраняют дребезг контактов. Как работают цифровые фильтры и как их настроить, читаем в статье.

STM32F101-107 HAL Input filters цифровые фильтры на входах портов таймеров.

Подключаем энкодер к контроллеру: A0 к S2, A1 к S1 и B11 к KEY.

NO

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

NO

Пример кода программы настройки и использования энкодера.

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

NONOПодключение библиотеки.

Ниже код программы для механического энкодера, в котором есть только нужные настройки таймера для работы в режиме энкодера. В программе показано, как настроить таймер для отсчёта одна риска энкодера +-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);


Комментариев нет  Только зарегистрированные пользователи могут оставлять комментарии!