Энкодер

Энкодер – это цифровой датчик угла поворота.


Рисунок 1. Внешний вид механического энкодера.

Для стороннего пользователя принцип действия простой – крутим крутилку, получаем информацию о том, как крутилка была повернула.
Энкодеры бывают абсолютные – те, которые выдают в качестве результата двоичный код текущего угла поворота, и инкрементальные – они выдают импульсы, по которым можно определить направление поворота; посчитав количество импульсов, можно узнать количество поворотов и, соответственно, текущий угол.
По принципу работы энкодеры бывают механическими, оптическими, магнитными, магнитнорезисторными и т.д. Мы будем рассматривать самый простой энкодер, который позволяет определять направление кручения – механический инкрементный энкодер. Можно сказать, что он представляет собой две кнопки; в зависимости от направления поворота ручки энкодера, попеременно включаются то одна, то другая кнопка. Сравнивая текущие состояния кнопок, можно понять, куда крутится энкодер.


Рисунок 2. Состояния кнопок энкодера при вращении.

Для работы с энкодером мы будем использовать виртуальную машину защиты от дребезга; таким образом, сможем получать «посылки» от двух кнопок энкодера: «Передний фронт» и «Задний фронт».
На рисунке выше на самом деле представлены «идеальные» варианты работы с энкодером. В жизни все намного сложнее:


Рисунок 3. Состояния кнопок энкодера при повороте или возврате в исходное состояние.

Если определим передний фронт у кнопки А энкодера как RE_a (rising edge a), передний фронт у кнопки B – как RE_B (rising edge b), задний фронт у кнопки А – FE_a (falling edge a), задний фронт у кнопки B – FE_b (falling edge b), то поворот вправо будет закодирован как последовательность RE_b, RE_a, FE_b, FE_a, а поворот влево – как RE_a, RE_b, FE_a, FE_b.
При этом нужно учитывать, что пользователь может, например, начать поворачивать энкодер, а потом на середине «щелчка» (когда обе кнопки нажаты, или нажата только одна ещё) передумать и вернуть все обратно – эти ситуации тоже нужно корректно обрабатывать.

Для упрощения будем считать, что поворот совершен, если получили передний фронт «первой» кнопки, передний фронт «второй» кнопки и задний фронт «первой» кнопки (прошли ¾ от исходного состояния до «щелчка» - нового стабильного состояния, когда кнопки отпущены).
В ситуациях, когда мы слишком редко вызываем машину, при заходе в очередное состояние машина получает на вход посылки о переднем или заднем фронте сразу от обеих кнопок; в этом случае мы не можем установить, был ли поворот, или пользователь вернул ручку энкодера в исходное состояние.

Попробуем продумать виртуальную машину, которая сможет генерировать посылки «Поворот влево» или «Поворот вправо»:


Рисунок 4. Виртуальная машина энкодера.

Машина получилась достаточно громоздкой, однако она всего лишь проверяет, выполняется ли
последовательность «RE_b, RE_a, FE_b» или «RE_a, RE_b, FE_a».
Рассмотрим машину чуть подробнее:
Шаг первый – убедиться, что в начале мы находимся в «нейтральном» положении – обе кнопки энкодера отпущены. Как только удостоверились в этом, очищаем все входные посылки о переднем или заднем фронте.
Дальше начинаем ждать начала поворота ручки энкодера: когда придёт только одна посылка о переднем фронте; также мы проверяем случай, когда проворонили начало поворота и уже пришли обе посылки (ситуация № д) – в этом случае мы возвращаемся в начало и снова ждём «нейтрального» положения.
Если же нам пришла одна посылка «Передний фронт» - запоминаем, какая кнопка была первой (тут же, а то при следующем вызове машины может «нажаться» уже и вторая кнопка). В следующем состоянии очищаем посылку и потом начинаем ждать передний фронт оставшейся кнопки. Дальше – аналогично – проверяем на получение посылки «Передний фронт» второй кнопки, если пользователь продолжает крутить энкодер, и тут же проверяем на получение посылки «Задний фронт» первой кнопки – если пользователь передумал поворачивать энкодер и возвращает его на исходное состояние (ситуация № в).
Пришла посылка «Передний фронт» второй кнопки - очищаем посылку и переходим в ожидание заднего фронта первой кнопки. При этом у нас снова несколько вариантов развития событий:

  1. энокдер продолжает крутиться в нужную сторону – тогда у нас придет посылка «Задний фронт» от первой кнопки, а вторая кнопка будет в это время все ещё нажата
  2. приходит посылка «Задний фронт» от второй кнопки, при этом первая кнопка все ещё нажата; это значит, что на середине поворота пользователь передумал поворачивать энкодер и возвращает его обратно (ситуация № г).
  3. мы пропустили момент, когда одна кнопка была отпущена, а вторая ещё была нажата – и теперь у нас пришли обе посылки «Задний фронт» (ситуация № е) – в этом случае мы ничего не можем сказать о повороте – завершил ли его пользователь или вернул обратно

