При разработке электронных устройств порой надо подключить инкрементный энкодер. Инкрементные энкодеры окружат нас повсюду. Это могут быть устройства с регулированием громкости звука в автомобиле, яркости свечения, регулировка климат контроля, датчик оборотов одометра, регулировка напряжения в блоках питания, регулировка развертки на осциллографах, энкодер для ввода пароля на сейфе и прочее. У меня на столе сейчас 6 энкодеров.

В этом тексте я показал как может работать программный компонент драйвера для опроса инкрементного энкодера.

инкрементный энкодер - это датчик угла поворота. Он показывает не только угол поворота, но и его направление: по часовой стрелке CW или против часовой стрелки ССW. Сигнал энкодера представляет собой два PWM, которые смещены по фазе. Взаимное смещение является направлением вращения. Таким образом можно утверждать, что квадратурный энкодер является не просто датчиком скорости и пройденного пути, но и самым настоящим фазовым детектором!

Постановка задачи
Разработать программный компонент для считывания показаний с инкрементного энкодера. Датчик подключен к GPIO пинам микроконтроллера PE0 и PE1. Написать на Си программную реализацию обработки сигнала с инкрементного энкодера.

Реализация

Опрос инкрементного энкодера это классический пример того как задача решается при помощи конечного автомата (КА). Как известно любой конечный автомат возводится за семь шагов: перечислить входы, перечислить выходы, перечислить состояния, нарисовать граф, заполнить таблицу переходов, заполнить таблицу выходов, написать код. Но обо всём попорядку.

Перечисление входов:
1) на пине A появился положительный перепад с 0V в 3.3V
2) на пине B появился положительный перепад с 0V в 3.3V
3) на пине A появился отрицательный перепад с 3.3V в 0V
4) на пине B появился отрицательный перепад с 3.3V в 0V

Перечисление состояний
Состояние энкодера целиком и полностью определяется значениями напряжения на GPIO в каждый конкретный момент времени. Есть всего 4 состояния.

A

B

Пояснение

0

0

0

A-0V ;B-0V

1

0

1

A-0V ; B-3.3V

3

1

1

A-3.3V; B-3.3V

2

1

0

A-3.3V; B-0V

Определить выходы автомата
1) Увеличить счётчки на 1
2) Уменьшить счётчик на 1
3) Сигнализировать об ошибке

Как вариант, в драйвер энкодера можно добивать отдельную счетчик переменную, которая обновляется синхронно, но имеет ограничители как сверху, так и снизу. Плюс имеет размерный множитель.

Нарисовать граф переходов конечного автомата

Особенность конечного автомата энкодера в том, что он работает в кодах Грея. То есть каждые соседние состояния отличаются только на 1 бит. При кручении вала против часовой стрелки состояния будут изменятся вот по такому маршруту: 0b00 --> 0b01 --> 0b11 --> 0b10.

Составим таблицу переходов

На основе графа переходов можно составить таблицу переходов.

Составление таблицы выходов.

На основе графа переходов можно составить таблицу выходов.

Программная часть

Исходный код драйвера энкодера размещен тут. Драйвер написан согласно методичке. Архитектура Хорошо Поддерживаемого Программного Компонента.

Для достижения максимальной производительности надо работать с внешними прерываниями. Основная идея драйвера в том, чтобы в прерываниях по GPIO определить исходное состояние датчика и считать входной сигнал. Далее поместить эти метаданные в очередь и выйти из прерывания по GPIO.

Далее в супер цикле драйвера извлекать из очереди события отдельных перепадов напряжения и подавать их на вход конечного автомата, который описан выше. Таким образом, я получил удобный для отладки лог последовательности сигналов и могу отлаживать весь механизм обыкновенным логированием в uart.

Отладка

Что ж, прошивка написана. Теперь надо проверить работу драйвера на каком-нибудь настоящем энкодере. Вот у меня есть энкодер американской компании Bourns. Модель ECW1J-C28-BC0024L.

