Январь 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)
Если у вас есть комментарий по существу — присылайте.