Pull to refresh

Comments 56

Честно скажу ещё не прочитал до конца. Но слишком много текста для чтения за один присест. Либо разбить на несколько частей, ну или хотя бы картинками украсить.
Написано же в начале, «Дню знаний посвящается» — первоклашкам сегодня тоже непросто :)
Так и писал я этот пост долго, конечно, с перерывами, но недели две. А насчет картинок — во первых, не умею, а во вторых, я инженер старой закалки и позволяю себе только схемы и временные диаграммы, може, последние тут и были бы не лишние, но сильно понимание не облегчили бы, а просто для привлечения внимения неохота их ставить.
Простите великодушно за подколку, но для кого писалась эта статья, для инженеров старой закалки?
Ах добрые старые времена, когда мы соревновались в написании библиотек BCD арифметики на 580ВМ80 и хвастались друг перед другом уменьшением размера кода на 3 байта и увеличением скорости работы на 4 такта. В этом была своя романтика, которая, видимо, уже никогда не будет доступна современным инженерам, конечно, без обид.
а) Вы не в твиттере
б) тема затрагивается больная, о ней нельзя коротко: уход от обфускации языка C в Arduino IDE к нормальному человеческому ассемблеру, пусть и не самым близким путём
Не-не-не. Как раз самое оно.
Взята строго одна тема и полностью рассмотрена одним блоком, а не размазана на несколько кусков.
Автору огромное спасибо, прочитал одним махом.
Ардуино (далее по тексту А, имейте в виду что под этой буквой будет прятаться как сам кристалл, так и среда разработки программ)

Что за «кристалл», простите?
По тексту понятно, что имеется ввиду AVR8.

Да и посыл автора верный, но скучный. Зачем пытаться понять укуренные мысли разработчиков библиотек Arduino, когда можно просто её не использовать? Достаточно чистого Си и тонн кода в сети для тысяч периферийных устройств, чтобы не изобретать велосипедов. Либо забить на всё это и писать скетчами.

Архитектура очень проста, и не требует массы библиотек для старта, как, например, с ARM.
Мысли разработчиков библиотек Arduino понять как раз несложно: библиотека универсальна для некоторого количества типов «кристаллов», в том числе и ARM. И оптимизация в ней далеко не приоритет. Но это не даёт повода мешать все «кристаллы», которые устанавливаются на Ардуины, в одну кучу. Интересней было бы почитать разбор libavrc, например. Хотя, думаю, там пищи для разоблачений поменьше было бы, но я могу ошибаться.
И оптимизация в ней далеко не приоритет
Собственно, я об этом. Если не устраивает такой подход, зачем «переделывать мир», когда можно просто пойти по другому пути?
Вы имели в виду AVR Libс? Надо бы посмотреть, но не могу найти официальный код, ссылкой не поделитесь?
http://www.nongnu.org/avr-libc/

Исходники можно найти в глубинах Arduino IDE: hardware\tools\avr
Или в комплекте с авэровским тулчейном.

