Как стать автором
Обновить

Комментарии 94

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

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

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

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

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

No comments. ?

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

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

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

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

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

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

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

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

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

Поддерживаю. Это ж трындец просто. У меня нанка обрабатывает 2 инкрементальника с 2500 имп/об и скоростью вращения до 600 об/мин с выводом на самодельноую УЦИшку через две ТМ1638. И, вот странно-то, ничего не "забивается".

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

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

15 кГц сигнал.

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

В курсе, а как же. Просто в данном случае человек взял Атмел не потому, что он любит/умеет писать под ядро, а потому, что ему показалось, что так будет проще. Распространенная ошибка.

С появлением голубой, а потом и черной, пилюли - ИМХО, вообще пропала нужда в ардуинах. Ну, как исключение, если есть много шилдов и требуется постоянно что-то прототипировать. На 8-битных Атмелах (по убеждениям) остались олдскульщики, которые начинали не то что на PICах, а на ДТЛ, и которые плюются при слове "HAL". Не, ну я тоже за "программировать под ядро, а не под библиотеку", но увы, реалии жизни таковы, что на STM32G4 реализовать, скажем, векторное управления электродвигателем настолько проще, что даже сравнивать нельзя.

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

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

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

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

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

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

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

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

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

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

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

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

В коде внутри прерывания два вызова 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-мегагерцовые АВР-ки, они много чего умеют, если их правильно приготовить.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Да ладно. Асинхронным интерфейсам пофиг. А вот за delay в loop-e надо к стулу приклеивать))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Да и как то цена не очень даже за такое, но наверное даже можно сделать PWM fan регулятор на полусофтварном ADC из компаратора?

Есть еще PIC10F322, там даже прерывания есть. )

Впрочем, сабжевая задача реализуется на любом из них.

Мой интерес ещё с этой статьи: «Ужасные» трёхцентовые MCU – краткий обзор микроконтроллеров стоимостью менее $0,1
Но я любитель и мне не в устройство 1млн шт, а для удовольствия. А ассемблер у меня оное не вызывает:) А в Си к сожаление не удалось даже для at90s1200. У него The stack is a 3-level-deep hardware stack dedicated for subroutines and interrupts. Хотел в качестве опыта как раз.

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

Я начинал с ассемблера, при чем в качестве "hellow world!" вместо моргания светодиодом у меня была связка PIC18+RTL8019 и самописный стек TCP/IP. Больше 20 лет прошло, а я до сих пор наизусть помню MPASM, заголовки TCP/IP и умею считать CRC в столбик. )

Моё почтение :)
ps начинал с z80, но даже до уровня использования индексных регистров не дошёл.

Под пички на сях нормально пишется.

скорее всего не сильно старался, но как то вот так:

avr-gcc -mmcu=at90s1200 ....
error: architecture 'avr1' supported for assembler only

где-то вроде видел упоминание на форумах, что без стека - ничего не получится

Во времена Z80 я в школе учился. Пытался что-то программировать, но до чего-то полезного не хватило усидчивости и доступности информации. Были идеи на базе плат АОНа делать всякие контроллеры.

где-то вроде видел упоминание на форумах, что без стека - ничего не получится

Наверное как-то можно эмулировать с ограничениями, но смысла нет.

PIC10 и выше всё есть в т.ч. стек. В MPLAB-X и компилятор и конфигуратор (что тоже удобно особенно для старших контроллеров) скачаются после выбора контроллера. В каком-нибудь platformio тоже наверняка есть поддержка, но я не пользовался. Мне пички как раз нравятся неплохой и условно бесплатной поддержкой вендора.

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

Были идеи на базе плат АОНа делать всякие контроллеры.

Да, с неё и начинали тоже, а кодили на ZX. А переносили на РУ10 с кондёром :) Но подробности не помню. Недавно нашёл в коробке полный комплект микросхем и Си есть на Z80, но практического смысла не нашлось да и как легко и изящно перенести код тоже не понял, а второй МК рядом ради загрузки - не интересно:)

Недавно думал, "не закодить ли tiny15 на Си"? Даже нашёл статью умельца, который добился этого. Но, по прочтении, решил "для разовой поделки — быстрее на АСМе".
Часов шестнадцать (чистого времени) на отладку взаимодействия с железом, часов 40 на "бизнес логику" неспешно. И коррекция баг-репорта через полгода — замена пары констант и пара часов для корректировки "бизнес логики".

Немного писал всё же, немного автоматики если - не страшно, но потом у меня появилась первая 16 битная переменная и вселенская грусть....)
Часы тут попросили починить, те что микре тактируемой от сети... древнющее! Но диоды - большие и красивые. Ну думаю - точно на asm буквально пару строк сейчас, делитель же только настроить, тем более вроде даже кварц подходящий был, но....
Зачем то захотелось сделать подстройку точности хода, да не по константе в день, в час, а в зависимости от значения - максимально на сколько возможно растащить на все сутки и...
А потом ещё и ШИМ на индикаторы в зависимости от засветки и соотв полупрограммный ADC от (на борту МК был компаратор), в общем - надо было сразу выкинуть всё и поставить нормальный МК)

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

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

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

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

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

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

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

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

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

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")" для синхронизации в внешним АЦП. После ловли фрейма, переливаем на анализацию.

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

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

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

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

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

не принципиально кмк
void delay(unsigned long ms)
{
        uint32_t start = micros();

        while (ms > 0) {
                yield();
                while ( ms > 0 && (micros() - start) >= 1000) {
                        ms--;
                        start += 1000;
                }
        }
}

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

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

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

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

Тот случай, когда оптимизация кода важна и становится заметным громоздкий ардуиновский код. Я бы лучше stm32 взял, там в таймерах есть функция энкодера, ничего выдумывать не надо по коду, все на аппаратном уровне делается. Да и вообще, может механический энкодер из статьи и может что-то измерить, но я их лично воспринимаю как крутилки для ввода всяких величин. Я бы использовал для высоких оборотов магнитные энкодеры наподобие as5040. Уже всё готово в одной микросхеме, остаётся только считывать по SPI. Хорошее разрешение и до 10000 оборотов в минуту может мерить.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории