Небольшой очерк как решить простую практическую задачу по обработке показаний с инкрементарного энкодера (E6B2 -CWZ1X) на arduino. Данная задача возникла в связи с необходимостью точного измерения пройденного расстояния в помещении. Энкодер соединен с колесом достаточно большого диаметра через редуктор. Размеры колеса, редуктора для целей задачи пока не имеют значение. Первично — считывать показания энкодера на достаточно больших оборотах.

Шаг первый. Uno


За основу был взят «золотой стандарт»: arduino uno и код, скорость работы которого, не подвергалась скептическому анализу:

код для arduino
/*
 Максимально быстрый универсальный код для обработки энкодера
 Работает на перывании (используется одно)
 Тут код построен на bitRead(PIND..) - только для Arduino NANO!
*/
#define ENC_A 2       // пин энкодера
#define ENC_B 4       // пин энкодера
#define ENC_TYPE 1    // тип энкодера, 0 или 1
volatile int encCounter;
volatile boolean state0, lastState, turnFlag;
void setup() {
  Serial.begin(9600);
  attachInterrupt(0, int0, CHANGE);
}
void int0() {
  state0 = bitRead(PIND, ENC_A);
  if (state0 != lastState) {
#if (ENC_TYPE == 1)
    turnFlag = !turnFlag;
    if (turnFlag)
      encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1;
#else
    encCounter += (bitRead(PIND, ENC_B) != lastState) ? -1 : 1;
#endif
    lastState = state0;
  }
}
void loop() {
  Serial.println(encCounter);
  delay(100);
}


*ссылка на оригинал кода и статью.

Код работал без нареканий, однако после крепления энкодера на вал (через редуктор), выяснилось следующее. При движении, энкодер шлет слишком большой поток показаний (ticks) и вывод быстро ими забивается и виснет. Это связано, как выяснилось, не только с самой моделью энкодера, который выдавал 1000 ticks на оборот, но и с микроконтроллером arduino.
Были предприняты попытки выводить не все шаги энкодера, а каждый 10 или каждый 100 шаг, заменить arduino uno на nano, увеличить скорость serial portа до максимума, использовать иные варианты кода для arduino. Однако проблему это не решило, и arduino все так же умирал на высоких оборотах энкодера.

Встал вопрос: брать энкодер с меньшим количеством шагов (минимальный 100 против текущих 1000) у того же производителя либо заменить arduino на что-то еще. Пошли по второму пути, поглядывая на первый.

Шаг второй. Чем заменить Uno


Выбор пал на достаточно доступную в продаже Nodemcu v.3 на esp8266, у которой и достаточное количество пинов и частота (тактовая частота: 80 – 160 МГц против 16 МГц arduino). Однако найти внятный, быстрый код под плату не удалось, а колхозить не было времени и желания. Кроме того, плата оказалась с дефектом и не работала через micro-usb.

Очень интересной показалась Wemos ESP32, на которой еще и уютно расположился micro-display, но на шаге вытянутой руки ее не было, и тут на глаза попалась raspberry pico. Но, с pico тоже оказалось не все так гладко в части скорости работы с прерываниями — "
*фото из видео.

Поэтому решили временно в ее сторону не смотреть.

Самый мелкий arduino


В итоге, как всегда, остановились на том, что было «под рукой» — на Seeeduino-XIAO, который по размерам чем-то напоминает digispark, но выгодно отличается по характеристикам (до 48 МГц против 16 МГц).



Подробно о том как с ним работать можно почитать на странице разработчика.

Так как данный микроконтроллер можно условно отнести к семейству arduino, предыдущий код на нем заработал с небольшими косметическими правками.

Код для xiao
#define ENC_A 0       // пин энкодера
#define ENC_B 1       // пин энкодера
volatile int encCounter;
volatile boolean flag, resetFlag;
volatile byte curState, prevState;
void setup() {
  Serial1.begin(115200);
  while (!Serial);
  attachInterrupt(0, int0, CHANGE);
  attachInterrupt(1, int0, CHANGE);
}
void int0() {
  encTick();
}