А, нет, вру, там одни хидеры и статические библиотеки. Значит, остаётся первая ссылка.
Спасибо, скачал, но почему ее на самом сайте Atmel нет, хотя документация на сайте лежит — старанно как то, или они на ASF переключились?
Документация лежит, потому что их тулчейн был построен вокруг gcc и libavr.
Про ASF пока ничего не знаю. Но раз уж он уже доступен, заодно и в его сторону посмотреть. Но я не уверен на счёт доступности его исходников.
Конечно же семейство AVR MEGA, Вы абсолютно правы, надо было уточнить.
Ардуино (далее по тексту А

Скажите, зачем это сокращение? Лично мне читать совершенно неудобно.
А я думал так прикольно будет, но вообще то это от лени.
Там же C++, можно наспециализировать шаблонов, да ещё и предложить компилятору делать инлайн — вот вам и будет выбор адреса и команды во время компиляции.
Я это и сделал, только через дефайн. Просто через шаблона лучше не получалось, я пробовал, но смысл становился менее понятен, все таки отнести шаблоны к интуитивно понятным частям С++ я не могу.
В той же ардуинке «Интел эдисон» данный подход просто невозможен и выигрыша не даст.
Но… даже 34 такта на переключение порта это дикость, даже с проверками в то время как сам контроллер выполняет это за пару тактов.
Там кстати с портами засада полнейшая, если мы берём простой AVR-контроллер вроде ATMEGA8 то проблем нет — там все наличные порты укладываются в 5 бит и адресуются командами IN/OUT но берём контроллер с более широкой периферией и у него адресное пространство регистров значительно шире, и до некоторых портов можно дотянутся только командами LDS/STS и к несчастью регистры большинства портов ввода-вывода находятся именно там.
И мне изначально непонятно зачем нужен динамический доступ к пинам? Обычно в скетче жестко указана функция пина и дальше не меняется т.е. они по факту используются как константы. А если нужен будет динамический доступ, то организовать специальную обёртку, которая будет медленной и неповоротливой как ныне существующая.
Похоже, что это писал x86 программист с учётом того что порты работают изначально медленно и все эти проверки выполнятся быстрее чем будет осуществлён непосредственно акт ввода-вывода из-за медленной шины. Это частично справедливо и для STM32 контроллеров — у них тоже изначально периферия работает значительно медленнее чем ядро выполняет инструкции, и элементарная операция ввода-вывода(даже на ассемблере) запросто может затянуться на 20...50 тактов ядра на ровном месте(одна инструкция).
Мне кажется что подход в корне неверен. Почему-то ни в статье, ни в комментариях я не увидел главной аббревиатуры — GPIO. Процессорное ядро не управляет напрямую пинами. Оно обращается к контроллеру GPIO, который как раз отвественнен за ногодрыжество. Из этого следует несколько интересных выводов:
1. У контроллера GPIO есть своё быстродействие, которое может быть ниже быстродействия процессора. Просто потому что переключать силовые транзисторы долго. То что процессор записал единичку в какой-то регистр — ещё не значит что эта единичка тут же появится на пине/пэде.
2. GPIO — это general purpose input/output. А значит он нужен для неспешного подергивания пинами. Если возникает задача управлять пинами с частотой даже в сотню килогерц — значит при проектировании что-то пошло сильно не так.

Поэтому проблема быстрого управления GPIO должна рассматриваться как чисто теоретическое упражнение.
В принципе Вы правы, но дьявол кроется в деталях. Периферию А чипов нельзя назвать очень богатой и иногда приходится через GPIO реализовывать стандартные интерфейсы типа I2S или SPI. А когда речь идет, например о последовательных шинах типа WS2811, то применение стандартного интерфейса хоть и возможно, но требует особого внимания в реализации, и пины здесь — весьма удобное подспорье.
У меня есть пост о SPI как универсальном интерфейсе, но их не так много на чипе.
контроллер GPIO? Наверно не стоит небольшой набор секвенциальной синхронной логики называть контроллером. Мне кажется, что в таком стиле можно и таймерный модуль назвать контроллером, хотя по сути там не больше десятка регистров и мультиплексоров. Но это вопрос конечно же терминологии и конкретной реализации.

По статье имею следующий комментарий:
Попытки программно управлять ногами контроллера с максимальной скоростью очевидно имеют предел, значительно меньший чем позволяют заточенные под это периферийные устройства. Тот же модуль выходного сравнения с привязкой на DMA очевидно даст точность, не хуже периода тактирования таймера. На входную последовательность в том же таймере есть модуль захвата. Обвязываем его с DMA и получаем высокую производительность.
Использование стандартных библиотек в контроллере для высокоскоростного программного управления ногами этой микросхемы, на мой взгляд, разработчиками этих библиотек и не предполагалось. А зачем, ведь есть аппаратная периферия, которая все это сделает, а CPU при этом может, например, кофе попить.
Ну на AVRках его может и тяжело назвать контроллером. Но на всяких ARM-based микроконтроллерах (и тем более SoCах) он бывает довольно продвинутым. Правда, всё равно, подорзеваю что там большую часть логики занимает сопряжение с шиной (включая реализацию регистрового банка).

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

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

ваши бы слова, да богу в уши. Cypress FX3, при конфигурировании их интерфейса GPIF II в 32 бит режим начисто отпадает блок SPI. А он нужен. Пришлось эмуляцию делать на GPIO. И пришлось мириться, что обновление прошивки происходит меееееееедленно. А она всего ~2.5 Мб (FX3 + FPGA). На последней борде получилось сменить FPGA и изменить схемотехнику, теперь FPGA сам умеет вычитывать свой код из SPI — теперь хотя бы старт платы происходит очень быстро. Но прошивка всё такая же неспешная.

Так я же и говорю — при проектировании что-то пошло сильно не так. На этот самый FX3 должна существовать errata которая описывает вашу ситуацию (и возможные решеиня). Если вы при этом всё равно выбрали этот чип — значит это ваше осознанное решение и значит вы готовы выжимать всё возможное из GPIO.
Но это — исключение из правил. Обычно всё-таки стараются выбрать такой чип, в котором работает вся ключавая периферия в нужных режимах.

А вы найдите ещё адекватные реализации Device USB 3.0 (собственно это и есть его основное предназначение). Errata нет, на форуме советуют bit-bang, что собственно и сделано.

Конечно же, последнее решение со специфическими командами — это реализация исключительно для AVR, я об этом сразу сказал.
А вот первый мой вариант с условной компиляцией будет жить где угодно, хотя на Edissone даст очень немного выигрыша (но все равно даст).
Все безопасно в отключении прерываний: между чтением sreg и вызовом cli вполне может произойти прерывание, но прерывания должны оставлять sreg неизменным перед возвратом, хоть этим и должен озаботиться программист.
А вот с этого момента поподробнее — почему это программист должен оставлять бит неизменным, конечно, управление конкретным прерыванием через глобальное нельзя отнести к хорошему стилю, но ведь это и не запрещено. Если бы в примерах от Atmel это было ясно прописано, то еще как бы сошло, но ведь такого нигде нет. По крайней мере, мне не встречались подобные ограничения, если Вы где то в примерах такое ограничение видели явно прописанным, бросьте ссылку.
Вы о каком бите, простите? Я про весь регистр sreg целиком говорю, иначе просто ничего работать не будет.
В том то и дело, что в AVR программа обработки прерываний должна сохранять на стек sreg сама и сама его восстанавливать при выходе. Но в процессе обработки она может модифицировать бит глобального разрешения и она имеет право это делать. Она также может восстановить sreg со стека, запретить прерывания (не влияя на флаги, кроме IE) и возвратить управление командой ret, чтобы не выставить его снова. И если она это сделает, то тем не менее не сможет прекратить обработку прерываний, поскольку мы восстановим сохраненный нами sreg с взведенным IE.
Для выхода из прерывания используется reti. Иначе вы выйдете из прерывания с sreg отличным от того, каким он был до прерывания.
RETI просто взводит флаг IE при выходе из прерывания (sei + ret), так как при входе он сбрасывается. Собственно, SREG будет отличаться только этим флагом, и когда мы сохраняем его при входе в прерывание — он уже изменён. Это к комменту выше:
И если она это сделает, то тем не менее не сможет прекратить обработку прерываний, поскольку мы восстановим сохраненный нами sreg с взведенным IE.
Еще раз утверждаю, и документация на микросхему со мной согласна, что sreg надо сохранять и восстанавлисать ручками.
Благо, если писать на Си, всё это сделает компилятор. Если не использовать всяких финтов типа вложенных прерываний.
Посмотрите на код, который породил компилятор, я его привел во вкладках, и, Бога ради, покажите мне строки, порожденные компилятором для сохранения и восстановления sreg. Вы хотите спорить о вкусе бананов с человеком, который их ест в значительных количествах?
Либо плохо смотрел, либо не увидел ни одного обработчика прерываний. Приведите, пожалуйста, цитату по тексту.
В ваших примерах защищаются только атомарные операции.
Вы хотите спорить о вкусе бананов с человеком, который их ест в значительных количествах?
Поверьте, вы не единственный на этом свете пробовали бананы :)
Ну да, согласен, с прерываниями погорячился, их тут нет. Значит, действительно многие бананы употребляют.
О чем тогда спор то? Оставляете sreg таким каким он был до прерывания и проблем нет. Содержимое восстанавливаете вручную, а I флаг восстановит инcтрукция reti. Начинаете выходить из прерываний инструкцией ret — получите проблемы.
Вижу, до Вас не вполне дошел смысл вопроса. Речь идет о том, что подпрограмма обработки прерывания может принять решение об запрете прерываний вообще и принять соответствующие меры для его выключения, в том числе гасить бит и выходить инструкцией ret. Но вся эта работа пропадет, посокльку Ваша программа восстановит старое значение sreg с включенным битом.
А, простите, я не хотел ввязываться в спор как решить проблемы которые будут если выходить из прерываний инструкцией ret. Внутри прерывания может быть сколько угодно инструкций ret/cli/sei, это допустимо, окончанием обработки прерывания является команда reti и она устанавливает флаг I в sreg, таким образом восстанавливает контекст, который был до прерывания, это распространяется и на вложенные прерывания: раз мы попали в прерывание — значит они были включены, не важно где мы были до этого, в контексте приложения или в контексте прерывания.
Пока вы не вызвали reti вы все ещё в прерывании.
Ваша программа восстановит старое значение sreg с включенным битом.