У него 24 стопора. Каждый щелчок увеличивает счетчик драйвера на 4 значения. Получается датчик может выдать 24*4=96 отсчетов на один оборот своей трещетки.

Пин датчика

GPIO

Подтяжка

Ext Int Event

Channel A

PE1

Up

Both

Channel B

PE0

Up

Both

Тестировочный прототип выглядит вот так.

В тестировочной прошивке заложен UART1 shell и можно пронаблюдать за тем, как работает энкодер. Вот я сделал один щелчок по часовой стрелке и один щелчок против часовой стрелки. Как и ожидалось, энкодер вернулся в ноль. То что появляются ошибки - это дело рук дребезга контактов. Однако наш продуманный конечный автомат энкодера способен в значительной мере игнорировать дребезг контактов и обрабатывать только корректные события. В этом и заключается ключевое достоинство конечно автоматной методики разработки системного ПО.

Приложения драйвера энкодера

1) Можно сделать фазовый детектор для SDR обработки сигналов. Если сигналы симфазные, то энкодер будет показывать ноль. Если A опережает по фазе B , то будет нарастать счетчик, если A отстает от B, то счетчик будет убывать.
2) Регулировать громкость проигрывателей, яркость светильников.
3) Оснащать обратной связью электрические моторы и сервоприводы.
4) Одометры, датчики скорости оборотов или измерители поступательного движения.

Итоги

Как видите, теория конечных автоматов отлично пригождается в повседневной работе программиста микроконтроллеров. Удалось составить драйвер инкрементного энкодера на основе FSM. Драйвер позволяет измерять количество оборотов вала и регистрировать направление вращения.

Если есть предложения по улучшению механизма обработки сигнала с энкодера, то пишите в комментариях.

Также пишите примеры, где вы в эпоху сенсорных экранов по-прежнему встречаете квадратурные энкодеры.

Ссылки

Название

URL

Код драйвера энкодера

https://github.com/aabzel/trunk/tree/main/source/sensitivity/incremental_encoder

Поляризатор = датчик угла

https://habr.com/ru/articles/742358/

Подключение энкодера к Ардуино и полнофункциональный код обработки для него

https://habr.com/ru/articles/586576/

Архитектура Хорошо Поддерживаемого Программного Компонента

https://habr.com/ru/articles/683762/

Инкрементальный энкодер: подключение и обработка его с помощью AVR (ATmega8/16/32/168/328)

https://habr.com/ru/articles/729382/

Энкодер модель ECW1J-C28-BC0024L

https://www.chipdip.ru/product/ecw1j-c28-bc0024l-bourns-8065411857

Словарь

Сокращение

Расшифровка

CW

clockwise

FSM

Finite State Machine

КА

Конечный автомат

CCW

counter clockwise

Вопросы:
1) Как в драйвере инкрементного энкодера определять текущее состояние энкодера: считывать из GPIO или вычислять на основе потока событий?

2) Как определить, что два PWM сигнала симфазные?