Первый случай – «благоприятный» - считаем, что энкодер «повернулся» и отправляем посылку: «Поворот влево» или «Поворот вправо», в зависимости от того, какая кнопка была первой. Что происходит дальше, нам не важно, мы просто дожидаемся очередного нейтрального положения энкодера, когда обе кнопки отпущены.
В остальных – «неблагоприятных» – случаях главный признак – в текущем состоянии («Ждём заднего фронта первой кнопки») приходит посылка «Задний фронт» от кнопки номер два. В этом случае мы говорим: «Ой, всё» - и тоже переходим в самое начало – ждём, когда энкодер окажется в нейтральном положении.
Реализуем машину по опыту и подобию прошлых:

Заголовочный файл:

#ifndef ENCODER_H
#define ENCODER_H

#include "stdint.h"
#include "PhysicalButton.h"

//возможные состояния вирутальной машины энкодера
typedef enum {ensWaitBtnsNotPressed, ensPrepareToTurn, ensWaitRisingEdge, ensFirstRisingEdge, ensWaitSecondRisingEdge,
              ensSecondRisingEdge, ensWaitFirstFallingEdge, ensEncoderIsTurned} TVMEncoderState;

//структура виртуальной машины энкодера
typedef struct
{
  //указатели на 
  TVMPhysicalButton *enLeftButtonMachine;
  TVMPhysicalButton *enRightButtonMachine;
  TVMEncoderState enMachineState;
  //"промежуточные" указатели на машину кнопки, которая была нажата первой, и на машину кнопки, которая была нажата второй
  TVMPhysicalButton *enFirstButtonMachine;
  TVMPhysicalButton *enSecondButtonMachine;
  
  uint8_t enOnLeftTurn: 1;
  uint8_t enOnRightTurn: 1;
} TVMEncoder;

//создали переменную для виртуальной машины энкодера (хотя для универсальности можно было бы создать массив, но тогда проигрывает быстродействие)
extern TVMEncoder VMEncoder;

//инициализация виртуальной машины энкодера
void EncoderMachineInit();
//сама виртуальная машина экнодера
void EncoderMachine();

#endif		//ENCODER_H

Файл .с:

#include "Encoder.h"
//переменная для виртуальной машины энкодера
TVMEncoder VMEncoder;