Вот этот момент хотелбы обсудить:
1) Вход в прервыние — флаг I сброршен аппаратно.
2) Сохранение SREG (флаг I сброршен)
3) Тело прерывания
4) Восстановление SREG (флаг I сброршен)

То есть, если выходить через RET, мы погасим прерывания. Такой финт тоже можно применить, если прерывание должно отработать разово, но я считаю это дурным тоном. Разбирающийся в таком коде должен проследить логику.
Автор странный. Было влом пройтись по тексту автозаменой и поменять «А» на «ардуино»? Что за мода на сокращения в стиле совковых НИИ?
Далее. «Не читал, но осуждаю». Автор пользуется перепечаткой исходников на левом сайте. А в исходники самой ардуино заглянуть почему не захотелось? Причём, даже на сайте написано, где исходник лежит.
The digitalWrite() is defined in hardware/arduino/cores/arduino/wiring_digital.c as below.

И там (в исходнике), к слову, написано, почему отказались от инлайновых функций в пользу свича:
// Forcing this inline keeps the callers from having to push their own stuff
// on the stack. It is a good performance win and only takes 1 more byte per
// user than calling. (It will take more bytes on the 168.)
//
// But shouldn't this be moved into pinMode? Seems silly to check and do on
// each digitalread or write.
//
// Mark Sproul:
// — Removed inline. Save 170 bytes on atmega1280
// — changed to a switch statment; added 32 bytes but much easier to read and maintain.
// — Added more #ifdefs, now compiles for atmega645
//
//static inline void turnOffPWM(uint8_t timer) __attribute__ ((always_inline));
//static inline void turnOffPWM(uint8_t timer)


