Pull to refresh

Comments 22

По поводу инструментария, согласен и поддерживаю. Спасибо за статью.
«Многие» конторы сейчас хотят максимально упростить программирование микроконтроллеров, поэтому ставят туда RTOS, Arduino, C++. Что бы легко переносить проект между разными контроллерами пишут кучу классов. После чего, что бы разабратся в простой программе нужно знание PCAD, AUTOCAD, Visual Studio,… умение паять,… права категории C D,… играть на баяне…
Если представленные примеры действительно взяты из книги автора поста, то я категорически НЕ рекомендую ее к прочтению, независимо от моего отношения к ассемблеру. Просто потому, что я вижу примеры плохого стиля программирования, независимо от конкретного языка.
Критикуя — предлагай. Может на книгах этого автора выучилось половина импортозамещенных АВР-щиков? Другой русскоязычной литературы раньше особо и не было.
Единственное, за что надо бить по пальцам — это делэи на лупах при времени задержки на порядки (десятичные) превышающие время машынного цыкла, да еще и с запретом прерываний. Для разовой программы это может и не страшно, но в качестве библиотечной функцыи — зло. И ведь это в каждой книжке для начинающих раньше было, от 8051 и до…
Ну как скажете… еще раз подчеркну, что я высказался не против содержательной части представленных программ (это тема отдельного разговора и тоже непростого, если пожелаете) а исключительно против стиля написания.

Сначала о второй части Вашего комментария — про программно реализованную задержку. Само по себе это не преступление, например в Linux короткие (относительно 1/HZ) задержки реализованы именно на циклах и никто по этому поводу не комплексует. Но я категорически против неправильных объяснений вроде
Чтобы их можно было задействовать в основной программе для каких-то других целей, прерывания на время задержек запрещаются
Любая программа обработки прерываний должна начинать с того, чтобы сохранить используемые ею ресурсы общего назначения и должна их восстановить перед завершением. Другое дело, что возможное прерывание удлинит отрабатываемый интервал и именно для этого их и запрещают. Тут возможны два варианта:
1. Автор забыл, зачем сделал запрещение прерываний — неприятно, но не смертельно.
2. Автор скопировал этот фрагмент, не понимая его — ну все бывает, но тогда не следует учить других.

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

Нам постулируют необходимость реализации задержек в 150мкс, 5мс и 500 мс, особенно забавляет фраза
они определены опытным путем, и подходят для любых типов дисплеев
(строго говоря, это не совсем/совсем не так, но это по содержанию, так что опустим), затем следует пассаж
Можно сделать одну универсальную процедуру Delay (сэкономив количество команд в коде), но для удобства программирования сделаем три отдельных процедуры задержек:
и (вуаля) перед нами три разные функции. Возможно, в книге показано, к каким именно удобствам приводит нарушение принципа DRY, но лично я таких удобств не знаю. Получение используемых в тексте констант приходится объяснять отдельно в сопровождающем тексте, </sarcasm on> что, несомненно, намного удобнее, чем определить их в тексте программы (через макросы или константные функции) и показать их реализацию </sarcasm off>.
Можно привести еще множество примеров сомнительных (с точки зрения стиля) решений, но проще предложить просто взглянуть на тексты программ под спойлерами и «ты все поймешь и все увидишь сам, ты все поймешь и все увидишь там».

Приводит в замешательство также уверенность автора, что возможность применить в литералах hex представление символа вместо символьного представления доступна только в ассемблере и является его неоспоримым преимуществом перед языком программирования С++ (это не я придумал, это прямо в посте написано).

Ну в общем, если перечислять все, что мне не понравилось, то получится пост подлиннее исходного, хочу в заключение отметить, что подход, заключающийся в следующем абзаце
Отметим, что функция createChar библиотеки LiquidCrystal в отношении OLED-дисплеев Winstar работает не очень надежно (иногда значок просто пропадает при первом включении), и причины мне установить не удалось. Соответствующая ассемблерная процедура (см. далее) не сбоила ни разу.
лично мне представляется сомнительным и это совсем не то, чему (опять таки, по моему личному мнению) следует учить.

