Занимаюсь новым интерфейсом для своих систем. Разработал простую и интересную программу для работы с энкодером. Мне она понравилась, и захотелось поделиться удачной программой.
Просматривая аналогичные решения по обработке сигналов от инкрементального энкодера, я увидел три способа решения задачи:
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 - занятие неблагодарное :)
Просматривая аналогичные решения по обработке сигналов от инкрементального энкодера, я увидел три способа решения задачи:
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 - занятие неблагодарное :)
Комментариев нет:
Отправить комментарий