//инициализация виртуальной машины энкодера
void EncoderMachineInit()
{
  //считаем, что находимся в неопределённом состоянии и должны дождаться момента, когда кнопки энкодера будут отпущены
  //поворота влево или вправо не было
  VMEncoder.enMachineState = ensWaitBtnsNotPressed;
  VMEncoder.enOnLeftTurn = 0;
  VMEncoder.enOnRightTurn = 0;
}
//сама виртуальная машина экнодера
void EncoderMachine()
{
  switch (VMEncoder.enMachineState)
  {
  case ensWaitBtnsNotPressed:                                                   //неопределённое состояние; ждём момента, когда обе кнопки энкодера будут отпущены
    if (!((VMEncoder.enLeftButtonMachine->pbIsOn) || (VMEncoder.enRightButtonMachine->pbIsOn)))
      VMEncoder.enMachineState = ensPrepareToTurn;
    break;
  case ensPrepareToTurn:                                                        //готовимся к анализу на поворот энкодера
    //очищаем входные посылки
    VMEncoder.enLeftButtonMachine->pbOnRisingEdge = 0;
    VMEncoder.enLeftButtonMachine->pbOnFallingEdge = 0;
    VMEncoder.enRightButtonMachine->pbOnRisingEdge = 0;
    VMEncoder.enRightButtonMachine->pbOnFallingEdge = 0;
    //переходим в состояние ожидания переднего фронта
    VMEncoder.enMachineState = ensWaitRisingEdge;
    break;
  case ensWaitRisingEdge:                                                       //ждём получения переднего фронта
    //если пришёл только один передний фронт - запоминаем, какая кнопка первая
    //если поворот влево
    if (VMEncoder.enLeftButtonMachine->pbOnRisingEdge && 
        (!(VMEncoder.enRightButtonMachine->pbOnRisingEdge)))
    {
      VMEncoder.enFirstButtonMachine = VMEncoder.enLeftButtonMachine;
      VMEncoder.enSecondButtonMachine = VMEncoder.enRightButtonMachine;
      VMEncoder.enMachineState = ensFirstRisingEdge;
    }
    //если поворот вправо
    else if (VMEncoder.enRightButtonMachine->pbOnRisingEdge && 
        (!(VMEncoder.enLeftButtonMachine->pbOnRisingEdge)))
    {
      VMEncoder.enFirstButtonMachine = VMEncoder.enRightButtonMachine;
      VMEncoder.enSecondButtonMachine = VMEncoder.enLeftButtonMachine;
      VMEncoder.enMachineState = ensFirstRisingEdge;
    }
    //если прозевали момент, когда одна кнопка нажата, а другая ещё нет
    else if (VMEncoder.enRightButtonMachine->pbOnRisingEdge && 
             VMEncoder.enLeftButtonMachine->pbOnRisingEdge)
      VMEncoder.enMachineState = ensWaitBtnsNotPressed;                         //будем ждать нейтрального состояния энкодера, когда обе кнопки отпущены
    break;
  case ensFirstRisingEdge:                                                      //получили передний фронт одной из кнопок - очищаем посылку   
    VMEncoder.enFirstButtonMachine->pbOnRisingEdge = 0;
    VMEncoder.enMachineState = ensWaitSecondRisingEdge;
    break;
  case ensWaitSecondRisingEdge:                                                 //ждём получения переднего фронта от второй кнопки
    if (VMEncoder.enSecondButtonMachine->pbOnRisingEdge)
      VMEncoder.enMachineState = ensSecondRisingEdge;
    else if (VMEncoder.enFirstButtonMachine->pbOnFallingEdge)                   //если пользователь передумал и крутит ручку энкодера обратно
      VMEncoder.enMachineState = ensWaitBtnsNotPressed;
    break;
  case ensSecondRisingEdge:
    VMEncoder.enSecondButtonMachine->pbOnRisingEdge = 0;
    VMEncoder.enMachineState = ensWaitFirstFallingEdge;
    break;
  case ensWaitFirstFallingEdge:
    if (VMEncoder.enSecondButtonMachine->pbOnFallingEdge)                       //если прозевали момент, когда первая кнопка была отпущена, или если пользователь начал крутить ручку энкодера обратно
      VMEncoder.enMachineState = ensWaitBtnsNotPressed;
    else if (VMEncoder.enFirstButtonMachine->pbOnFallingEdge)                   //если пользователь продолжает поворачивать ручку энкодера
      VMEncoder.enMachineState = ensEncoderIsTurned;
    break;
  case ensEncoderIsTurned:
    if (VMEncoder.enFirstButtonMachine == VMEncoder.enLeftButtonMachine)
      VMEncoder.enOnLeftTurn = 1;
    else
      VMEncoder.enOnRightTurn = 1;
    VMEncoder.enMachineState = ensWaitBtnsNotPressed;
    break;
  }
}

В основном файле редактируем функцию InitButtonsAndEncoder:

//инициализация кнопок и энкодера
void InitButtonsAndEncoder()
{
	uint8_t i;
	//определяем местоположение физических кнопок (4 штуки)
	for (i = 0; i < PHYSICAL_BUTTONS_COUNT; i++)
	{
    //записываем физические данные в структуру
    VMPhysicalButtons[i].pbPin = (uint8_t*)&PINC;
    VMPhysicalButtons[i].pbDdr = (uint8_t*)&DDRC;
    VMPhysicalButtons[i].pbPort = (uint8_t*)&PORTC;
    VMPhysicalButtons[i].pbPinNumber = i + 2;
	}
  PhysicalButtonMachineInit();                                                  //инициализируем машину "физических" кнопок - защиты от дребезга
  
  VMLogicalButtons[0].lbVMPhysicalButton = &VMPhysicalButtons[1];
  VMLogicalButtons[1].lbVMPhysicalButton = &VMPhysicalButtons[0];
  LogicalButtonMachineInit();                                                   //инициализируем структуры логических кнопок 
  
  VMEncoder.enLeftButtonMachine = &VMPhysicalButtons[3];
  VMEncoder.enRightButtonMachine = &VMPhysicalButtons[2];
  EncoderMachineInit();
}

void main( void )
{
  DDRB = 0;
  PORTB = 0;
  DDRD = 0;
  PORTD = 0;
  DDRC = 0;
  PORTC = 0;
  
  DisplayInit();
  InitButtonsAndEncoder();
  FastTimerInit();
  ClockTimerInit();
  while(1)
  {
    PhysicalButtonMachine();
    LogicalButtonMachine();
    EncoderMachine();
    if (VMEncoder.enOnLeftTurn)                                                 //если энкодер был повернут налево - отображаем сообщение об этом
    {
      DisplayShowStr("LEFT");
      VMEncoder.enOnLeftTurn = 0;
    }  
    if (VMEncoder.enOnRightTurn)                                                //если энкодер был повернут направо - отображаем сообщение об этом
    {
      DisplayShowStr("RHT");
      VMEncoder.enOnRightTurn = 0;
    }
  }
}

Вот проект и прошивка.

Итак, теперь мы умеем распознавать, когда энкодер поворачивается в одну, а когда – в другую сторону. Однако частенько этого мало – например, при задании температуры, и при настройке таймера (см. модель) нам нужно, чтобы при повороте энкодера влево какое-то число уменьшалось на заданный шаг – до определённого минимума – а при повороте вправо увеличивалось до максимума. В этих случаях логично использовать «более высокую» машину, которая будет получать посылки о импульсах влево-вправо от базовой машины энкодера и «переводить» их в некоторое новое значение. При этом при изменении значения должна отправляться посылка «Значение изменилось».
Помимо перевода импульсов в изменение некоторого значения машина счётчика энкодера также должна определять, активен энкодер или нет – мы хотели реализовать возможность возврата из состояния настройки в состояние отображения текущего значения: если настраиваемое значение не изменяется некоторое время, то мы засекаем таймер; как только этот таймер бездействия достигает какого-то максимума, переходим в спящее состояние и отсылаем посылку о спящем режиме; из спящего состояния выходим, если машина энкодера засекла движение ручки – в этом случае тоже нужно отправлять посылку о переходе в активный режим.
Рассмотрим машину счётчика энкодера:


Рисунок 5. Виртуальная машина счётчика энкодера.

Скажем сразу, при реализации возникли проблемы с определением того, какое событие будет свидетельствовать о выходе из спящего режима.
Сначала мы использовали условие, изображенное на рисунке: если состояние машины энкодера сигнализирует о том, что ручка энкодера находится в повороте (не в нейтральном положении); однако при этом возникла проблема: бывает так, что пользователь в запале неистово крутит ручку энкодера, а потом резко останавливается, при этом «не докрутя ручку» (вторая кнопка все ещё нажата) или, наоборот, «перекрутя» (первая кнопка нажата, а вторая – нет) – при этом импульс поворота уже послан (или ещё не послан), и пользователь на этом успокаивается – однако машина энкодера все ещё ждёт окончания поворота, и, соответственно, в нашем случае машина счётчика энкодера будет заходить в спящее состояние по таймеру, и тут же выходить оттуда!
Такой вариант нам не подходит.
Дальше мы попробовали сделать в качестве события для выхода из спящего режима получение посылки «Поворот вправо» или «Поворот влево» - это сработало, однако при этом этот первый поворот энкодера не засчитывается (мы же ещё не инициализировали сам счётчик, не задали текущее, максимальное и минимальное значение и шаг) – использовать можно, но этот потерянный щелчок жаль.
И, наконец, мы попробовали в качестве условия выхода использовать не множество состояний энкодера, а только одно состояние – например, если энкодер в состоянии «Получили передний фронт первой кнопки» (enMachineState == ensFirstRisingEdge?).
Это работает, но нужно следить, чтобы выбранное для условие состояние вызывалось единократно за поворот энкодера (в ином случае, например, если бы мы выбрали для проверки состояние «ensWaitSecondRisingEdge» («Ждём передний фронт второй кнопки»), когда первая кнопка нажата, а вторая – нет, то именно в этом состоянии мы как раз и можем застрять – то есть пользователь именно в этом положении остановит ручку энкодера). Состояние же ensFirstRisingEdge вызывается один раз, как только мы получили передний фронт первой кнопки; при этом, так как машины вызываются последовательно одна за другой, мы не пропустим получение этого состояния в другой машине; хотя, конечно, «по правилам» стоило бы создать отдельную посылку на этот случай.
Таким образом, исправленная машина выглядит так:


Рисунок 6. Исправленный вариант виртуальной машины счётчика энкодера.

Код будем размещать в библиотеке энкодера; так как используем константу времени, то не забываем подключить файл ""Timings.h".
В заголовочный файл добавим следующее:

#include "Timings.h"
...
//виртуальная машина счётчика энкодера
//максимальное значение счётчика бездействия
#define ENC_COUNTER_MAX_INACTIVITY_COUNTER (CLOCK_TIMER_INTERRUPTS_PER_SECOND * 3)
//возможные состояния виртуальной машины счётчика энкодера
typedef enum {ecnsSleep, ecnsInit, ecnsWaitTurn, ecnsLeftTurn, ecnsRightTurn} TVMEncoderCounterState;

//структура виртуальной машины счётчика энкодера
typedef struct
{
  TVMEncoder *ecntVMEncoder;                                                    //указатель на виртуальную машину энкодера
  volatile uint8_t ecntInactivityCounter;
  //параметры счётчика
  double ecntValue;
  double ecntMaxValue;
  double ecntMinValue;
  double ecntStep;
  //состояние счётчика
  TVMEncoderCounterState ecntMachineState;
  //посылки
  uint8_t ecntInNewData: 1;                                                     //входящая посылка об обновлении параметров счётчика
  uint8_t ecntOnValueChanged: 1;                                                //исходящая посылка о изменении значения счётчика 
  uint8_t ecntOnSleepMode: 1;                                                   //исходящая посылка о переходе в спящий режим
  uint8_t ecntOnActiveMode: 1;                                                  //исходящая посылка о переходе в активный режим
 } TVMEncoderCounter;

extern TVMEncoderCounter VMEncoderCounter;

//инициализация виртуальной машины счётчика энкодера
void EncoderCounterMachineInit();
//прерывание для виртуальной машины счётчика энкодера
void EncoderCounterMachineInterrupt();
//сама виртуальная машина счётчика энкодера
void EncoderCounterMachine();
//инициализация счётчика энкодера: изменение численных характеристик
void EncoderCounterSetValue(double CurrValue, double MaxValue, double MinValue, double Step);
//"включение" счётчика энкодера - когда численные характеристики изменять не нужно
void EncoderCounterActiveModeOn();

В файл Encoder.c добавим следующее:

TVMEncoderCounter VMEncoderCounter;

//инициализация виртуальной машины счётчика энкодера
void EncoderCounterMachineInit()
{
  __disable_interrupt();
  VMEncoderCounter.ecntInactivityCounter = 0;
  VMEncoderCounter.ecntMachineState = ecnsSleep;
  __enable_interuupt();
  VMEncoderCounter.ecntMaxValue = 0;
  VMEncoderCounter.ecntMinValue = 0;
  VMEncoderCounter.ecntOnValueChanged = 0;
  VMEncoderCounter.ecntStep = 1;
  VMEncoderCounter.ecntValue = 0;
  VMEncoderCounter.ecntOnActiveMode = 0;
  VMEncoderCounter.ecntOnSleepMode = 0;
  VMEncoderCounter.ecntVMEncoder = &VMEncoder;
}