Вот с этим «Любая программа обработки прерываний должна начинать с того, чтобы сохранить используемые ею ресурсы общего назначения и должна их восстановить перед завершением» уже я готов поспорить. Совершенно не «любая» и далеко не в каждом случае. Проще поотслеживать примение РОН, чем загромождать программу пуш-попами.
Я согласен, что это не общий случай, но насчет общих случаев я уже ниже высказался. В данном случае пуш-попов будет меньше, если их спрятать именно в делеях. И, к вашему сведению, ни в статье, ни в книге нет скопированных фрагментов — все продумано.

Про hex-представление я ваших слов не понял. И не понял, чем вам не нравится определение усредненных задержек «опытным путем».

Вот с чем я согласен — это с критикой фразы, содержащей «причины мне установить не удалось». Так, конечно, нельзя (и в книге такого не встречается), но написана чистая правда: я и в самом деле не смог установить причины, почему createChar ИНОГДА не срабатывает. Проверялось на двух версиях Arduino и на двух экземплярах дисплеев разных типов. И на них же отлично работает ассемблерный вариант.
Ну если мы говорим о исключении пуш-поп, по компилятор С вполне может провести (и проводит) локальную оптимизацию и часто используемые переменные выносит в регистры, при этом он сам учитывает этот факт при планировании операций с другими переменными.

В программе на С++ Вам никто не мешает написать
unsigned char st[] ={0x20,0x27,0xbd,'a','p',0xC7,0x20};
как Вы сделали на асме, но это не делает данный фрагмент хорошим с точки зрения стиля (магические константы).
Необходимые задержки следует не определять «опытным путем», а брать из документации на используемый электронный прибор, после чего увеличивать их бессмысленно (и поэтому не следует), а уменьшать опасно (и поэтому нельзя). Насколько я помню (хотя могу и ошибаться), у этих индикаторов есть признак готовности и именно им следуем руководствоваться.

И с вероятностью в 90% непостоянные сбои при запуске — это как раз дефекты временных диаграмм, оставшиеся 10% — это нестабильность питания, но в описанном Вам случае вероятнее первое.
локальную оптимизацию и часто используемые переменные выносит в регистры
Спасибо, я именно по этой причине и применяю ассемблер, если это возможно. Именно потому, что «локальная оптимизация» мне только мешает, я сам однозначно лучше компилятора распланирую действия с переменными — не многозадачную ОС же пишем, где на это ума действительно не хватит.
А hex-константы — совершенно не мешают, между прочим. Потому что для электронщика мыслить в hex- или bin-константах — совершенно естественное состояние. (Последние, кстати, как я потом узнал, в каноническом С вообще не определены, что меня жутко удивило — а как они вообще тогда системные программы умудряются творить? Я большую часть констант для AVR употребляю именно в bin-форме, так намного удобнее, даже чем в hex).

Вы в полной мере усвоили программистский стиль мышления, против которого я и возражаю. Электроника — это ДРУГАЯ наука, совсем не программирование. Формально ни один ЯВР для МК не подходит, уже вследствие гарвардской архитектуры. Вот их и натягивают, как могут. А я перфекционист, который еще при составлении кода (а не при отладке) должен четко понимать, что там в каком регистре делается.

Что же касается «не определять «опытным путем»», «а брать из документации», то тут я с вами совершенно солидарен. Везде, где могу, я так и поступаю. Но вы, вероятно, не читали китайские даташиты на дисплеи. И тем более отечественые фирмы МЭЛТ (которых и вовсе может не быть в природе). А я их чуть ли не наизусть учил — те, которые имеются. Тогда бы у вас не возникло вопроса, почему приходится опытным путем определять задержки. Пример хотите? Образец кода инициализации, который мне любезно прислали из Winstar, не имеет никакого отношения к тому, что написано у них в разных даташитах (причем нередко по-разному). Вот и вся причина «опытного определения». Это совсем не редкий случай. Причем, насколько я в курсе, в программировании такое тоже не исключение.

