Главная » Хабрахабр » К вопросу об AVR и мировых рекордах

К вопросу об AVR и мировых рекордах

Делай хорошо, плохо само получится

Поводом к посту послужила недавняя (когда я начинал писать этот пост, она действительно была недавней, но что-то пост долго пролежал в папке Неоконченное) публикация на Хабре относительно аспектов реализации программного UART на МК фирмы AVR. Сами по себе поднятые вопросы небезынтересны, но даны на них столь странные ответы, что посчитал своим долгом внести необходимые разъяснения. Тема обозначена, те, кто захочет прочитать о «королях, капусте и башмаках», то есть требованиях стандартов, чтении (правильном) технической документации и рекордах в программировании на ассемблере для AVR, могут нажать на кнопочку ниже.
Обозначим вопрос более подробно — возможна ли реализация ИРПС (привычное мне название интерфейса, в девичестве имеющего имя UART) на МК типа AVR ( конкретно речь шла о Tiny13) при работе от внутреннего генератора. Дело в том, что данный генератор имеет не слишком хорошие показатели по точности удержания частоты, почему данный вопрос и возникает. Сразу оговорюсь, что не имеет значения, будем ли мы рассматривать программную реализацию (как было предложено в исходном посте) либо использовать аппаратные блоки МК. Результаты одного способа (в части точностных параметров по времени) практически полностью транслируются на другой.

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

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

5/9. Конечно, зная протокол передачи, можно из общих соображений найти максимально допустимое рассогласование скоростей передатчика и приемника (0. 2%), но это будет исследование сферического коня сами знаете где, поскольку: 5=5.

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

Странствия по просторам Инета привели на AppNote от Atmel (ну раз мы все равно используем МК этой фирмы), где прямо говорится о допустимом рассогласовании в 2% с равным бюджетом, что приводит к требования точности поддержания частоты передатчика в 1%. Поверим уважаемой фирме и предположим, что они имеют доступ к секретным материалам и эта цифра верна, тем более, что выглядит она правдоподобно. Понимаю уязвимость подобной позиции, но мне, честно говоря, надоело искать точный ответ на такой простой вопрос, и не терпится перейти к следующей части.

Сначала немного об устройстве внутреннего генератора, тем более, что оно более-менее описано. Следующая половина ответа лежит внутри МК и определяется технической документацией на него. Чтобы сделать данный параметр более предсказуемым, производители добавили аппаратный узел, управляемый через калибровочный байт. Генератор использует в качестве времязадающего элемента RC цепочку и, поскольку задача формирования в интегральном исполнении точного конденсатора, да и точного резистора, весьма нетривиальна, то итоговая частота от экземпляра к экземпляру МК будет существенно различаться. Этот узел позволяет изменять частоту генератора в широких пределах и, соответственно, получать требуемое значение со значительно более высокой точностью.

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

Интересующие нас параметры находятся в разделе «Calibrated Internal RC Oscillator», далее при необходимости идем по ссылкам. Так что открываем документацию (Вы можете открыть файл в просмотрщике, а у меня есть типографская версия описания, отпечатанная самой фирмой-производителем — да, раньше такое бывало) и ищем соответствующий раздел. Как утверждают психиатры, «нет здоровых людей, есть не дообследованные» и внимательное изучение соответствующего раздела подтвердило эту истину, как я мог раньше не замечать таких провалов в документации. И вот тут нас (меня точно, насчет Вас не уверен) ждало первое разочарование — я давно (лет 15) работаю с продукцией Atmel, и всегда считал, что у них хорошая документация на МК. В свое оправдание могу только сказать, что:

  1. я никогда не использовал в данных МК внутренний генератор, поэтому особенно внимательно его не изучал;
  2. когда я начинал работать с этими МК (намного больше 10 лет назад) я был молодой (ну точно моложе, чем сейчас) и глупый и не понимал в должной степени необходимости хорошей (понятной, исчерпывающей и однозначной) документации;
  3. я готов себя многое простить, просто потому, что я себе многое прощаю, и все мои недостатки не фатальные (последний аргумент особенно убедителен, не правда ли).

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