//прерывание для виртуальной машины счётчика энкодера
void EncoderCounterMachineInterrupt()
{
  if ((VMEncoderCounter.ecntMachineState == ecnsWaitTurn) &&
      (VMEncoderCounter.ecntInactivityCounter < ENC_COUNTER_MAX_INACTIVITY_COUNTER))
    VMEncoderCounter.ecntInactivityCounter++;
}

//сама виртуальная машина счётчика энкодера
void EncoderCounterMachine()
{
  switch (VMEncoderCounter.ecntMachineState)
  {
  case ecnsSleep:
    if (VMEncoderCounter.ecntVMEncoder->enMachineState == ensFirstRisingEdge)   //если энкодер начинает поворачиваться - переходим к инициализации счётчика и отправляем посылку о переходе в активный режим
    {
      VMEncoderCounter.ecntMachineState = ecnsInit;
      VMEncoderCounter.ecntOnActiveMode = 1;
      VMEncoderCounter.ecntInactivityCounter = 0;                               //обнуляем счётчик бездействия
    }
    break;
  case ecnsInit:                                                                //инициализация энкодера - ждём, когда пользователь задаст новые параметры (Value, Max, Min, Step - и отправит посылку InNewData)
    if (VMEncoderCounter.ecntInNewData == 1)
    {
      VMEncoderCounter.ecntInNewData = 0;                                       //очищаем входящую посылку и посылки энкодера
      VMEncoderCounter.ecntVMEncoder->enOnLeftTurn = 0;
      VMEncoderCounter.ecntVMEncoder->enOnRightTurn = 0;
      VMEncoderCounter.ecntMachineState = ecnsWaitTurn;
    }
    break;
  case ecnsWaitTurn:                                                            //режим ожидания поворота
    //проверяем, был ли импульс влево
    if (VMEncoderCounter.ecntVMEncoder->enOnLeftTurn == 1)
      VMEncoderCounter.ecntMachineState = ecnsLeftTurn;
    //проверяем, был ли импульс вправо
    else if (VMEncoderCounter.ecntVMEncoder->enOnRightTurn == 1)
      VMEncoderCounter.ecntMachineState = ecnsRightTurn;
    //проверяем, не нужно ли переходить в спящий режим
    else if (VMEncoderCounter.ecntInactivityCounter >= ENC_COUNTER_MAX_INACTIVITY_COUNTER)
    {
      VMEncoderCounter.ecntMachineState = ecnsSleep;
      VMEncoderCounter.ecntOnSleepMode = 1;
      VMEncoderCounter.ecntInNewData = 0;
    }      
    break;
  case ecnsLeftTurn:                                                            //был импульс влево
    VMEncoderCounter.ecntVMEncoder->enOnLeftTurn = 0;                           //очищаем посылку
    if ((VMEncoderCounter.ecntValue - VMEncoderCounter.ecntStep) >= VMEncoderCounter.ecntMinValue)
    {
      VMEncoderCounter.ecntValue = VMEncoderCounter.ecntValue - VMEncoderCounter.ecntStep;
      VMEncoderCounter.ecntOnValueChanged = 1;
    }
    VMEncoderCounter.ecntInactivityCounter = 0;                                 //обнуляем счётчик бездействия
    VMEncoderCounter.ecntMachineState = ecnsWaitTurn;                           //снова возвращаемся в ожидание поворота
    break;
  case ecnsRightTurn:                                                           //был импульс вправо
    VMEncoderCounter.ecntVMEncoder->enOnRightTurn = 0;                          //очищаем посылку
    if ((VMEncoderCounter.ecntValue + VMEncoderCounter.ecntStep) <= VMEncoderCounter.ecntMaxValue)
    {
      VMEncoderCounter.ecntValue = VMEncoderCounter.ecntValue + VMEncoderCounter.ecntStep;
      VMEncoderCounter.ecntOnValueChanged = 1;
    }
    VMEncoderCounter.ecntInactivityCounter = 0;                                 //обнуляем счётчик бездействия
    VMEncoderCounter.ecntMachineState = ecnsWaitTurn;                           //снова возвращаемся в ожидание поворота
    break;
  }
}