Забыл добавить, что «сбои» в данном случае никакого отношения в временным диаграммам не имеют, так как имели место в Arduino, где библиотеки писал не я, а просто делал, как написано. Внесенные мной изменения на CreateChar точно не влияли.
Вот вы правильно поняли насчет разовой программы. Это программисты обожают все сводить к общим случаям, а для меня, как электронщика, нет вообще ни одного общего приема, который бы годился на все случаи жизни. То, что отлично идет в одном случае, совершенно не годится в другом. То, что здесь удобно, вон там вообще работать не будет. Мое мнение, что только так и правильно.
100 лет уже не писал на AVR ассемблере. Но все равно его помню, хоть и немного смутно уже.

Акститесь какой такой холивар AVR Си vs AVR Asm или тому подобное. Бои уже давно отгремели, а участники разошлись писать мемуары.
Сейчас актуально ARM vs AVR, и то скорее ввиде отдельных локальных конфликтов :)

Мне одному такие переходы, от Arduino к Asm, кажутся киданием из крайности в крайность? Что мешает писать на НОРМАЛЬНОМ Си?
Напоминает одного друга, к которого программа работала только с отключенными оптимизациями, иначе gcc выкидывал половину программы. Так он, вместо того что бы почитать Кернигана или Дейтелов, решил всё писать на Асме. В итоге большой проект чуть не загнулся. Помог небольшой рефакторинг кода.
По задержкам циклами с отключением прерываний, а как вы предлагаете выдерживать временные интервалы на уровне производительности процессора, когда у вас нет времени даже на цикл? Иногда приходится тупо NOPы вставлять.
Когда много пишешь для МК, то стоит смотреть во что компилятор превращает твой код. Со временем учишься писать так, что бы собранный код был максимально близок к тому, что ты напишешь на Асме сам.

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

Ваш друг, у которого «программа работала только с отключенными оптимизациям» и есть такой случай. Я его очень понимаю — он ведь собирался электронный прибор делать, а не Кернигана изучать, который к тому же свой С изобретал совершенно по другому случаю и для других архитектур. Я не одно десятилетие изучал AVR и строил на нем различные устройства — еще с семейства Classic начинал. И в сложных случаях, когда, большой объем точных вычислений или заковыристый алгоритм, без ЯВР и готовых библиотек не оботись, я в курсе. Но в остальном так до сих и не понял, зачем меня обязывают «смотреть во что компилятор превращает твой код», когда я прекрасно могу написать сразу, какой надо для данного конкретного случая. Возможно, я навсегда прибитый тем, что ассемблер разучил гораздо раньше С, синтаксис которого до сих пор держу за совершенно мутный и нечитаемый.
как вы предлагаете выдерживать временные интервалы на уровне производительности процессора

В данной задаче никак, потому что здесь этого совершенно не требуется. А когда потребуется, на ассемблере я все равно сделаю это лучше, чем «оптимизирующий компилятор».
Я всегда стараюсь максимально подогнать инструментарий под задачу. Мне кажется, что универсальные решения всегда проигрывают подогнанным под конкретный случай. Иногда без них не обойтись, но все-таки следует стараться не применять прецизионный станок с ЧПУ там, где достаточно ручного шуруповерта, это много лишних накладных расходов, лишь усложняющих процесс достижения результата. Как-то так.

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

Что вы думаете о использовании Forth для программирования микроконтроллеров?
Почему-то у меня опять комментарий отправился ниже по ветке, извините.
Добавлю еще к сказаному: вполне допускаю, что оптимальными будут разные решения для разных типов контроллеров. Пример — STM8, для которого программирование на ассемблере имеет куда меньше смысла, чем для AVR.
Пример — STM8, для которого программирование на ассемблере имеет куда меньше смысла, чем для AVR

