Pic мк. Эксперимент №20. Энкодер.

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

Энкодер – штуковина, с помощью которой мы можем количественно выразить на какой угол мы сделали поворот и в каком направлении он осуществлялся.

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

Сопротивления по 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. Добавил процедуру обработки методом опроса состояний.

Pic мк. Эксперимент №20. Энкодер.: 3 комментария

  1. Уведомление: Pic Lab, PIC16, Experiment #20, The encoder | diymicro.org

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.