Январь 2017 г.
Материалов по теме много, но почему-то большинство авторов предлагает для работы с энкодером использовать таймер. Это оправдано, когда нужно определять скорость вращения. Мне же просто требовалось знать: крутит ли пользователь ручку, и если да — в какую сторону.
Энкодеры бывают разные, в частности: абсолютные и импульсные (инкрементные). У абсолютных много контактов, с которых можно считывать код, соответствующий углу поворота. У импульсных (наш случай): два выхода и земля. Если вращать вал энкодера, эти выходы будут выдавать импульсы одинаковой частоты, но смещённые по времени.
Например. Крутим по часовой стрелке: на землю замыкается сначала выход 0, потом — выход 1. Крутим против часовой: замыкается выход 1, потом — выход 0. Никуда не крутим: состояние выходов не меняется.
Технически грамотное описание принципа работы энкодеров можно найти в Википедии.
B0, B1 — два выхода, упомянутые выше. B2 — кнопка, которая срабатывает, если нажать на ручку.
Рис. 1. Схема подключения энкодера.
Для примера рассмотрим управление уровнем громкости при помощи энкодера.
Исходя из принципа действия, нам нужно:
Проще всего это сделать при помощи прерываний.
#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_exti.h" #include "misc.h" // Нужен для NVIC // Максимальное значение уровня громкости #define VOLUME_MAX_VAL 51 // Текущий уровень громкости volatile uint8_t nVol = 10; int main(void) { // Включаем тактирование модуля ввода-вывода 'B' RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Переменные (структуры) для инициализации GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; // Прерывания - это альтернативная функция порта, включаем RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // Настройка пинов B0 и B1 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // Вход с подтяжкой вверх GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // Добавляем вектор прерывания NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; // Устанавливаем приоритет NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x08; // 0x00 - 0x0F NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x08; // Разрешаем прерывание NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); // PB0 подключен к EXTI_Line0 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); EXTI_InitStruct.EXTI_Line = EXTI_Line0; // Разрешаем прерывание EXTI_InitStruct.EXTI_LineCmd = ENABLE; // По спаду EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStruct); while(1) { // * * * } } // Обработчик прерывания для энкодера по спаду PB0 void EXTI0_IRQHandler(void) { uint32_t nCount = 3; // Убеждаемся, что флаг прерывания установлен if (EXTI_GetITStatus(EXTI_Line0) != RESET) { if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 1) { // Кто-то крутит ручку по часовой стрелке if(nVol < VOLUME_MAX_VAL) { nVol++; } } else { // Ручку крутят против часовой стрелки if(nVol > 0) { nVol--; } } for(nCount *= 100000; nCount; nCount--); // Сбрасываем флаг прерывания EXTI_ClearITPendingBit(EXTI_Line0); } }
Примечание: Если входные пины инициализированы с "подтяжкой вверх", использовать подтягивающие резисторы R1-R3 не обязательно.
Отслеживать нажатие кнопки можно аналогичным образом.
Среда разработки: КуКокс. (www.coocox.org/software/coide.php)
Если у вас есть комментарий по существу — присылайте.