А можете пояснить, почему?
Потому что там архитектура более приспособлена к работе в парадигме ЯВУ, когда предполагается активная работа с ОЗУ. AVR-ассемблер позволяет написать программу (теоретически — сколь угодно объемную), которая ни разу не обратится напрямую к ОЗУ, только к стеку, а все переменные так и останутся в регистрах общего назначения, которых аж 32 штуки. Обращение к переменным в памяти в AVR засоряет код беспрестанными ST/LD, на которые только тратятся лишние такты. В STM8 это не имеет значения, там все равно все в памяти происходит, можете насоздавать сколько угодно РОН, и ни одного лишнего такта не потратить. Ну, или почти ничего, я не настолько хорошо знаю STM8 изнутри, чтобы утверждать это категорически.
Охотно верю, что он может в чем-то кому-то помочь. Меня же всегда настораживала вот такая фраза из введений в язык: «Большое количество систем, достаточно плохо совместимых, поскольку имеется три стандарта Форта...». Возможность каждому создать свой вариант Форта, потратив на это пару дней, имхо, совершенно не достоинство языка, а крупнейший его недостаток. Тратить время на чтение руководств по контроллерам куда полезнее.

Но насчет языка я могу и ошибаться — допускаю, что путем Форта можно работать быстрее. Говорят, на нем проще ваять логически сложные алгоритмы — перечень решенных на нем задач и правда впечатляет. Но вот в чем я ошибаться не могу, так это в том, что для каждой конкретной задачи можно найти свой инструмент, наиболее подходящий к данному случаю. Причем поиски следует вести снизу вверх — если можно быстро сделать руками на ассемлере (или вообще в машинных кодах, хотя такие умельцы, как фон Нейман, разумеется давно перевелись), то и надо делать на ассемблере. Если есть хорошие готовые решения (а они обычно связаны с С или С++), то надо воспользоваться ими. И так далее. Главное, чтобы поиски не превратились в самоцель, что встречается очень часто — программистский образ мышления, когда пытаются найти общее универсальное решение для всех случаев жизни, необычайно заразен.

Читая один из интересных прикладных документов я наткнулся на такой пассаж:

В семействе AVR различают три типа оперативной памяти: регистры, порты и собственно память. Для доступа к каждому из них существуют особенные команды (MOV, IN, OUT, LDS, STS). По сути они выполняют одно и тоже — копирование байт из одного места в другое, только они еще явно указывают к какому типу памяти будет происходить обращение.
Всё было хорошо, пока однажды команда OUT не смогла отправить байт в настроечный порт. Оказалось, что данный порт переехал в ОЗУ и теперь к нему нужно обращаться через STS
Написал слова IN и OUT, которые по адресу операндов определяют в какую память идет обращение и компилируют соответствующую команду. Оказалось удобно:
code AAA \ пример использования «умного» IN
in r0,r1
in r1,portB
in r0,0x60
c;
code BBB \ пример использования «умного» OUT
out r0,r1
out portB,r1
out 0x60,r0
c;

и вот что получилось:
AAA: MOV R0,R1
IN R1,PORTB
LDS R0,0x60

BBB: MOV R0,R1
OUT PORTB,R1
STS 0x60,R0



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

Что вы думаете о таком методе? Может быть я не вижу его недостатков?
Вопроса не понял. Метод адресации портов через память универсальный, совершенно никто не мешает его применять вообще везде и всюду. Неудобство там в том, что для команд sts и lds довольно навороченые правила указания адресов для разных групп регистров. Углубляться в них я здесь не буду и комментировать приведенные вами примеры кода тоже (что означает загадочная фраза «Написал слова IN и OUT»? Надеюсь, эти слова под закон о запрете мата не попадают?). И собственно метода по поводу этих «слов» здесь не приведено, он остался за кадром. Очень внятно про эту проблему рассказал DiHalt в этой давней статье, в которой и предложил универсальный и удобный способ решения, не предполагающий указания конкретных адресов в числах.
Заметьте еще, что in/out перестают действовать для контроллеров, начиная с Mega88 (там, где число регистров ввода-вывода превышает 64, и появляются memory mapped регистры). А для задач, которые я считаю целесообразными решать на ассемблере, почти нет препятствий к применению старых типов AVR (Mega8/16, tiny2313 и пр.), где этих ограничений нет. Но, если хочется об этом не думать, метод DiHalt'а вполне годится.
Sign up to leave a comment.

Articles