Страницы

четверг, 25 сентября 2014 г.

Обработка сигналов энкодера

Занимаюсь новым интерфейсом для своих систем. Разработал простую и интересную программу для работы с энкодером. Мне она понравилась, и захотелось поделиться удачной программой.
Просматривая аналогичные решения по обработке сигналов от инкрементального энкодера, я увидел три способа решения задачи:
1) с помощью условных переходов и статических переменных, хранящих предыдущие состояния энкодера;
2) с помощью машины состояний, которая переключается по сигналам энкодера, исключая запрещённые состояния.
3) вариация номера два с использованием switch - case с метками возможных состояний.
Однако, все варианты имеют недостатки.
Вариант номер один использует несколько переменных и переключается один раз за четыре возможных состояния энкодера.
Вариант номер два  использует одну переменную, в которой хранятся в битовом представлении текущее и предыдущее состояние энкодера. Анализ состояний производится с помощью массива данных. Это вариант мне понравился больше всего. Но в том состоянии алгоритм не защищён от ложных срабатываний и пропуска данных.
Вариант номер три использует такое же представление данных, как и вариант номер два, но вместо массива данных использует переход по множеству case меток. Самый неудобный и трудный для восприятия.
Взяв второй метод за основу, я доработал его до приемлемого вида.
Вначале основы.
При повороте энкодера по часовой стрелке состояние выводов изменяются как
00, 01, 11, 10, 00...
А при повороте против часовой стрелки состояние выводов изменяется в другой последовательности:
 00, 10, 11, 01, 00...
Считывать состояния энкодера можно один, два или четыре раза за период, но необходимо учитывать предыдущее состояние. Например, если текущее состояние энкодера 11 и предыдущее значение 01, то энкодер повернули по часовой стрелке. А если предыдущее значение 10, то энкодер повернули против часовой стрелки. Соответственно, предыдущие состояния 00 и 11 будут ошибочными, и их нужно игнорировать.
Итак, мы имеем 4 варианта текущего состояния и 4 варианта предыдущего состояния энкодера. Вместе они дают 16 комбинаций. Если взять переменную из четырёх бит, и в младшие два бита записать текущее состояние, а в старшие два - предыдущее состояние, и использовать получившееся число как индекс в массиве, то с помощью значений чисел в массиве возможно точно определить состояние энкодера. Например, 1 - поворот вправо, -1 - поворот влево, 0 - запрещённая комбинация.
Что самое интересное, это то, что такой алгоритм можно применять как для одного изменения энкодера за период, так и для двух и четырёх изменений. А это важно, потому что встречаются энкодеры с разным количеством периодов за оборот. Например, энкодеру с 12 периодами однозначно понадобится 2 или 4 изменения за период, а энкодеру с 48 периодами не больше 1.
Возможность подстройки под тип энкодера это первое преимущество. Второе - я добавил защиту от повторных срабатываний при одинаковом положении энкодера. Например, встретилось два подряд значения 01. Без фильтрации это вызовет запрещённое состояние 01-01 и ошибку в определении следующего состояния. Почему - ответ в третьем преимуществе. Программа анализирует три последовательные состояния энкодера, и при переходе, например, 00-01-00 (то есть шаг туда, шаг сюда) не будет двух ложных срабатываний энкодера. Без фильтрации мы можем получить последовательность состояний 00-01-01-00 и сравнение текущего и третьего состояний не решит проблему дрожания энкодера. Такая проверка нужна для 1-го и 2-х срабатываний за период энкодера.
Программу я писал для микропроцессора STM8S в среде IAR. Текст приведён ниже:

