Управление инкрементальным энкодером на AVR

Я думаю многие радиолюбители и профессионалы разработчики сталкивались в своей работе с инкрементальными энкодерами, например, для управления громкостью в автомагнитолах, в компьютерных мышах и др. С помощью энкодера можно сделать удобный интерфейс управления для любого устройства, ну даже, скажем, для токарного станка. В этой статье я хочу предложить метод обработки сигналов от энкодера микроконтроллером AVR, который был предложен как раз для управления коробкой передач токарного станка здесь. Вообще энкодеры бывают разных типов, сейчас же остановимся на обыкновенном механическом инкрементальном энкодере, который чем-то напоминает переменный резистор, но в отличие от него не имеет ограничений по прокручиванию ни в одну из сторон, т.е. можно сделать довольно неплохой счетчик оборотов.

Принцип работы энкодера

Принцип работы нашего энкодера до безобразия прост, при отладке прошивки я не имел под рукой аналогичного энкодера, поэтому эмулировал его работу двумя кнопками) Итак, рассмотрим диаграмму импульсов от энкодера.

Принцип работы энкодера

Как я уже сказал, электрически энкодер представляет собой две кнопки, которые срабатывают поочередно, сначала одна, потом вторая и так по кругу. В зависимости от того, какая срабатывает кнопка раньше мы имеем определенное направление вращения энкодера. Не забываем, что сигнальные ножки энкодера должны быть подтянуты к плюсу питания либо внешними резисторами, либо включением подтяжки pull-up на портах микроконтроллера.

Собственно диаграмма нам показывает, что при вращении контакты энкодера поочередно замыкаются на землю, в устойчивом состоянии на контактах энкодера плюс напряжения питания т.е. 11 в двоичном виде, если начинаем вращать по часовой стрелке замыкаем сперва одну кнопку – 01, далее замыкается вторая кнопка – 00, вращаем еще, размыкаем первую кнопку – 10, ну и возвращаемся в устойчивое состояние – 11 при котором как раз и слышится характерный механический щелчок энкодера. При вращении против часовой стрелки все то же самое только наоборот. Ну вроде разобрались с принципом работы, наш энкодер имеет 4 состояния в процессе вращения, именно эти 4 состояния нужно обрабатывать в программе управления энкодером на МК AVR.

Описание эксперимента

В своем эксперименте я подключал «энкодер» или точнее кнопки к ножкам PD2 и PD3 микроконтроллера Atmega328P, который находился на борде Arduino Nano, я вообще очень люблю использовать платы arduino в качестве отладки для своего кода, программирую в Atmel Studio, а прошиваю AVR Dragon через ISP. Считывать состояние ножек буду простым логическим И — &. Var = PIND & 0b00001100 или 12 в шестнадцатеричной системе счисления. Все, в принципе, больше никаких особых тем при обработке данных от энкодера нет, можно писать код. У меня будет переменная sw34 (почему такое имя не знаю, так уж исторически сложилось), которую я буду изменять увеличивая или уменьшая ее значение в зависимости от вращения энкодера.

Код программы

Чтобы долго не томить выложу сразу весь код, а потом немного пояснений:

/* Подключаем инклуды */
#include <avr/io.h>
#include <avr/interrupt.h>

/* Дефайны */
#define F_CPU 1000000UL                // тактовая частота 1MHz
#define PIND_MASK 0b00001100        // Маска для сравнения с PIND

/* Переменные */
volatile uint8_t next_state, prev_state, up_state, down_state;
uint8_t sw34;                        // Передача

/* Прерывания */
ISR (TIMER1_COMPA_vect)
{
    next_state = PIND & PIND_MASK; // Считываем текущее значение битов
    if (next_state != prev_state)
    {
        switch (prev_state)
        {
            case 8:
            {
                if (next_state == 12) up_state++;
                if (next_state == 0) down_state++;
                break;
            }
            case 0:
            {
                if (next_state == 8) up_state++;
                if (next_state == 4) down_state++;
                break;
            }
            case 4:
            {
                if (next_state == 0) up_state++;
                if (next_state == 12) down_state++;
                break;
            }
            case 12:
            {
                if (next_state == 4) up_state++;
                if (next_state == 8) down_state++;
                break;
            }
            default:
            {
                break;
            }
        }
        prev_state = next_state;    // Текущее состояние становится предыдущим
    }
    TCNT1H=0x00;
    TCNT1L=0x00;                    // Обнуляем счетчик
}

void timer1_init()
{
    TCCR1A = 0x00;
    TCCR1B |= (1<<CS11);            // Тактировать с коэффициентом деления 8
    TCNT1H = 0x00;                    // Обнуляем счетный регистр старший байт
    TCNT1L = 0x00;                    // Обнуляем счетный регистр младший байт
    OCR1AH=0x03;                    // Настраиваем регистр сравнения старший байт
    OCR1AL=0xE8;                    // Настраиваем регистр сравнения младший байт
  
    // Разрешаем прерывание таймера по совпадению с OCR1A
    TIMSK1 |= (1<<OCIE1A);
}

int main(void)
{
    DDRD = 0x00;
    PORTD = 0x0C;
    timer1_init();                    // Инициализация таймера1
  
    sei();                            // Разрешить глобальные прерывания
    while(1)
    {
        if (up_state >= 4)            // 1 раз за 4 импульса изменяем состояние передачи
        {
            sw34++;                    // Передача +
            up_state = 0;
        }
        if (down_state >= 4)
        {
            sw34--;                    // Передача -
            down_state = 0;
        }
    }
}

next_state = PIND & PIND_MASK; — так считываем состояние ножек энкодера.
В прерывании конструкция switch (prev_state) определяет в какую сторону крутится энкодер.
if (next_state != prev_state) — если состояние не изменилось значит энкодер не вращался.
В основном цикле все просто, 1 раз за 4 импульса от энкодера, т.е. от «щелчка до щелчка» изменяем состояние нужной переменной sw34 — передача станка.
Timer1 настроен на прерывание по совпадению с регистром OCR1A, генерирует прерывание 1000 раз в секунду.

5 Ответов в “Управление инкрементальным энкодером на AVR

  1. Наконец-то грамотный код!!!
    Метод фильтрации по таблице состояний автоматически подавляет дребезг контактов, ибо отклонения от штатного режима не обрабатываются и идут нахер. И таким образом становятся не нужны ни уёбищные конденсаторы параллельно портам (вызывающие преждевременное обгорание микроконтактов), ни ублюдские паузы в десятки мс после отслеживания замыканий, снижающие время реакции на энкодер.

  2. Добавил в main инициализацию порта С:
    DDRC=0xFF;
    PORTC=0x00;
    а в цикл строку :
    while(1)
    {
    if (up_state >= 4) // 1 раз за 4 импульса изменяем состояние передачи
    {
    sw34++; // Передача +
    up_state = 0;
    }
    if (down_state >= 4)
    {
    sw34—; // Передача —
    down_state = 0;
    }
    PORTC=sw34;
    }
    Однако работает нестабильно ,со сбоями,подключение конденсаторов не помогло.
    при увеличении sw34 более -менее нормально,при вращениив обратную сторону не реагирует совсем.

  3. Если можно поподробнее объясните как работает в этом коде оператор switch.
    Прямо по строчкам.
    Спасибо.

  4. Заработало, но переключает на каждом щелчке.

Комментарии отключены.