Дальше. То, что ардуино тормозная и неэффективная по использованию памяти — достаточно широко известно. Но, ардуинохейтеры, она не для того, чтобы бить рекорды по быстродействию. Это конструктор, и относиться к нему надо так же. Позволяет по-быстрому прикинуть конструкцию, что-то затестить. Не надо писать на ардуинке систему управления ядерным реактором. Так же как не надо систему управления ядерным реактором доверять писать пьяному студенту-первокурснику. Под каждую задачу должно быть своё средство.
Я так толком и не понял, за ассемблерным жонглированием, была ли предложена альтернативная функция DigitalWrite, работающая абсолютно с любым ардуино-совместимым железом (включая ESP8266, всякие Edison-ы), и при этом меньше жрущая памяти и процессорного времени?
P.S. Спасибо хоть, без указателей обошлось.
А вообще, ИМХО, проблема надумана. Кому важно получить выполнение функции не за 70 тактов, а за 25 — ну погуглите, как это сделать.
А ещё можно каждый пин выставлять отдельно, тратя по 70 таков на это. А можно порт писать…
Автор такой, какой есть. Да, Вы правы, именно в стиле советских НИИ, и я Вас уверяю, там были и высококлассные специалисты, так что прошу Вас поосторожнее с эпитетами. Стиль сокращений произошел в то время в том числе и потому, что функции автозамены на пишущих машинках не было.