//инициализация счётчика энкодера: изменение численных характеристик
void EncoderCounterSetValue(double CurrValue, double MaxValue, double MinValue, double Step)
{
  VMEncoderCounter.ecntValue = CurrValue;
  VMEncoderCounter.ecntMaxValue = MaxValue;
  VMEncoderCounter.ecntMinValue = MinValue;
  VMEncoderCounter.ecntStep = Step;
  VMEncoderCounter.ecntInNewData = 1;                                           //сообщаем машине, что данные обновлены
}

//"включение" счётчика энкодера - когда численные характеристики изменять не нужно
void EncoderCounterActiveModeOn()
{
  VMEncoderCounter.ecntInNewData = 1;
}

Ну и, наконец, главный файл: не забываем инициализировать машину счётчика энкодера при инициализации кнопок и энкодера, а также не забываем добавить функцию работы со счётчиком в прерывание часового таймера. Вот часть основного файла:

//прерывание таймера 2 на часовом кварце
#pragma vector = TIMER2_OVF_vect
__interrupt void ClockTimerInterrupt(void)
{
	LogicalButtonMachineInterrupt();																							//работа с счётчиком длительности нажатия для логических кнопок
  EncoderCounterMachineInterrupt();
}

//инициализация кнопок и энкодера
void InitButtonsAndEncoder()
{
	uint8_t i;
	//определяем местоположение физических кнопок (4 штуки)
	for (i = 0; i < PHYSICAL_BUTTONS_COUNT; i++)
	{
    //записываем физические данные в структуру
    VMPhysicalButtons[i].pbPin = (uint8_t*)&PINC;
    VMPhysicalButtons[i].pbDdr = (uint8_t*)&DDRC;
    VMPhysicalButtons[i].pbPort = (uint8_t*)&PORTC;
    VMPhysicalButtons[i].pbPinNumber = i + 2;
	}
  PhysicalButtonMachineInit();                                                  //инициализируем машину "физических" кнопок - защиты от дребезга
  
  VMLogicalButtons[0].lbVMPhysicalButton = &VMPhysicalButtons[1];
  VMLogicalButtons[1].lbVMPhysicalButton = &VMPhysicalButtons[0];
  LogicalButtonMachineInit();                                                   //инициализируем структуры логических кнопок 
  
  VMEncoder.enLeftButtonMachine = &VMPhysicalButtons[3];
  VMEncoder.enRightButtonMachine = &VMPhysicalButtons[2];
  EncoderMachineInit();
  EncoderCounterMachineInit();
}

void main( void )
{
  DDRB = 0;
  PORTB = 0;
  DDRD = 0;
  PORTD = 0;
  DDRC = 0;
  PORTC = 0;
  
  DisplayInit();
  InitButtonsAndEncoder();
  VMEncoderCounter.ecntValue = 20;
  VMEncoderCounter.ecntMaxValue = 30;
  VMEncoderCounter.ecntMinValue = 10;
  VMEncoderCounter.ecntStep = 2;  
  
  FastTimerInit();
  ClockTimerInit();
   
  DisplayShowStr("SLEEP");
  while(1)
  {
    PhysicalButtonMachine();
    LogicalButtonMachine();
    EncoderMachine();
    EncoderCounterMachine();
    if (VMEncoderCounter.ecntOnSleepMode)                                       //при переходе в спящий режим выводим на дисплее "SLEEP"
    {
      VMEncoderCounter.ecntOnSleepMode = 0;
      DisplayShowStr("SLEEP");
    }
    if (VMEncoderCounter.ecntOnActiveMode)                                      //при переходе в активный режим выводим на дисплей текущее значение
    {
      VMEncoderCounter.ecntOnActiveMode = 0;
      EncoderCounterActiveModeOn();
      DisplayShowNumber((int16_t)(VMEncoderCounter.ecntValue)); 
    }
    if (VMEncoderCounter.ecntOnValueChanged)                                    //при изменении текущего значения обновляем число на дисплее
    {
      VMEncoderCounter.ecntOnValueChanged = 0;
      DisplayShowNumber((int16_t)(VMEncoderCounter.ecntValue)); 
    }
  }
}

Вот проект и прошивка для счётчика энкодера.

Наверх

Автор - Moriam  =ˆˆ=

Обсудить на форуме