Первая часть марлезонского балета-номинальная точность.

Сразу же находим нужный параметр — таблицу точности настройки генератора, в которой видим две строки «Factory Calibrated» с указанным значением ±10% и «Manual Calibrating» с аналогичным параметром ±2%.

Для первой строки в самой таблице указаны температура (окружающей среды или самого МК — неясно, но это уже капризы с моей стороны) и напряжение питания, кроме того, в примечании сказано (на мой взгляд, излишне) что данное измерение проводится в конкретной точке пространства внешних условий. По поводу этих данных сразу возникает ряд вопросов — что они означают и как проводятся измерения данного параметра. Все более-менее ясно и интерпретируется почти однозначно (хотя в разрезе исследования технической документации следовало бы сказать, что все нечетко и допускает вариации в трактовках, и это недопустимо, но если мы так сделаем, то тема дальнейшего обсуждения просто исчезает (и о чем дальше писать, непонятно, поэтому проявим снисходительность). Можно догадаться, что при этом мы должны использовать записанный на предприятии-изготовителе калибровочный коэффициент, хотя вот это как раз лучше было бы явно указать в примечании.

У меня сразу же возникает вопрос — если этого можно добиться повсюду (в любой точке температуры и питания) и фирма-изготовитель знает, как это сделать, то почему она это не сделала сама при фабричной калибровке в конкретной точке условий? А вот со второй строкой дела похуже — даны пределы изменения температуры и напряжения питания и утверждается, что применением некоей волшебной процедуры калибровки можно достичь существенно лучшего, нежели заводской, результата во всем диапазоне. 17% изменения частоты на единицу значения калибровки, что должно дать ожидаемую точность лучше, чем в 1%. Обращаемся к описанию калибровочного байта и видим, что он принимает 128 значений и и этом перекрывает диапазон от 50% до 200% номинала, что соответствует 150/128~1. Я категорически отказываюсь понимать как, и главное зачем, фирма захотела сделать именно такой закон регулировки, но ей удалось), что явно указано в рекомендациях, то следует считать точность в 2% вполне достижимой. Но дальше нам следует учесть, что регулировочная характеристика явно не линейна и в области больших значений калибровки имеем 60%/32~2% шага (данные взяты с графика, я неоднократно высказывал свое отношение к подобному методу представления технических параметров, но повторюсь — это неприемлемый метод, хотя, конечно, лучше, чем ничего), что дает точность в 1% и если мы учтем не монотонность регулировочной характеристики (да, именно так указано в документации, не нарисовано в графике, а явно указано в тексте. В данной части следует считать документацию вполне понятной и непротиворечивой, критерий правильности лежит вне пределов нашей компетенции. Мне не очень нравится, что пришлось смотреть график, но это не обязательно и табличные данные являются достаточными.

— влияние внешних условий. Вторая часть марлезонского балета.

Вместо таблиц значений нам предлагается смотреть картинки (в документации они почему то называются графиками типовых значений), а, как известно, «главное преимущество графического представления информации — его наглядность, и других преимуществ у него нет». А вот дальше начинается «трэш, угар и содомия». Не знаю, как кто, лично я глубоко убежден, что указать типичные (или типовые, не знаю, как правильнее, в одном фильме говорили «типическая внешность») значения, хоть в виде графика, хоть в таблице — это не указать просто ничего. Можно было бы пользоваться даже такой информацией и с графика снять граничные значения («хотя это и оскорбительно для коллектива»), если бы данный график не приводился в разделе «Typical Characteristics». На них нельзя ориентироваться при проектировании, поскольку эти параметры непонятно что означают и любое отклонение значений от типовых допустимо, в отличие от минимальных и максимальных значений, переход которых означает неисправность устройства.

Аналогичная картина и с напряжением питания — только типовые графики и результирующая погрешности в -6 +2% от 3. Ладно, проехали, попытаемся извлечь хоть какую-то информацию и видим, что при изменении температуры от -40 до +80°С частота генератора изменяется на ±4%. 5. 3 до 5. Данных же по старению генератора просто не дают, что, в общем, логично, поскольку на фоне уже приведенных параметров точность в единицу процента за 5 лет (характерная величина для кремния) уже никого не волнует.

Следует также учесть, что если калибровку под напряжение питания и конкретный МК можно сделать при изготовлении устройства и надеяться, что они не изменятся во времени, то учет температуры возможен только «на лету» и требует внешнего эталона времени соответствующей точности. Теперь у нас есть все данные для ответа на наш изначальный вопрос — при фабричной калибровке генератор не отвечает требованиям интерфейса по точности, при калибровке под конкретные условия применения — отвечает граничным требованиям, но не отвечает стандарту. Обратим внимание, что приведенный вывод мы сделали при анализе документации и сформулировали именно таким способом, чтобы подчеркнуть, что на конкретном экземпляре МК все может и получиться, если звезды встанут удачно. Поскольку при разработке устройств следует руководствоваться правилом «в Бога мы верим, все остальное требует доказательств», а возможность соответствия требованиям мы не доказали, то правильный ответ — гарантировать реализацию ИРПС, отвечающего требованиям стандарта, в данном МК с внутренним генератором невозможно. То есть наш вывод противоречит ранее упомянутому посту, как могло такое получится, ведь у человека все замечательно работает — давайте разбираться.

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

  1. Хороший способ — измерить критичные параметры интерфейса устройства и сравнить их с требованиями стандарта — это можно сделать с помощью универсальных приборов (в нашем случае осциллограф и длина битового интервала либо полной посылки), либо с помощью специализированного прибора, который сертифицирован на выполнение проверки данного интерфейса.
  2. Так себе способ — организовать взаимодействие с другим устройством, которое реализует ответную часть интерфейса и является доказанным (отвечает требованиям стандарта). Конечно, такая проверка совершенно недостаточна, и скорее, может быть применена более для подтверждения неисправности тестируемого устройства, но делает хоть что-то.
  3. Плохой способ — самостоятельно реализовать ответную часть интерфейса (в этом же устройстве или в ином) и взаимодействовать с ней. Поскольку оба устройства очевидно не доказаны, то польза от подобной проверки весьма и весьма сомнительно. Хорошим примером такого подхода является «эхо» на последовательном канале, которое ничего, кроме того факта, что устройство в принципе не сломано и способно что-то как-то передать, не доказывает и о скорости передачи сообщает ненамного больше, чем ничего.
  4. Ужасный способ — взять в качестве тестирующего вообще не отвечающее требованиям стандарта (а лучше противоречащее им) устройство и работать, как в предыдущем пункте.

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

Общий вывод из вышесказанного:

  1. при проектировании устройств следует обращать основное внимание на документацию (RTFM),
  2. необходимо изучать документацию и правильно интерпретировать прочитанное (RTFMF),
  3. иметь в виду, что в документации в наше время могут быть недоговоренности, неточности (и даже ошибки), поэтому
  4. проверять полученную информацию на непротиворечивость и правдоподобность, и
  5. использовать экспериментально полученную информацию только для подтверждения полученных из анализа документации выводов, при этом
  6. особо тщательно выбирать методы экспериментов по тестированию аппаратуры для получения достоверного результата.

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

.equ delay=15
TX_Byte: cli
; ld r18,Z+
; cp r18,r1
; breq Exit_Transmit
; dec r1 cbi port, TX_line
Delay_TX: ldi r16,delay
Do_Delay_TX: nop dec r16 brne Do_Delay_TX TX_Bit: sbrc r18,0 sbi port,TX_line sbrs r18,0 cbi port,TX_line lsr r18 lsr r17 brcs Delay_TX sbi port, TX_line ldi r16,delay
Stop_Bit_TX: nop dec r16 brne Stop_Bit_TX Sei

И сразу в глаза бросается ошибка в программе — в строке 3 (закоментированна) значение регистра 1 должно быть нулевым, но явно в функции присвоение не прописано. После выполнения цикла передачи одного байта данное значение гарантируется строкой 12, но при первом проходе — нет. Поэтому должна быть добавлена инициализация, что потребует увеличения размера кода.

Не то, чтобы это давало очень сильное влияние, но, если можно исправить недостаток, не удлиняя программу, то почему бы и нет — смотри эпиграф. Второй недостаток — собственно формирование уровня в строках 4-7, поскольку принятая автором методика выдачи очередного бита приведет к джитированию фронта на 2 такта при различных переходах (0-1 и 1-0), что повлечет за собой повышение требований к точности удержания частоты. Да, исправленный вариант требует более глубокого изучения архитектуры МК, но кто говорил, что будет легко. Первоначальный вариант занимал 4 слова и исполнялся за 4 такта, новый занимает 4 слова и исполняется за те же 4 такта. Если бы в рассматриваемом МК был настоящий битовый процессор, как в архитектуре 51, то мы могли бы написать идеальный фрагмент, сочетающий все преимущества обоих подходов (и даже был бы чуть короче), но что мечтать о несбыточном… С другой стороны, в первом варианте модификация порта атомарна, а во втором — нет, в данном случае это неважно (мы явно запретили прерывания), но осадок остается.

Я неоднократно высказывал свое отношение к магическим константам, которые мы видим в преамбуле данной программы. Третий недостаток — вопрос более стиля. Дело в том, что мы должны явно предъявить читателю метод формирования конкретного значения, а не создать синоним для значения, полученного неизвестным путем. Еще раз подчеркну — от того, что автор задает константу в преамбуле программы, а не непосредственно в операторе, «обычная уличная магия» никуда не девается. Так и сделано в нижеприведенном тексте, причем обратите внимание, что мы проводим преобразование в целое число только в последний момент и правильно округляем, что позволяет не потерять точность результата. Можно, конечно, написать комментарий к строке со значением, в котором указать расчетную формулу, но лучше явно использовать расчетную формулу при формировании константы и тогда комментарий просто не понадобится (разумеется, при говорящих именах применяемых констант).

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

Первое, что бросается в глаза — наличие двух выдержек времени, что плохо, поскольку нарушает принцип DRY (общее требование) и увеличивает размер кода (конкретное требование). Ну а теперь, когда мы исправили ошибки (кроме последней), попробуем программу улучшить в смысле главного критерия (для достижения рекорда, в данном конкретно случае) — длины кода. Можно было бы оформить данный фрагмент в виде подпрограммы и мы все равно выиграли бы по длине, поскольку добавляем мы 3 слова кода (по 1 для вызова в двух местах и 1 для возврата), а экономим 4, но есть значительно более красивый путь — аккуратная организация цикла передачи байта, которую можно видеть в следующем тексте.

.equ delay=15
TX_Byte: cli sec ; потребуется для стоп-бита clt ; устанавливаем старт-бит
TransBit: ; собственно формирование бита in r17,port bld r17,Tx_line out port,r17
Delay_TX: ; формируем длину битового интервала ldi r17,delay
Do_Delay_TX: nop dec r17 brne Do_Delay_TX TX_Bit: bst r16,0 ror r16 clc brne TransBit ; есть что передавать brcs TransBit ; передаем стоп-бит
Exit_Transmit: Sei

Обратим внимание, как мы используем передаваемый байт совместно с битом переноса в качестве счетчика битов, красивое решение, но обладает одним недостатком — длительность последнего бита данных будет несколько (на 2 такта) длиннее остальных, за счет задержки перехода. Если бы речь шла о стоповом бите, то «наплевать и забыть», поскольку минимальный интервал между передачами нам не задан, но это значащий бит, да мы еще только что критиковали исходную программу за подобное поведение. Не будем уподобляться библейскому персонажу из притчи о соринке в чужом глазу и примем меры к устранению. Данное явление легко можно было бы компенсировать введением задержки на 2 такта, но увеличится длина кода, а это ключевой параметр. Поэтому пойдем классическим путем и поменяем время на память — используем отдельный регистр для организации счетчика передаваемых битов, и получим совершенно одинаковые битовые интервалы при том же размере кода.

Если мы сделаем его 3х тактовым (минимально возможным в данном МК), то сможем сэкономить один байт кода и потенциально можем улучшить точностные параметры, поскольку дискретность задержки станет меньше (отклонение не превосходит половины размера дискрета при правильном округлении). Следующее улучшение связано с формированием длительности битового интервала, которое в исходной программе выполняется на 4х тактовом цикле. Еще одно обстоятельство, которое могло повлиять на выбор именно такой длительности цикла — максимальный размер задержки при байтовом счетчике составляет 256 значений — для имеющегося варианта можно использовать скорости от 9600 бод и выше, а вот с 3х цикловой задержкой это невозможно. Но следует иметь в виду, что в конкретном случае мы можем и потерять точность, все зависит от исходных данных. Ну и внести соответствующие модификации в макросы формирования параметра для формирования задержки, не забывая использовать «говорящие» имена для обозначения переменных. Было бы весьма неплохо отразить данное обстоятельство (минимально допустимая скорость порта) в комментариях к программе и заодно вывести предупреждающее сообщение в случае нарушения данного требования.

.equ Freq = 8000000
.equ BaudRate = 115200
.equ PayLoad = 9 ; количество тактов вне цикла
.equ CycleTime = 3 ;количество тактов в цикле
.equ delay=((Freq*2/BaudRate - PayLoad*2)+CycleTime)/(CycleTime*2)
TX_Byte: cli ldi r18,10 sec ; потребуется для стоп-бита clt ; устанавливаем старт-бит
TransBit: in r17,port bld r17,Tx_line out port,r17
Delay_TX: ldi r17,delay
Do_Delay_TX: dec r17 brne Do_Delay_TX TX_Bit: bst r16,0 ror r16 dec r18 brne TransBit
Exit_Transmit: sei

Теперь посмотрим на результат — размер кода уменьшился с 20 до 16 слов (если учитывать только собственно передачу, то еще более разительно — с 18 до 14, джиттер фронтов исчез (конечно, только та компонента джиттера, которая обусловлена особенностями программы, на аппаратную составляющую мы не посягаем), точность выдерживания временных интервалов улучшилась, программа стала нагляднее и проще в понимании (за счет комментариев, поскольку даже хорошо написанная программа на ассемблере само-документируемой, как правило, не является).

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

В конце прошлого года (2016, вот как долго ждал своей очереди этот пост) анонсирован новый МК из семейства MSP430, который наряду с уникально низкой ценой (26 центов — ждем появления китайских устройств на его основе) имеет и уникально малый размер памяти программ — 512 байт (нет, я не ошибся, буквы «к» сразу после цифры нет). Ну и в заключение — задача написания кода минимального размера в наше время выглядит несколько надуманной, но, совершенно неожиданно, получает подтверждение своей жизненности. Так что размер кода может оказаться критичным при использовании данного прибора, да и вообще написание подобных экстремальных программ требует углубленного изучения МК, а «труд уже сам по себе есть благо».


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Внедрение зависимостей в сервис Apache Ignite.NET

Разрабатывая различные приложения, использующие популярную библиотеку Castle Windsor для внедрения зависимостей и Apache Ignite.NET в качестве «ключика», который открывает дверь в «облачные» вычисления, я столкнулся с небольшим неудобством: у меня не было никакой возможности внедрить зависимость в сервис, запускаемый через ...

[recovery mode] Сравнение станков лазерной резки Raylogic 11G и Raylogic V12

Всем добрый день! Сегодня мы решили сделать для вас сравнение станков лазерной резки Raylogic 11G и Raylogic V12. С вами компания 3Dtool. Ну, а если говорить конкретней, то мы расскажем вам об изменениях в лазерных станках Raylogic. Как оказалось, при ...