Комментарии (26)


  1. Lev3250
    08.06.2026 20:23

    Я прям как в универ попал снова. Спасибо за статью!


    1. aabzel Автор
      08.06.2026 20:23

      Пожалуйста. Надеюсь это поможет создать полезные устройства с энкодером.


  1. iliasam
    08.06.2026 20:23

    Посмотрел исходники.
    20 файлов для для библиотеки работы с энкодером - не многовато ли?
    При этом, честно сказать, сам конечный автомат я с ходу найти и не смог...


    1. aabzel Автор
      08.06.2026 20:23

      Драйвер написан согласно вот этой методичке.

      Архитектура Хорошо Поддерживаемого Программного Компонента
      https://habr.com/ru/articles/683762/

      Если коротко, то много файлов нужны для выборочной компиляции в зависимости от располагаемых в MCU ресурсами.

      Ядро драйвера в файле incremental_encoder_mcal.c


    1. aabzel Автор
      08.06.2026 20:23

      20 файлов для для библиотеки работы с энкодером - не многовато ли?

      Если проводить аналогию из медицины, то это как центрифугирование крови. Кровь делится на плазму, эритроциты , тромбоциты, красные кровяные тельца и прочее. Так и программный компонент тоже делится на свои фракции. Это ядро, константы, типы данных, диагностика, параметры для NVRAM, файл скрипта сборки, зависимости, конфиги, CLI и тесты. Вот как-то так.


  1. kovserg
    08.06.2026 20:23

    Хз, я на pic16 обошолся всего 2мя функциями и графиком в 2D пространстве S0*S1:

    /*
    	rotation encoder
    
    	10<-11
    	V    ^
    	00->01
    
    	00 01 11 10 ->  10110100 0xB4
    
     input:  s=[ any:4   cur:2 prev:2 ]
     output:   [ trash:4 res:2 cur:2  ]
    
    	     00 01 10 11 cur
    	00   00 01 10 11    11100100 0xE4
    	01   10 00 11 01    01110010 0x72
    	10   01 11 00 10    10001101 0x8D
    	11   11 10 01 00    00011011 0x1B
    	prev
    */
    enum { renc_inc=4, renc_dec=8 };
    char renc_pos(char pos) { return (0xB4>>((pos&3)<<1))&3; }
    char renc_upd(char s) { static const char t[4]={ 0xE4,0x72,0x8D,0x1B };
    	return ((t[s&3]>>((s>>1)&6))<<2)|((s>>2)&3);
    }
    /* 4bits state usage:
    	char pos=0, enc=(S1<<1) | S0;
    	...
    	enc=renc_upd( (S1<<3) | (S0<<2) | (enc&3) );
    	if (enc & renc_inc) pos++;
    	if (enc & renc_dec) pos--;
    	code=renc_pos(pos);
    */
    

    renc_upd просто обновляет состояние enc, у которого можно узнать увеличиваем положение или уменьшаем. если оба одновременно 1 то недопустимое значение и pos не изменяется. а renc_pos позволяет по положению генерировать S0 и S1 (это если надо эмулировать энкодер)


    1. kipar
      08.06.2026 20:23

      Я и график не понимаю зачем нужен, одной строчкой считал:

      int32_t delta = pin_a_changed ^ pin_a ^ pin_b ? -1 : 1;
      


      1. rukhi7
        08.06.2026 20:23

        об этом ни статью, не методичку не напишешь! Оказывается задача решается с помощью элементарной арифметики полей Галуа.


    1. pvvv
      08.06.2026 20:23

      с отсутсвием в убогих МК barrel shifterов вот это вот 0xB4>>x будет ужОс во что скомпилировано(циклы с побитовым сдвигом), проще 16байт флэша на полную таблицу потратить.

      int delta(bool a, bool b){
        static bool pa=0, pb=0;
        bool pos = a^pb, neg = b^pa, moved = pos^neg;
        pa=a; pb=b;
        return (moved&pos) - (moved&neg);
      }


    1. aabzel Автор
      08.06.2026 20:23

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


  1. VelocidadAbsurda
    08.06.2026 20:23

    Недавно натыкался на специализированный МК для управления мотором - Nuvoton NM1520, так у него целый отдельный периферийный модуль интерфейса экнодера оказался, фильтрует дребезг, определяет направление вращения (и его смену), отсчитывает время от сигнала нулевого положения итд, красота!


    1. aabzel Автор
      08.06.2026 20:23

      В stm32 тоже некоторые аппаратные таймеры обладают режимом энкодера.

      Но что бы будете делать если надо к одному МК подключить 30 энкодеров?


  1. Coder007
    08.06.2026 20:23

    Статья из серии как из простого сделать сложное. То, что вы описали, как способы применения энкодера (в данном случае фазного) :

    1) Можно сделать фазовый детектор для SDR обработки сигналов. Если сигналы симфазные, то энкодер будет показывать ноль. Если A опережает по фазе B , то будет нарастать счетчик, если A отстает от B, то счетчик будет убывать.

    А в чем смысл данного детектора? Что вы собрались здесь инкрементировать? Обычно вычисляется разность фаз, а не наличие того, какая фаза раньше или позже. Не понимаю целесообразность применения. Если это на СВЧ, то алгоритм конечного автомата будет работать только в теории, на практике совсем другой подход. И вряд ли он сможет показать ноль, если сигналы симфазные. У него задача определить, какая фаза была раньше. И любой программный код имеет последовательность считывания сигнала с пинов. Прерывание с одного пина в любом случае прочитается или раньше или позже, в зависимости от того, что сработало первым. Микроконтроллер - последовательная система обработки данныз. Отличие может быть в наносекундах, но оно будет. Или же вы сами будете выставлять тайминги, в которые сигналы будут считаться синфазными.

    2) Регулировать громкость проигрывателей, яркость светильников.

    Тут вообще все примитивно и обрабатывается банальным алгоритмом. Даже прерывания можно не трогать. И с кодом тоже нет смысла заморачиваться и соответственно тратить ресурсы микроконтроллера на сложную обработку простого решения. Даже пропуск одного такта не сыграет роли.

    3) Оснащать обратной связью электрические моторы и сервоприводы.

    Здесь посложнее и все зависит от того, что именно вы хотите обработать. Если сервопривод, то да, нужен очень качественный энкодер, а не то, что вы привели в пример. Я бы сказал не просто качественный, а совсем другого принципа абсолютный энкодер (оптический, магнитный, емкостный) с цифровым интерфейсом и знающий свое положение на момент включения (абсолютную позицию, угол, сдвиг и так далее) .

    Если это электродвигатель, то там либо оптический, либо магнитный, либо трансформаторный (аналоговый). И ни в случае с сервоприводом ни с электродвигателем, простой фазный энкодер типа А/В ставиться не будет! И работать он там не сможет! Ну и стоит учесть, что при больших оборотах двигателя, изменение направления вращения быстрым не будет. Да, отчасти все эти энкодеры будут работать как фазовые, но на таких частотах, что длительность имнульсов будет измеряться в наносекундах. При этом магнитный энкодер эмулирует фазовый сдвиг, а оптический имеет собственную систему обработки данных. И все они передают информацию в цифровых интерфейсах.

    4) Одометры, датчики скорости оборотов или измерители поступательного движения.

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

    По сути, фазный энкодер - это вчерашний день. Да он имеет место быть и применяется, но обладает рядом недостатков, поэтому его применение ограничено простыми и не особо ответственными системами. Выбор пункта меню, регулирование громкости и прочие моменты.

    А вообще, для разных задач необходимо подбирать датчики персонально, и прежде чем считать, что математикой можно решить ряд задач для "всего", как вы считаете в этой статье, то спешу разочаровать вас. Так не работает в реальной жизни и вам стоит разобраться в теме более серьёзно.

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

    Автору удачи и успехов!

    Конечные автоматы - дело хорошее, но необходимо рассмотреть их применение более тщательно. Мы это проходили в конце 90х и тогда это было актуально и проектировали мы это на бинарной логике.


    1. aabzel Автор
      08.06.2026 20:23

      Конечные автоматы - дело хорошее, но необходимо рассмотреть их применение более тщательно. 

      Конечный автомат золотой шаблон проектирования системного ПО.
      Выручал много раз при решении прикладных задач.

      Декодирование BPSK Модуляции из Звука (или передача данных по воздуху) https://habr.com/ru/articles/848068/

      Задача про две ёмкости для жидкости
      https://habr.com/ru/articles/662561/

      Запуск I2S Трансивера на Artery [часть 2] (DMA, FSM, PipeLine) https://habr.com/ru/articles/834304/

      Квантование на Триггерах Шмитта
      https://habr.com/ru/articles/1003262/

      Конечный Aвтомат Аппаратного I2C-Трансивера
      https://habr.com/ru/articles/856548/

      Обзор Протокола ISO-TP [ISO 15765-2]
      https://habr.com/ru/articles/798489/

      ПасТильда: ещё одна прошивка
      https://habr.com/ru/articles/706470/

      Передача и прием данных по лазерному лучу (SDR декодирование BPSK в реальном времени)
      https://habr.com/ru/articles/1023062/

      Принцип Определения Дальности Между UWB Трансиверами (Конечный Автомат Для DS-TWR)
      https://habr.com/ru/articles/723822/

      Протокол TBFP
      https://habr.com/ru/articles/969948/

      Пуск Беспроводной CLI на Микроконтроллере
      https://habr.com/ru/articles/929086/

      Синтаксический разбор CSV строчек
      https://habr.com/ru/articles/765066/

      Теория управления шаговым двигателем (или как вертеть PTZ камеру) https://habr.com/ru/articles/709500/

      Техникум: Конечный Aвтомат Обработки Сигнала с Кнопки https://habr.com/ru/articles/760088/

      Техникум: Распознавание Вещественного Числа из Строчки https://habr.com/ru/articles/757122/

      Cross-Detect для Проверки Качества Пайки в Электронных Цепях https://habr.com/ru/articles/762142/

      H-мост: Load Detect (или как выявлять вандализм)
      https://habr.com/ru/articles/709374/

      Load-Detect для Проверки Качества Пайки
      https://habr.com/ru/articles/756572/


  1. Arhammon
    08.06.2026 20:23

    При разработке электронных устройств порой надо подключить инкрементный энкодер.

    Может все таки НЕ надо - энкодеры помоему худшее что могло произойти с железными интерфейсами...

    Приходиться... еще можно понять и простить старые полулюбительский вещи только с 1 энкодером...


    1. aabzel Автор
      08.06.2026 20:23

       - энкодеры помоему худшее что могло произойти с железными интерфейсами

      Почему тогда осциллографы и блоки питания все еще настраиваются энкодерами?


  1. mozg37
    08.06.2026 20:23

    В любом mcu есть куча таймеров, и несколько из них аппаратно поддерживают энкодеры


    1. aabzel Автор
      08.06.2026 20:23

      В любом mcu есть куча таймеров, и несколько из них аппаратно поддерживают энкодеры

      А как быть если нужен не просто энкодер, а энкодер с ограничением?
      Чтобы упершись в максимум можно было крутить только назад CCW.
      И напротив, упершись в минимум, можно было крутить только вперед CW.
      Вернее крутить можно всегда, а софт должен программно накладывать пределы.
      Ведь это основа любого HMI.


  1. AndreyDmitriev
    08.06.2026 20:23

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


    1. aabzel Автор
      08.06.2026 20:23

      Настоящее "искусство" начинается там, где надо устанавливать энкодером довольно большой интервал значений, 

      Для этого энкодеры HMI делают с кнопкой, чтобы нажатием переключать десятичный разряд, который надо устанавливать.

      По крайней мере так сделано на лабораторных блоках питания (AC-DC).


  1. JIexa21
    08.06.2026 20:23

    Стопитсоттысячная реализация... Зачем?


    1. aabzel Автор
      08.06.2026 20:23

      Сделано по iso26262


      1. rukhi7
        08.06.2026 20:23

        и по методичке. Мне кажется самое интересное вы прячете в методичке!


        1. aabzel Автор
          08.06.2026 20:23

          Вот она
          ISO 26262-6 разбор документа (или как писать безопасный софт)

          https://habr.com/ru/articles/757216/


          1. rukhi7
            08.06.2026 20:23

            Интересно:

            В целом в стандарте ISO-26262 не всё понятно, но полезные советы для себя обнаружить можно.

            как же вы определяете соответствие тому, что пока не полностью понятно?


            1. aabzel Автор
              08.06.2026 20:23

              Что понятно, то и делаю. Что не понятно - пропускаю.