// определение количества событий за период энкодера (1, 2, 4)
#define    ENC_COUNT        2   
// массив правильных значений переходов
// между предыдущим и настоящим значением энкодера
#if (ENC_COUNT == 1)
// для 1-го переключения за период
int8_t enc_array [16] =
{
     0,  0,  0,  0,
    -1,  0,  0,  0,
     1,  0,  0,  0,
     0,  0,  0,  0
};
#elif (ENC_COUNT == 2)
// для 2-х переключений за период
int8_t enc_array [16] =
{
     0,  0,  0,  0,
    -1,  0,  0,  1,
     1,  0,  0, -1,
     0,  0,  0,  0
};
#else           
// для 4-х переключений за период
int8_t enc_array [16] =
{
     0,  1, -1,  0,
    -1,  0,  0,  1,
     1,  0,  0, -1,
     0, -1,  1,  0
};
#endif

uint8_t      state            = 0;    // сохранённые состояния энкодера
int8_t        count_encoder    = 0;    // переменная приращения энкодера

/*****************************************************************************
* Настройка энкодера.
* Ножки на вход с подтяжкой к питанию
* Прерывания на этот порт с активацией по фронту и спаду
* Приоритет высокий
*****************************************************************************/
void encoder_init (void)
{
    // настройка контроллера прерываний
    __disable_interrupt();
    EXTI->CR1 &= ~EXTI_CR1_PAIS_MASK;
    EXTI->CR1 |= 0x03;    //EXTI_CR1_PAIS_FR;        // Прерывания А по фронту и спаду
    // настройка приоритетов прерываний
    ITC->ISPR2 &= ~ITC_SPR2_PORTA_MSK;
    ITC->ISPR2 |= ITC_SPR2_PORTA_LVL3;            // высокий уровень прерываний порта A
    // настройка ножек
    GPIO_ConfigInput (ENC_A_PORT1, ENC_A_PIN1, PinPullup, PinIrqOn);
    GPIO_ConfigInput (ENC_B_PORT1, ENC_B_PIN1, PinPullup, PinIrqOn);
    // установка текущего состояния энкодера
    if (ENC_A_PIN)
        state |= 0x01;
    if (ENC_B_PIN)
        state |= 0x02;
    __enable_interrupt();
}

/*****************************************************************************
* Прерывание по порту A
* здесь опрашивается состояние энкодера
* результат прерывания сохраняется в переменной count_encoder
*****************************************************************************/
INTERRUPT_HANDLER (Port_A_EXTISR, 3)
{
    int8_t    stt;
    state <<= 2;                                    // освободили место для новых значений
    if (ENC_A_PIN)
        state |= 0x01;
    if (ENC_B_PIN)
        state |= 0x02;                                // считали данные энкодера
    if ((state & 0x03) != ((state >> 2) & 0x03))    // если разные значения с предыдущим
    {
        stt = enc_array [state & 0x0F];                // получили условие из таблицы
        if (stt)                                    // при изменении энкодера
        {
#if (ENC_COUNT == 1 || ENC_COUNT == 2)       
            if ((state & 0x03) != ((state >> 4) & 0x03))    // и если энкодер не вернулся обратно   
#endif
            {
                count_encoder += stt;                    // изменим счётчик энкодера
            }
        }
    }
    else
    {
        state >>= 2;                                // если одинаковые значения - вернуть state
    }
}


/*****************************************************************************
* функция int_8t encoder() возвращает изменение положения
* энкодера после последнего чтения, если энкодер не двигался, то 0
******************************************************************************/
int8_t encoder (void)
{
    int8_t status = count_encoder;
    count_encoder = 0;
    return status;
}

Форматирование убежало, но надеюсь, смысл ясен.

P.S. Написал больше для себя, чтобы не забыть, уж очень хорошо код работает. Для себя добавил пропорциональное управление энкодером. В зависимости от скорости вращения переменная count_encoder изменяется или на единицу (медленное вращение), или на десяток (вращение со средней скоростью), или на сотню (быстрое вращение). Это позволило быстро вводить энкодером числа от 1 до 1000. Согласитесь, крутить 50 оборотов, чтобы выставить 1000 - занятие неблагодарное :)

Комментариев нет:

Отправить комментарий