Энкодер
Время рассматривать всякие кнопочки-датчики)
Начнём с простейшего – с механического накапливающего энкодера.
Энкодер, он же датчик угла поворота – устройство, "предназначенное для преобразования угла поворота вращающегося объекта (вала) в электрические сигналы, позволяющие определить угол его поворота". Иными словами, крутим крутилку, а на выход нам даётся информация о том, насколько мы повернули ручку.
По способу выдачи информации энкодеры бывают абсолютные и накапливающие (инкрементные). Абсолютные показывают непосредственно сам угол поворота, накапливающие формируют импульсы «Был поворот вправо» или «Был поворот влево».
Мы будем рассматривать накапливающие энкодеры. Вот принцип их работы:
Рисунок 1. Принцип работы механического накапливающего энкодера
Таким образом, если датчик оказывается в пропуске пластины, то с него есть сигнал.
По принципу действия энкодеры бывают оптическими, резистивными, магнитными, индуктивными и механическими.Наиболее простые – механические накапливающие.
Их можно рассматривать как просто две кнопки. Если мы поворачиваем крутилку вправо, то сначала включается кнопка b, если влево – то кнопка a. Подробнее на рисунке:
Рисунок 2. Состояние "кнопок" внутри энкодера при вращении
Таким образом, можно просто регулярно проверять две ножки микросхемы, и мы будем знать, куда крутится энкодер.
Попробуем реализовать простенькую программку: пусть у нас будет ATtiny2313, энкодер и парочка светодиодов. Крутим энкодер в одну сторону – горит один светодиод, крутим в другую – другой.
Заморачиваться с погашением светодиодов не будем. Сейчас главное – понять, как работает энкодер. "Пощупать", так сказать!
Итак, алгоритм программы:
- Проверяем состояние энкодера. В "спокойном состоянии" на ножках микросхемы "единичка" - ножки, слушающие энкодер, установлены на вход с подтяжкой. Как только на этих ножках оказывается не "11", а "01" или "10" - энкодер начал крутиться. Запоминаем, что нажалось первое, и переходим на шаг 2.
- Ждём следующего состояния энкодера – нажатия двух внутренних кнопок – "00". Как только получили его, считаем, что энкодер уже точно щелкнул в ту или иную сторону, поэтому зажигаем нужный светодиод и гасим ненужный. Переходим на шаг 3.
- Просто дожидаемся окончания поворота энкодера – то есть, прихода "11". Как только получили, переходим снова на шаг 1.
Тут программа на Си.
typedef unsigned char byte; typedef unsigned int word; //глобальные переменные и константы, определяющие распиновку //переменные для энкодера byte EncoderDDR at DDRB; byte EncoderPORT at PORTB; byte EncoderPIN at PINB; const byte ENCODER_OUT_A_PIN = 0; const byte ENCODER_OUT_B_PIN = 1; //переменные для светодиодов byte LED_DDR at DDRD; byte LED_PORT at PORTD; const byte LED_A_PIN = 0; const byte LED_B_PIN = 1; enum TTurnSide {tsLeft, tsRight}; //перечисление сторон поворота энкодера enum TEncoderState {esWaitChanges, //перечисление состояний энкодера: ожидание изменений ("01 или 10"); esWaitValidation, //ожидание подтверждения поворота ("00"); esWaitEnding}; //ожидание окончания поворота ("11"); void main() { //объявляем переменные enum TTurnSide TurnSide; //переменная, определяющая сторону поворота энкодера. enum TEncoderState EncoderState = esWaitChanges; //переменная, определяющая текущее состояние энкодера; изначально - "ждём изменений" //начальная инициализация - все ножки в Z-состояние PORTA = 0; DDRA = 0; PORTB = 0; DDRB = 0; PORTD = 0; DDRD = 0; //определяем конкретные биты: EncoderDDR &= ˜((1 << ENCODER_OUT_A_PIN) | (1 << ENCODER_OUT_B_PIN)); EncoderPORT |= (1 << ENCODER_OUT_A_PIN) | (1 << ENCODER_OUT_B_PIN); LED_DDR |= (1 << LED_A_PIN) | (1 << LED_B_PIN); LED_PORT |= (1 << LED_A_PIN) | (1 << LED_B_PIN); while(1) { switch (EncoderState) //в зависимости от состояния энкодера { case esWaitChanges: //состояние "Ждём начала поворота" //если есть изменения ("01" или "10") if ((EncoderPIN.ENCODER_OUT_A_PIN == 0) ˆ (EncoderPIN.ENCODER_OUT_B_PIN == 0)) { //то запоминаем, в какую сторону поворот if (EncoderPIN.ENCODER_OUT_A_PIN == 0) TurnSide = tsLeft; else TurnSide = tsRight; //переходим на следующий шаг - ждём подтверждения поворота EncoderState = esWaitValidation; } break; case esWaitValidation: //состояние "Ждём подтверждения поворота" //если пришло подтверждение поворота ("00"): if ((EncoderPIN.ENCODER_OUT_A_PIN == 0) && (EncoderPIN.ENCODER_OUT_B_PIN == 0)) { //зажигаем нужный светодиод и гасим ненужный if (TurnSide == tsLeft) { LED_PORT.LED_A_PIN = 0; LED_PORT.LED_B_PIN = 1; } else { LED_PORT.LED_B_PIN = 0; LED_PORT.LED_A_PIN = 1; } //переходим на следующий шаг - ждём окончания поворота EncoderState = esWaitEnding; } break; case esWaitEnding: //состояние "Ждём окончания поворота" //если перешли в "спокойное" состояние ("11") if ((EncoderPIN.ENCODER_OUT_A_PIN == 1) && (EncoderPIN.ENCODER_OUT_B_PIN == 1)) EncoderState = esWaitChanges; break; } } }
А теперь – невероятно, но факт! На mikroPascal не были реализованы ни перечисления, ни наборы, ни объединения.
Жизнь – боль. Удар ниже пояса.
Соответственно, придётся обходить перечисления просто числами – но тогда код становится более запутанным.
Вот программа на Паскале.
program Encoder; const ENCODER_OUT_A_PIN = 0; ENCODER_OUT_B_PIN = 1; LED_A_PIN = 0; LED_B_PIN = 1; var EncoderDDR: byte at DDRB; EncoderPORT: byte at PORTB; EncoderPIN: byte at PINB; LED_DDR: byte at DDRD; LED_PORT: byte at PORTD; TurnSide: byte; EncoderState: byte; begin EncoderState := 0; //начальная инициализация - все ножки в Z-состояние PORTA := 0; DDRA := 0; PORTB := 0; DDRB := 0; PORTD := 0; DDRD := 0; //определяем конкретные биты: EncoderDDR := EncoderDDR and (not((1 shl ENCODER_OUT_A_PIN) or (1 shl ENCODER_OUT_B_PIN))); EncoderPORT := EncoderPORT or (1 shl ENCODER_OUT_A_PIN) or (1 shl ENCODER_OUT_B_PIN); LED_DDR := LED_DDR or (1 shl LED_A_PIN) or (1 shl LED_B_PIN); LED_PORT := LED_PORT or (1 shl LED_A_PIN) or (1 shl LED_B_PIN); while(1) do begin case (EncoderState) of //в зависимости от состояния энкодера 0: //состояние "Ждём начала поворота" //если есть изменения ("01" или "10") if ((EncoderPIN.ENCODER_OUT_A_PIN = 0) xor (EncoderPIN.ENCODER_OUT_B_PIN = 0)) then begin //то запоминаем, в какую сторону поворот if (EncoderPIN.ENCODER_OUT_A_PIN = 0) then TurnSide := 0 else TurnSide := 1; //переходим на следующий шаг - ждём подтверждения поворота EncoderState := 1; end; 1: //состояние "Ждём подтверждения поворота" //если пришло подтверждение поворота ("00"): if ((EncoderPIN.ENCODER_OUT_A_PIN = 0) and (EncoderPIN.ENCODER_OUT_B_PIN = 0)) then begin //зажигаем нужный светодиод и гасим ненужный if (TurnSide = 0) then begin LED_PORT.LED_A_PIN := 0; LED_PORT.LED_B_PIN := 1; end else begin LED_PORT.LED_B_PIN := 0; LED_PORT.LED_A_PIN := 1; end; //переходим на следующий шаг - ждём окончания поворота EncoderState := 2; end; 2: //состояние "Ждём окончания поворота" //если перешли в "спокойное" состояние ("11") if ((EncoderPIN.ENCODER_OUT_A_PIN = 1) and (EncoderPIN.ENCODER_OUT_B_PIN = 1)) then EncoderState := 0; end; end; end.
Итак – оно работает. Но если долго пробовать крутить энкодер в разные стороны, можно заметить, что иногда может, например, включиться не тот светодиодик, а изредка микроконтроллер вообще пропускает обороты.
Почему все работает не идеально? Не будем забывать, что раз у нас энкодер – это просто две кнопки, то, значит, может возникнуть дребезг контактов - это раз. А во-вторых, может так случиться, что начало нашего поворота - "01" или "10" - придется не на момент проверки именно этих состояний.