Далее по комментарию — там такой же исходный текст? Если да (а так оно и есть) то в чем пафос замечания? То есть я разбирал действительно исходный текст, взятый с другого ресурса и это что то изменило?

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

Я ни в коем случае не отношусь к ненавистикам А, и решения, предложенные в моем посте, направлены на улучшение быстродействия именно этой платформы. И если Вы можете улучшить работы своих скетчей в плане быстродействия, то я не понимаю, почему это не сделать.

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

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

И, простите великодушно, рекомендация писать порт — это как раз из разряда юнных гуру, о которых я упоминал в самом начале поста, со всеми недостатками, которые я пречислил.

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

Раздел «I/O Ports» в Atmel документации на 8 битные процы короче и понятнее. Да и другие спеки на микроконтроллеры лаконичны, полноценны и понятны.

Ардуино — зло. Когда описание библиотек превосходит по объему описание контроллера…
А — не зло, а инструмент, злом может быть только бездумное его использование.

И я не очень понял, что именно Вы хотите — уменьшить описание библиотек (категорически не согласен с таким подходом) или увеличить описание МК (берите STM, они реализовали Вашу мечту на 300+ страницах).
А — не зло, а инструмент, злом может быть только бездумное его использование.

Когда описание инструмента (обертка вокруг фактических вызовов) и время затраченное на его изучение начинает превышать время затраченное на изучение исходной документации… Это становится странным.
Да еще использование этой обертки не дает использовать многие фичи контроллеров!
Аа… впрочем, если только дергать ножками..


Как то пытался найти реализацию для работы в ультразвуковым датчиком HC-SR04 на STM32Fx. Лениво самому было писать.
И везде дурной "ардуиновский" (уже можно слово сделать нарицательным) подход. Цикл по опросу и дерганье ножек с задержками, реализованными циклам. В лучшем случае таймер с опросом регистра CNT в цикл для формирования задержки.


Пришлось по быстрому свою реализацию делать "по честному" с нормальным использованием возможностей таймера.


берите STM, они реализовали Вашу мечту на 300+ страницах

Кстати, весьма понятная и приятная документация. Больше возможностей контроллера — больше объем.
И глава "8 General-purpose and alternate-function I/Os (GPIOs and AFIOs)" занимает всего 35 страниц где 60% это картинки и таблицы.


Ну не бывает "универсальной" библиотеки оболочки на все контроллеры. Даже в пределах одной линейки полно тонких фич, которые нужно учитывать


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

Объём кода одинаков, но объём ньюансов, которые необходимо учесть на конкретной платформе — не сопоставим. Например те же обновлённые меги — с индексами P и PA сильно отличаются в ньюансах некоторых мелочей, старые не умели к примеру одной командой аппаратно проинвертировать пин.
Привет, Паша :). Надеюсь, после перечитывания всех моих постов станете не только паять.
я все прочел уже давно, потому могу еще перепаивать)))
Как в анекдоте?
— Что можешь?
— Могу паять!
— А ещё что?
— Могу не паять!
:)
Only those users with full accounts are able to leave comments. Log in, please.