// алгоритм со сбросом от Ярослава Куруса
void encTick() {
  curState = digitalRead(ENC_A) | digitalRead(ENC_B) << 1;  // digitalRead хорошо бы заменить чем-нибудь более быстрым
  if (resetFlag && curState == 0b11) {
    if (prevState == 0b10) encCounter++;
    if (prevState == 0b01) encCounter--;
    resetFlag = 0;
    flag = true;
  }
  if (curState == 0b00) resetFlag = 1;
  prevState = curState;
}
void loop() {  
  if (flag) {
    Serial1.println(encCounter);
    flag = 0;
  }
}


Вместо serial — serial1, пины 0,1. Не все digital пины можно использовать, как оказалось: 4-й, 5й и 7й одновременно. Сам seral port «висит» на 6,7 пинах. Однако, этого достаточно, так как для общения с энкодером нужно 2 пина.

Еще один момент который необходимо иметь в виду, это то, что xiao использует 3,3 V логику и подключать напрямую к нему энкодер небезопасно. Поэтому использовался логический согласователь уровней 5V-3,3V.

Общая схема сопряжения выглядит так:



При работе с xiao также есть небольшие особенности. Контроллер имеет type-C вход и на это сразу «покупаешься», когда видишь. Однако, этот вход только для питания и прошивки. Как serial port его использовать нельзя, а жаль.

Также для перезагрузки xiao нет никакой внешней кнопки (в виду размеров самого контроллера видимо) и чтобы его перезагрузить необходимо замкнуть два контакта на лицевой стороне либо на оборотной стороне (об этом написано на странице разработчика). В остальном работа с ним такая же как и с другими представителями семейства arduino.

Итог


