Разберемся с подключением механического инкрементального энкодера. По совместительству данный эксперимент будет одним из шагов на пути к усилителю.
Энкодер – штуковина, с помощью которой мы можем количественно выразить на какой угол мы сделали поворот и в каком направлении он осуществлялся.
Обычно у энкодера есть три ноги, чтобы его подключить к мк я использовал следующую схемку:
Сопротивления по 4.7 кОм.
У меня энкодер с дополнительной кнопкой, которую можно нажимать (хочу обойтись без лишних кнопок на корпусе). О ней речь пойдет чуть позже, а пока что мы просто подтягиваем ее на питание через резистор как обычную кнопку.
Итак, как же определить направление вращения и посчитать импульсы?
Привожу диаграммки, которые на мой взгляд наиболее просто и без лишней информации дают нужное представление. Представим, что мы вращаем энкодер в одну сторону, если подключиться в этот момент к осциллографу, то мы увидим следующую картинку:
А теперь будем крутить в другую сторону:
То есть, в момент переключения состояния порта А, в зависимости от направления вращения на порту С будет либо 0 либо 1. Осталось только завести обработчик прерываний на один из этих портов, и в момент срабатывания проверять состояние другого порта.
Все хорошо, но есть один подводный камень – дребезг. Есть разные пути устранения его и более правильный способ – это проверка состояний линий с определенной частотой в основном цикле программы (а не в прерываниях), затем состояние линий сравнивается с предыдущим и определяется направление поворота. Но основной цикл у меня предположительно будет и так неплохо забит, поэтому будем пытаться настроить антидребезг. Для начала я решил подпаять емкости по 100 нФ на линии выходов и убрать программные задержки, много людей на форумах писало что им это помогло. То есть изначально у меня был примерно такой код:
volatile static bit encoder_flag = 0; //глобальная переменная для отслеживания прерывания по энкодеру if (encoder_flag) { GIE = 0; //выключаем прерывания на время обработки if ((!right) && (left)) up = 1; //крутим вправо if ((right) && (!left)) up = 1; //крутим вправо if ((right) && (left)) down = 1; //крутим влево if ((!right) && (!left)) down = 1; //крутим влево encoder_flag = 0; //сбрасываем флаг GIE = 1; //включаем прерывания }
Переменные up и down – статусные переменные, которые показывают в какую сторону происходит вращение.
Right и left – пины 5 и 2 порта В, не знаю почему я так их назвал, но мне это почему-то показось наиболее логичным.
Этот код обрабатывался не совсем корректно – часто вылетали ошибки определения вращения. Поэтому было решено добавить программную задержку, которуя определил опытным путем.
Итоговый код для обработки вращения энкодера (в разрезе использования для аудиоусилителя):
volatile static bit encoder_flag = 0; //глобальная переменная для отслеживания прерывания по энкодеру if (encoder_flag) { GIE = 0; //выключаем прерывания на время обработки __delay_us(20); if ((!right) && (left)) up = 1; //крутим вправо if ((right) && (!left)) up = 1; //крутим вправо if ((right) && (left)) down = 1; //крутим влево if ((!right) && (!left)) down = 1; //крутим влево encoder_flag = 0; //сбрасываем флаг GIE = 1; //включаем прерывания } //Обработчик перывания interrupt isr() { if (RBIF) { encoder_flag = 1; RBIF = 0; }
Обработчик прерывания пока что самый простой, без определения по какому пину произошло прерывание. Данный код у меня выдает примерно 1 ложное срабатывание на 15, с учетом что использование драгоценных ресурсов мк возросло всего лишь на пару процентов, меня такая версия вполне устраивает.
Идем дальше, какая главная функция крутелки в усилителе? Правильно, регулировка громкости. Но ведь у нас этот энкодер это основной орган управления, имеющийся на корпусе (пульта то у нас пока нету). Тут и приходит на помощь встроенная кнопка энкодера. На данный момент логика обработки вращений/нажатий энкодера примерно такова:
- Крутанули энкодер вправо/влево – уменьшили/увеличили громкость (дефолтное состояние)
- Нажали на кнопку энкодера в дежурном состоянии – выполнился вход в главное меню
- Крутанули энкодер после нажатия кнопки – скроллим пункты меню
- Нажали на кнопку после входа в главное меню – выбираем конкретный пункт
- Нажали на кнопку после входа в пункт – выход в главное меню (если внутри конкретного пункта не предусмотрено других действий)
Отлично, теперь нужно думать как добавить на прерывания кнопку :(.
Сначала я думал пойти стандартным путем и ксорить предыдущее значение с текущим, но следующие тесты показали, что этот вариант у меня работать отказывается. Поэтому я решил упростить себе жизнь, просто повесив кнопку на порт В0 и организовав работу по прерыванию INTF
interrupt isr() { if (INTF) { volume_flag = 1; //1 - значит вход в основное меню, а не изменение громкости INTF = 0; }
Пришло время проверки, для отладки я как обычно использую уарт, добавляем в код небольшие дополнения
if ((up)&&(!volume_flag)) { //увеличение громкости if (volume<100) volume++; printf("\r\n volume [%d]", volume); up = 0; } if ((down)&&(!volume_flag)) { //уменьшение громкости if (volume>0) volume--; printf("\r\n volume [%d]", volume); down = 0; } if ((up)&&(volume_flag)) { if (itemp < 10) { if (itemp<9) { itemp++; exit_status = 0; //остаемся в меню } else { itemp = 10; exit_status = 1; //если в десятом пункте меню, то можем выходить } } else { itemp = 1; exit_status = 0; } printf("\r\n itemp [%d]", itemp); printf("\r\n exit status - %d", exit_status); up = 0; } if ((down)&&(volume_flag)) { if (itemp > 1 ) { itemp--; exit_status = 0; } else { itemp = 10; exit_status = 1; } printf("\r\n itemp [%d]", itemp); printf("\r\n exit status - %d", exit_status); down = 0; }
Небольшие разъяснения:
Как я уже упоминал ранее, по изменению состоянию энкодера может происходить несколько действий, но среди них можно выделить два главных:
- Скролл громкости
- Скролл пунктов меню
Имено за эти два главных пункта отвечает глобальная переменная volume_flag, если она в нуле то переходим в скролл громкости, если 1 – то скролл меню (понимаю что не совсем логично :)).
Для хранения состояния громкости я завел переменную volume (пока локальную, но потом я планирую ее зашивать в EEPROM, хранению различных настроек в энергонезависимой памяти будет посвящена отдельная статья). Точно также была заведена переменная itemp, показывающая текущее состояние меню.
Из меню должен быть организован выход, на данной стадии я предположил что 10 пункт меню будет выходом, поэтому при itemp равном 10 глобальная переменная exit_status становится равной 1. Ну и собственно прерывание, в котором и происходит главное действие.
interrupt isr() { if (INTF) { //нажата кнопка printf("\r\n\EButtoN"); if (!volume_flag) volume_flag = 1; //если было изменение громкости, то входим в меню if (exit_status) { //если выход, то выходим в изменение громкости exit_status = 0; volume_flag = 0; } INTF = 0; } if (RBIF) { //индикатор крутелки encoder_flag = 1; RBIF = 0; }
Ну вроде все, подключаемся по уарту и тестируем:
Ну как то так, хватит пока что. Дальше буду организовывать меню на жк дисплее
UPD. Добавил процедуру обработки методом опроса состояний.
Надо будет повторить процедуру лично.Спасибо
Лучше использовать вторую процедуру http://diymicro.ru/pic-mk-eksperiment-20-1-enkoder-metod-oprosa-sostoyanij.html
Уведомление: Pic Lab, PIC16, Experiment #20, The encoder | diymicro.org