В результате замены arduino uno на собрата меньшего размера с лучшими характеристиками проблема была решена. Задержек при выводе в serial и подвисаний не выявлено:

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


  1. count_enable
    06.02.2022 19:12
    +28

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

    Прерывания? Таймеры в режиме счётчика? Забудьте. Настоящий разработчик должен знать две вещи: где скачать подходящий скетч, и где купить более мощную плату, если старой не хватает.


    1. nochkin
      06.02.2022 19:28
      +7

      Ага, а потом на более мощной плате запускать MicroPython.


    1. zoldaten Автор
      06.02.2022 20:05
      -8

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


      1. forthuser
        06.02.2022 20:13
        +5

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

        No comments. ????


      1. unsignedchar
        06.02.2022 21:03
        +3

        Вы заткнули баг в коде мегагерцами. Тоже решение. Но хвастаться тут нечем. Ладно бы на этапе выбора железа посчитали и решили что для этой задачи нужен более другой контроллер. А так - это метод тыка.


        1. RTFM13
          06.02.2022 21:20
          +4

          Там кроме мегагерцев есть еще нюанс. Передавать на 8 битном м/контроллере 16 битные данные из прерывания тоже надо уметь. С таким кодом он иногда должен проскакивать 256 тиков. Решение в лоб (не очень хорошее) - запрещать прерывания при чтении данных в основном цикле.

          Может в этом всё и дело. По описанию автора сложно что-то понять.


        1. Mike_soft
          07.02.2022 09:08
          +2

          баг не в коде...
          Подходит начинающий программист к опытному программисту и показывает листинг неработающей программы.
          — Подскажи, пожалуйста, в чем у меня ошибка?
          — В ДНК, — вздыхает опытный…
          ©


      1. Elmot
        06.02.2022 21:15
        +6

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


      1. apkotelnikov
        07.02.2022 02:24
        +1

        А вам я порекомендую заглянуть вот сюда

        Тынц

        *отредактировал, добавлена ссылка.


      1. scruff
        07.02.2022 09:50
        -5

        Не обращайте внимания на критику. Таких "знатоков" на хабре - +100500 и каждый мнит себя великим гуру. Вы сделали всё правильно - в условиях недостаточности времени,теоретических знаний и другиз ресурсов придумали рабочее решенее. Главное работает, а как - уже вторично. Сейчас всё в ИТ меняется и в конечном итоге выиграет тот, кто сделает (не делает, не проделывает, а именно сделает) работу быстро, недорого и с четко измеряемым результатом.


        1. zoldaten Автор
          07.02.2022 10:01
          -2

          Да, когда нет времени и нужного мк под рукой, приходится дырки тряпками затыкать.
          На Хабре приятно получать тумаки, но, когда эти тумаки с решениями или конструктивной критикой.
          А комментарии типа, «ты только вчера взял отвертку в руки и полез в розетку» это популизм, меня они не задевают.


        1. AndreyUA
          07.02.2022 12:28
          +2

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


    1. black_list_man
      07.02.2022 01:31

      Автор же как раз и использует прерывания по импульсу в обоих скетчах.


  1. REPISOT
    06.02.2022 19:18
    +2

    Ох уж эти ардуинские обозначения. Я тут голову ломаю, почему порт A и D на одних и тех же ногах… А это, оказывается «аналоговые» и «цифровые».


  1. iliasam
    06.02.2022 19:53

    «arduino все так же умирал на высоких оборотах энкодера»
    Я так и не понял из статьи, а какая вообще была скорость вращения энкодера.
    Сколько оборотов в секунду?


    1. zoldaten Автор
      06.02.2022 20:03

      13-15 оборотов*1000 ticks на оборот


      1. count_enable
        06.02.2022 20:34
        +3

        15 кГц сигнал.

        Я очень давно не программировал AVR, но насколько я помню, минимальная длительность прерывания 4 цикла. Пусть будет даже 100 циклов на прерывание. При 16 МГц можно спокойно работать с сигналом на 160 кГц.

        10 раз это и есть минимальная цена "гугления" готового решения.


        1. zoldaten Автор
          06.02.2022 20:54
          +1

          Вы правы, слово "умирал" скорее сильно преувеличенный эпитет. arduino не совсем умирает, он начинает пропускать шаги, иногда целыми секциями.


          1. unsignedchar
            07.02.2022 11:31

            начинает пропускать шаги


            Ну так достаточно очевидное решение — упростить код внутри прерывания. Вместо 2 вызовов функции — 1 чтение из регистра. Можно и на ассемблере этот код переписать, вместо 5 строчек будет 25.
            С чтением 2-байтной переменной из ISR — неочевидно, да. Но и тут есть как минимум 2 способа обхода проблемы ;)


            1. sterr
              08.02.2022 00:32

              Насколько я помню - у ардуины программный serial. Я как то раз так попал. Отладку в сериал пихал. И ничего не работало, тайминги дикие. Как только отключил - все сразу заработало. Может быть автор не в курсе? Просто при аппаратном сериале с буфером таких задержек скорее всего не будет. Вначале. До переполнения буфера при 9600.

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


              1. unsignedchar
                08.02.2022 08:47
                +1

                у ардуины с этим очень большие проблемы.

                Внутри Arduino IDE самый обычный gcc. В чем проблема?


                1. RTFM13
                  08.02.2022 15:15

                  В чем проблема?

                  В библиотеках?

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


                  1. unsignedchar
                    08.02.2022 15:44
                    +1

                    Любая высокоуровневая абстракция вредная в каком то смысле. Она же скрывает нюансы ;)
                    Ну скрывает — не вижу проблемы. Нужно просто понимать, где этими нюансами можно пренебречь, а где нельзя.


                    1. RTFM13
                      08.02.2022 16:09
                      +1

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

                      Но в целом - да, любая (почти) абстракция может быть вредна т.к. универсальные решения как правило проигрывают по техническим параметрам.

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

                      Ну вот статья про задачу простую как три копейки, а уже куча нюансов всплыла. Проще только мограние светодиодом. Элемнтарная задача с треском провалена.


                      1. unsignedchar
                        08.02.2022 16:56

                        про задачу простую как три копейки


                        Ну, чтобы сразу сообразить про неатомарность некоторых операций — это нужно либо знать заранее, либо писать на ассемблере.


  1. slog2
    06.02.2022 20:20
    +7

    Освоил ардуину и накачал скетчей и я уже инженер-разработчик. Господа инженеры, вы в курсе что кроме разных ардуин существуют другие микроконтроллеры? И даже есть такие, у которых таймера умеют считать импульсы с квадратурных энкодеров. И их теперь полно всяких разных и за копейки. Выбирай на вкус


    1. BigDflz
      06.02.2022 20:33

      Выбирай на вкус

      вот автор и выбрал, вы ведь не знаете всех условий которые стояли перед автором.


  1. iliasam
    06.02.2022 20:40
    +2

    По моим прикидкам, тут должна была и ATMEGA справиться.
    В коде автора использовано прерывание по одной из линий. Если не ошибаюсь, при скорости 15 об/сек, энкодер автора должен давать частоту импульсов 7.5 кГц (по одной линии), что дает частоту прерываний — 15 кГц.
    При частоте контроллера 16 МГц, получаем, что на прерывание приходится около 1000 тактов контроллера. Странно, что контроллер не успевает.


    1. zoldaten Автор
      06.02.2022 20:46
      -1

      attachInterrupt(0, int0, CHANGE);
      attachInterrupt(1, int0, CHANGE);

      по двум линиям ? A,B


      1. RTFM13
        06.02.2022 21:04
        +1

        Да всё равно, обработчик прерывания более чем на пол сотни ассемблерных комманд не получается. При желании, примерно в 20-25 тактов можно уложиться. Вы смотрели во что ваш код скомпилился?


      1. iliasam
        06.02.2022 21:13
        +1

        По вашей ссылке: alexgyver.ru/encoder есть вариант с двумя прерываниями: «Ещё хороший вариант на апп. прерываниях», но там не используется bitRead.

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


    1. REPISOT
      06.02.2022 21:27
      +1

      По моим прикидкам, тут должна была и ATMEGA справиться.
      Тоже так думаю. Прям руки тянутся достать из ящика mega8 и посмотреть, до каких частот будет хватать скорости…


      1. REPISOT
        07.02.2022 10:36

        Проверил в эмуляторе. 15 кГц считает.


  1. RTFM13
    06.02.2022 20:47
    +8

    Добавил в закладки.

    Когда меня опять кто-нибудь спросит почему я стараюсь держаться подальше от ардуино, у меня уже будет ответ. Хотя в данном случае, скорее всего, если выкинуть println то всё заработает.

    Для сравнения возможности 8-битного МК:

    http://wiki.pic24.ru/doku.php/osa/articles/vga_game


  1. AntonSor
    06.02.2022 21:04
    +1

    Та же STM32F103C8T6, которая в Blue Pill, имеет таймеры, к которым может подключаться энкодер для "железного" счета импульсов. Без ограничений программного счета ардуино


    1. zoldaten Автор
      06.02.2022 21:10
      -1

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


      1. Elmot
        06.02.2022 21:17
        +4

        Китайский Блю пилл стоит 2 доллара. Так что Дуня и есп - это дорогие решения


  1. vitsam
    06.02.2022 21:18
    +3

    В коде внутри прерывания два вызова digitalRead(), эти функции много чего за собой тащат ненужного. Тут статья неплохая, пишут, что прямая работа с регистрами может дать от 26 до 72-кратный прирост скорости:

    "It turns out that 100,000 runs of digitalRead() takes 489.7ms on a 16MIPS Arduino Uno. That turns out to be 4.9us per digitalRead() or 78 ticks (or instructions) per digitalRead()."
    ...
    "So the Arduino routines are anywhere from 26x – 72x slower than what you can achieve through direct access to the registers."

    Мне кажется, рано списывать в утиль 16-мегагерцовые АВР-ки, они много чего умеют, если их правильно приготовить.


    1. zoldaten Автор
      06.02.2022 21:47
      -1

      да, но автор статьи не замеряет вывод. я так полагаю, именно он съедает все преимущества.


  1. RTFM13
    06.02.2022 21:51
    +5

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

    Если вы передаете данные из прерываний в основной цикл, тогда вместо

    void loop() {
      Serial.println(encCounter);
      delay(100);
    }

    должно быть как-то так:

    void loop() {
    	noInterrupts();
      encCounterTMP = encCounter;
    	Interrupts();
      Serial.println(encCounterTMP);
      delay(100);
    }

    актуально для данных размером более 1 байта для 8битных контроллеров. Между запретом и разрешением прерываний желательно должна быть максимально короткая операция, по этому используется еще одна переменная.


    1. zoldaten Автор
      07.02.2022 09:32

      разве с паузой в цикле все поедет быстрее? или это «остатки кода»?


      1. RTFM13
        07.02.2022 14:24
        +1

        Со скоростью это никак не связано.

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

        Моя идея в кратковременном запрете прерываний на время считывания переменной которая обновляется в прерывании. Т.к. переменная имеет ширину более 8 бит, то на данном контроллере ее считывание происходит в несколько комманд. И прерывание может вклиниться между ними. В итоге мы получим 1 байт от одного значения, а второй от другого. Это актуально когда происходит переполнение младшего байта. Например старое значение было 255, мы считали младший байт 255, затем значение обновилось до 256, мы считали старший байт 1 и получили в итоге 1*256+255=511. Либо наоборот, в зависимости от порядка выполнения операций. Ситуация конечно редкая, но ее проявление вопрос времени.

        Для решения проблем со скоростью вам надо дизассемблировать участок обработки прерывания и найти там проблему если она есть (или сразу сделать на регистрах как вам уже советовали) и отказаться от println.


        1. enjoyneering
          07.02.2022 15:04

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

          Я не настоящий сварщик, но читал, что вроде как для этот используют atomic переменные. Или я не прав?


          1. unsignedchar
            07.02.2022 15:16
            +1

            Все команды пересылки данных в AVR — однобайтные. Кроме одной ;)


          1. RTFM13
            07.02.2022 15:23
            +1

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

            Моя идея была напомнить о нюансе и предложить схематично и наглядно решение. Ни на оптимальность ни на полноту освещения вопроса я не претендую.


    1. zoldaten Автор
      09.02.2022 15:04

      void loop() {
        noInterrupts();
        encCounterTMP = encCounter;
        interrupts(); // именно с маленькой
        Serial.println(encCounterTMP);
        delay(100);
      }

      выводит следующее:

      encCounterTMP объявлен как -
      int encCounterTMP;


      1. RTFM13
        09.02.2022 15:33
        +2

        Это хорошо или плохо?

        Если недостаточна частота то надо отказываться от println.

        Если есть пропуски, то разбираться с прерыванием.

        1. Заменить на функцию преобразующую int->decimal->ascii. или, еще лучше, int->hex->ascii (сли это приемлемо). Далее отправлять это всё в юарт на как можно более низком уровне. ЮАРТ на такой скорости может выдавать до 160 строк в секунду, но с оговорками. По крайней мере 100 - достижимый результат.

        1а. Выводить в юарт не абсолютное значение счетчика, а только приращение, не в ASCII а в бинарном виде. Ограничиться 1 байтом (int8_t). Тогда вообще не нужны никакие преобразования. Частоту, конечно, поднять до необходимой чтобы не было переполнения.

        2. Посмотреть, наконец, дизассемблированный код прошивки на предмет работы в прерывании.

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


  1. WFF
    06.02.2022 22:20
    -1

    Допустим, что микроконтроллер не в состоянии переварить падающую на него частоту. Ну поставьте на вход десятичный счетчик-делитель, он 50 рублей стоит...


    1. unsignedchar
      06.02.2022 22:56

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


  1. Jacksonn
    07.02.2022 07:59

    Кстати, по поводу согласования уровней с 5V до 3.3V. Насколько я помню, большинство подобных энкодеров имеют выходы NPN с открытым коллектором с фазы A и B. Соответственно, просто ставим подтягивающий резистор между выходом с фазы энкодера и VCC микроконтроллера (те же 3.3V), а при тике энкодера коллектор будет замыкаться на землю. Соотвественно, согласование уровней не нужно, напряжение на пине МК будет зависеть от того, куда подключен подтягивающий резистор на выходах энкодера.


    1. zoldaten Автор
      07.02.2022 09:31

      выход Line driver (RS-422)


  1. premierhr
    07.02.2022 08:47
    +1

    Помимо вышеописанных камней рискну предложить PICи с процессоронезависимой периферией. Что-то типа 16F1615 легко справится с вашими потребностями.


    1. RTFM13
      07.02.2022 14:38
      +1

      Для данной задачи pic12 - за глаза.


      1. premierhr
        07.02.2022 16:27

        Не спорю, на 1615 еще экран I2C можно посадить или BT модуль, аппаратный PID на двигатель завернуть, логи писать на флеш, и еще половина ресурсов останется)


        1. RTFM13
          07.02.2022 17:09

          на pic12 это всё тоже можно. при желании я бы уложился в pic10 в корпусе sot23-6.


          1. vitsam
            07.02.2022 18:22

            Интереса ради глянул на Чипе и Дипе цену (за один корпус). atmega328p: 1090 рублей, pic12: 140 рублей.


            1. RTFM13
              07.02.2022 18:53

              Даже можно так:

              https://aliexpress.com/item/1005003286980988.html

              10 шт примерно как одна китайская ардуина.

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


              1. vitsam
                07.02.2022 18:58

                Надо присмотреться к этим чипам.


              1. vitsam
                08.02.2022 16:43

                А не подскажете, из опыта, чем лучше готовить pic10 (тулчейн, прошивалка)?


                1. RTFM13
                  08.02.2022 19:41

                  Я использую для подавляющего числа пичков MPLAB/MPLAB X и PICKIT 3, так исторически сложилось. Еще валяется, кажется, ICD2 но давно им не пользуюсь.


                  1. vitsam
                    08.02.2022 20:37

                    понятно, спасибо!


              1. vitsam
                08.02.2022 18:02

                stm8 по цене 180 рублей за штуку, но помощнее pic12 будет


                1. RTFM13
                  08.02.2022 20:01

                  Там самый маленький по количеству ног корпус - SO8, на сколько я в курсе. Но аппаратный UART/SPI и т.п. многое упрощает конечно.

                  Еще есть ATtiny но там все еще грустнее чем у пичка.


  1. Okmor
    07.02.2022 09:26

    Я так и не понял с какой максимальной частотой поступает сигнал.

    Мне удавалось снимать показания АЦП с частотой 4 000 000 мГц на Atmega32, для карманного осциллографа.

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

    1. Надо забыть про всякие Readln и тому подобное. Только Регистры.

    2. Никаких Println. Только хардкор и прямая работа с портом.

    3. Вообще забыть про всякие библиотеки.

    Судя по описанию E6B2 -CWZ1X , максимальное разрешение 100 кгц. - это 160 тиков arduino. Вполне можно уложится.


    1. vitsam
      07.02.2022 10:27

      Мне тоже кажется, что atmega328p вполне уложится в задачу. Но там в приведенном коде два digitalRead() подряд в прерывании, которые только сами по себе займут почти 150 тиков.


    1. VT100
      07.02.2022 22:48
      +3

      4 миллиона миллигерц, т.е. — 4 кГц. Так?


      1. Okmor
        08.02.2022 11:59

        MyBuff_ADC[ 0 ] = PIND ; asm("nop");
        MyBuff_ADC[ 1 ] = PIND ; asm("nop");
        MyBuff_ADC[ 2 ] = PIND ; asm("nop");
        MyBuff_ADC[ 3 ] = PIND ; asm("nop");
        MyBuff_ADC[ 4 ] = PIND ; asm("nop");
        MyBuff_ADC[ 5 ] = PIND ; asm("nop");
        MyBuff_ADC[ 6 ] = PIND ; asm("nop");
        MyBuff_ADC[ 7 ] = PIND ; asm("nop");
        MyBuff_ADC[ 8 ] = PIND ; asm("nop");
        MyBuff_ADC[ 9 ] = PIND ; asm("nop");

        Вставка "asm("nop")" для синхронизации в внешним АЦП. После ловли фрейма, переливаем на анализацию.


        1. unsignedchar
          08.02.2022 12:59
          +1

          Можно пояснительную бригаду?


          1. Okmor
            08.02.2022 13:24

            http://arduino.ru/forum/proekty/virtos-samyi-prostoi-dvukhluchevoi-ostsillograf-ostsillograf-na-arduino

            Захват сигнала на arduino с частотой 4 МЕГАГЕРЦА!!!!!


      1. Okmor
        08.02.2022 13:27
        -1

        4 миллиона миллигерц, т.е. — 4 кГц. Так?

        4 МЕГАГЕРЦА !!!!


  1. customtema
    07.02.2022 11:30

    C какой точностью нужно производить измерения?

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

    Снимаем среднее напряжение с конденсатора - получаем оценку скорости вращения вала. Можно калибровать.

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

    С "обычной Arduino" задача решается.


    1. iliasam
      07.02.2022 13:50

      Насколько я понимаю, задача автора - не просто измерять частоту, а точно подсчитывать и число импульсов, и направление.


  1. customtema
    07.02.2022 11:43

    https://github.com/ArminJo/Arduino-FrequencyDetector через преобразование Фурье тоже можно.


  1. xStSx
    07.02.2022 11:46

    А не проще ли и быстрей использовать для таких целей PIO в rp2040(pico) ?


  1. Polaris99
    07.02.2022 12:08

    О сколько нам открытий чудных готовит Ардуино дух! Пусть оно как-то там работает под капотом и компилируется в хрен знает что, зачем нам разбираться в тонкостях, если есть более мощные ардуины и еще куча разных библиотек на гитхабе??


  1. unbrokendub
    08.02.2022 09:22

    Что-то мне подсказывает, что вся проблема в функции delay(). Попробуйте заменить её на такую конструкцию:

    void mdelayMicroseconds(uint32_t us) {
    uint32_t tmr = micros();
    while (micros() - tmr < us);
    }


    1. unsignedchar
      08.02.2022 18:11

      не принципиально кмк
      void delay(unsigned long ms)
      {
              uint32_t start = micros();
      
              while (ms > 0) {
                      yield();
                      while ( ms > 0 && (micros() - start) >= 1000) {
                              ms--;
                              start += 1000;
                      }
              }
      }
      


    1. zoldaten Автор
      09.02.2022 15:20

      void loop() {
        Serial.println(encCounter);
        mdelayMicroseconds(100);
      }

      немного получше, но все равно пропуски + переполнение ?:


      1. RTFM13
        09.02.2022 16:43

        А при чем тут переполнение? Переполнение определяется разрядностью переменной. Увеличите - будет еще медленней.


  1. chyvack
    08.02.2022 17:21
    +1

    Когда-то давно, нужно было отслеживать показания на похожем энкодере для станка. Проводил тест atmega8 на прерываниях (fw для контроллера на mikropascal), частота я думаю была 14.ххМГц. В качестве источника сигнала для тестов использовал генератор сложной формы на работе. По моей памяти, максимальная частота сработки была около 100кГц.


  1. chyvack
    09.02.2022 13:35

    вот нашел ссылку: прерывание на С - 200кГц, на ассемблерной вставке - 600кГц (attiny13a 9.6MHz)

    http://www.count-zero.ru/2018/gnu_assembler/